深入分析 Spring 基于注解的 AOP 实现原理
一、AOP 的基本使用
AOP 的使用分为三步走:
- 将业务逻辑组件和切面类都加入到容器中:告诉 Spring 哪个是切面类;
@Aspect
- 在切入类上的每一个通知方法上标注通知注解:告诉 Spring 何时何地运行(切入点表达式)
@Pointcut
、@Before
~~~ - 在配置类上开启基于注解的
AOP
模式;@EnableAspectJAutoProxy
使用 aop
相关的注解必须先导入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
下面以一个计算器的例子来介绍 AOP 的基本使用:
1、待增强类
这是一个简单的计算器类,为了能够演示异常,所以创建了一个有除法的方法。
public class MathCalculator {
/**
* 除法
*
* @param i 被除数
* @param j 除数
* @return 返回运算结果
*/
public int div(int i, int j) {
return i / j;
}
}
2、增强类
我们想通过 AOP 实现记录除法运行的日志信息,所以新建一个 Log 类。
@Aspect
public class LogAspect {
/**
* 抽取出来的切入点表达式
*/
@Pointcut("execution(* top.wsuo.aop.MathCalculator.*(..))")
public void pointCut() {
}
/**
* 前置通知
*
* @param joinPoint 连接点
*/
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName() + "运行...参数列表是:{" + Arrays.toString(joinPoint.getArgs()) + "}");
}
/**
* 后置通知
*
* @param joinPoint 连接点
*/
@After("pointCut()")
public void logEnd(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName() + "结束...");
}
/**
* 返回通知
*
* @param joinPoint 连接点
* @param result 执行结果
*/
@AfterReturning(value = "pointCut()", returning = "result")
public void logReturn(JoinPoint joinPoint, Object result) {
System.out.println(joinPoint.getSignature().getName() + "正常返回...运行结果:{" + result + "}");
}
/**
* 异常通知
*
* @param joinPoint 连接点
* @param exception 异常信息
*/
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void logException(JoinPoint joinPoint, Exception exception) {
System.out.println(joinPoint.getSignature().getName() + "出现异常...异常信息:{" + exception.getMessage() + "}");
}
}
3、配置类
最后在配置类上开启注解版 AOP,同时注册组件到容器中。
@Configuration
// Spring 中有很多 EnableXXX 代表开启某一项功能: 取代了配置
@EnableAspectJAutoProxy
public class MainConfigOfAOP {
@Bean
public MathCalculator mathCalculator() {
return new MathCalculator();
}
@Bean
public LogAspect logAspect() {
return new LogAspect();
}
}
4、测试
测试及运行结果。
@Test
public void test12() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
MathCalculator calculator = context.getBean(MathCalculator.class);
System.out.println(calculator.div(4, 2));
}
二、注解 AOP 的实现原理
1、@EnableAspectJAutoProxy
整个 AOP 要想起作用,必须加上 @EnableAspectJAutoProxy
注解,这个注解的作用是什么呢?
点进去该注解:
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
看到了要导入一个 AspectJAutoProxyRegistrar
类组件,它继承自一个接口 ImportBeanDefinitionRegistrar
,这个接口我们之前讲过,他是添加自定义组件的接口,在这里:https://blog.csdn.net/weixin_43941364/article/details/107243459。
这说明 @EnableAspectJAutoProxy
注解的作用就是给容器中添加组件, 追踪 AspectJAutoProxyRegistrar
类的方法,发现有这么一段代码:
这段代码的作用就是先看一下容器中有没有
public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
"org.springframework.aop.config.internalAutoProxyCreator";
internalAutoProxyCreator
这个类,同时我们看到在调用上述方法的时候,传入了一个类型:
该类型是 AspectJAwareAdvisorAutoProxyCreator
实体类,看一下该类的继承结构。
2、AspectJAwareAdvisorAutoProxyCreator
可以看到该类实现了一个接口,就是 BeanPostProcessor
接口,他是一个 后置处理器 。这个接口是 Bean 生命周期相关的接口。
所以我们要重点分析一下该类的执行顺序,接下来 打断点调试 之前举的计算器的例子。
3、容器的创建流程
从容器启动开始分析:
@Test
public void test12() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
MathCalculator calculator = context.getBean(MathCalculator.class);
System.out.println(calculator.div(4, 2));
}
首先传入配置类,创建 IOC 容器;然后注册配置类,调用 refresh
方法刷新容器;
3.1、注册后置处理器
使用 registerBeanPostProcessors(beanFactory)
注册 Bean 的后置处理器,来拦截 Bean 的创建
- 先获取 IOC 容器中已经定义了的需要创建对象的所有
BeanPostProcessor
- 给容器中加别的
BeanPostProcessor
- 优先注册实现了
PriorityOrdered
接口的BeanPostProcessor
- 再注册实现了
Ordered
接口的BeanPostProcessor
- 之后注册没实现
Ordered
接口的BeanPostProcessor
- 最后
registerBeanPostProcessors
执行,注册BeanPostProcessor
,实际上就是创建BeanPostProcessor
对象,保存在容器中。 创建org.springframework.aop.config.internalAutoProxyCreator
的BeanPostProcessor
,它的类型是AnnotationAwareAspectJAutoProxyCreator
。- 首先创建 Bean 的实例
instanceWrapper = createBeanInstance(beanName, mbd, args)
- 然后给属性赋值
populateBean(beanName, mbd, instanceWrapper)
- 最后初始化 Bean
exposedObject = initializeBean(beanName, exposedObject, mbd)
-
invokeAwareMethods(beanName, bean)
初始化 Aware 接口的方法回调; -
applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)
执行后置处理器的postProcessBeforeInitialization
方法; -
invokeInitMethods(beanName, wrappedBean, mbd)
执行自定义初始化方法; -
applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)
执行后置处理器的postProcessAfterInitialization
方法;
-
- 到此为止
AnnotationAwareAspectJAutoProxyCreator
类型的 BeanPostProcessor 创建成功;
- 首先创建 Bean 的实例
- 创建完
BeanPostProcessor
对象之后,注册到beanFactory
中 registerBeanPostProcessors(beanFactory, internalPostProcessors) 注册方法的实现: for (BeanPostProcessor postProcessor : postProcessors) { beanFactory.addBeanPostProcessor(postProcessor); }
到此为止 AnnotationAwareAspectJAutoProxyCreator
就算是创建成功了,而它作为一个后置处理器,肯定有作用,下面分析一下他作为后置处理器做了什么事情。
注意 AnnotationAwareAspectJAutoProxyCreator
是 InstantiationAwareBeanPostProcessor
类型的后置处理器。
3.2、初始化剩下的单实例 Bean
finishBeanFactoryInitialization(beanFactory)
完成 BeanFactory 的初始化工作
- 遍历获取容器中所有的 Bean,依次创建对象:
getBean、doGetBean、getSingleton
; - 创建 Bean(业务逻辑组件和切面组件);
- 先从缓存中获取当前 Bean,如果能获取到,说明 Bean 是之前创建过的,直接使用,否则再创建; 先从缓存中检查有没有这个 Bean // Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName); 如果 他等于 null,才会继续执行下面的方法 sharedInstance = getSingleton(beanName, () -> { 只要创建好的 Bean 都会被缓存起来,这也是 Spring 保证单实例 Bean 的实现原理。
-
createBean
创建 Bean:AnnotationAwareAspectJAutoProxyCreator
会在任何 Bean 创建完成之前先尝试返回 Bean 的实例,其实就是拦截; BeanPostProcessor 是在对象创建Bean完成初始化前后调用的,而 InstantiationAwareBeanPostProcessor 是在创建Bean实例之前先尝试用后置处理器返回对象的。-
Object bean = resolveBeforeInstantiation(beanName, mbdToUse)
,这句话的意思是希望后置处理器返回一个代理对象,如果能返回代理对象就使用,如果不能就继续;这个方法的实现就是拿到所有后置处理器,如果是 InstantiationAwareBeanPostProcessor,就执行 postProcessBeforeInstantiation 方法 bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName); if (bean != null) { bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); } -
Object beanInstance = doCreateBean(beanName, mbdToUse, args)
,真正的去创建一个 Bean,和之前3.6
的流程是一样的。
-
所以
AnnotationAwareAspectJAutoProxyCreator
会在任何 Bean 创建完成之前先尝试返回 Bean 的实例,因为他实现了InstantiationAwareBeanPostProcessor
接口,这个接口有两个方法,一个是postProcessBeforeInstantiation
,另一个是postProcessAfterInstantiation
,这两个方法是在 Bean 创建完成前后执行的,而BeanPostProcessor
接口的两个方法是在创建完成并且初始化前后调用的。
- 在每一个 Bean 创建之前调用
postProcessBeforeInstantiation
方法,在这一步找出需要增强的 Bean;- 判断当前 Bean 是否在
advisedBeans
中(它保存了所有需要增强的 Bean ) - 判断当前 Bean 是否是基础类型
isInfrastructureClass
或者是切面。
- 判断是否该跳过
shouldSkip
:源码如下 @Override protected boolean shouldSkip(Class<?> beanClass, String beanName) { // TODO: Consider optimization by caching the list of the aspect names List<Advisor> candidateAdvisors = findCandidateAdvisors(); for (Advisor advisor : candidateAdvisors) { if (advisor instanceof AspectJPointcutAdvisor && ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) { return true; } } return super.shouldSkip(beanClass, beanName); } 首先获取所有候选的增强器,增强器就是切面里面的通知方法; 0 = {InstantiationModelAwarePointcutAdvisorImpl@2180} "InstantiationModelAwarePointcutAdvisor: expression [pointCut()]; advice method [public void top.wsuo.aop.LogAspect.logStart(org.aspectj.lang.JoinPoint)]; perClauseKind=SINGLETON" 1 = {InstantiationModelAwarePointcutAdvisorImpl@2181} "InstantiationModelAwarePointcutAdvisor: expression [pointCut()]; advice method [public void top.wsuo.aop.LogAspect.logEnd(org.aspectj.lang.JoinPoint)]; perClauseKind=SINGLETON" 2 = {InstantiationModelAwarePointcutAdvisorImpl@2182} "InstantiationModelAwarePointcutAdvisor: expression [pointCut()]; advice method [public void top.wsuo.aop.LogAspect.logReturn(org.aspectj.lang.JoinPoint,java.lang.Object)]; perClauseKind=SINGLETON" 3 = {InstantiationModelAwarePointcutAdvisorImpl@2183} "InstantiationModelAwarePointcutAdvisor: expression [pointCut()]; advice method [public void top.wsuo.aop.LogAspect.logException(org.aspectj.lang.JoinPoint,java.lang.Exception)]; perClauseKind=SINGLETON" 可以看到这就是我们的那几个通知方法。
只不过他把这些通知方法包装成为了一个
List<Advisor> candidateAdvisors
集合,每一个封装的通知方法的增强器是InstantiationModelAwarePointcutAdvisor
。 这段代码的逻辑就是判断每一个增强器是否是AspectJPointcutAdvisor
类型的,如果是返回 true ,如果不是就返回 false ; - 判断当前 Bean 是否在
- 在 Bean 创建之后调用
postProcessAfterInitialization
方法,在这一步增强需要增强的 Bean: @Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (!this.earlyProxyReferences.contains(cacheKey)) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }- 在
wrapIfNecessary
方法中,获取当前 Bean 的所有增强器(通知方法),判断是否需要包装(增强)。 // Create proxy if we have advice. Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); @Nullable protected Object[] getAdvicesAndAdvisorsForBean( Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) { List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName); if (advisors.isEmpty()) { return DO_NOT_PROXY; } return advisors.toArray(); } 那么他是 怎么找的增强器 呢 ?我们继续查看方法调用。 protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) { List<Advisor> candidateAdvisors = findCandidateAdvisors(); List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); extendAdvisors(eligibleAdvisors); if (!eligibleAdvisors.isEmpty()) { eligibleAdvisors = sortAdvisors(eligibleAdvisors); } return eligibleAdvisors; } 这一步是找到候选的所有增强器,即哪些通知方法是需要切入当前 Bean 方法的。 然后下面的方法findAdvisorsThatCanApply
是获取到能在当前 Bean 使用的增强器,它使用了canApply
方法判断。 public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) { if (advisor instanceof IntroductionAdvisor) { return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass); } else if (advisor instanceof PointcutAdvisor) { PointcutAdvisor pca = (PointcutAdvisor) advisor; return canApply(pca.getPointcut(), targetClass, hasIntroductions); } else { // It doesn't have a pointcut so we assume it applies. return true; } } 最后是给这些增强器排序。
- 保存当前 Bean 到
advisedBeans
中; - 如果当前 Bean 需要增强,创建当前 Bean 的代理对象;
- 获取所有的增强器(通知方法)
- 保存到
proxyFactory
中; proxyFactory.getProxy(getProxyClassLoader()); - 创建代理对象,Spring 自动决定使用哪一种动态代理
@Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces( Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }
可以看到有两种自动代理,分别是
JdkDynamicAopProxy
和ObjenesisCglibAopProxy
。 此时创建完成之后,代理对象为 top.wsuo.aop.MathCalculator,通过 Spring 增强的类型。
4.所以最后
wrapIfNecessary(bean, beanName, cacheKey)
方法就是返回了当前组件使用的cglib
增强了的代理对象。 5.以后容器中获取到的就是这个组件的 代理对象 ,执行目标方法的时候,代理对象就会执行通知方法的流程。 - 在
3.3、执行目标方法
我们在测试方法上面打断点,看看除法运行的时候都有啥:
观察到此时的对象已经是 cglib 代理之后的对象了,这个对象中保存了详细信息,比如所有的增强器和目标对象。
- 下面进入到
org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor
的intercept
方法中。 本来是想执行目标的,但是代理之后就要先被拦截一下。 - 然后根据
ProxyFactory
对象获取将要执行的目标拦截器链; List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass) 拦截器链是如何获取的? 主要是在getInterceptorsAndDynamicInterceptionAdvice
方法中。- 首先创建一个集合保存所有的拦截器,默认有 5 个List<Object> interceptorList = new ArrayList<>(advisors.length);
这 5 个包括一个默认的
org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR
和 4 个增强器。
- 遍历所有的增强器,将其转为
Interceptor
。for (Advisor advisor : advisors) registry.getInterceptors(advisor) - 将增强器转为
List<MethodInterceptor>
:- 如果本来就是
MethodInterceptor
,则直接加到集合中; - 如果不是,则使用
AdvisorAdapter
适配器转为MethodInterceptor
。 怎么转的呢,其实这里就是强转然后包装了一下,源码如下。 @Override public MethodInterceptor getInterceptor(Advisor advisor) { AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice(); return new AfterReturningAdviceInterceptor(advice); } 可以看到这就是 最终通知 。 - 转化完成返回
MethodInterceptor
数组。
- 如果本来就是
- 所以 拦截器链 就是每一个通知方法又被包装成为方法拦截器,利用
MethodInterceptor
的机制控制执行顺序。
- 首先创建一个集合保存所有的拦截器,默认有 5 个List<Object> interceptorList = new ArrayList<>(advisors.length);
这 5 个包括一个默认的
- 如果没有拦截器链,直接执行目标方法 retVal = methodProxy.invoke(target, argsToUse);
- 如果有拦截器链,把需要执行的目标对象,目标方法,拦截器链等信息传入创建一个
CglibMethodInvocation
对象,并调用它的proceed
方法。 // We need to create a method invocation... retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed(); - 拦截器链的触发过程,触发方法就是
proceed
,所以只需要分析一下这个方法即可。- 如果没有拦截器或者是最后一个拦截器就执行目标方法if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return invokeJoinpoint(); }
- 如果有拦截器就链式的获取每一个拦截器,拦截器执行
invoke
方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行。这里的返回值是还是拦截器,传入的是这个拦截器本身,每次调用都会减少一个长度,并且改变当前的拦截器,所以执行顺序是栈式的结构。 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);- 首先执行到
interceptorOrInterceptionAdvice
的实现类ExposeInvocationInterceptor
,就是方法本身;
跟进去执行的是
org.springframework.aop.interceptor.ExposeInvocationInterceptor
的invoke
方法,该方法的实现如下: @Override public Object invoke(MethodInvocation mi) throws Throwable { MethodInvocation oldInvocation = invocation.get(); invocation.set(mi); try { return mi.proceed(); } finally { invocation.set(oldInvocation); } } 这一块代码的核心业务是放在finally
中的,所以肯定会执行,下面接着跟进去proceed
方法:- 这个时候再次来到 proceed 方法,此时的下标变为 0,执行到
AspectJAfterThrowingAdvice
,即异常通知;
跟进去执行
org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
的invoke
方法,该方法的实现如下: @Override public Object invoke(MethodInvocation mi) throws Throwable { try { return mi.proceed(); } catch (Throwable ex) { if (shouldInvokeOnThrowing(ex)) { invokeAdviceMethod(getJoinPointMatch(), null, ex); } throw ex; } } 注意到这里有异常的捕捉,所以异常发生时是在这里处理的,没有异常则不会执行,继续跟进proceed
方法。- 这个时候再次来到 proceed 方法,此时的下标变为 1,执行到
AfterReturningAdviceInterceptor
,即返回(最终)通知;
跟进去执行
org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor
的invoke
方法,该方法的实现如下: @Override public Object invoke(MethodInvocation mi) throws Throwable { Object retVal = mi.proceed(); this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis()); return retVal; } 就是先执行其他的,然后执行返回通知的内容,继续跟进proceed
方法。- 这个时候再次来到 proceed 方法,此时的下标变为 2,执行到
AspectJAfterAdvice
,即后置通知;
跟进去执行
org.springframework.aop.aspectj.AspectJAfterAdvice
的invoke
方法,该方法的实现如下: @Override public Object invoke(MethodInvocation mi) throws Throwable { try { return mi.proceed(); } finally { invokeAdviceMethod(getJoinPointMatch(), null, null); } } 就是先执行后面的前置通知,然后执行后置通知的内容,继续跟进proceed
方法。- 这个时候再次来到 proceed 方法,此时的下标变为 3,执行到
MethodBeforeAdviceInterceptor
,即前置通知;
跟进去执行
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor
的invoke
方法,该方法的实现如下: @Override public Object invoke(MethodInvocation mi) throws Throwable { this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis()); return mi.proceed(); } 这块代码是先执行自己的业务,再往下传递,我们继续跟进proceed
方法:这个时候再次来到 proceed 方法,此时的下标变为 4,还是执行
MethodBeforeAdviceInterceptor
但是现在已经开始回溯了,因为方法都已经入栈了,此时执行 前置通知 中的方法,控制台输出如下:
然后执行 后置通知 :
执行 返回通知 :
最后所有的通知执行完毕,由于没有异常产生,所以没有执行异常通知:
- 首先执行到
- SDP(8):文本式数据库-MongoDB-Scala基本操作
- SDP(7):Cassandra- Cassandra-Engine:Streaming
- TensorFlow实现神经网络入门篇
- 27.反射,类加载器,设计模式,jdk新特性
- SDP(6):分布式数据库运算环境- Cassandra-Engine
- 配置dg broker的问题分析及修复(r6笔记第84天)
- SDP(5):ScalikeJDBC- JDBC-Engine:Streaming
- SDP(4):ScalikeJDBC- JDBC-Engine:Updating
- SDP(3):ScalikeJDBC- JDBC-Engine:Fetching
- SDP(2):ScalikeJDBC-Connection Pool Configuration
- 使用外部表关联MySQL数据到Oracle(r6笔记第100天)
- 使用selenium模块模拟浏览器爬去网页,并进行点击定位内容笔记
- python 报错'gbk' codec can't encode character 'ue5d1' in position 0:
- python文件打开方式详解——a、a+、r+、w+区别
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 无人驾驶环境感知 | 01 车道线检测网络LanNet原理及实现
- 对象存储COS-数据处理能力升级!“组合拳”助力存储新时代!
- 基于python图像处理API的使用示例
- python 的topk算法实例
- android使用viewpager计算偏移量实现选项卡功能
- Android画板开发之撤销反撤销功能
- Android实现复制Assets文件到SD卡
- Android画板开发之添加背景和保存画板内容为图片
- 直播插件体系设计
- linux 下selenium chrome使用详解
- WeTest:五年沉淀,打造游戏品质的坚实后盾
- ES5新增方法
- 筛选商品案例
- volatile关键字在Android中到底有什么用?
- Python无头爬虫下载文件的实现