深入分析 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 创建成功;
-
-
创建完
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 创建之后调用
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 个
这 5 个包括一个默认的List<Object> interceptorList = new ArrayList<>(advisors.length);
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 个
-
如果没有拦截器链,直接执行目标方法
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
但是现在已经开始回溯了,因为方法都已经入栈了,此时执行 前置通知 中的方法,控制台输出如下:
然后执行 后置通知 :
执行 返回通知 :
最后所有的通知执行完毕,由于没有异常产生,所以没有执行异常通知:
-
- 如果没有拦截器或者是最后一个拦截器就执行目标方法
本文地址:https://blog.csdn.net/weixin_43941364/article/details/107322784
上一篇: 还没提到容貌
下一篇: 网络流初学之网络流24题