从上一篇的笔记中,我们可以了解到 SpringAOP 的使用详情。这篇笔记将对 SpringAOP 实现原理的理解做一些记录。
1、织入与织入的时期
1.1、何为织入?
织入是 把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有 3 个时期 可以进行织入,这三个时期分别为 编译期,类加载期、运行期。
1.2、织入的时期
- 编译期——切面在目标类编译时期被织入,这种方式需要特殊的编译器。AspectJ 的织入编译器就是以这种方式织入切面。
- 类加载期——切面在目标类加载到 JVM 时被织入 ,这种方式需要特殊的类加载器,他可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5 的 加载时织入(load-time weaving,LTW) 就支持这种织入方式
- 运行期——切面在应用运行期间的某个时刻被织入。一般情况下,在织入切面的时候,AOP 容器会为目标对象动态的创建代理对象。Spring AOP 就是以这种方式织入切面。(==√==)
2、原理概述:运行时织入
- 运行时织入是怎么实现的? 答案是代理对象
- 从静态代理到动态代理
- 基于接口代理与基于继承代理
3、代理模式
3.1、静态代理
在程序运行前,由程序员创建或特定工具自动生成源代码并对其编译生成 .class
文件。代理类和委托类的关系在 运行前 就确定了。
缺点:
不是实现 AOP 的 don’t repeat yourself。如果需要被代理的类有很多个方法,那么静态代理需要重复好多次,那不得累死。所以 动态代理 出现啦 ~ ~ ~
3.2、动态代理 (SpringAOP 底层实现机制)
在实现阶段不用关心代理类,而在 运行阶段 才指定哪一个对象
jdk 动态代理(优先): 针对实现了接口的类产生的代理(基于接口)
原理:实现目标对象实现的 所有 接口
cglib 代理:针对没有实现接口的类产生的代理,应用的是底层的 字节码增强技术,生成当前类的子类对象(基于继承)
时序图:
动态代理怎么体现动态?
以 jdk 代理为例子
这里有一个参数,把它设置为 true 后,运行后会把 jdk 代理生成的 class 文件保存下来
1 | System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); |
运行后可以看到项目的跟目录多了个文件夹
打开这个文件,可以看到,该代理类对被代理类的每个方法都进行了增强操作。
那这怎么体现出动态代理呢?
你可以尝试下,只要你往被代理类的接口添加多少个方法(当然你肯定要实现了这些方法)。并不需要我们手写其他代码就能动态增强被代理类的方法了。
加完方法后运行,你会发现 被代理类的 class 文件已经帮我们增强了~ ~ ~
3.3、jdk 代理和 cglib 代理对比
- jdk 代理 只 能针对实现接口的类的 实现方法 进行动态代理
- cglib 代理基于继承来实现代理,无法对 static、final 修饰的
类
进行代理 - cglib 代理基于继承来实现代理,无法对 private、static 修饰的
方法
进行代理
3.4、SpringAOP对两种实现的选择
- 如果目标对象实现了接口,则默认采用 jdk 动态代理
- 如果目标对象没有实现接口,则采用 cglib 动态代理
- 如果目标对象实现了接口,且强制 cglib 动态代理,则使用 cglib 动态代理
那么如何强制使用 cglib 代理呢?
在配置类(启动类也属于配置类)中加上 @EnableAspectJAutoProxy(proxyTargetClass = true)
即可
环境/版本一览:
- 开发工具:Intellij IDEA 2018.2.2
- springboot: 2.1.0.RELEASE
- jdk:1.8.0_171
- maven:3.3.9
- spring-boot-starter-aop:2.1.0.RELEASE
静态代理
ISubject.java
1 | package com.fatal.static_proxy; |
RealSubject.java
1 | package com.fatal.static_proxy; |
Proxy.java
1 | package com.fatal.static_proxy; |
Client.java
1 | package com.fatal.static_proxy; |
显示
启动 Client.main()
,控制台如下
jdk 代理
ISubject.java
1 | package com.fatal.dynamic_proxy; |
RealSubject.java
1 | package com.fatal.dynamic_proxy; |
MyInvocationHandler.java
1 | package com.fatal.jdk_dynamic_proxy; |
Client.java
1 | package com.fatal.dynamic_proxy; |
显示
启动 Client.main()
,控制台如下
cglib 代理
MyMethodInterceptor.java
1 | package com.fatal.cglib_dynamic_proxy; |
Client.java
1 | package com.fatal.cglib_dynamic_proxy; |
显示
参考资料
总结
SpringBoot
的知识已经有前辈在我们之前探索了。比较喜欢的博主有:唐亚峰 | Battcn、方志朋的专栏、程序猿DD、纯洁的微笑。对这门技术感兴趣的可以去他们的博客逛逛。谢谢他们的分享~~
以上文章是我用来学习的Demo
,都是基于 SpringBoot2.x
版本。
源码地址: https://github.com/ynfatal/springboot2-learning/tree/master/chapter25_3