Spring源码学习(五):Spring AOP

免责声明

本人还处于学习阶段,如果内容有错误麻烦指出,敬请见谅!!!

Demo

xml 复制代码
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.8</version>
</dependency>

AOP标签解析源码

​   其实AOP的标签解析与component-scan的逻辑一样,都会走parseCustomElement 方法:

​   进入parseCustomElement 方法后,解析aop标签的步骤和解析component-scan标签的步骤就一模一样,先是得到命名空间处理器(handler),再是得到aop标签的解析器(parser),然后调用解析器的parse方法完成标签解析。

​   由于之前在component-scan详细说了handler和parser的获取过程,这里就简单提一下:

java 复制代码
component-scan: ContextNamespaceHandler->ComponentScanBeanDefinitionParser
aop: AopNamespaceHandler->ConfigBeanDefinitionParser

​   让我们再进一步看看ConfigBeanDefinitionParserparse方法:

1.configureAutoProxyCreator干了什么

​   尝试向容器注入或者升级AspectJAwareAdvisorAutoProxyCreator类型的自动代理创建者bean定义,专门用于后续创建AOP代理对象,注意这里是bean定义嗷,并没有实例化。

​   configureAutoProxyCreator 首先会调用AopNamespaceUtils工具类的registerAspectJAutoProxyCreatorIfNecessary方法:

1.1 registerAspectJAutoProxyCreatorIfNecessary

​   调用AopConfigUtilsregisterAspectJAutoProxyCreatorIfNecessary 方法,通过方法名也很好理解,该方法的作用是如果需要的话注册一个代理对象创建者。该方法会进一步调用registerOrEscalateApcAsRequired方法,这里面才是真正创建代理创建者的逻辑。

​   registerOrEscalateApcAsRequired方法的底层逻辑是这样的:

1.在beanFactory中找有没有beanName为"org.springframework.aop.config.internalAutoProxyCreator"的beanDefinition,有的话就尝试升级。
2.没有的话创建AspectJAwareAdvisorAutoProxyCreator的beanDefiniton,并以"org.springframework.aop.config.internalAutoProxyCreator"为beanName存入beanFactory中
1.1.1 不同代理创建者的优先级是什么(findPriorityForClass)

​   从上述代码可以看到,如果beanFactory缓存命中,则会去比较当前类型的代理创建者和beanFactory中类型的代理创建者的优先级,findPriorityForClass方法的实现也蛮朴实无华的:

​   从registerAspectJAutoProxyCreatorIfNecessary 方法中我们可以看到,我们指定的代理创建者类型是AspectJAwareAdvisorAutoProxyCreator,其实还有其他:

1.< tx:annotation-driven />标签或者@EnableTransactionManagement事物注解:InfrastructureAdvisorAutoProxyCreator.class;
2.< aop:config />标签:AspectJAwareAdvisorAutoProxyCreator.class;
3.< aop:aspectj-autoproxy />标签或者@EnableAspectJAutoProxy注解:AnnotationAwareAspectJAutoProxyCreator.class。

​   从findPriorityForClass实现逻辑来看,这三者的优先级是:

AnnotationAwareAspectJAutoProxyCreator>AspectJAwareAdvisorAutoProxyCreator>InfrastructureAdvisorAutoProxyCreator

​   这三个也有相同的父类:

1.2 useClassProxyingIfNecessary干了什么

​   在升级或者注册了自动代理创建者的bean定义之后,这一步用于解析、设置proxy-target-class和expose-proxy属性到这个bean定义的proxyTargetClassexposeProxy属性中。

1.3 registerComponentIfNecessary干了什么

​   还记得我们在parse方法最开始的时候创建了一个CompositeComponentDefinition类型的对象吗,这里我们就用上了,将创建代理对象的beanDefiniton封装进BeanComponentDefinition中,然后再存入CompositeComponentDefinition里面。

2 parsePointcut方法解析< aop:pointcut/>切入点标签

​   parsePointcut 方法干的事情其实挺好理解的,方法内部根据< aop:pointcut/>标签创建一个beanDefinition,然后将这个beanDefinifition存入beanFactory中,最后将beanDefinition封装进PointcutComponentDefinition,然后进行注册(其实就是放入我们再事先创建好的CompositeComponentDefinition)。

2.1 createPointcutDefinition

​   createPointcutDefinition 方法主要是根据< aop:pointcut/>标签的expression属性创建一个RootBeanDefinition类型的beanDefinition,该beanDefinition对应的class为AspectJExpressionPointcut

3. parseAdvisor解析<aop:advisor/>通知器标签

​   < aop:advisor/>通知器标签,它和< aop:aspect >类似,相当于一个小切面,它的advice-ref属性可以指向一个通知类bean,该bean要求实现Advice相关接口,不需要单独配置通知,但接口只有前置通知、后置通知和异常通知的方法。

​   通常,advisor被用来管理事物,它的advice-ref属性配置对一个< tx:advice >的id引用,后面学习事物的时候就知道了。

​   类似的,parseAdvisor 方法也会生成一个beanDefinition然后存入beanFactory中,也会注册进CompositeComponentDefinition中:

3.1 createAdvisorBeanDefinition

​   让我们看看advisor标签对应的beanDefinition:

3.2 parsePointcutProperty

​   parsePointcutProperty方法主要是获取切入点的信息:

4. parseAspect解析< aop:aspect/>切面标签

​   < aop:aspect/>标签用于配置切面,其内部可以定义具体的应用到哪些切入点的通知,这是一个非常重要的标签,相比其他两个标签,parseAspect方法稍微有些复杂:

4.1 parseAdvice:解析通知标签

​   让我们继续看看parseAdvice方法,该方法主要是用来解析通知标签,包括< aop:before/>、< aop:after/>、< aop:after-returning/>、< aop:after-throwing/>、< aop:around/>,解析的最终bean定义同样会注入到容器中:

​   可以看到parseAdvice 方法创建了一系列的beanDefinition,我们结合Xml配置文件看看,parseAdvice方法干了什么事情:

xml 复制代码
<aop:aspect id="aspect" ref="aspectMethod">
     <aop:before method="aspect" pointcut-ref="pointcut"/>
     <aop:after method="aspect" pointcut-ref="pointcut"/>
     <aop:after method="aspect" pointcut-ref="pointcut"/>
</aop:aspect>
  1. 首先会创建MethodLocatingFactoryBean类型的beanDefinition,并向该beanDefinition中添加属性methodName ,其值为method属性的值。添加targetBeanName属性,其值为父标签aspect的ref属性的值。(内部bean,不会注册到beanFactory中)
  2. 创建SimpleBeanFactoryAwareAspectInstanceFactory类型的beanDefinition,并添加aspectBeanName属性,其值为父标签aspect的ref属性的值。SimpleBeanFactoryAwareAspectInstanceFactory是一个实例工厂,专门用于获取切面实例对象,也就是通知类对象。(内部bean,不会注册到beanFactory中)
  3. 将上面两个beanDefiniton作为参数传入createAdviceDefinition中,该方法返回的也是一个beanDefinition,表示advice通知标签的beanDefinition,也就是< aop:before/>、< aop:after/>对应的beanDefinition。(内部bean,不会注册到beanFactory中)
  4. 再创建一个AspectJPointcutAdvisor类型的beanDefinition,这是切入点通知器bean定义,把第三步中的beanDefinition作为构造器参数添加至bean定义中。(注册到beanFactory中)
4.1.1 createAdviceDefinition创建通知bean定义

​   createAdviceDefinition方法内部会根据不同通知的类型创建不同类型的beanDefinition,然后设置beanDefinition中的属性值。特殊的是,方法内部还会设置构造器参数:

​   我们进入到getAdviceClass方法内部可以看到,不同通知类型对应着不同类型的class:

​   从类的继承关系来看,这五个通知类型都继承至AbstractAspectJAdvice

​   其实这五个子类的构造方法都是调用父类的构造方法,因此这五个通知类型对应的class不一样,但是它们的构造器参数却是一样的:


AspectJAwareAdvisorAutoProxyCreator创建代理对象

​   之前我们已经学习了aop标签解析的相关源码,标签解析主要干了两件事:首先创建了一个创建代理对象的对象(AspectJAwareAdvisorAutoProxyCreator)主要是用来后面创建代理对象的,然后就是解析aop的子标签,创建并注册相关的beanDefinition。

​   接下来我们具体来看看,Spring是如何用AspectJAwareAdvisorAutoProxyCreator创建代理对象的。

写在前面

​   我们先来看看AspectJAwareAdvisorAutoProxyCreator实现了哪些接口,可以看到这些接口在我们Spring初始化启动源码分析时都有讲到过,特别是BeanPostProcessor这个超级接口,我们会回调里面的方法干很多事情。

​   让我们来稍微回顾一下Spring初始化的流程,主要是看Spring对后处理器的处理流程:

1. postProcessBeforeInstantiation方法

​   实际上是执行AspectJAwareAdvisorAutoProxyCreator的父类AbstractAutoProxyCreator的方法:

1.1 getCacheKey:根据beanName和beanClass获取cacheKey

​   getCacheKey方法实现比较简单,先判断beanName是否为空,不为空的话再判断是不是FactoryBean,是FactoryBean则返回&+beanName,不是FactoryBean则返回beanName。

​   如果beanName为空,则返回beanClass。

1.2 isInfrastructureClass:是否是Spring AOP基础结构类

​   如果当前beanClass是Spring AOP的基础结构类,则isInfrastructureClass 返回true,不进行代理。默认实现是将Advice、Pointcut、Advisor、AopInfrastructureBean接口及其实现均作为AOP基础结构类。

1.3 shouldSkip:是否跳过代理

​   该方法在AspectJAwareAdvisorAutoProxyCreator中的逻辑如下:

​   该方法在父类AbstractAutoProxyCreator中的逻辑如下:如果beanName以beanCassName开头,并且以".ORIGINAL"结束,那么返回true,否则返回false。

1.4 getCustomTargetSource:获取自定义目标源

​   获取当前bean的自定义目标源TargetSource 。如果获取到了TargetSource ,那么将会直接在postProcessBeforeInstantiation 方法中创建代理,避免没必要的Spring初始化和实例化流程,同时后面的postProcessAfterInitialization方法中不会再创建代理对象。

​   但是通常情况下我们并未设置TargetSource ,因此不会走这个逻辑创建代理对象,创建代理对象的逻辑还是在postProcessAfterInitialization中。

1.5 postProcessBeforeInstantiation方法的调用时机

​   当我们把AspectJAwareAdvisorAutoProxyCreator的beanDefinition加载到beanFactory中,并实例化后,我们再来看看createBean方法的部分逻辑:

​   让我们继续进到resolveBeforeInstantiation 方法里面看看,很明显重点在于applyBeanPostProcessorsBeforeInstantiationapplyBeanPostProcessorsAfterInitialization 方法,这方法内部分别会回调postProcessBeforeInstantiationpostProcessAfterInstantiation方法:

​   值得注意的是AspectJAwareAdvisorAutoProxyCreator后处理器属于InstantiationAwareBeanPostProcessorapplyBeanPostProcessorsBeforeInstantiation 里面会回调postProcessBeforeInstantiation 方法,在这个方法内部会缓存advisedBean 。因此advisedBean 实例化时,会在这里被缓存到AspectJAwareAdvisorAutoProxyCreator后处理器里面。

​   但是由于不会设置TargetSource ,因此这里返回的bean通常为null,不会进一步执行applyBeanPostProcessorsAfterInstantiation方法。

2. postProcessAfterInitialization方法

​   postProcessAfterInitialization 包含了创建代理对象的核心方法,核心逻辑在wrapIfNecessary里面:

2.1 wrapIfNecessary方法

​   在文章开头给出的案例中,对于AspectTarget会执行到第四步,进行代理对象的创建:

2.1.1 getAdvicesAndAdvisorsForBean:获取可用Advisors

​   获取当前bean(本案例中指的是AspectTarget)可用的Advisor通知器,该方法由子类实现。在解析**< aop:config/>标签时, < aop:advisor/>标签的DefaultBeanFactoryPointcutAdvisor < aop:declare-parents/>**标签的DeclareParentsAdvisor,通知标签的AspectJPointcutAdvisor,他们都属于Advisor类型,也就是通知器,通常一个切入点和一个通知方法就组成通知器。

​   在本案例中其实主要的Advisor就是通知标签的AspectJPointcutAdvisor

​   可以看到getAdvicesAndAdvisorsForBean 方法内部的核心方法是findEligibleAdvisors 来获得当前bean的可用通知器Advisors,我们继续来看看findEligibleAdvisors方法的具体实现:

2.1.1.1 findAdvisorsThatCanApply:找到可应用当前bean的Advisor

​   findAdvisorsThatCanApply 主要是来找到可以应用到当前bean的Advisor,会分别对**< aop:declare-parents/>**和通知标签做处理,判断是否可用于当前bean也很简单,根据切入点表达式来对类和方法进行匹配即可:

2.1.1.2 extendAdvisors:扩展通知器链

​   添加一个特殊的Advisor到Advisors链头部,使用 AspectJ 切入点表达式和使用 AspectJ 样式的Advice时都需要添加,添加的是ExposeInvocationInterceptor.ADVISOR,实际类型是一个DefaultPointcutAdvisor类型的单例实例,它内部保存了ExposeInvocationInterceptor拦截器的单例实例,当进行拦截时Advisors链的第一个方法就是调用该拦截器的invoke方法。

​   在本案例中,执行完这一步后,对于AspectTarget的Advisor链,应该有四个Advisor(3个通知Advisor+1个特殊Advisor)。

2.1.2 createProxy:创建代理对象

​   为给定的 bean 创建 AOP 代理。这一步会选择到底是通过JDK代理创建还是通过CGLIB代理创建。

2.1.2.1 evaluateProxyInterfaces:是否有合理接口

​   如果当前接口不是一个容器回调接口,并且当前接口不是内部语言接口,并且接口方法个数至少为1个(不是标志性接口)。同时满足上面三个条件,当前接口就是一个合理的代理接口。

​   在本案例中,因为AspectTarget没有合理接口,因此会在这一步将proxyTargetClass属性设置为true,最后会走CGLIB代理模式。

2.1.2.2 getProxy:获取代理对象

​   获取代理对象分为两步:首先我们需要创建对应类型的AopProxy(JDK or CGLIB),然后再根据AopProxy获取代理对象。

​   我们先看看createAopProxy 方法,Spring最终会调用DefaultAopProxyFactorycreateAopProxy方法:

​   当我们得到了AopProxy,我们可以进一步获取代理对象,根据AopProxy的不同,获取代理对象的方式也不同。

​   我们先看看JDK代理模式获取代理对象:

​   再看看CGLIB代理模式获取代理对象,稍微复杂一点:

​   再看看createProxyClassAndInstance方法:

代理对象的增强方法的执行原理

写在前面

​   还是以文章开头的Demo为例,我们获取目标类(AspectTarget)的Bean时,Spring容器会返回给我们AspectTarget的代理对象:

​   可以看到,我们的Demo最终还是走的是CGLIB代理模式,由于JDK和CGLIB在方法增强的逻辑其实差不多,我们就简单分析一下CGLIB执行的原理,其基本思想是:责任链模式和方法递归调用来实现方法的代理和增强的逻辑。

增强入口:DynamicAdvisedInterceptor的intercept方法

​   进一步地,获取到代理对象后,我们调用目标类的target 方法,最终会被第一个拦截器DynamicAdvisedInterceptor拦截,调用里面的intercept方法:

​   以文章开头的Demo为例,现在代理对象有四个Advisor(一个特殊+三个通知),特殊Advisor会匹配所有方法,三个通知Advisor匹配目标类的target方法,我们看看chain中有哪些东西:

增强逻辑:CglibMethodInvocation

​   在找到匹配当前方法的Advisor后,我们继续看看增强方法的调用逻辑,首先来看看CglibMethodInvocationproceed 方法,其实是调用了父类ReflectiveMethodInvocationproceed方法:

​   我们看看Demo中对应的三个类型的拦截器的invoke方法:

​   我们要明白两件事,首先是tryreturnfinally的执行顺序:

try中的代码块(包括return语句中的代码,但是不执行return返回操作)> finally代码块 > return操作

​   其次是

java 复制代码
mi.proceed()最后执行的还是ReflectiveMethodInvocation的proceed方法

​   让我们来看看Demo中的递归栈变化和增强方法的执行:

相关推荐
云和数据.ChenGuang4 分钟前
《XML》教案 第1章 学习XML基础
xml·java·学习
王·小白攻城狮·不是那么帅的哥·天文11 分钟前
Java操作Xml
xml·java
今天你比昨天博学了吗19 分钟前
CFD POST导出动画
学习
γ..20 分钟前
基于MATLAB的图像增强
开发语言·深度学习·神经网络·学习·机器学习·matlab·音视频
发飙的蜗牛'21 分钟前
23种设计模式
android·java·设计模式
music0ant30 分钟前
Idean 处理一个项目引用另外一个项目jar 但jar版本低的问题
java·pycharm·jar
小王爱吃月亮糖30 分钟前
C++进阶-1-单继承、多继承、虚继承
开发语言·c++·笔记·学习·visual studio
zxguan1 小时前
Springboot 学习 之 logback-spring.xml 日志压缩 .tmp 临时文件问题
spring boot·学习·spring
纪伊路上盛名在1 小时前
爬虫1:uniprot蛋白质序列数据+canvas图片
数据库·学习·知识图谱·学习方法
陈大爷(有低保)1 小时前
logback日志控制台打印与写入文件
java