本篇文章详情很长,吐血输出,看完需花费一定时间,建议自己亲自动手跑跑
Spring AOP概述及使用
-
AOP指的是:在程序运行期间动态地将某段代码切入到指定方法指定位置进行运行的编程方式,相关设计模式为动态代理模式。
-
AOP是Spring的核心重要功能,本文将深入spring核心源码分析spring AOP的运行原理。在阅读本文前你需要理解spring的生命周期BeanPostProcess(后置处理器)工作时机。
下面我来体验下Spring AOP, 本文是把github上Spring源码拉到本地运行测试,这样做方便在源码上注释(也可以启用springBoot项目导入pom依赖在本地运行测试AOP)。注意:Spring官方使用的构建工具是gradle。
切面类MyAspect :
@Aspect
@Component
public class MyAspect {
@Pointcut("execution(* com.my.aop.TargetClass.targetMethod(..))")
public void pointcut() {
}
/**
* 前置通知
*/
@Before("execution(* com.my.aop.TargetClass.targetMethod(..))")
public void logStart(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
System.out.println("logStart()==>"+name+"....【args: "+ Arrays.asList(joinPoint.getArgs()) +"】");
}
/**
* 返回通知
* @param joinPoint
* @param result
*/
@AfterReturning(value = "execution(* com.my.aop.TargetClass.targetMethod(..))", returning = "result")
public void logReturn(JoinPoint joinPoint, Object result) {
String name = joinPoint.getSignature().getName();
System.out.println("logReturn()==>"+name+"....【args: "+ Arrays.asList(joinPoint.getArgs()) +"】【result: "+result+"】");
}
/**
* 后置通知
* @param joinPoint
*/
@After("execution(* com.my.aop.TargetClass.targetMethod(..))")
public void logAfter(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
System.out.println("logEnd()==>"+name+"....【args: "+ Arrays.asList(joinPoint.getArgs()) +"】");
}
@AfterThrowing(value = "execution(* com.my.aop.TargetClass.targetMethod(..))", throwing = "e")
public void logError(JoinPoint joinPoint, Exception e) {
String name = joinPoint.getSignature().getName();
System.out.println("logError()==>"+name+"....【args: "+ Arrays.asList(joinPoint.getArgs()) +"】【exception: "+e+"】");
}
}
-
该切面类包含:
前置通知(@Before):在目标方法被调用之前调用通知功能;
后置通知(@After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
返回通知(@AfterReturning):在目标方法成功执行之后调用通知;
异常通知(@AfterThrowing):在目标方法抛出异常后调用通知。
配置类开启@EnableAspectJAutoProxy
目标类目标方法:
/**
* @author fangYaJun
* @date 2021/7/28 15:50
*/
@Component
public class TargetClass {
public Integer targetMethod(String arg) {
System.out.println("目标方法执行,参数是:" + arg);
return 2;
}
}
测试类及运行结果:
public class MainTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class);
TargetClass targetClass = context.getBean("targetClass", TargetClass.class);
targetClass.targetMethod("传入的参数");
}
}
运行结果:
logStart()==>targetMethod....【args: [传入的参数]】
目标方法执行,参数是:传入的参数
logReturn()==>targetMethod....【args: [传入的参数]】【result: 2】
logEnd()==>targetMethod....【args: [传入的参数]】
从结果可以看出切面的各个通知方法已经织入到了目标方法的各个执行阶段。
通知的顺序在不同的Spring版本中有所不同:
Spring4.x
正常情况:@Before —-> 目标方法 —-> @After —-> @AfterReturning
异常情况:@Before —-> 目标方法 —-> @After —-> @AfterThrowing
Spring5.x
正常情况:@Before —-> 目标方法 —-> @AfterReturning —-> @After
异常情况:@Before —-> 目标方法 —-> @AfterThrowing —-> @After
下面我们来研究Spring AOP源码工作原理,其实熟悉spring源码的同学都知道,在spring的生命周期中会有各种BeanPostProcess(后置处理器)来介入增强目标对象,所以我们探究AOP功能的时候,spring想要通过AOP增强目标方法,我们可以猜出spring AOP肯定是通过某个BeanPostProcess后置处理器来实现AOP功能的。带着这个想法我们来探究源码:
@EnableAspectJAutoProxy 开启AOP功能
在spring需要开启AOP功能注解版只需要加上@EnableAspectJAutoProxy
注解就可以开启AOP功能。
源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
...
}
看源码可知该注解通过@Import导入了AspectJAutoProxyRegistrar
,我们来看看其源码:
我们发现AspectJAutoProxyRegistrar实现ImportBeanDefinitionRegistrar,了解spring源码的通知都知道通过实现ImportBeanDefinitionRegistrar接口可以往容器中注册组件,下面我们来看看AspectJAutoProxyRegistrar往容器注册了什么组件:
结合上面2段源码我们可以看出 AspectJAutoProxyRegistrar 往容器中注册了AnnotationAwareAspectJAutoProxyCreator
这个组件。
通过上述分析@EnableAspectJAutoProxy注解,往IOC容器中注册了类型为AnnotationAwareAspectJAutoProxyCreator
的Bean,下面我们只需要分析AnnotationAwareAspectJAutoProxyCreator组件怎么实现AOP功能即可。
AnnotationAwareAspectJAutoProxyCreator是什么
之前我们分析过spring 想要增强目标bean,一般都是通过BeanPostProcess后置处理器来实现,那么
AOP组件AnnotationAwareAspectJAutoProxyCreator到底是什么呢?我们来看看他的类继承图:
通过类图我们可以清楚的可以看到AnnotationAwareAspectJAutoProxyCreator
的父类AbstractAutoProxyCreator
实现了SmartInstantiationAwareBeanPostProcessor
和BeanFactoryAware接口。实现BeanFactoryAware用于在Bean初始化时注入BeanFactory,而SmartInstantiationAwareBeanPostProcessor接口的父类为InstantiationAwareBeanPostProcessor接口,该接口继承自BeanPostProcessor
接口。
实际上AnnotationAwareAspectJAutoProxyCreator就是一个BeanPostProcessor后置处理器。
熟悉spring生命周期的BeanPostProcessor后置处理器我们知道,BeanPostProcessor接口包含一些用于Bean实例化初始化前后进行自定义操作的方法,所以我们大体可以猜测出目标Bean的代理是在这些接口方法里实现的。我们只需要在这些方法打上断点我们就可以根据debug分析就可以了,本人比较熟悉该源码就直接分析了。
我们来看看AbstractAutoProxyCreator实现了SmartInstantiationAwareBeanPostProcessor,肯定要实现他的方法,
AbstractAutoProxyCreator实现的postProcessBeforeInstantiation和postProcessAfterInitialization方法:
上面2个是否是BeanPostProcessor接口的实现,在spring容器创建bean对象的时候会调用BeanPostProcessor的方法来实现对目标bean进行增强。而AOP功能就是在上述的方法中实现的,下面我们只需要分析该2个方法的调用过程就可以了。
增强器和AOP代理创建过程
增强器创建
在spring容器中创建bean的时候会执行各个BeanPostProcessor方法的回调,用来增强bean,
后置处理器AbstractAutoProxyCreator的postProcessBeforeInstantiation方法执行主要是提前构建增强器,
增强器构建是通过 shouldSkip()方法实现的,方法调用链源码如下,分析请看注释:
增强构建过程:
- 每个bean创建的时候都会调用postProcessBeforeInstantiation从而调用shouldSkip()
- shouldSkip()调用会先去获取增强器,如果没有缓存就去构建增强器
- 构建增强器会去IOC容器获取所有容器所有bean的名字遍历
- 判断当前遍历的bean是否有@Aspect注解 (是否是切面类)
- 获取到切面类后,遍历切面类所有的方法找到标有@Before @AfterReturning @After等注解的方法封装成增强器Advisor
- 缓存增强器并返回
AOP代理创建
ps: 要理解AOP代理必须掌握cglib代理原理
后置处理器AbstractAutoProxyCreator的postProcessAfterInitialization
方法AOP代码创建在此完成。
我们来看AOP代理创建的方法源码调用链,请看关键注释:
后置处理器的postProcessAfterInitialization,每个bean创建都会调用
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 如果需要就包装,也就是代理对象在此创建
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
做一些判断,根据当前bean是否有应用的增强器来决定是否创建代理
封装代理工厂,通过代理工厂来创建代理
根据目标类是否有接口来选择使用jdk代理还是cglib代理
使用cglib来创建代理并设置回调
我们来看看ciglib的代理设置的回调 Callbacks:
看回调源码可知设置回调DynamicAdvisedInterceptor
,并把增强传给该回调,在目标方法执行的时候,都会调用该回调的intercept
方法
该回调封装了增强器。
AOP代理创建过程:
- 先判断有没有应用当前bean的增强器,如果有就开始创建
- 创建proxyFactory代理工厂,给代理工厂封装增强器,目标对象,复制一些熟悉等
- 根据目标类是否有接口选择使用JDK代码还是Cglib代理(本文使用Cglib)
- 根据代理工厂创建Cglib代理对象,设置Cglib代理对象的回调
- 在回调中有DynamicAdvisedInterceptor回调,目标方法执行的时候会调用该方法的intercept,增强逻辑在此实现
通过以上分析,spring aop创建代理其实很简单,spring容器在创建每一个bean对象的时候会去调用后置处理器AbstractAutoProxyCreator
的postProcessAfterInitialization
方法,而该方法的逻辑是判断当前bean使用有应用的增强器,如果有就创建代理对象,而创建代理对象就是根据Cglib或者jdk代理创建,并设置回调DynamicAdvisedInterceptor,并返回代理对象。
如果目标方法执行就会调用DynamicAdvisedInterceptor的intercept方法,该intercept方法实现了AOP对面目标方法增强的逻辑,下面我们只需要研究 DynamicAdvisedInterceptor的intercept方法的执行过程即可。
AOP代理对象执行调用
AOP代理对象生成后,我们接着关注代理对象的目标方法执行时,通知方法是怎么被执行的。
我们知道cglib生成的代理对象执行目标方法的时候,会执行设置的回调DynamicAdvisedInterceptor的intercept方法,下面我们研究intercept方法:
在该回调intercept方法中有2个重要的地方,生产拦截器链,和执行拦截器链方法:
生成拦截器链MethodInterceptor
在intercept执行的时候,会把增强器advisors封装成拦截器链。获取拦截器链是通过intercept方法中下面的源代码实现的:
// 获取拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
先去缓存中获取拦截器链,没有就去创建
以下源码在创建拦截器链过程中,先遍历所有的增强器(创建代理对象的时候,封装好的增强器),看看当前增强器是否和目标方法匹配,匹配的话就根据增强器获取方法拦截器。
以下源码是把增强器变成方法拦截器的过程,先判断增强器是否是拦截器,是就直接添加,在增强器集合中默认第一个ExposeInvocationInterceptor就是(系统默认添加的增强器)该类型。
不是该类型就获取所有的增强器适配器AdvisorAdapter,遍历增强器适配器,判断该适配器是否适配该增强的通知,支持的话,就通过适配器把增强器变成适配器
增强器适配器有以下几种,
我们来看看MethodBeforeAdviceAdapter增强适配器的实现,有2个实现方法,一个是判断是否是该类型的,一个是把增强器变成MethodInterceptor类型的方法拦截器。
以上就是生成方法拦截器链的过程。
拦截器链第一个元素类型为ExposeInvocationInterceptor,是默认的拦截器,后面会介绍到它的作用。剩下四个依次为:MethodBeforeAdviceInterceptor、AspectJAfterAdvice、AfterReturningAdviceInterceptor和AspectJAfterThrowingAdvice,它们都是MethodInterceptor的实现类
生成拦截器链的过程
- 先检查缓存中有没有拦截器器,没有就创建
- 先获取当前所有的增强器集合遍历,看看当前增强器是否能匹配到当前目标方法,可以匹配就根据当前增强器创建拦截器
- 创建拦截器过程中会先去获取所有的增强器适配器,遍历增强器适配器,看看当前增强器适配是否支持当前增强器的通知Advice
- 如果支持,就根据当前支持的增强器适配把增强器的通知advice转成当前的advice,并封装成方法拦截器
- 把生产的方法拦截器保存到拦截器链中,并缓存
链式调用通知方法
上面生成好了拦截器链,下面我们来探究器调用过程
如下源码,会创建CglibMethodInvocation并执行proceed()方法,
继续调用父类的proceed方法
父类的proceed方法如下, 该方法维护有一个执行的拦截器索引, 该索引从-1开始 ,先索引++操作,然后根据索引取出方法拦截器,最后执行方法拦截器的invoke方法
先前我们知道所有的方法拦截器链如下:
-
那么第一个执行的ExposeInvocationInterceptor的invoke方法并把当前对象ExposeInvocationInterceptor传入,源码如下,看源码我们可以知道,会继续执行调用ReflectiveMethodInvocation的proceed方法,相当于递归调用,
-
当第二次调用ReflectiveMethodInvocation的proceed方法时,维护的索引会++currentInterceptorIndex,取出第二个方法拦截器MethodBeforeAdviceInterceptor,执行invok方法,源码如下: 前置通知执行,然后在调用proceed方法
-
第三次调用proceed方法时会根据维护的索引取出AspectJAfterAdvice拦截器,执行invok方法,
源码如下,继续调用proceed方法,
-
第四次调用processd方法会取出AfterReturningAdviceInterceptor拦截器,执行invok方法,源码如下,该源码会继续调用proceed方法,
-
第五次调用取出AspectJAfterThrowingAdvice拦截器执行invok方法,源码如下, 会继续调用proceed方法,
-
当第6次调用processd方法的时候,this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1 条件成立
条件成立后就执行目标方法,并返回方法的返回值.。
根据上面的链式调用,目标方法执行后返回,接着继续执行AspectJAfterThrowingAdvice.invok剩下的方法,依次类推然后在执行AfterReturningAdviceInterceptor.invok剩下的方法,此时返回通知执行,然后执行执行AspectJAfterAdvice后置通知的invok剩下的方法,此时,后置通知执行,返回整个链式调用结束.
整个链式调用过程如下流程图:
我们已经成功在目标方法的各个执行时期织入了通知方法
总结:
springAOP 源码其实没有想象的复杂,不知道你能否看懂,个人建议,一定要自己动手debug走一遍或多遍,理解增强器的创建时机,代理对象啥时候创建的,是怎么创建,以及执行目标方法的时候会把增强器封装成拦截器链,为什么要这么做,最后执行拦截器链的过程是怎么样的,一定要自己动手跑一遍才会深刻的理解AOP整个原理。