Spring注解驱动开发之AOP
Spring注解驱动开发系列:
Spring注解驱动开发之AOP
面向切面编程,不改变原有代码的前提下,进行功能增强。
@EnableAspectJAutoProxy
在配置类上加上此注解,开启增强类的自动代理
@Aspect
标在类上,声明这是一个增强类
具体的切入点注解
@Before标在方法上,在被增强方法执行前调用
其中,传入切入点表达式
相似的还有@After、@AfterReturning、@AfterThrowing、@Around,分别在方法执行后(无论正常结束还是异常退出),方法正常执行返回后,方法出现异常后,而@Around可以自由的在方法执行前后切入
具体的细节请看示例
首先,模拟一个已有方法
1 |
|
我们先要增强这个方法,随后进行了一系列操作
- 在配置类上加上注解@EnableAspectJAutoProxy,开启功能
- 在增强类上标注@Aspect
- 编写具体的增强类
1 | // 声明这是一个增强类 |
完成了这三步后,我们从容器中获取DivTest的实例对象调用div方法时,就会得到了增强
执行结果:
正常结束
1 | arroundDiv div joinPoint.proceed前>>>>>>>>> |
异常结束
1 | arroundDiv div joinPoint.proceed前>>>>>>>>> |
可以看到,各种通知都有的情况下,正常结束执行顺序为:
@Around环绕通知的前置部分
@Before
被增强方法
@Around环绕通知的后置部分
@After
@AfterReturing
出现异常情况的顺序:
@Around环绕通知的前置部分
@Before
被增强方法
@After
@AfterThrowing
注意点:
- 不要忘记标注@EnableAspectJAutoProxy开启功能
- 不要忘记标注@Aspect注解声明增强类
- 不要忘记将增强类和被增强类的实例加入容器
- 在增强方法中,如果使用了JoinPoint类的形参,要将JoinPoint形参放在方法的参数列表中的第一位
- 在@Around方法中形参是ProceedingJoinPoint,区别于其他方法的JoinPoint
- 如果需要指定返回值和抛出的异常,不要忘记在注解中使用相关属性指定
- 要想使用增强的方法,必须从容器中获取相关的bean,而不是手动new Xxx();
源码分析
@EnableAspectJAutoProxy
@EnableAspectJAutoProxy注解作为AOP的一个开关,我们从他开始分析
1 |
|
值得注意的是,利用@Import(AspectJAutoProxyRegistrar.class),引入了AspectJAutoProxyRegistrar注册器
我们再分析一下AspectJAutoProxyRegistrar
AspectJAutoProxyRegistrar
1 | class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { |
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)方法代码:
至此,容器中已经注册好了名为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过程的探讨
主要关心三个问题:
- AnnotationAwareAspectJAutoProxyCreator有什么用
- 代理对象是怎么创建的
- 增强方法调用的过程
针对这三个问题,我们重新打上断点
第一处:为了分析业务逻辑类和切面类实例化前,AnnotationAwareAspectJAutoProxyCreator对其的处理
第二处:为了分析业务逻辑类和切面类初始化后,AnnotationAwareAspectJAutoProxyCreator对其的处理
第三处:为了分析增强方法被调用时的具体过程
在正式分析前,我们先总的来梳理一下:
- AnnotationAwareAspectJAutoProxyCreator有什么用
- AnnotationAwareAspectJAutoProxyCreator会在bean创建之前,调用postProcessBeforeInstantiation();
- 判断是否已经增强
- 判断是否是”基础类型“或被@Aspect 注解标注
- 判断是否有TargeSource目标源
- bean创建出来后,还有执行初始化后的后置处理方法
- 代理对象是怎么创建的(在初始化后的后置处理方法中包装成代理对象)
- return wrapIfNecessary(bean, beanName, cacheKey)
- 获取到当前bean所有的增强器
- 保存当前bean到advicedBean中
- 如果需要增强,创建代理对象
- 给容器中返回当前组件使用cglib增强的代理对象
- 以后容器中获取的就是这个bean的代理对象
- 增强方法调用的过程
- CglibAopProxy.intercept();拦截目标方法的执行
- 根据ProxyFactory对象获取将要执行的目标方法拦截器链
- 如果没有拦截器链,直接执行目标方法;
- 如果有拦截器链,把需要执行的目标对象,目标方法,拦截器链等信息传入创建一个CglibMethodInvocation 对象,并调用 Object retVal = mi.proceed();链式调用拦截器或被增强方法
现在开始调试
首先来到了实例化前后置处理方法处
具体分析一下上图中的:判断是否是“基础类型”,这里我们的bean是业务逻辑类,并不是切面类,所以不是@Aspect注解修饰的。
判断是否需要跳过
实例化前后置处理方法最后返回null,将执行初始化方法,我们放行来到了初始化后的后置处理方法
我们进一步分析:
我们先分析获取增强器的具体过程:
我们接着分析代理对象的创建过程
再下一层:
逐层返回后,代理对象就创建好了。以后就是使用代理对象去代替原来的对象了
接着,我们来分析增强方法的调用过程:
放行至调用div方法处
进入发现,程序被CglibAopProxy.intercept()拦截
获取到拦截器链后,把需要执行的目标对象,目标方法,拦截器链等信息传入创建一个 CglibMethodInvocation 对象。随后开始链式调用
注意:第一次的proceed方法和后面的proceed方法各不相同,他们是不同类下的同名方法。看似是循环,实则是链
调用最后一个拦截器时,就会开始调用增强方法(或者被增强方法了),然后返回,再从下而上地依次调用增强方法(或被增强方法)
细心的小伙伴可能会发现,下图中的拦截器的顺序和实际的执行顺序不同。
那是因为,调用到环绕增强时,和其他的增强方法不同,他的过程比较复杂
下图中,环绕通知方法已经i开始执行,但是其中的joinpoint.proceed()方法会被前置增强的拦截器拦截。所以,环绕通知的前一部分先执行,随后执行@before方法,@before方法运行完出栈后,环绕通知方法中的joinpoint.proceed()就执行(目标方法执行),随后,环绕通知方法后半部分执行。最后,依次放行给方法栈中下面的拦截器。
到这里,AOP过程基本上就实现了。
参考链接:
尚硅谷Spring注解驱动教程(雷丰阳源码级讲解)
如果此博客对你有帮助,请帮忙点点关注,点赞呗!谢谢啦!