最后更新于
这有帮助吗?
最后更新于
这有帮助吗?
这一章咱主要研究两个大问题:
Advisor
是什么,它是怎么构建的
TargetSource
是什么,SpringFramework 的 AOP 为什么要代理它而不是原始对象
在上一章中,咱有看到一个 shouldSkip
的跳过动作:
这里面它会先获取到一些候选的增强器,而这个方法的底层,其实就是解析切面类,构造 Advisor
增强器的过程,咱深入研究一下。
findCandidateAdvisors
方法的源码还算简单:
总共两个步骤,分别代表切面的两个来源:SpringFramework 原生 AOP 的增强器,以及解析完 AspectJ 切面类构造的增强器。咱一个一个来看。
来到父类 AbstractAdvisorAutoProxyCreator
中:
可以发现它委托了一个 advisorRetrievalHelper
来处理 SpringFramework 原生的 AOP 增强器。而它这个方法的篇幅就比较长了,咱拆解出核心的主干逻辑研究。
注意看源码中的注释:
这里的主要动作,是将 IOC 容器中所有类型为 Advisor
的实现类都找出来,看看到底有没有,如果没有,那就不用走下面的流程了。
BeanFactoryUtils
的 beanNamesForTypeIncludingAncestors
方法,底层是用的 getBeanNamesForType
方法去找 bean 的名称(单纯的找 bean 的名称不会创建具体的 bean 对象,SpringFramework 在此做得很谨慎),感兴趣的小伙伴可以自己去看一下,小册在此就不展开了。
下面的源码蛮长的,不过这里咱只关心主干流程的重点,那无非就是 beanFactory.getBean
了,非常常规的 bean 初始化的动作。只不过我们自始至终都没有讲过 SpringFramework 原生的增强器,因为编写它实在是太复杂了,主流的编写还是以 AspectJ 形式为主,所以咱这里知道一下就可以了。
至于中间省略的 catch 块,其实流程还挺复杂的,里面做了循环依赖的异常处理,由于涉及的内容过于复杂,而且咱前面也说过了,循环依赖的部分可以在 SpringBoot 小册的第 15 章完整的学习,所以咱这里就不多展开了。
通过 Debug ,也可以发现并没有原生的增强器被创建:
以上就是在父类 AbstractAdvisorAutoProxyCreator
中的 findCandidateAdvisors
方法的逻辑,下面咱来看另一部分的委托:aspectJAdvisorsBuilder
。
从方法名上理解,它就是将 Aspect 切面类,转换为一个一个的增强器。这个方法也是长得很,咱分段来研究。
这段源码一下子把缩进拉上去了,阅读观感可能不是特别好,不过好在逻辑不算复杂:
可以发现,这一段的核心逻辑,是将 IOC 容器,以及它的父 IOC 容器中,所有的 bean 的名称全部取出(直接声明父类型为 Object
,显然是取所有),之后,它会逐个解析这些 bean 对应的 Class
。
Debug 中也发现,它真的把包括内部的一些 bean ( environment )在内的所有 bean 的名称全部拿了出来:
注意一个细节,框架在这里控制的很好,它借助 BeanFactory
去取 bean 的类型,而不是先 getBean
后再取类型,这样可以保证 bean 不会被提前创建。而没有初始化 bean 实例的前提下,要想获取 bean 的 Class
,那就只能靠 BeanDefinition
了,所以我们可以在 AbstractBeanFactory
的 getType
方法中看到合并 RootBeanDefinition
的动作,随后调用 RootBeanDefinition
的 getBeanClass
方法获取 bean 的 Class
类型。
这段缩进实在是太大了,我给调小了一点,小伙伴们看着还舒服点:
这一步就是判断当前解析的 bean 所属的 Class
是不是一个切面类了,如果是,则会进入到里面的结构体,把这个类中的通知方法都构造出来。
这两个小动作,咱拿出来解析一下。
1.2.2.1 判断Class是通知类
如何判断当前 Class 是不是通知类(切面类)呢?很简单,看看类上是不是有标注 @Aspect
注解就完事了呗?但是源码中还多判断了一步:
请注意一点,它额外判断了是不是被 ajc 编译器编译,这是为什么呢?我们可以从文档注释中获取到一些信息:
简单的理解来说,SpringFramework 的 AOP 有整合 AspectJ 的部分,而原生的 AspectJ 也可以编写 Aspect 切面,而这种切面在特殊的编译条件下,生成的字节码中类上也会标注 **@Aspect**
注解,但是 SpringFramework 并不能利用它,所以这里它做了一个额外的判断处理,避免了这种 Class 被误加载。
1.2.2.2 advisorFactory.getAdvisors:构造增强器
这个方法又是好复杂呀,咱来到 ReflectiveAspectJAdvisorFactory
中来看:
整段源码阅读下来,思路倒是蛮明确,它果然与我们的推测一致,就是解析 Aspect 切面类中的通知方法,只不过它在上下文的逻辑中补充了一些额外的校验、处理等等逻辑。
重点关注这里面的两个小动作:1) 通知方法是怎么收集的;2) 增强器的创建都需要什么东西。
1.2.2.3 切面类中通知方法的收集
进入到 getAdvisorMethods
方法中:
其实会发现,这个方法很简单的,它就是把咱们定义的切面类中除了通用的切入点表达式抽取以外的所有方法都取出来!并且取出来之后又做了一个排序的动作,而排序的原则就是按照 Unicode 编码来的,这个咱已经研究过了。
1.2.2.4 增强器的创建
getAdvisor
方法就是创建 Advisor
增强器了,可能有的小伙伴会产生疑惑,上面的方法只是取出了自己定义的非 @Pointcut
方法,那对于没有声明切入点表达式的方法,它岂不是也一起返回了?不过没关系,其实它在这里又做了一次过滤:
可以发现框架还是做得滴水不漏的。注意看下面的构造方法中传入的关键参数,它们分别是:
expressionPointcut :AspectJ 切入点表达式的封装
candidateAdviceMethod :通知方法本体
this :当前的 ReflectiveAspectJAdvisorFactory
aspectInstanceFactory :上面的那个装饰者 MetadataAwareAspectInstanceFactory
刨去工厂本身,其实增强器的结构就是一个切入点表达式 + 一个通知方法,与之前的推测完全一致。
1.2.2.5 解析通知注解上的切入点表达式
到此为止其实增强器本身已经没有什么问题了,咱再来关注一下这个切入点表达式的解析,进入到 getPointcut
方法中:
步骤很简单,先去找通知方法上标注的注解,然后把切入点表达式提取出来,返回。很明显 findAspectJAnnotationOnMethod
的逻辑是相对重要的,咱进去看看:
果然,它在 AbstractAspectJAdvisorFactory
中已经提前定义好了所有可以声明切入点表达式的注解,并在此处一一寻找,找到就返回。
在此 Debug 会发现它只是把切入点表达式的内容,以及参数等信息,封装到 AspectJExpressionPointcut
中了:
上面我们只是看到了单实例切面 bean 的处理和解析,下面的 else 部分是原型切面 bean 的处理逻辑:
可以发现,对于原型切面 bean 的解析,它的核心解析动作依然是 advisorFactory.getAdvisors
方法,只是这里面不会再用到 advisorsCache
这个缓存区了,这也说明原型切面 bean 的解析是多次执行的。
最后一部分到了整理的环节了,前面已经把所有的切面类都解析完了,这里只需要把这些构造好的增强器都集中到一个 List 中,返回即可。源码很简单,小伙伴们扫一眼就好:
至此,切入点表达式也就解析完了,通知方法也有了,Advisor
增强器也就顺理的创建出来了。
Debug 返回的时候,可以发现 Logger
中的 5 个增强器都封装好了:
所有的增强器创建完成后,接下来的内容就是承接上一章的 bean 匹配,决定是否跳过 bean 的增强的步骤了。
上一章咱简单的提了一嘴,AOP 的代理其实不是代理的目标对象本身,而是目标对象包装后的 **TargetSource**
对象,SpringFramework 为什么要这么做,这样做有什么好处,咱这里也来深入研究一下。
之前在复习动态代理的时候,咱说代理对象中直接组合了原始对象,直观一点的理解,就可以是这样子:
但是在 SpringFramework 的 AOP 中,代理对象并没有直接代理 Target ,而是给 Target 加了一个壳,而加的这个壳就是 **TargetSource**
,用图示理解就是这样:
是不是一下子就容易理解了呢?TargetSource
可以看做是目标对象 Target 的一个包装、容器,原本代理对象要执行 method.invoke(target, args)
这样的逻辑时,本来要拿到的是目标对象,但被 TargetSource
包装之后,就只能调用 method.invoke(targetSource.getTarget(), args)
这样的形式了。
既然每次调用代理对象的方法,最终会调用到 TargetSource
的 getTarget
方法,而这个 getTarget
方法是 TargetSource
决定如何返回的,那这里面可就大有文章了。举个最简单的例子:每次 getTarget
的值可以不一样吧?每次 getTarget
的时候可以从一个对象池中取吧?哎,是不是突然想到了数据库连接池?其实 TargetSource
也有基于池的实现。
所以咱可以总结出来,让 AOP 代理 TargetSource
的好处,是可以控制每次方法调用时作用的具体对象实例,从而让方法的调用更加灵活。
翻开 TargetSource
的源码,可以发现它是一个接口,
可以发现除了 getTarget
方法之外,还有一个 releaseTarget
,咱也能很快的猜到它的作用是交回 / 释放目标对象之类的操作,它也是用于那些基于对象池的 TargetSource
,在目标对象调用方法完成后,紧接着调用 releaseTarget
方法来释放目标对象的。
另外还有一个 isStatic
方法,可能小伙伴们会疑惑:bean 哪来的静态一说?对于 Java 来讲,Class
与 object 的作用域可以分出来静态和非静态( Class
级别的成员是静态的,object 级别的成员是非静态的),那对于 SpringFramework 来讲,单实例 bean 与原型 bean 的作用域也可以划分出静态和非静态的概念:单实例 bean 是一个 ApplicationContext
中只有一个实例,原型 bean 是每次获取都会拿到一个全新的实例,所以单实例 bean 就可以划为 “静态 bean ”,原型 bean 则为非静态 bean 。
SpringFramework 中针对不同场景不同需求,预设了几个 TargetSource
的实现,咱可以稍微了解一下:
SingletonTargetSource
:每次 getTarget
都返回同一个目标对象 bean (与直接代理 target 无任何区别)
PrototypeTargetSource
:每次 getTarget
都会从 BeanFactory
中创建一个全新的 bean (被它包装的 bean 必须为原型 bean )
CommonsPool2TargetSource
:内部维护了一个对象池,每次 getTarget
时从对象池中取(底层使用 apache 的 ObjectPool
)
ThreadLocalTargetSource
:每次 getTarget
都会从它所处的线程中取目标对象(由于每个线程都有一个 TargetSource
,所以被它包装的 bean 也必须是原型 bean )
HotSwappableTargetSource
:内部维护了一个可以热替换的目标对象引用,每次 getTarget
的时候都返回它(它提供了一个线程安全的 swap
方法,以热替换 TargetSource
中被代理的目标对象)
这些设计在底层都不算复杂,小伙伴们可以自行翻一把源码看看,小册就不大张旗鼓的贴源码了。
We consider something to be an AspectJ aspect suitable for use by the Spring AOP system if it has the annotation, and was not compiled by ajc. The reason for this latter test is that aspects written in the code-style (AspectJ language) also have the annotation present when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
如果它有 注解,并且不是由 ajc 编译的,我们才认为这个 Class 类是适合 Spring AOP 系统使用的 AspectJ 切面。不用 ajc 编译的原因,是因为以代码风格(AspectJ 语言)编写的方面在由带有 -1.5 标志的 ajc 编译时也存在注解,但它们不能被 Spring AOP 使用。