1.前言

AOP是面向切面编程,即“Aspect Oriented Programming”的缩写。面对切面,就是面向我们的关注面,不能让非关注面影响到我们的关注面。而现实中非关切面又必不可少,例如获取资源、释放资源、处理异常、记录日志等,太多的非关切面会让关切面的代码变得杂糅,难以维护。此时面向切面编程便是解决此问题的方案,减少非关切面的东西,让我们只专注于核心业务代码。而要理解AOP,就必须要懂得代理模式是啥,能干啥,特别要清楚Cglib动态代理是咋回事(不懂的可以看这篇文章)。

2.代码

为了直观看到各个类,我将示例所需的类作为静态内部类放在外层类中。

 实现过程

1)先定义一个业务类

这个简单的业务类有保存用户、删除用户的功能。

 public static class UserDaoImpl {public int addUser(String user) {System.out.println("保存了一个用户 " + user);return 1;}public int deleteUser(int id) {if (id <= 0 || id > 99999999) {throw new IllegalArgumentException("参数id不能大于99999999或小于0");}System.out.println("删除了id为" + id + "用户");return 1;}}

 

2)自定义一个拦截器(实际上是一个拦截器)

这个Advice通知实现了 MethodInterceptor接口,而MethodInterceptor接口继承了Interceptor接口,Intercepto接口又继承了Advice接口,因此我个将这拦截器称为一个通知。

为了偷懒,此处的Advice通知一次性实现了两个功能,前置增强和后异常处理增强,明显不满足“单一职责”的原则,此处同时要处理两种增强。而spring框架中,当读到配置文件的"<aop:before>"标签,就安排一个AspectJMethodBeforeAdvice类(继承于AbstractAspectAdive抽象类,此抽象类实现了Advice接口)去处理前置增强;当读到配置文件的"<aop:after-throwing>"标签,则会安排一AspectJAfterThrowingAdvice类(实现了MethodInterceptor接口)去做异常增强处理。spring框架切实做到了遵循”单一职责“原则,专门组织一个类去处理一种类型(前置、后置或最终等)的增强。

另外需要注意的是:这里的MethodInterceptor接口是位于"org.aopalliance.intercept"包,除此之外Cglib工具包中的"org.springframework.cglib.proxy"包也有MethodInterceptor接口,但这个接口不是我们所需要的。

public static class UserMethodInterceptor implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation mi) throws Throwable {String methodName = mi.getMethod().getName();// 方法名Object returnVal = null;/** 根据方法名的不同设置不同的拦截策类 似于配置文件中的<aop:pointcut* expression="execution( xxxmethodName)"/>,以此定义切入点。** spring框架中可以根据方法对应的包、对应的参数、返回值、方法名等多个条件,并结合正则表达式来定义切入点* 为了简单化,此处我只用方法名的前缀来定义切入点。**/if (methodName.startsWith("add")) {returnVal = beforeEnhance(mi);} else if (methodName.startsWith("delete")) {returnVal = afterThrowingEnhance(mi);} else {returnVal = mi.proceed();}return returnVal;}/** 前置增强策略*/private Object beforeEnhance(MethodInvocation mi) throws Throwable {/** spring框架通过配置文件或注解来获得用户自定义的增强方法名及对其JavaBean的类名, 然后通过反射机制动态地调用用户的增强方法。* 为了简单化的测试,我这里将增强方法及对应的类给定死了、不能改动, 增强JavaBean就是ConsoloEnhancer,* 增强方法就是"public void before(MethodInvocation mi)"**/new ConsoloEnhancer().before(mi); // 调用用户定义的前置处理方式Object returnVal = mi.proceed(); // 执行目标方法return returnVal;}/** 异常处理策略*/private Object afterThrowingEnhance(MethodInvocation mi) throws Throwable {try {return mi.proceed();// 执行目标方法} catch (Throwable e) {new ConsoloEnhancer().afterThrowing(mi, e); // 调用用户定义的异常处理方式throw e;}}
}

 

3) 用户定义包含增强方法的JavaBean

MthodInvocation接口比较有意思,它的祖先级接口是Joinpoint ,注意不是开发中的JoinPoint,但MthodInvocation封装的信息和JoinPoint差不多,都包含目标对象、方法的参数、目标方法等。

public static class ConsoloEnhancer {/*** 前置增强方法** @param mi*/public void before(MethodInvocation mi) {/** 这里的MethodInvocation类型参数mi和开发中常用的切点工具类JoinPoint的作用类似,它可以获取* 目标对象("Object getThis()"方法)、目标方法("Method getMethod()"方法)、* 方法参数("Object[] getArguments()"方法)等信息*/System.out.print("调用了" + mi.getThis() + "对象的" + mi.getMethod().getName() + "方法。方法入参原始值是"+ Arrays.toString(mi.getArguments()) );if (mi.getThis().getClass() == UserDaoImpl.class) {         //对方法入参进行修改Object[] args = mi.getArguments();String enhancedAgr ="user-" +(String) args[0]  ;args[0] = enhancedAgr;}System.out.println(".而增强后的方法入参是"+Arrays.toString(mi.getArguments()) );System.out.println("*********************前置增强加星号 *********************");}/*** 处理异常的增强方法** @param mi* @param e*/public void afterThrowing(MethodInvocation mi, Throwable e) {System.out.println("调用了" + mi.getThis() + "对象的" + mi.getMethod().getName() + "方法,当方法入参是"+ Arrays.toString(mi.getArguments()) + "时,发生了异常。");System.out.println("异常的堆栈信息是" + Arrays.toString(e.getStackTrace()));System.out.println("!!!!!!!!!!!!!!!!!!!!!异常加感叹号!!!!!!!!!!!!!!!!!!!!!!");}}

 

4)方法测试

public static void main(String[] args) {UserDaoImpl userDaoImpl = new UserDaoImpl();ProxyFactory proxyFactory = new ProxyFactory(); // 初始化一个代理工厂// 设置目标类,以便于Cglib工具包动态生成目标类的子类,即我们所需的代理类proxyFactory.setTarget(userDaoImpl);/** 设置拦截器,而拦截器的"public Object invoke(MethodInvocation mi)"定义了* 代理类(实际是UserDaoImpl的子类)的方法生成策略。**/proxyFactory.addAdvice(new UserMethodInterceptor());// 向上转型,转型为父类类型UserDaoImpl proxyUserDaoImpl = (UserDaoImpl) proxyFactory.getProxy();/*** 调用代理方法*/proxyUserDaoImpl.deleteUser(2);  //应该正常执行,没有什何增强效果System.out.println("");// 换行proxyUserDaoImpl.addUser("李华");  //入参值会被修改System.out.println("");// 换行proxyUserDaoImpl.deleteUser(-1);  // 将异常处理效果}

  控制台输出

3.总结

spring框架的AOP非常强大,可以实现前置增强、后置增强、最终增强、环绕增强等处理,另外还可以对方法入参进行控制过滤,而影响目标方法的执行中的状态。AOP都是基于(Cglib)代理模式实现的,其中的关键点在于实现MethodInterceptor接口,在其“public Object invoke(MethodInvocation mi)”方法中制定代理方法的生成策略,而从此方法的MethodInvocation类型参数mi中可以获得目标对象、方法的参数、目标方法等信息,根据这些信息可以精确地控制增强效果。

深入理解spring中的AOP原理——实现MethodInterceptor接口,自已动手写一个AOP相关推荐

  1. 前端面试 vue生命周期钩子是如何实现的?理解vue中模板编译原理?

    生命周期钩子在内部会被vue维护成一个数组(vue 内部有一个方法mergeOption)和全局的生命周期合并最终转换成数组,当执行到具体流程时会执行钩子(发布订阅模式),callHook来实现调用. ...

  2. 深入理解JDK动态代理原理,使用javassist动手写一个动态代理框架

    文章目录 系列文章索引 一.动手实现一个动态代理框架 1.初识javassist 2.使用javassist实现一个动态代理框架 二.JDK动态代理 1.编码实现 2.基本原理 (1)getProxy ...

  3. 自己动手写一个推荐系统,推荐系统小结,推荐系统:总体介绍、推荐算法、性能比较, 漫谈“推荐系统”, 浅谈矩阵分解在推荐系统中的应用...

    自己动手写一个推荐系统 废话: 最近朋友在学习推荐系统相关,说是实现完整的推荐系统,于是我们三不之一会有一些讨论和推导,想想索性整理出来. 在文中主要以工程中做推荐系统的流程着手,穿插一些经验之谈,并 ...

  4. Spring Boot 动手写一个 Start

    我们在使用SpringBoot 项目时,引入一个springboot start依赖,只需要很少的代码,或者不用任何代码就能直接使用默认配置,再也不用那些繁琐的配置了,感觉特别神奇.我们自己也动手写一 ...

  5. spring aop实例讲解_小实例理解Spring中的AOP----面向切面编程

    关于面向切面编程(Spring AOP),是在java面试中经常提及的,只有在充分理解了,日常工作中才能得心应手. 如何理解AOP呢?首先我们要思考为什么要使用切面编程,如下图: 对于一个系统来说保存 ...

  6. python ioc框架_轻松理解 Spring 中的 IOC

    Spring 简介 Spring 是一个开源的轻量级的企业级框架,其核心是反转控制 (IoC) 和面向切面 (AOP) 的容器框架.我们可以把 Spring 看成是对象的容器,容器中可以包含很多对象, ...

  7. 【SpringBoot】 理解Spirng中的IOC原理

    前言 前文已经介绍了Spring Bean的生命周期,在这个周期内有一个重要的概念就是: IOC容器 大家也知道IOC是Sping 的重要核心之一,那么如何理解它呢,它又是产生什么作用呢?本文就IOC ...

  8. factorybean 代理类不能按照类型注入_快速理解Spring中的FactoryBean接口

    1.前提概要 很多java开发者在使用Spring框架中都见过后缀为FactoryBean的类,比如Mybatis-Spring中的SqlSessionFactoryBean.说到这里就不得不提Bea ...

  9. java的lookup方法_深入理解Spring中的Lookup(方法注入)

    前言 本文主要给大家介绍了关于Spring中Lookup(方法注入)的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍: 在使用Spring时,可能会遇到这种情况:一个单例的Be ...

最新文章

  1. 爱奇艺效果广告的个性化探索与实践
  2. gcc 常用命令-Wall
  3. 【NLP】一文了解基于深度学习的自然语言处理研究
  4. 成员变量 局部变量 类变量
  5. 使用Preference保存设置
  6. Visual Studio调试技巧
  7. ElasticSearch 介绍及使用方法
  8. 转,docker学习笔记
  9. mysql5.7卸载服务_win10卸载原mysql安装64位mysql5.7并修改root密码
  10. 软件开发过程中的一些感悟
  11. win2008php一键,WIN2008 一键安装PHP环境PHP5.3+FastCGI
  12. 美团笔试.最大子段和
  13. html中让页面点击向左滑动,HTML5页面点击和左右滑动页面滚动详解
  14. arduino步进电机程序库_Arduino步进电机控制示例
  15. 新浪微博爬虫:模拟登陆+爬取原始页面
  16. 交叉报表制作--Smartbi报表工具一步完成
  17. QTextStream 类(文本流)和 QDataStream 类(数据流)
  18. sketch插件开发_适用于Web开发人员的10个免费Sketch插件
  19. vue3实现微信公众号一次性订阅消息+ios和Android的63002 config:invalid signature问题
  20. 自适应直方图均衡(CLAHE) 代码及详细注释【OpenCV】

热门文章

  1. PowerDesigner添加索引
  2. python如何设置rgb颜色_【Python图像处理】RGB颜色转HSV颜色的快速实现
  3. OSChina 周三乱弹 ——掌握写代码核心科技的名人们!
  4. WPS表格 三级下拉列表的建立 以及一些问题
  5. 原装苹果手机_华强北二手苹果手机怎么分辨是否原装屏幕呢?
  6. 编译类型和运行时类型不同_不同类型的游戏
  7. VTK 实现 曲面重建(CPR)
  8. 人生就像一张茶几,摆满了各种杯具洗具餐具
  9. 菲索干涉仪 (Fizeau interferometer) 的基本原理
  10. nohup 命令指定输出文件名