Spring源码深度解析 AOP总结

动态 AOP 使用示例

创建用于拦截的 bean
在这里插入图片描述
Spring源码深度解析 AOP总结_第1张图片

创建 AdvisorSpring源码深度解析 AOP总结_第2张图片

创建配置文件
Spring源码深度解析 AOP总结_第3张图片

测试
在这里插入图片描述
Spring源码深度解析 AOP总结_第4张图片

动态 AOP 自定义标签
Spring 中的自定义注解,如果声明了自定义的注解,那么就一定会在程序中的某
个地方注册了对应的解析器
在 AopNamespaceHandler 中对应着这样一段函数:
Spring源码深度解析 AOP总结_第5张图片
在解析配 文件的时候, 旦遇到 aspectjautoproxy 注解时就会使用解析器
AspectJAutoProxyBeanDefinitionParser 进行解析

注册 AnnotationAwareAspectJAutoProxyCreator
所有解析器,因为是对 BeanDefinitionParser 接口的统一实现,入口都是从 parse 函数开始

Spring源码深度解析 AOP总结_第6张图片

关键逻辑的实现
Spring源码深度解析 AOP总结_第7张图片

注册或者升级 AnnotationAwareAspectJAutoProxyCreator
Spring源码深度解析 AOP总结_第8张图片

处理 proxy-target-class 以及 expose-proxy 属性
Spring源码深度解析 AOP总结_第9张图片
proxy-target-class: Spring AOP 部分使用 JDK 动态代理或者 CGLIB 来为目标对象创建
代理(建议尽量使用 JDK 的动态代理) 如果被代理的目标对象实现了至少一个接口,
则会使用 JDK 动态代理 所有该目标类型实现的接口都将被代理 若该目标对象没有
实现任何接口,则创建一个 CGLIB 代理 如果你希望强制使用 CGLIB 代理(例如希
望代理目标对象的所有方法,而不只是实现自接口的方法),那也可以 但是需妥考虑
以下两个问题
无法通知( advise ) Final 方法,因为它们不能被覆写
你需要将 CGLIB 二进制发行包放在 classpath 下面
与之相比 JDK 本身就提供了动态代理 强制使用 CGLIB 代理需要将 proxy-target-class 属性设为 true
在这里插入图片描述
当需要使用 CGLIB 代理和 @AspectJ 自动代理支持,可以按照以下方式设置<aop:aspectjauto
proxy> 的 proxy-target-class 属性:
在这里插入图片描述
而实际使用的过程中才会发现细节问题的差别, The devil is in the details
JDK 动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接
口的实现类未完成对目标对象的代理
CG LIB 代理:实现原理类似于 IDK 动态代理,只是它在运行期间生成的代理对象是
针对目标类扩展的子类 CGLIB 是高效的代码生成包,底层是依靠 ASM (开源的 Java
字节码编辑类库)操作字节码实现的,性能比 JDK 强
expose-proxy :有时候目标对象内部的自我调用将无法实施切面中的增强,如下示例:
Spring源码深度解析 AOP总结_第10张图片
此处的 this 指向目标对象,因此调用 this.b ()将不会执行 b 事务切面,即不会执行事务增强,
因此 b 方法的事务定义“ @Transactional(propagation = Propagation.REQUIRES_ NEW)”将不会
实施,为了解决这个问题,我们可以这样做
在这里插入图片描述
然后将以上代码中的“this.b ();”修改为 ((AService) AopContext.currentProxy() ).b() ;”即可
通过以上的修改便可以完成对 a和b 方法的同时增强

创建 AOP 代理
AnnotationAwareAspectJAutoProxyCreator 类的层次结构
Spring源码深度解析 AOP总结_第11张图片

在父类 AbstractAutoProxyCreator 的 postProcessAfterlnitialization 中代码如下:
Spring源码深度解析 AOP总结_第12张图片
Spring源码深度解析 AOP总结_第13张图片

创建代理主要包含了两个步骤
获取增强方法或者增强器
根据获取的增强进行代理
核心逻辑的时序图
Spring源码深度解析 AOP总结_第14张图片

获取增强方法的实现逻辑
Spring源码深度解析 AOP总结_第15张图片

获取增强器
对于findCandidateAdvisors 的实现其实是由AnnotationAwareAspectJAutoProxyCreator 类完成的
Spring源码深度解析 AOP总结_第16张图片
AnnotationAwareAspectJAutoProxyCrator 间接继承了 AbstractAdvisorAutoProxyCreator,
在实现获取增强的方法中除了保留父类的获取配置文件中定义的增强外,同时添加了获取
Bean 的注解增强的功能,那么其实现正是由 this.aspectJAdvisorsBuilder.buildAspectJAdvisors()
来实现的

函数提供的大概功能框架
获取所有 beanName ,这一步骤中所有在 beanFacotry 中注册的 bean 都会被提取出来。
遍历所有 beanName 并找出声明 AspectJ 注解的类 ,进行进一步的处理
对标记为 Aspect 注解的类进行增强器的提取
将提取结果加入缓存
Spring源码深度解析 AOP总结_第17张图片
Spring源码深度解析 AOP总结_第18张图片
上面的步骤中最为重要也最为繁杂的就是增强器的获取
而这一功能委托给了 getAdvisors 方法去实现 this.advisorFactorγ.getAdvisors(factory)
Spring源码深度解析 AOP总结_第19张图片
Spring源码深度解析 AOP总结_第20张图片

普通增强器的获取
普通增强器的获取逻辑通过 getAdvisor 方法实现,实现步骤包括对切点的注解的获取以及
根据注解信息生成增强
Spring源码深度解析 AOP总结_第21张图片
切点信息的获取 所谓获取切点信息就是指定注解的表达式信息的获取,如
@Before (” test()”)
Spring源码深度解析 AOP总结_第22张图片
根据切点信息生成增强 所有的增强都由 Advisor 的实现类 InstantiationModelAwarePointcutAdvisorlmpl 统一封装的
在这里插入图片描述
Spring源码深度解析 AOP总结_第23张图片
在封装过程中只是简单地将信息封装在类的实例中,所有的信息单纯地赋值,在实例初始
化的过程中还完成了对于增强器的初始化 因为不同的增强所体现的逻辑是不同的,比如
@Before (“ test()” )与@After (“tes ()” )标签的不同就是增强器增强的位置不同 ,所以就需
要不同的增强器来完成不 同的 逻辑,而根据注解中的信息初始化对应的增强器就是在
instantiateAdvice 函数中实现的
Spring源码深度解析 AOP总结_第24张图片
Spring源码深度解析 AOP总结_第25张图片
Spring源码深度解析 AOP总结_第26张图片
从函数中可以看到, Spring 会根据不同的注解生成不同的增强器,例如 AtBefore 会对应
AspectJMethodBeforeAdvice ,而在 AspectJMethodBeforeAdvic 中完成了增强方法的逻辑
Spring源码深度解析 AOP总结_第27张图片
其中的属性 MethodBeforeAdvice 代表着前置增强的 AspectJMethodBeforeAdvice
Spring源码深度解析 AOP总结_第28张图片
Spring源码深度解析 AOP总结_第29张图片
invokeAdviceMethodWithGivenArgs 方法中的 aspectJAdviceMethod 正是对于前置增强的方法
,在这里实现了调用

后置增强与前置增强有稍许不一致的地方,大致的结构是在拦
截器链中放置 MethodBeforeAdviceInterceptor ,而在 MethodBeforeAdviceinterceptor 中又放置了
AspectJMethodBeforeAdvice ,并在调用 invoke 时首先串联调用 但是在后置增强的时候却不
样,没有提供中间的类 ,而是直接在拦截器链中使用了中间的 AspectJAfterAdvice
Spring源码深度解析 AOP总结_第30张图片
Spring源码深度解析 AOP总结_第31张图片
增加同步实例化增强器
如果寻找的增强器不为空而且又配置了增强延迟初始化,那么就需要在首位加入同步实例
化增强器
Spring源码深度解析 AOP总结_第32张图片
获取 DeclareParents 注解
DeclareParents 主要用于引介增强的注解形式的实现,而其实现方式与普通增强很类似 ,只
不过使用 DeclareParentsAdvisor 对功能进行封装
Spring源码深度解析 AOP总结_第33张图片

寻找匹配的增强器
Spring源码深度解析 AOP总结_第34张图片
findAdvisorsThatCanApply 函数的主要功能是寻找所有增强器中适用于当前 class 的增强
器 引介增强与普通的增强处理是不一样的 所以分开处理 而对于真正的匹配在 canApply 中实现
Spring源码深度解析 AOP总结_第35张图片
Spring源码深度解析 AOP总结_第36张图片

创建代理
Spring源码深度解析 AOP总结_第37张图片
Spring源码深度解析 AOP总结_第38张图片
对于代理类的创建及处理, Spring 委托给了 ProxyFactorγ 去处理,而在此函数中主要是对
ProxyFactory 的初始化操作 ,进而对真正的创建代理做准备 ,这些初始化操作包括如下内容
获取当前类中的属性
添加代理接口
封装 Advisor 并加入到 ProxyFactory 中
设置要代理的类
当然在 Spring 中还为子类提供了定制的函数 customizeProxyFactory ,子类可以在此函
数中进行对 ProxyFactory 的进一步封装
进行获取代理操作
其中,封装 Advisor 并加入到 ProxyFactory 中以及创建代理是两个相对繁琐的过程,可以
通过 ProxyFactory 提供的 addAdvisor 方法直接将增强器置人代理创建工厂中,但是将拦截器
封装为增强器还是需要一定的逻辑的
Spring源码深度解析 AOP总结_第39张图片
Spring源码深度解析 AOP总结_第40张图片
由于 Spring 中涉及过多的拦截器、增强器、增强方法等方式来对逻辑进行增强,所以非常
有必要统一封装成 Advisor 来进行代理的创建,完成了增强的封装过程,那么解析最重要的一
步就是代理的创建与获取了
在这里插入图片描述

创建代理
Spring源码深度解析 AOP总结_第41张图片

Spring 是如何选择代理方式的
从 if 中的判断条件可以看到3个方面影响着 Spring 的判断
optimize 用来控制通过 CGLIB 创建的代理是否使用激进的优化策略 除非完全了解
AOP 代理如何处理优化,否则不推荐用户使用这个设直 目前这个属性仅用于 CGLIB
代理,对于 JDK 动态代理(默认代理)无效
proxyTargetClass 这个属性为 true 时, 目标类本身被代理而不是目标类的接口 如果
这个属性值被设为 true, CGLIB 代理将被创建,设置方式为<aop:aspectj-autoproxy-
proxy-target-class=“true” />
hasNoUserSuppliedProxyinterfaces :是否存在代理接口
下面是对 JDK 与 Cglib 方式的总结
如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP
如果目标对象实现了接口,可以强制使用 CGLIB 实现 AOP
如果目标对象没有实现接口,必须采用 CGLIB 库, Spring 会自动在 JDK 动态代理和
CGLIB 之间转换
如何强制使用 CGLIB 实现 AOP
添加 CGLIB 库, Spring_HOME/cglib/* .jar
在Spring 配直文件中加入<aop:aspectj-autoproxy proxy-target-class= “true”/>
JDK 动态代理和 CGLIB 字节码生成的区别?
JDK 动态代理只能对实现了接口的类生成代理,而不能针对类
CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为
是继承,所以该类或方法最好不要声明成final

获取代理
JDK 代理使用示例
创建业务接口,业务对外提供的接口,包含着业务可以对外提供的功能
Spring源码深度解析 AOP总结_第42张图片
创建业务接口 实现类
Spring源码深度解析 AOP总结_第43张图片
创建自定义的 InvocationHandler ,用于对接口提供的方法进行增强。
在这里插入图片描述
Spring源码深度解析 AOP总结_第44张图片
最后进行测试 验证对于接口的增强是否起到作用
Spring源码深度解析 AOP总结_第45张图片
Spring源码深度解析 AOP总结_第46张图片

使用 JDK 代理的方式,在整个创建过程中,对于 InvocationHandler
的创建是最为核心的,在自定义的 InvocationHandler 中需要重写 3 个函数
构造函数,将代理的对象传入
invoke 方法,此方法中实现了 AOP 增强的所有逻辑
getProxy 方法,此方法千篇一律,但是必不可少

Spring 中的 JDK 代理实现是不是也是这么做的呢
到达JdkDynamicAopProxy 的 getProxy
Spring源码深度解析 AOP总结_第47张图片

在 JdkDynamicAopProxy 中一定会有个 invoke 函数,并且 JdkDynamicAopProxy 会把 AOP
的核心逻辑写在其中
Spring源码深度解析 AOP总结_第48张图片
Spring源码深度解析 AOP总结_第49张图片
Spring源码深度解析 AOP总结_第50张图片

在 proceed 方法中是怎么实现前置增强在目标方法前调用后置增
强在目标方法后调用的逻辑呢
Spring源码深度解析 AOP总结_第51张图片

CGLIB 使用示例
Spring源码深度解析 AOP总结_第52张图片
Spring源码深度解析 AOP总结_第53张图片

完成 CGLIB 代理的类是委托给 Cglib2AopProxy 类去实现的
在 Cglib2AopProxy 类的 getProxy 方法中实现了 Enhancer 的创建及接口封装
Spring源码深度解析 AOP总结_第54张图片
Spring源码深度解析 AOP总结_第55张图片

这里最重要的是通过 getCallbacks 方法设置拦截器链
Spring源码深度解析 AOP总结_第56张图片
Spring源码深度解析 AOP总结_第57张图片
Spring源码深度解析 AOP总结_第58张图片

对于 CGLIB 方式实现的代理,其核心逻辑在 DynamicAdvisedlnterceptor 中的 intercept 中
Spring源码深度解析 AOP总结_第59张图片
Spring源码深度解析 AOP总结_第60张图片

静态 AOP 使用示例
如果想从动态代理的方式改成静态代理的方式需要做如下改动
Spring 全局配置文件的修改 ,加入 LWT 开关
Spring源码深度解析 AOP总结_第61张图片
Spring源码深度解析 AOP总结_第62张图片
加入 aop.xml 在 class 目录下的META-INF (没有则自己建立) 文件夹下建立 aop.xml
内容如下:
Spring源码深度解析 AOP总结_第63张图片
加入启动参数。 如果是在 Eclipse 中启动的话需要加上启动参数
Spring源码深度解析 AOP总结_第64张图片
测试
在这里插入图片描述
Spring源码深度解析 AOP总结_第65张图片

创建 AOP 静态代理
Instrumentation 使用
写 ClassFileTransformer 类
Spring源码深度解析 AOP总结_第66张图片
Spring源码深度解析 AOP总结_第67张图片
编写 agent 类
Spring源码深度解析 AOP总结_第68张图片
上面两个类就是 agent 的核心了, JVM 启动时在应用加载前会调用 PerfMonAgent.premain ,
然后 PerfMonAgent.premain 中实例化了一个定制的 ClassFileTransforme ,即 PerfMonXformer
并通过 inst.addTransformer( trans )把 PerfMonXformer 的实例加入 Instrumentation 实例(由 JVM
传入),这就使得应用中的类加载时, PerfMonXformer.transform 都会被调用 你在此方法中可
以改变加载的类 真的有点神奇,为了改变类的字节码 使用了 JBoss 的 Javassist ,
JBoss 的 Javassist 真的很强大,能让你很容易地改变类的字节码 在上面的方
法中通过改变类的字节码,在每个类的方法入口中加入了 long stime =System.nanoTime(),在方
法的出口加入了:
在这里插入图片描述

打包 agent
对于 agent 的打包,有点讲究
JAR META-INF/MANIFEST.MF 加入 Premain-Class: xx, xx 在此语境中就是
agent 类, 即 org.toy.PerfMonAgent
如果 agent 类引入别的包,需使用 Boot-Class-Path :xx, xx 在此语境中就是上面提
到的 JBoss javassit ,即 /home/pw/lazy/.m2/repository/javassist/javassist/3.8.0.GA/javassist3.8.0.GA.jar

Maven 的 POM
Spring源码深度解析 AOP总结_第69张图片
Spring源码深度解析 AOP总结_第70张图片
打包应用
Spring源码深度解析 AOP总结_第71张图片
Java 选项中有 -javaagent:xx ,其中 xx 就是 agent JAR, Java 通过此选项加载 agent ,由
agent 来监控 classpath 下的应用
最后的执行结果:
Spring源码深度解析 AOP总结_第72张图片

至少在 AspectJ 中会有如下功能
读取 META-INF/aop.xml
将aop.xm 中定义的增强器通过自定义的 ClassFileTransformer 织入对应的类中

自定义标签
ContextNamespaceHandler 中有这样一段函数

作为 BeanDefinitionParser 接口的实现类,
它的核心逻辑是从 parse 函数开始的,而经过父类的封装, LoadTimeWeaverBeanDefinitionParser
类的核心实现被转移到了 doParse 函数中
Spring源码深度解析 AOP总结_第73张图片
Spring源码深度解析 AOP总结_第74张图片

上面函数的核心作用其实就是注册一个对于 ApectJ 处理的类 org.Springframework.context.
weaving.AspectJWeavingEnaber ,它的注册流程总结起来如下
是否开启 AspectJ
Spring 会帮助我们检测是否可
以使用 AspectJ 功能,而检测的依据便是文件 META-INF/aop.xml 是否存在
Spring源码深度解析 AOP总结_第75张图片

将org.Springframework.context.weaving.AspectJWeavingEnabler 封装在 BeanDefinition中
注册
在LoadTimeWeaverBeanDefinitionParser 类中有这样的函数:
Spring源码深度解析 AOP总结_第76张图片
当 Spring 在读取到自定义标签<context: load-time-weaver/>
后会产生一个 bean ,而这个 bean 的 id 为 loadTimeWeaver, class 为 org.Springframework.context.weaving.
DefaultContextLoadTimeWeaver ,也就是完成了 DefaultContextLoadTimeWeaver 类的注册
在 AbstractApplicationContext中的 prepareBeanFactory 函数中有这样一段代码
Spring源码深度解析 AOP总结_第77张图片
在 AbstractApplicationContext 中的 prepareBeanFactory 函数是在容器初始化时候调用的,
也就是说只有注册了 LoadTime WeaverAwareProcessor 才会激活整个 AspectJ 的功能

织入
在 LoadTimeWeaverAwareProcessor 中的 postProcessBeforelnitialization 函数中完成了什么样的逻辑呢
Spring源码深度解析 AOP总结_第78张图片
在LoadTimeWeaverAwareProcessor 中的 postProcessBeforelnitialization 函数中,因为最开
始的 if 判断注定这个后处理器只对 LoadTimeWeaverAware 类型的 bean 起作用,而纵观所有的
bean 实现 LoadTimeWeaver 接口的类只有 AspectJWeavingEnabler
当在 Spring 中调用 AspectJWeavingEnabler 时, this.loadTimeWeaver 尚未被初始化,那么,会
直接调用 beanFactory.getBean 方法获取对应的 DefaultContextLoadTimeWeaver 类型的 bean 并将其设
置为AspectJWeavingEnabler 类型 bean 的 loadTimeWeaver 属性中 当然 AspectJWeavingEnabler同
样实现了 BeanClassLoaderAware 以及 Ordered 接口,实现 BeanClassLoaderAware 接口保证了在 bean
初始化的时候调用 AbstractAutowireCapableBeanFactory 的 invokeAwareMethods 的时候将
beanClassLoader 赋值给当前类 而实现 Ordered 接口则保证在实例化 bean 时当前 bean 会被最先初
始化
而 DefaultContextLoadTimeWeaver 类又同时实现了 LoadTimeWeaver BeanClassLoaderAware
以及 DisposableBean 其中 DisposableBean 接口保证在 bean 销毁时会调用 destroy 方法进行 bean
的清理,而 BeanClassLoaderAware 接口则保证在 bean 的初始化调用 AbstractAutowireCapabl巳-
BeanFactory 的 invokeAwareMethods 时调用 setBeanCJassLoader 方法
Spring源码深度解析 AOP总结_第79张图片
Spring源码深度解析 AOP总结_第80张图片
上面的函数中有一句很容易被忽略但是很关键的代码:
在这里插入图片描述
这句代码不仅仅是实例化了一个 InstrumentationLoadTimeWeaver 类型的实例,而且在实例
化过程中还做了一些额外的操作
在实例化的过程中会对当前的 this. instrumentation 属性进行初始化,而初始化的代码如下:
this.instrumentation = getlnstrumentation (),也就是说在 InstrumentationLoadTimeWeaver 实例化后
其属性 Instrumentation 已经被初始化为代表着当前虚拟机的实例了 综合我们讲过的例子,对
于注册转换器,如 addTransformer 函数等 ,便可以直接使用此属性进行操作了
也就是经过以上程序的处理后,在 Spring 中的 bean 之间的关系如下
AspectJWeavingEnabler 类型的 bean 中的 loadTimeWeaver 属性被初始化为 Default
ContextLoadTimeWeaver 类型的 bean
DefaultContextLoadTime Weaver 类型的 bean 中的 loadTimeWeaver 属性被初始化为
InstrumentationLoadTimeWeaver
因为 AspectJWeavingEnabler 类同样实现了 BeanFactoryPostProcessor ,所以当所有 bean 解,
析结束后会调用其 postProcessBeanFactory 方法
Spring源码深度解析 AOP总结_第81张图片
Spring源码深度解析 AOP总结_第82张图片
AspectJClassBypassingClassFileTransformer 的作用仅仅是告诉 AspectJ 以 org.aspectj 开头的
或者 org/aspect 开头的类不进行处理

你可能感兴趣的:(Spring源码深度解析,spring)