1. 前言&目录
AOP切面编程主要用于抽取与具体业务逻辑无关的逻辑并组织起来以另一种方式重新与业务逻辑“耦合”在一起。比如,在WEB项目中,往往需要对接口做鉴权、性能统计、日志记录、事务处理等,这些逻辑跟业务逻辑无关、是独立的,但它也是多数业务逻辑都需要的。
将这些横跨多种业务逻辑的、与业务无关的逻辑抽取出来,在合适的地点“切入”目标方法的过程就叫做横切,而这些抽取的逻辑就叫横切关注点。举个例子,鉴权功能就是一个横切关注点,在WEB系统中的所有接口几乎都需要鉴权,也就是说鉴权功能“横切”着整个WEB系统的接口,所以在这里鉴权成为了横切整个WEB系统的关注点——横切关注点。
当然这些非业务逻辑可以通过定义某个类、某些方法的形式去实现,然后在调用接口前后调用该类或方法去实现。但是,这种形式就称不上是切面编程了,仍然是面向对象编程或者面向过程编程。
Spring框架很好的支持了AOP切面编程,它通过代理模式的方法去实现切面编程。
本文将以下图目录讲解AOP切面编程原理。
2. 概述
2.1 AOP切面相关概念
使用AOP切面编程,我们必须了解以下几个概念:切面、切入点、通知、连接点、织入。
2.1.1 切面
切面是一个类,定义了切入点、通知等信息,具体表现为哪里干、怎么干的集合。在SpringBoot注解编程中,在给定的类上标注@Aspect表示为一个切面类。
2.1.2 切入点
切入点即表示横切面的关注点,AOP切面编程重要的步骤就是找到一个能切入进去的点,通常有方法切入点、异常切入点、变量切入点,但是在Spring中仅支持方法切入点,具体表现为在哪里干。在SpringBoot注解编程中,在切面类中可在方法上标注@Pointcut表示一个切入点。
2.1.3 连接点
连接点其实是根据切入点、通知等信息找到具体要进行切面编程的方法,即目标方法,具体表现为哪个方法要干。
2.1.4 通知
通知即横切面的主要实现逻辑,它需要搭配切入点,告诉程序“我”需要在哪里干,具体表现为怎么干。在SpringBoot注解编程中,在实现切面逻辑的方法上标注@Around等注解表示一个通知,每种通知都会被封装为Advice对象,它们是通知方法的执行载体。
通知一共有五种:
①@Around环绕通知,在方法执行前后执行。
②@Before前置通知,在方法执行前执行。
③@After后置通知,在方法执行后(无论成功还是失败)执行。
④@AfterReturning返回通知,在方法成功执行、无报错后执行。
⑤@AfterThrowing异常通知,在方法执行期间捕获到异常后执行。
2.1.5 织入
织入就是代理对象将通知和连接点编织在一起的动作,在Spring中AOP切面编程是通过代理对象实现的,因此通过代理模式的这种织入方式,将连接点、通知、代理对象编织在一起。
2.2 AOP编程简单实例
在SpringBoot进行切面编程,我们需要在一个Bean上声明注解@Aspect,表示当前Bean是切面类,然后通过@Pointcut注解定义一个切入点,Spring支持方法切入表达式和注解切入表达式,这里我们选择方法切入表达式。接着,用如@Around、@Before等注解和切点组合在一起标注在具体的通知方法,具体使用哪种类型的通知注解取决于你的目的是什么。
比如,环绕通知可以进行性能统计,在目标方法调用前后记录执行时间,前置通知可以进行流量统计、接口访问统计.....
当我们通过目标对象方法目标方法时,控制台将输出如下。
3. 准备工作
在SpringBoot框架中使用AOP切面编程,我们需要额外引入以下aspectjweaver依赖,不过我们这里只需引入spring-boot-starter-aop依赖即可,因为包含了aspectjweaver依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--aspectjweaver依赖在spring-boot-starter-aop有引入-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>compile</scope>
</dependency>
在spring-boot-starter-aop依赖中包含了自动配置的依赖spring-boot-autoconfigure,启动SpringBoot项目的时候会读取其路径META-INF/spring.factories下的配置值(自动装配功能可以通过SpringBoot自动装配原理文章了解),最终为容器中注入AopAutoConfiguration的配置类,这个配置类又会通过@EnableAspectJAutoProxy往容器注入AnnotationAwareAspectJAutoProxyCreator的Bean,这个Bean正是支持SpringBoot实现AOP切面编程的重要客观基础。
4. SpringBoot实现切面编程
前面章节介绍过,SpringBoot实现切面编程的客观基础是AnnotationAwareAspectJAutoProxyCreator这个Bean,为什么是它呢?因为它负责寻找、解析切面类中的通知、为使用到切面编程的bean增强为代理对象的工作,当前两步都做完后,调用某些Bean切入点的方法时就会通过代理对象先执行通知的具体逻辑,接着才实现后面的实际逻辑。
4.1 寻找、解析切面类中的通知
@Aspect的注解其实并非是Spring框架的注解,它是org.aspectj.lang.annotation包下的,正是上面讲到的aspectjweaver依赖导入的包,通过这些也可以知道Spring并没有重新定义诸如切面、通知、切入点等注解,因为没有这种必要,使用已定义好的包,一来可以省下时间,二来这个切面编程并非Spring独有,Spring要做的事情是去兼容它、而非推翻它重新开发一套。
在SpringBoot中使用切面编程,必须先找到所有切面类并解析其中的通知,这一步是在AnnotationAwareAspectJAutoProxyCreator#postProcessBeforeInstantiation(beanClass, beanName)中的findCandidateAdvisors()完成的。
public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator {
@Override
protected List<Advisor> findCandidateAdvisors() {
// 添加根据超类规则找到的所有Spring的通知.
List<Advisor> advisors = super.findCandidateAdvisors();
// 为Bean工厂中的所有 AspectJ切面构建通知
if (this.aspectJAdvisorsBuilder != null) {
// 通过BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors()查找
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}
}
从上面伪代码中看到,寻找@Aspect标注的切面类中的通知,又是通过BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors()去执行的,这里面主要做的事情就是将当前容器中的所有bean的class对象获取出来,判断其有没有标注@Aspect的注解,如果有则说明它是一个切面类,则委托给ReflectiveAspectJAdvisorFactory#getAdvisors(...)方法解析该切面类的通知方法并最终生成Advisor对象。
4.1.1 Advisor对象
在ReflectiveAspectJAdvisorFactory#getAdvisors(...)方法中,会遍历切面类的所有标注了@Around、@After、@Before、@AfterReturning、@AfterThrowing注解的方法,因为它们就是“通知”。
获取到切面中的通知方法后,会将几个重要属性通知方法的Method对象、切点表达式对象以及切面实例工厂对象(保存了切面Bean的class对象)等去创建一个实例对象是InstantiationModelAwarePointcutAdvisorImpl的Advisor对象。
4.1.1.1 Advice对象
在InstantiationModelAwarePointcutAdvisorImpl的构造函数中,会根据通知类型的不同而创建出不同的Advice对象并存入其中。对于外界来说,切面中的通知方法的Advisor对象都是实例为InstantiationModelAwarePointcutAdvisorImpl的对象,但是真正起作用的是这些Advice对象。
在SpringBoot中,@Around、@Before、@After、@AfterReturing、@AfterThrowing五种类型的通知最终会分别被创建为AspectJAroundAdvice、AspectJMethodBeforeAdvice、AspectJAfterAdvice、AspectJAfterReturningAdvice、AspectJAfterThrowingAdvice的Advice对象,这些xxxAdvice实例对象均继承了AbstractAspectJAdvice、实现了Advice接口。
这五种Advice对象实例并没有显示声明有参构造函数,而是调用父类AbstractAspectJAdvice的有参构造函数。可以看到,当一个Advice对象通过有参构造函数声明以后,它将会包含许多重要属性,如通知方法所在的切面类的class对象、通知方法的名称、通知方法的参数类型、通知方法本身对应的Method对象、切入点对象、切面实例工厂对象(包含切面类的class对象)。
以上概念请尽可能记住,因为在本小节还不会深入研究它们的构成以及使用原理,但是我们必须提前知道这些Advice实例对象正是通知方法的执行载体,记住它们将会非常有用。
4.2 将用到切面编程的Bean增强为代理对象
当一个Bean中的方法和切面的通知方法匹配上时,它会被增强为CGLIB代理对象。这个操作是在AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization(bean,beanName)方法实现的,不过AnnotationAwareAspectJAutoProxyCreator并没有重写postProcessAfterInitialization方法,而是调用其父类AbstractAutoProxyCreator的实现。
在AbstractAutoProxyCreator#postProcessAfterInitialization(bean, beanName)方法中,又是通过wrapIfNecessary(...)去完成代理对象的创建。
在wrapIfNecessary(...)方法中,主要完成两件事情:
①将当前bean和此前在已经找到的通知对象Advisor比对,获取与该bean匹配的Advisor对象。
②如果能找到匹配的Advisor对象,则该bean会被增强为代理对象。
将一个Bean增强为代理对象是通过AbstractAutoProxyCreator#createProxy(...)方法实现的,在该方法里面,会创建一个ProxyFactory实例对象,这个ProxyFactory就是Spring创建代理对象的一个工厂对象。
ProxyFactory对象会保存两个重要属性,一是与该Bean匹配的Advisor对象数组,对于非接口类的Bean来说,会创建为CGLIB代理对象,这些Advisor对象将作为回调函数被使用。二是目标Bean对象本身的一个实例对象,用处之一便是判断其是否是接口,如果是则创建实例是JdkDynamicAopProxy的AopProxy代理,否则创建实例是ObjenesisCglibAopProxy的AopProxy代理。
4.2.1 CGLIB代理对象
从上个小节知道,对于非接口类Bean,Spring会创建一个实例对象是ObjenesisCglibAopProxy的AopProxy代理,这个AopProxy代理还不是最终的CGLIB代理对象。不过笔者并不打算深入讲解CGLIB代理的原理,在这里我会将CGLIB代理对象使用到的一些关键属性作讲解,因为这些和后面要讲述的切面中通知方法执行时机、地点息息相关,我们必须了解其大致流程。
真正创建一个CGLIB代理实例对象是通过ObjenesisCglibAopProxy继承的父类CglibAopProxy#getProxy(...)方法实施的。在getProxy方法里面会用到两个关键属性,正是前个小节讲到的Advisor对象和目标对象本身。
4.2.1.1 DynamicAdvisedInterceptor
在getCallBacks(rootClass)方法中,包含了目标Bean对象和Advisor对象的ProxyFactory对象会作为构造函数入参去创建一个DynamicAdvisedInterceptor对象,并且赋值给其AdvisedSupport对象,因为ProxyFactory继承的父类之一便是AdvisedSupport。
DynamicAdvisedInterceptor对象非常重要,因为它实现了MethodInterceptor接口、MethodInterceptor接口又继承了CallBack接口,也就是说DynamicAdvisedInterceptor对象本质上就是一个CallBack回调函数对象。
在CGLIB代理对象执行目标对象方法时正是通过一系列的CallBack回调函数实现的,我们可以不知道底层是怎么通过回调函数最终调用到目标对象方法中,但是记住这个结论对理解后面的讲解非常重要。
CallBack仅仅是一个标记接口,因为它里面没有定义任何抽象方法。起作用的是MethodInterceptor接口,它定义了intercept(...)方法。当CGLIB代理对象调用目标对象方法时会进入一系列的CallBack回调函数中,在这里也就是会进入到DynamicAdvisedInterceptor重写的intercepte方法。其具体调用逻辑不会在本小节讲述,但是我们必须记得DynamicAdvisedInterceptor的成员变量(AdvisedSupport)advised就是那个拥有目标Bean对象和bean匹配上的Advisor对象的ProxyFactory对象【ProxyFactory的父类之一就是AdvisedSupport】。
4.2.1.2 Enhancer
在CglibAopProxy#getProxy(classLoader)方法中,除了创建回调函数外,还创建了Enhancer对象。当最终通过ObjenesisCglibAopProxy#createProxyClassAndInstance(...)方法创建终极CGLIB代理对象实例时,便会用到Enhancer对象和包含了Advisor对象、目标Bean对象的回调函数实例对象DynamicAdvisedInterceptor。
这个Enhancer对象作用之一就是创建一个CGLIB代理class对象,其class对象的名称就需要用到目标类的全限定类名,在Enhancer创建之际就将目标类的class对象赋值给其superClass属性了。当利用Enhancer对象创建出CGLIB代理实例对象以后,先前保存了Advisor对象和目标Bean对象的回调函数DynamicAdvisedInterceptor会赋值给这个CGLIB代理实例对象。
4.2.1.3 CGLIB代理对象调用逻辑
从前面两个小节得知,CGLIB代理对象是通过Enhancer对象创建的,并且拥有一个DynamicAdvisedInterceptor拦截器,这个拦截器拥有的Advisor对象就是切面中对应的通知方法。因为DynamicAdvisedInterceptor本质上就是一个回调函数,而CGLIB代理对象执行目标方法的调用逻辑其实通过调用回调函数,在回调函数里面进一步调用通知方法、连接点方法。
4.3 通知执行逻辑——链式调用
在前面章节介绍中,CGLIB代理对象调用目标对象方法其实是通过一系列回调函数实现的,具体会进入回调函数们实现接口MethodInterceptor的重写方法invoke(...)里面。而与某个目标Bean对象匹配的Advisor对象以及目标Bean对象本身都会随着ProxyFactory实例对象赋值给DynamicAdvisedInterceptor这个回调函数实例中去。因此,切面中的通知方法其实正是在DynamicAdvisedInterceptor重写的intercept方法执行的。
4.3.1 MethodInterceptor拦截器
在DynamicAdvisedInterceptor重写的intercept方法里面,首先会将Advisor对象包装成实现了接口MethodInterceptor的拦截器对象,因为目标对象方法的调用又是通过CglibMethodInvocation#proceed()方法处理的。
CglibMethodInvocation#proceed()方法又是引用父类ReflectiveMethodInvocation#process()方法,从下图可以看到那些Advisor对象最终其实会被包装为(如果自己没有实现MethodInterceptor接口)实现了MethodInterceptor接口的拦截器对象。
那么这里ReflectiveMethodInvocation的方法拦截器对象是怎么创建的呢?需要我们回顾DynamicAdvisedInterceptor重写的intercept方法,是通过List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)创建的。
AspectJAroundAdvice、AspectJAfterAdvice、AspectJAfterThrowingAdvice自己实现了MethodInterceptor接口本质上也是方法拦截器,而AspectJMethodBeforeAdvice、AspectJAfterReturningAdvice没实现,需要被包装为MethodBeforeAdviceAdapter、AfterReturningAdviceInterceptor拦截器。
这些拦截器都是实现了接口MethodInterceptor,它的抽象方法中可以看到入参是实现了接口MethodInvocation的对象。
不过,在讲解DynamicAdvisedInterceptor重写的invoke方法前,我们必须先了解五种通知对象Advisor对应的Advice对象,Advisor对象只是封装了Advice对象,实际起作用的是Advice对象。在前文中并没有深入讲解Advice对象,只是介绍了Advice对象是通知方法的执行载体。
4.3.1.1 AspectJAroundAdvice
AspectJAroundAdvice是环绕通知方法的执行载体,在它的invoke方法中,可以看到将一个实例是ReflectiveMethodInvocation对象封装成ProceedingJoinPoint对象(简称pjp对象),pjp对象将作为环绕通知方法的入参被使用,接着便是调用父类AbstractAspectJAdvice#invokeAdviceMethod(...)方法通过反射执行环绕通知方法的逻辑。
对于环绕通知来说ReflectiveMethodInvocation对象很关键的,其具体作用将在下文详解,但是我们可以提前记住ReflectiveMethodInvocation对象拥有代理对象、目标对象、目标对象的Class对象、目标方法的Method对象、目标方法入参、包含通知方法拦截器的集合六个重要属性,它正是负责执行通知方法、连接点方法的重要载体。
4.3.1.2 AspectJMethodBeforeAdvice
AspectJMethodBeforeAdvice是前置通知方法的执行载体,起作用的是它的before方法,在before方法里面可以看到,逻辑非常简单,就是通过反射调用前置通知方法。但是这个AspectJMethodBeforeAdvice还需要搭配MethodBeforeAdviceInterceptor这个前置通知拦截器方法使用。
MethodBeforeAdviceInterceptor前置通知拦截器,将在下文详细介绍,但是我们可以提前记住在MethodBeforeAdviceInterceptor#invoke(...)方法里面会先调用AspectJMethodBeforeAdvice#before(...)方法,后再执行其他通知方法、连接点方法。
4.3.1.3 AspectJAfterAdvice
AspectJAfterAdvice是后置通知方法的执行载体,在它的invoke方法里面,逻辑也比较简单,等其他通知方法、连接点方法都执行完以后,才会回溯到这里调用后置通知方法。并且由于它使用了try-finally语句机制,也就是说无论怎么样后置通知方法都会被执行。
4.3.1.4 AspectJAfterReturningAdvice
AspectJAfterReturningAdvice是调用成功后、无报错通知方法的执行载体,它起作用的是afterReturing(...)方法,同样它需要被包装为AfterReturningAdviceInterceptor拦截器。在AfterReturningAdviceInterceptor拦截器里面可以看到,需要先调用后面其他的通知方法、连接点方法才会回溯并最终调用AspectJAfterReturningAdvice#afterReturing(...)方法。
在AspectJAfterReturningAdvice#afterReturing(...)方法里面,将目标方法Method对象和目标方法返回值通过shouldInvokeOnReturnValueOf(method,returnValue)去判断返回值是否符合预期,符合预期才会最终调用后置通知方法。
当目标方法没有返回值、是void方法或者Method对象的返回类型和实际返回值类型匹配时,就会符合预期,调用后置通知方法。
4.3.1.5 AspectJAfterThrowingAdvice
AspectJAfterThrowingAdvice是调用失败后捕获到异常的通知方法的执行载体,在它的invoke(...)方法里面可以看到,它也是先通过proceed()方法执行其他拦截器方法、连接点方法后才会回溯到这里,如果捕获到异常并且异常属于Object.class的子类(默认情况之一)则会执行异常通知方法。
4.3.1.6 Advice对象调用顺序
Advice对象的排序其实就是Advisor对象的排序,在从切面类找出所有的Method对象时就已经做好了排序,它们的顺序是@Around>@Before>@After>@AfterReturing>@AfterThrowing。
这个顺序是有讲究的,因为通知方法的调用模式是链式调用,也就是一种责任链的调用。将在下文详细讲解其链式调用的细节,当看完以后就会知道为什么需要这么排序了,可能会有一种醍醐灌顶的感觉。
4.3.2 CglibMethodInvocation调用器
在DynamicAdvisedInterceptor重写的intercept方法里面,会传入CGLIB代理对象、目标方法Method对象、目标方法入参、目标方法代理对象,然后通过DynamicAdvisedInterceptor的成员变量advised获取目标Bean对象和Advisor对象构成的拦截器对象链(chain变量集合),将这些重要属性去创建一个CglibMethodInvocation调用器,这个调用器有两个重要方法:proceed方法和invokeJoinpoint方法。
4.3.2.1 ReflectiveMethodInvocation调用器
ReflectiveMethodInvocation对象有6个重要属性:代理对象、目标对象、目标对象的Class对象、目标方法的Method对象、目标方法入参、包含通知方法拦截器的集合。
4.3.2.2 ReflectiveMethodInvocation#proceed()
在CglibMethodInvocation#proceed()方法里调用了ReflectiveMethodInvocation#process()方法的,里面主要做的事情就是通过链式调用的方式,将所有“排序”好的通知方法拦截器获取出来,以链式调用的方法,逐个调用它们的invoke(MethodInvocation invocation)方法。
这是一种责任链的设计模式,跟WEB应用的Filter过滤器模式如出一辙,将所有过滤器/拦截器存放到一个数组集合中,以链式的方式逐个调用。
链式调用的模式,需要一个“不变”的对象供给所有拦截器使用,在这里可以看到拦截器的invoke方法需要一个MethodInvocation对象,正是ReflectiveMethodInvocation这个方法调用器,它实现了接口MethodInvocation,也就是说每个拦截器进入invoke方法时都能知道代理对象、目标对象、目标对象的Class对象、目标方法的Method对象、目标方法入参、包含通知方法拦截器的集合等重要属性。
在前面小节讲过,五种通知的Advice对象是排好序的,Around>Before>After>AfterReturning>AfterThrowing,这个顺序对于链式调用通知方法拦截器非常重要。那么这五种通知方法是怎么链接起来、链式调用的呢?由于文字比较难以描述,因此准备了一个大致的流程图。
此流程图列出的情况是最复杂的一种情况,即一个连接点(目标方法)被织入了五种通知方法。当进入ReflectiveMethodInvocation方法调用器时,会通过一个维护好的拦截器下标变量,初始值是-1,每次进入proceed()方法,下标变量都先自增1。
4.3.2.2.1 ExposeInvocationInterceptor拦截器
第一次执行ReflectiveMethodInvocation#proceed()方法时,下标变量自增1变成0,获取的是一个ExposeInvocationInterceptor拦截器,此拦截器仅仅作为一个拦截入口使用,其invoke方法只是将入参的ReflectiveMethodInvocation对象继续调用proceed()方法。
4.3.2.2.2 AspectJAroundAdvice拦截器
第二次进入ReflectiveMethodInvocation#proceed()方法时,此时下标变量自增1变成1,获取AspectJAroundAdvice拦截器对象,其invoke方法里面,将ReflectiveMethodInvocation对象先包装一个ProceedingJoinPoint对象作为环绕通知方法入参,接着调用环绕通知的Method方法,进入到实际逻辑中去。
这里要注意的是,环绕通知方法的第一个入参必须是ProceedingJoinPoint对象,并且要调用该入参的proceed()方法进入下一次递归,因为这个是链式调用,而AspectJAroundAdvice又是排在五种通知拦截器的第一位,只有我们手动调用环绕通知方法中的proceed()方法后,才能调用后面的通知拦截器和连接点方法。
4.3.2.2.3 MethodBeforeAdviceInterceptor拦截器
第二次进入ReflectiveMethodInvocation#proceed()方法时,此时下标变量自增1变成2,获取MethodBeforeAdviceInterceptor拦截器对象,其invoke方法里面,先调用了AspectJMethodBeforeAdvice#before(...)方法,从下图看到before(...)方法没什么特别的,也就是当实际的前置通知方法执行完成后会继续递归调用proceed()方法找到下一个拦截器并调用。
4.3.2.2.4 AspectJAfterAdvice拦截器
第三次进入ReflectiveMethodInvocation#proceed()方法时,此时下标变量自增1变成3,获取AspectJAfterAdvice拦截器对象,其invoke方法里面,逻辑很简单,直接先递归调用proceed()方法,由于AspectJAfterAdvice拦截器排第三,也就是后面两个AfterReturningAdviceInterceptor和AspectJAfterThrowingAdvice都调用完成后,才会回溯到AspectJAfterAdvice#invoke方法里。
并且由于这里的proceed()方法是通过try语句包裹的,后置通知方法的执行是被finally语句包裹的
也就是无论通知方法执行成功还是失败出现异常,后置通知方法一定会被执行的。
4.3.2.2.5 AfterReturningAdviceInterceptor拦截器
第四次进入ReflectiveMethodInvocation#proceed()方法时,此时下标变量自增1变成4,获取AfterReturningAdviceInterceptor拦截器对象,其invoke方法里面,还是依旧递归调用proceed方法,那么将会找到最后一个AspectJAfterThrowingAdvice拦截器,如果它没有抛出异常的话,会带着连接点的执行结果回溯到这里invoke方法,接着调用AspectJAfterReturningAdvice#afterReturning(...)方法。
在AspectJAfterReturningAdvice#afterReturning(...)方法里面,默认情况之一当目标方法返回值是void或者是Object的子类的时候,会调用成功调用后的通知方法。
4.3.2.2.6 AspectJAfterThrowingAdvice拦截器
第四次进入ReflectiveMethodInvocation#proceed()方法时,此时下标变量自增1变成4,获取AspectJAfterThrowingAdvice拦截器对象,其invoke方法里面,依旧继续先递归proceed方法,不过由于AspectJAfterThrowingAdvice已经是最后的通知方法拦截器,因此当进入proceed方法后会找到连接点方法,利用反射调用真正的目标方法。
如果捕获到异常,默认情况下捕获到的异常是Object的子类的时候(默认永远都成立,因为JAVA中所有类的超类都是Object),则会调用异常的通知方法。
4.3.2.3 CglibMethodInvocation#invokeJoinpoint()
当ReflectiveMethodInvocation#proceed()方法中的下标变量已经是整个拦截器集合大小减去1的数值,表示所有拦截器都找到并被调用了。最后通过CglibMethodInvocation#invokeJoinpoint()方法会找到连接点方法并利用反射调用它,即最终的目标方法正在此时此刻被执行。
至此,SpringAOP编程的调用链路就结束了,当拦截器方法都执行以后,总是会在最后时刻调用连接点方法,即目标方法。
5. 全文总结
Spring的AOP切面编程,底层是通过方法反射实现的。以CGLIB代理对象为例,先将目标对象、通知Advisor对象(包含通知方法Method对象)赋值给DynamicAdvisedInterceptor这个回调函数,然后代理对象访问目标方法(连接点)时,会带着目标方法的Method对象进入DynamicAdvisedInterceptor#intercept(...)方法中,在这里会将通知Advisor对象包装为拦截器对象(如果没有实现MethodInterceptor接口)。
然后创建一个CglibMethodInvocation的方法调用器,此调用器拥有代理对象、目标对象、目标对象的Class对象、目标方法Method对象、目标方法入参、拦截器对象、目标方法代理对象等6个重要入参。
CglibMethodInvocation继承了ReflectiveMethodInvocation类,这两个类的工作职责划分的很明确,在ReflectiveMethodInvocation#proceed()方法里面负责将所有拦截器都获取出来、通过链式调用的模式去执行它们重写的invoke(MethodInvocation invocation)方法,每个拦截器调用invoke方法时都会将当前的CglibMethodInvocation对象传入,这是链式调用的一个重要客观基础,链式调用中用到的资源需要从一个“不可变”的对象获取。
CglibMethodInvocation#invokeJoinpoint()方法则是等proceed()中所有拦截器都被调用后,最终通过反射去调用连接点方法、即目标方法。当invokeJoinpoint()方法完成后,针对此连接点的切面编程也随之完成。
这种链式调用拦截器的方式其实就是责任链的设计模式,和WEB项目中的过滤器调用模式如出一辙,将所有拦截器/过滤器放到一个有序集合中,以顺序的方式获取每个拦截器调用,调用结束后找到下一个拦截器继续调用,当所有拦截器都工作完成后,才会调用目标接口/方法。
标签:拦截器,SpringBoot,对象,通知,调用,源码,切面,AOP,方法 From: https://blog.csdn.net/qq_39108894/article/details/139608422