Spring注解驱动开发系列:

  1. Spring 组件注册
  2. Spring Bean的生命周期
  3. Spring属性赋值
  4. Spring自动转配
  5. Spring注解驱动开发之AOP

Spring注解驱动开发之AOP

面向切面编程,不改变原有代码的前提下,进行功能增强。

@EnableAspectJAutoProxy

在配置类上加上此注解,开启增强类的自动代理

@Aspect

标在类上,声明这是一个增强类

具体的切入点注解

@Before标在方法上,在被增强方法执行前调用

其中,传入切入点表达式

相似的还有@After、@AfterReturning、@AfterThrowing、@Around,分别在方法执行后(无论正常结束还是异常退出),方法正常执行返回后,方法出现异常后,而@Around可以自由的在方法执行前后切入

具体的细节请看示例

首先,模拟一个已有方法

1
2
3
4
5
6
7
@Component
public class DivTest {
public int div(int i, int j){
System.out.println("DivTest...div()被调用了");
return i/j;
}
}

我们先要增强这个方法,随后进行了一系列操作

  1. 在配置类上加上注解@EnableAspectJAutoProxy,开启功能
  2. 在增强类上标注@Aspect
  3. 编写具体的增强类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 声明这是一个增强类
@Aspect
@Component
public class DivAspect {
// 抽取公共部分
@Pointcut("execution(public int south.block.aop.DivTest.div(..))")
public void pointCut(){}

// 可以使用切入点表达式,也可以使用@Pointcut标注的方法
@Before("execution(public int south.block.aop.DivTest.div(..))")
public void beforeDiv(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().getName();
Object pointThis = joinPoint.getThis();
System.out.println( "beforeDiv>>>>>>>" + pointThis + "." + name + Arrays.toString(args) );
}

@After("pointCut()")
public void afterDiv(){
System.out.println("afterDiv>>>>>>>>>");
}

// 可以使用returning属性指定方法的返回值,Spring会给被指定的形参赋好值
@AfterReturning(value = "pointCut()",returning = "result")
// 需要注意的是:当条件中有joinPoint时,需要将其放在形参的首位,否则报错 Caused by: java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut
public void afterReturingDiv(JoinPoint joinPoint, Object result ){
System.out.println("afterReturingDiv>>>>>>>>" + joinPoint.getSignature().getName() + "方法正常结束,返回值是" + result);
}

// 类似的,也可以用throwing属性指定异常,Spring会自动给形参赋值
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void afterThrowingDiv(JoinPoint joinPoint, Exception exception){
System.out.println("afterThrowingDiv>>>>>>>>" + joinPoint.getSignature().getName() + "方法出现了" + exception);
}

@Around("pointCut()")
// 注意使用的是ProceedingJoinPoint,而不是JoinPoint
// 另外,也要注意:方法的返回值要和被增强方法一样
public int arroundDiv(ProceedingJoinPoint joinPoint) throws Throwable {
String name = joinPoint.getSignature().getName();
System.out.println("arroundDiv " + name + " joinPoint.proceed前>>>>>>>>>");
Integer result = (Integer)joinPoint.proceed();
System.out.println("arroundDiv " + name + " joinPoint.proceed后>>>>>>>>>");
return result;
}
}

完成了这三步后,我们从容器中获取DivTest的实例对象调用div方法时,就会得到了增强

执行结果:

正常结束

1
2
3
4
5
6
arroundDiv  div joinPoint.proceed前>>>>>>>>>
beforeDiv>>>>>>>south.block.aop.DivTest@3e96bacf.div[1, 1]
DivTest...div()被调用了
arroundDiv div joinPoint.proceed后>>>>>>>>>
afterDiv>>>>>>>>>
afterReturingDiv>>>>>>>>div方法正常结束,返回值是1

异常结束

1
2
3
4
5
arroundDiv  div joinPoint.proceed前>>>>>>>>>
beforeDiv>>>>>>>south.block.aop.DivTest@3e96bacf.div[1, 0]
DivTest...div()被调用了
afterDiv>>>>>>>>>
afterThrowingDiv>>>>>>>>div方法出现了java.lang.ArithmeticException: / by zero

可以看到,各种通知都有的情况下,正常结束执行顺序为:

@Around环绕通知的前置部分

@Before

被增强方法

@Around环绕通知的后置部分

@After

@AfterReturing

出现异常情况的顺序:

@Around环绕通知的前置部分

@Before

被增强方法

@After

@AfterThrowing

注意点:

  1. 不要忘记标注@EnableAspectJAutoProxy开启功能
  2. 不要忘记标注@Aspect注解声明增强类
  3. 不要忘记将增强类和被增强类的实例加入容器
  4. 在增强方法中,如果使用了JoinPoint类的形参,要将JoinPoint形参放在方法的参数列表中的第一位
  5. 在@Around方法中形参是ProceedingJoinPoint,区别于其他方法的JoinPoint
  6. 如果需要指定返回值和抛出的异常,不要忘记在注解中使用相关属性指定
  7. 要想使用增强的方法,必须从容器中获取相关的bean,而不是手动new Xxx();

源码分析

@EnableAspectJAutoProxy

@EnableAspectJAutoProxy注解作为AOP的一个开关,我们从他开始分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies. The default is {@code false}.
*/
boolean proxyTargetClass() default false;

/**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;

}

值得注意的是,利用@Import(AspectJAutoProxyRegistrar.class),引入了AspectJAutoProxyRegistrar注册器

我们再分析一下AspectJAutoProxyRegistrar

AspectJAutoProxyRegistrar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

/**
* Register, escalate, and configure the AspectJ auto proxy creator based on the value
* of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
* {@code @Configuration} class.
*/
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}

}

AspectJAutoProxyRegistrar会自动调用registerBeanDefinitions()方法

在这里插入图片描述

进入查看AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry)方法,

发现依次调用registerAspectJAnnotationAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source)方法

及registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source)方法

在这里插入图片描述

查看registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source)方法代码:

image-20210407211356462.png

至此,容器中已经注册好了名为internalAutoProxyCreator,类型为AnnotationAwareAspectAutoProxyCreator的组件,后面只是一些设置,暂不探讨。

AnnotationAwareAspectAutoProxyCreator

我们继续分析AnnotationAwareAspectAutoProxyCreator类

查看下AnnotationAwareAspectAutoProxyCreator的继承树

在这里插入图片描述

在AbstractAutoProxyCreator类中,发现该类实现了SmartInstantiationAwareBeanPostProcessor接口,而该接口继承于BeanPostProcessor。BeanPostProcessor其中定义了初始化前后执行的两个方法,详情请看Spring Bean的生命周期。XxxAware接口是可以在初始化时将Spring底层Xxx组件注入到别的组件中,详情请看Spring注解实现赋值与自动装配

在这里插入图片描述

AbstractAutoProxyCreator类中实现了setBeanFactory()方法,但是又被子类重写了

在这里插入图片描述

我们跳转至子类AbstractAdvisorAutoProxyCreator的setBeanFactory()方法,在其中调用了initBeanFactory()方法,而且该方法被子类重写了

在这里插入图片描述

我们继续跳转至AnnotationAwareAspectJAutoProxyCreator类中的initBeanFactory()方法

在这里插入图片描述

分析得知:AnnotationAwareAspectJAutoProxyCreator会调用AbstractAdvisorAutoProxyCreator定义的setBeanFactory()方法,在该方法中又会调用AnnotationAwareAspectJAutoProxyCreator类定义的initBeanFactory()方法。我们在这两方法处打上断点,在配置类中创建MathCalculator类bean对象和LogAspects类bean对象处打上断点,开始debug分析。

我们需要重点分析AnnotationAwareAspectAutoProxyCreator的注册,因为它是Spring实现AOP的关键。

这里先说一下大致流程,下面再分析源代码

1)、传入配置类、创建IOC容器

2)、注册配置类,刷新容器

3)、注册Bean的后置处理器

3.1)、获取IOC容器中已经定义的需要创建对象的后置处理器
    
3.2)、添加一些别的后置处理器
    
3.3)、将后置处理器按优先级分成PriorityOrdered、Ordered、其他类
    
3.4)、先后注册这些后置处理器
    
    3.4.1)、createBean
    
        3.4.1.1)、resolveBeforeInstantiation执行实例化之前的Bean后置处理方法
    
        3.4.1.2)、创建Bean的实例
    
    3.4.2)、populateBean 给属性赋值
    
    3.4.3)、initializeBean 初始化Bean
    
        3.4.3.1)、invokeAwareMethods 调用XxxAware接口中定义的方法,将Xxx注入当前bean
    
        3.4.3.2)、applyBeanPostProcessorsBeforeInitialization 执行初始化前的后置处理方法
    
        3.4.3.3)、invokeInitMethods 执行初始化方法
    
        3.4.3.4)、applyBeanPostProcessorsAfterInitialization 执行初始化后的后置处理方法
    
    3.4.4)、BeanPostProcessor 创建成功
    
3.5)、把BeanPostProcessor注入容器中

4)、finishBeanFactoryInitialization(beanFactory) 完成BeanFactory初始化工作;创建剩下的单实例bean

4.1)、获取容器中已经定义了的所有的bean
    
4.2)、判断是否单例、是否抽象、是否懒加载
    
4.3)、如果是抽象单例懒加载,就getBean()-->doGetBean()
    
    4.3.1)、先在缓存中检查此bean是否已经被手动注册
    
    4.3.2)、缓存中有就直接使用
    
    4.3.3)、没有则继续创建createBean()
    
        4.3.3.1)、resolveBeforeInstantiation()实例化前解析工作,尝试返回一个代理对象
    
        4.3.3.2)、doCreateBean 创建bean对象,此处过程就和3.4)过程类似了,此处不再说明

分析一下方法栈:

我们发现容器已经创建好了

在这里插入图片描述

此时,开始注册BeanPostProcessor,程序运行至我们打的断点处

在这里插入图片描述

发现internalAutoProxyCreator应在orderedPostProcessorNames中

在这里插入图片描述

咱们给程序放行,程序来到AbstractAdvisorAutoProxyCreator类的setBeanFactory方法,说明了程序已经在初始化internalAutoProxyCreator了

在这里插入图片描述

继续放行,程序来到了AnnotationAwareAspectJAutoProxyCreator类的initBeanFactory方法。方法执行结束后,Factory已成功注入到internalAutoProxyCreator中了。

在这里插入图片描述

逐行运行,查看方法栈(这也是后面创建其他Bean的过程)

在这里插入图片描述

我们先看createBean方法:

其中的有调用实例化前后的后置处理方法和bean的创建

在这里插入图片描述

我们查看resolveBeforeInstantiation方法,发现实现了InstantiationAwareBeanPostProcessor接口的后置处理器可以被调用实例化前的后置处理方法,而我们的AnnotationAwareAspectJAutoProxyCreator类实现的SmartInstantiationAwareBeanPostProcessor接口继承于InstantiationAwareBeanPostProcessor接口。之后bean的创建都会调用AnnotationAwareAspectJAutoProxyCreator的实例化前后的后置处理方法。当然!internalAutoProxyCreator自身的创建并不会调用。

在这里插入图片描述

按照方法栈,接着查看doCreateBean方法:

在这里插入图片描述

按照方法栈,查看AbstractAutowireCapableBeanFactory类的initializeBean方法

在这里插入图片描述

此方法最终返回一个bean的包装对象,再经过一系列返回,最终到达PostProcessorRegistrationDelegate的invokeBeanFactoryPostProcessors方法中,将刚创建的internalAutoProxyCreator组件注册进容器中。至此,AnnotationAwareAspectJAutoProxyCreator成功创建并注入容器中。

在这里插入图片描述

随后,经过相似的过程,将其他的PostProcessor注入容器中

Spring在AnnotationConfigApplicationContext构造器中的refresh()方法中调用接着完成BeanFactory的初始化工作,创建剩下的单实例非懒加载的bean

在这里插入图片描述

我们继续分析其他普通bean的创建过程,从这开始AnnotationAwareAspectJAutoProxyCreator就开始生效了。

由于我们需要分析AOP的过程,所以我们比较关心MathCalculator业务逻辑类LogAspects切面类的创建过程。

直接将程序运行至断点处

在这里插入图片描述

分析方法栈:

在这里插入图片描述

从preInstantiateSingletons开始分析

在这里插入图片描述

getBean中直接调用doGetBean方法,我们直接看doGetBean方法

在这里插入图片描述

继续查看createBean方法

在这里插入图片描述

真正的创建Bean接下来的过程就和AnnotationAwareAspectJAutoProxyCreator类似了,我们不再讨论。

真正的AOP

我们现在可以开始进入对AOP过程的探讨

主要关心三个问题:

  1. AnnotationAwareAspectJAutoProxyCreator有什么用
  2. 代理对象是怎么创建的
  3. 增强方法调用的过程

针对这三个问题,我们重新打上断点

第一处:为了分析业务逻辑类和切面类实例化前,AnnotationAwareAspectJAutoProxyCreator对其的处理

在这里插入图片描述

第二处:为了分析业务逻辑类和切面类初始化后,AnnotationAwareAspectJAutoProxyCreator对其的处理

在这里插入图片描述

第三处:为了分析增强方法被调用时的具体过程

在这里插入图片描述

在正式分析前,我们先总的来梳理一下:

  1. AnnotationAwareAspectJAutoProxyCreator有什么用
    1. AnnotationAwareAspectJAutoProxyCreator会在bean创建之前,调用postProcessBeforeInstantiation();
      1. 判断是否已经增强
      2. 判断是否是”基础类型“或被@Aspect 注解标注
      3. 判断是否有TargeSource目标源
    2. bean创建出来后,还有执行初始化后的后置处理方法
  2. 代理对象是怎么创建的(在初始化后的后置处理方法中包装成代理对象)
    1. return wrapIfNecessary(bean, beanName, cacheKey)
      1. 获取到当前bean所有的增强器
      2. 保存当前bean到advicedBean中
      3. 如果需要增强,创建代理对象
      4. 给容器中返回当前组件使用cglib增强的代理对象
      5. 以后容器中获取的就是这个bean的代理对象
  3. 增强方法调用的过程
    1. CglibAopProxy.intercept();拦截目标方法的执行
    2. 根据ProxyFactory对象获取将要执行的目标方法拦截器链
    3. 如果没有拦截器链,直接执行目标方法;
    4. 如果有拦截器链,把需要执行的目标对象,目标方法,拦截器链等信息传入创建一个CglibMethodInvocation 对象,并调用 Object retVal = mi.proceed();链式调用拦截器或被增强方法

现在开始调试

首先来到了实例化前后置处理方法处

在这里插入图片描述

具体分析一下上图中的:判断是否是“基础类型”,这里我们的bean是业务逻辑类,并不是切面类,所以不是@Aspect注解修饰的。

在这里插入图片描述

判断是否需要跳过

在这里插入图片描述

实例化前后置处理方法最后返回null,将执行初始化方法,我们放行来到了初始化后的后置处理方法

在这里插入图片描述

我们进一步分析:
在这里插入图片描述

我们先分析获取增强器的具体过程:

在这里插入图片描述

我们接着分析代理对象的创建过程

在这里插入图片描述

再下一层:

在这里插入图片描述

逐层返回后,代理对象就创建好了。以后就是使用代理对象去代替原来的对象了

接着,我们来分析增强方法的调用过程:

放行至调用div方法处

在这里插入图片描述

进入发现,程序被CglibAopProxy.intercept()拦截

在这里插入图片描述

获取到拦截器链后,把需要执行的目标对象,目标方法,拦截器链等信息传入创建一个 CglibMethodInvocation 对象。随后开始链式调用

注意:第一次的proceed方法和后面的proceed方法各不相同,他们是不同类下的同名方法。看似是循环,实则是链

在这里插入图片描述

调用最后一个拦截器时,就会开始调用增强方法(或者被增强方法了),然后返回,再从下而上地依次调用增强方法(或被增强方法)

在这里插入图片描述

细心的小伙伴可能会发现,下图中的拦截器的顺序和实际的执行顺序不同。

在这里插入图片描述

那是因为,调用到环绕增强时,和其他的增强方法不同,他的过程比较复杂
下图中,环绕通知方法已经i开始执行,但是其中的joinpoint.proceed()方法会被前置增强的拦截器拦截。所以,环绕通知的前一部分先执行,随后执行@before方法,@before方法运行完出栈后,环绕通知方法中的joinpoint.proceed()就执行(目标方法执行),随后,环绕通知方法后半部分执行。最后,依次放行给方法栈中下面的拦截器。

在这里插入图片描述

到这里,AOP过程基本上就实现了。

参考链接:
尚硅谷Spring注解驱动教程(雷丰阳源码级讲解)

如果此博客对你有帮助,请帮忙点点关注,点赞呗!谢谢啦!