深入分析 Spring 基于注解的 AOP 实现原理

2020-07-31 16:11:01 浏览数 (1)

一、AOP 的基本使用

AOP 的使用分为三步走:

  1. 将业务逻辑组件和切面类都加入到容器中:告诉 Spring 哪个是切面类;@Aspect
  2. 在切入类上的每一个通知方法上标注通知注解:告诉 Spring 何时何地运行(切入点表达式)@Pointcut@Before~~~
  3. 在配置类上开启基于注解的 AOP 模式;@EnableAspectJAutoProxy

使用 aop 相关的注解必须先导入依赖:

代码语言:javascript复制
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.1.2.RELEASE</version>
</dependency>

下面以一个计算器的例子来介绍 AOP 的基本使用:

1、待增强类

这是一个简单的计算器类,为了能够演示异常,所以创建了一个有除法的方法。

代码语言:javascript复制
public class MathCalculator {

    /**
     * 除法
     *
     * @param i 被除数
     * @param j 除数
     * @return 返回运算结果
     */
    public int div(int i, int j) {
        return i / j;
    }
}

2、增强类

我们想通过 AOP 实现记录除法运行的日志信息,所以新建一个 Log 类。

代码语言:javascript复制
@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,同时注册组件到容器中。

代码语言:javascript复制
@Configuration
// Spring 中有很多 EnableXXX 代表开启某一项功能: 取代了配置
@EnableAspectJAutoProxy
public class MainConfigOfAOP {

    @Bean
    public MathCalculator mathCalculator() {
        return new MathCalculator();
    }

    @Bean
    public LogAspect logAspect() {
        return new LogAspect();
    }
}

4、测试

测试及运行结果。

代码语言:javascript复制
@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 注解,这个注解的作用是什么呢?

点进去该注解:

代码语言:javascript复制
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

看到了要导入一个 AspectJAutoProxyRegistrar 类组件,它继承自一个接口 ImportBeanDefinitionRegistrar,这个接口我们之前讲过,他是添加自定义组件的接口,在这里:https://blog.csdn.net/weixin_43941364/article/details/107243459。

这说明 @EnableAspectJAutoProxy 注解的作用就是给容器中添加组件, 追踪 AspectJAutoProxyRegistrar 类的方法,发现有这么一段代码:

这段代码的作用就是先看一下容器中有没有

代码语言:javascript复制
public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
			"org.springframework.aop.config.internalAutoProxyCreator";

internalAutoProxyCreator 这个类,同时我们看到在调用上述方法的时候,传入了一个类型:

该类型是 AspectJAwareAdvisorAutoProxyCreator 实体类,看一下该类的继承结构。

2、AspectJAwareAdvisorAutoProxyCreator

可以看到该类实现了一个接口,就是 BeanPostProcessor 接口,他是一个 后置处理器 。这个接口是 Bean 生命周期相关的接口。

所以我们要重点分析一下该类的执行顺序,接下来 打断点调试 之前举的计算器的例子。


3、容器的创建流程

从容器启动开始分析:

代码语言:javascript复制
@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 的创建

  1. 先获取 IOC 容器中已经定义了的需要创建对象的所有 BeanPostProcessor
  1. 给容器中加别的 BeanPostProcessor
  2. 优先注册实现了 PriorityOrdered 接口的 BeanPostProcessor
  3. 再注册实现了 Ordered 接口的 BeanPostProcessor
  4. 之后注册没实现 Ordered 接口的 BeanPostProcessor
  5. 最后 registerBeanPostProcessors 执行,注册 BeanPostProcessor ,实际上就是创建 BeanPostProcessor 对象,保存在容器中。 创建 org.springframework.aop.config.internalAutoProxyCreatorBeanPostProcessor,它的类型是 AnnotationAwareAspectJAutoProxyCreator
代码语言:txt复制
1.  首先创建 Bean 的实例 `instanceWrapper = createBeanInstance(beanName, mbd, args)`
代码语言:txt复制
2.  然后给属性赋值 `populateBean(beanName, mbd, instanceWrapper)`
代码语言:txt复制
3.  最后初始化 Bean `exposedObject = initializeBean(beanName, exposedObject, mbd)`
代码语言:txt复制
    - `invokeAwareMethods(beanName, bean)` 初始化 Aware 接口的方法回调;
    - `applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)` 执行后置处理器的 **`postProcessBeforeInitialization`** 方法;
    - `invokeInitMethods(beanName, wrappedBean, mbd)` 执行自定义初始化方法;
    - `applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)` 执行后置处理器的 **`postProcessAfterInitialization`** 方法;
4.  到此为止 `AnnotationAwareAspectJAutoProxyCreator` 类型的 BeanPostProcessor 创建成功;
  1. 创建完 BeanPostProcessor 对象之后,注册到 beanFactory 中 registerBeanPostProcessors(beanFactory, internalPostProcessors) 注册方法的实现: for (BeanPostProcessor postProcessor : postProcessors) { beanFactory.addBeanPostProcessor(postProcessor); }

到此为止 AnnotationAwareAspectJAutoProxyCreator 就算是创建成功了,而它作为一个后置处理器,肯定有作用,下面分析一下他作为后置处理器做了什么事情。

注意 AnnotationAwareAspectJAutoProxyCreatorInstantiationAwareBeanPostProcessor 类型的后置处理器。

3.2、初始化剩下的单实例 Bean

finishBeanFactoryInitialization(beanFactory) 完成 BeanFactory 的初始化工作

  1. 遍历获取容器中所有的 Bean,依次创建对象:getBean、doGetBean、getSingleton
  2. 创建 Bean(业务逻辑组件和切面组件);
    • 先从缓存中获取当前 Bean,如果能获取到,说明 Bean 是之前创建过的,直接使用,否则再创建; 先从缓存中检查有没有这个 Bean // Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName); 如果 他等于 null,才会继续执行下面的方法 sharedInstance = getSingleton(beanName, () -> { 只要创建好的 Bean 都会被缓存起来,这也是 Spring 保证单实例 Bean 的实现原理。
代码语言:txt复制
-  `createBean` 创建 Bean:`AnnotationAwareAspectJAutoProxyCreator` 会在任何 Bean 创建完成之前先尝试返回 Bean 的实例,其实就是拦截;  BeanPostProcessor 是在对象创建Bean完成初始化前后调用的,而 InstantiationAwareBeanPostProcessor 是在创建Bean实例之前先尝试用后置处理器返回对象的。
 
代码语言:txt复制
    - `Object bean = resolveBeforeInstantiation(beanName, mbdToUse)`,这句话的意思是希望后置处理器返回一个代理对象,如果能返回代理对象就使用,如果不能就继续;这个方法的实现就是拿到所有后置处理器,如果是 InstantiationAwareBeanPostProcessor,就执行 postProcessBeforeInstantiation 方法 bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);  if (bean != null) { 	bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); }
代码语言:txt复制
    - `Object beanInstance = doCreateBean(beanName, mbdToUse, args)`,真正的去创建一个 Bean,和之前 **`3.6`** 的流程是一样的。

所以AnnotationAwareAspectJAutoProxyCreator 会在任何 Bean 创建完成之前先尝试返回 Bean 的实例,因为他实现了 InstantiationAwareBeanPostProcessor 接口,这个接口有两个方法,一个是postProcessBeforeInstantiation,另一个是postProcessAfterInstantiation,这两个方法是在 Bean 创建完成前后执行的,而 BeanPostProcessor 接口的两个方法是在创建完成并且初始化前后调用的。

  1. 在每一个 Bean 创建之前调用 postProcessBeforeInstantiation方法,在这一步找出需要增强的 Bean;
代码语言:txt复制
-  判断当前 Bean 是否在 `advisedBeans` 中(它保存了所有需要增强的 Bean )
代码语言:txt复制
-  判断当前 Bean 是否是基础类型 `isInfrastructureClass`

或者是切面。

代码语言:txt复制
-  判断是否该跳过 `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

  1. 在 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; }
代码语言:txt复制
-  在 **`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(); }
 那么他是 
代码语言:txt复制
1. 保存当前 Bean 到 `advisedBeans` 中;
2. 如果当前 Bean 需要增强,创建当前 Bean 的代理对象; 
    -  获取所有的增强器(通知方法)
代码语言:txt复制
    -  保存到 `proxyFactory` 中; proxyFactory.getProxy(getProxyClassLoader());
 
代码语言:txt复制
    -  创建代理对象,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); 	} }
 可以看到有两种自动代理,分别是 

4.所以最后 wrapIfNecessary(bean, beanName, cacheKey) 方法就是返回了当前组件使用的 cglib 增强了的代理对象。

5.以后容器中获取到的就是这个组件的 代理对象 ,执行目标方法的时候,代理对象就会执行通知方法的流程。

3.3、执行目标方法

我们在测试方法上面打断点,看看除法运行的时候都有啥:

观察到此时的对象已经是 cglib 代理之后的对象了,这个对象中保存了详细信息,比如所有的增强器和目标对象。

  1. 下面进入到 org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptorintercept 方法中。 本来是想执行目标的,但是代理之后就要先被拦截一下。
  2. 然后根据 ProxyFactory 对象获取将要执行的目标拦截器链; List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass) 拦截器链是如何获取的? 主要是在 getInterceptorsAndDynamicInterceptionAdvice 方法中。
代码语言:txt复制
- 首先创建一个集合保存所有的拦截器,默认有 5 个List<Object> interceptorList = new ArrayList<>(advisors.length); 这 5 个包括一个默认的 
代码语言:txt复制
- 遍历所有的增强器,将其转为 `Interceptor`。for (Advisor advisor : advisors) 	registry.getInterceptors(advisor)
代码语言:txt复制
- 将增强器转为 `List<MethodInterceptor>`: 
    -  如果本来就是 `MethodInterceptor`,则直接加到集合中;
代码语言:txt复制
    -  如果不是,则使用 `AdvisorAdapter` 适配器转为 `MethodInterceptor`。 怎么转的呢,其实这里就是强转然后包装了一下,源码如下。
 @Override public MethodInterceptor getInterceptor(Advisor advisor) { 	AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice(); 	return new AfterReturningAdviceInterceptor(advice); }
 可以看到这就是 
代码语言:txt复制
    -  转化完成返回 `MethodInterceptor` 数组。
代码语言:txt复制
- 所以 **拦截器链** 就是每一个通知方法又被包装成为方法拦截器,利用 `MethodInterceptor` 的机制控制执行顺序。如果没有拦截器链,直接执行目标方法
retVal = methodProxy.invoke(target, argsToUse);
  1. 如果有拦截器链,把需要执行的目标对象,目标方法,拦截器链等信息传入创建一个 CglibMethodInvocation 对象,并调用它的 proceed 方法。 // We need to create a method invocation... retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
  2. 拦截器链的触发过程,触发方法就是 proceed,所以只需要分析一下这个方法即可。
代码语言:txt复制
- 如果没有拦截器或者是最后一个拦截器就执行目标方法if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { 	return invokeJoinpoint(); }
代码语言:txt复制
- 如果有拦截器就链式的获取每一个拦截器,拦截器执行 `invoke` 方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行。这里的返回值是还是拦截器,传入的是这个拦截器本身,每次调用都会减少一个长度,并且改变当前的拦截器,所以执行顺序是栈式的结构。 return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
代码语言:txt复制
    -  首先执行到 `interceptorOrInterceptionAdvice` 的实现类 **`ExposeInvocationInterceptor`**,就是方法本身;

跟进去执行的是 org.springframework.aop.interceptor.ExposeInvocationInterceptorinvoke 方法,该方法的实现如下:

@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 方法:

代码语言:txt复制
    -  这个时候再次来到 proceed 方法,此时的下标变为 0,执行到 **`AspectJAfterThrowingAdvice`**,即异常通知;

跟进去执行 org.springframework.aop.aspectj.AspectJAfterThrowingAdviceinvoke 方法,该方法的实现如下:

@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 方法。

代码语言:txt复制
    -  这个时候再次来到 proceed 方法,此时的下标变为 1,执行到 **`AfterReturningAdviceInterceptor`**,即返回(最终)通知;

跟进去执行 org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptorinvoke 方法,该方法的实现如下:

@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 方法。

代码语言:txt复制
    -  这个时候再次来到 proceed 方法,此时的下标变为 2,执行到 **`AspectJAfterAdvice`**,即后置通知;

跟进去执行 org.springframework.aop.aspectj.AspectJAfterAdviceinvoke 方法,该方法的实现如下:

@Override public Object invoke(MethodInvocation mi) throws Throwable { try { return mi.proceed(); } finally { invokeAdviceMethod(getJoinPointMatch(), null, null); } }

就是先执行后面的前置通知,然后执行后置通知的内容,继续跟进 proceed 方法。

代码语言:txt复制
    -  这个时候再次来到 proceed 方法,此时的下标变为 3,执行到 **`MethodBeforeAdviceInterceptor`**,即前置通知;

跟进去执行 org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptorinvoke 方法,该方法的实现如下:

@Override public Object invoke(MethodInvocation mi) throws Throwable { this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis()); return mi.proceed(); }

这块代码是先执行自己的业务,再往下传递,我们继续跟进 proceed 方法:

这个时候再次来到 proceed 方法,此时的下标变为 4,还是执行 MethodBeforeAdviceInterceptor

但是现在已经开始回溯了,因为方法都已经入栈了,此时执行 前置通知 中的方法,控制台输出如下:

然后执行 后置通知

执行 返回通知

最后所有的通知执行完毕,由于没有异常产生,所以没有执行异常通知:

0 人点赞