惊人!Spring5 AOP 默认使用Cglib? 从现象到源码深度分析
spring5 aop 默认使用 cglib 了?我第一次听到这个说法是在一个微信群里:
真的假的?查阅文档
刚看到这个说法的时候,我是保持怀疑态度的。
大家都知道 spring5 之前的版本 aop 在默认情况下是使用 jdk 动态代理的,那是不是 spring5 版本真的做了修改呢?于是我打开 spring framework 5.x 文档,再次确认了一下:
文档地址:https://docs.spring.io/spring/docs/5.2.0.release/spring-framework-reference/core.html#aop
简单翻译一下。spring aop 默认使用 jdk 动态代理,如果对象没有实现接口,则使用 cglib 代理。当然,也可以强制使用 cglib 代理。
什么?文档写错了?!
当我把官方文档发到群里之后,又收到了这位同学的回复:
springboot 2.x 代码示例
为了证明文档写错了,这位同学还写了一个 demo。下面,就由我来重现一下这个 demo 程序:
运行环境:springboot 2.2.0.release 版本,内置 spring framework 版本为 5.2.0.release 版本。同时添加 spring-boot-starter-aop 依赖,自动装配 spring aop。
public interface userservice { void work(); } @service public class userserviceimpl implements userservice { @override public void work() { system.out.println("开始干活...coding..."); } }
@component @aspect public class userserviceaspect { @before("execution(* com.me.aop.userservice.work(..))") public void logbefore(joinpoint joinpoint) { system.out.println("userserviceaspect.....()"); } }
userserviceimpl
实现了userservice
接口,同时使用userserviceaspect
对userservice#work
方法进行前置增强拦截。
从运行结果来看,这里的确使用了 cglib 代理而不是 jdk 动态代理。
难道真的是文档写错了?!
@enableaspectjautoproxy 源码注释
在 spring framework 中,是使用@enableaspectjautoproxy
注解来开启 spring aop 相关功能的。
spring framework 5.2.0.release 版本@enableaspectjautoproxy
注解源码如下:
通过源码注释我们可以了解到:在 spring framework 5.2.0.release 版本中,proxytargetclass
的默认取值依旧是false
,默认还是使用 jdk 动态代理。
难道文档和源码注释都写错了?!
@enableaspectjautoproxy 的 proxytargetclass 无效了?
接下来,我尝试使用@enableaspectjautoproxy
来强制使用 jdk 动态代理。
运行环境:springboot 2.2.0.release 版本,内置 spring framework 版本为 5.2.0.release 版本。
通过运行发现,还是使用了 cglib 代理。难道@enableaspectjautoproxy
的 proxytargetclass
设置无效了?
spring framework 5.x
整理一下思路
- 有人说 spring5 开始 aop 默认使用 cglib 了
- spring framework 5.x 文档和
@enableaspectjautoproxy
源码注释都说了默认是使用 jdk 动态代理 - 程序运行结果说明,即使继承了接口,设置
proxytargetclass
为false
,程序依旧使用 cglib 代理
等一下,我们是不是遗漏了什么?
示例程序是使用 springboot 来运行的,那如果不用 springboot,只用 spring framework 会怎么样呢?
运行环境:spring framework 5.2.0.release 版本。
userserviceimpl 和 userserviceaspect 类和上文一样,这里不在赘述。
运行结果表明: 在 spring framework 5.x 版本中,如果类实现了接口,aop 默认还是使用 jdk 动态代理。
再整理思路
- spring5 aop 默认依旧使用 jdk 动态代理,官方文档和源码注释没有错。
- springboot 2.x 版本中,aop 默认使用 cglib,且无法通过
proxytargetclass
进行修改。 - 那是不是 springboot 2.x 版本做了一些改动呢?
再探 springboot 2.x
结果上面的分析,很有可能是 springboot2.x 版本中,修改了 spring aop 的相关配置。那就来一波源码分析,看一下内部到底做了什么。
源码分析
源码分析,找对入口很重要。那这次的入口在哪里呢?
@springbootapplication
是一个组合注解,该注解中使用@enableautoconfiguration
实现了大量的自动装配。
enableautoconfiguration
也是一个组合注解,在该注解上被标志了@import
。关于@import
注解的详细用法,可以参看笔者之前的文章:https://mp.weixin.qq.com/s/7arh4svh1mlhe0gvvbz84q
@target(elementtype.type) @retention(retentionpolicy.runtime) @documented @inherited @autoconfigurationpackage @import(autoconfigurationimportselector.class) public @interface enableautoconfiguration {
autoconfigurationimportselector
实现了deferredimportselector
接口。
在 spring framework 4.x 版本中,这是一个空接口,它仅仅是继承了importselector
接口而已。而在 5.x 版本中拓展了deferredimportselector
接口,增加了一个getimportgroup
方法:
在这个方法中返回了autoconfigurationgroup
类。这是autoconfigurationimportselector
中的一个内部类,他实现了deferredimportselector.group
接口。
在 springboot 2.x 版本中,就是通过autoconfigurationimportselector.autoconfigurationgroup#process
方法来导入自动配置类的。
通过断点调试可以看到,和 aop 相关的自动配置是通过org.springframework.boot.autoconfigure.aop.aopautoconfiguration
来进行配置的。
真相大白
看到这里,可以说是真相大白了。在 springboot2.x 版本中,通过aopautoconfiguration
来自动装配 aop。
默认情况下,是肯定没有spring.aop.proxy-target-class
这个配置项的。而此时,在 springboot 2.x 版本中会默认使用 cglib 来实现。
springboot 2.x 中如何修改 aop 实现
通过源码我们也就可以知道,在 springboot 2.x 中如果需要修改 aop 的实现,需要通过spring.aop.proxy-target-class
这个配置项来修改。
#在application.properties文件中通过spring.aop.proxy-target-class来配置 spring.aop.proxy-target-class=false
这里也提一下spring-configuration-metadata.json
文件的作用:在使用application.properties
或application.yml
文件时,idea 就是通过读取这些文件信息来提供代码提示的,springboot 框架自己是不会来读取这个配置文件的。
sringboot 1.5.x 又是怎么样的
可以看到,在 springboot 1.5.x 版本中,默认还是使用 jdk 动态代理的。
springboot 2.x 为何默认使用 cglib
springboot 2.x 版本为什么要默认使用 cglib 来实现 aop 呢?这么做的好处又是什么呢?笔者从网上找到了一些资料,先来看一个 issue。
spring boot issue #5423
use @enabletransactionmanagement(proxytargetclass = true) #5423
https://github.com/spring-projects/spring-boot/issues/5423
在这个 issue 中,抛出了这样一个问题:
翻译一下:我们应该使用@enabletransactionmanagement(proxytargetclass = true)来防止人们不使用接口时出现讨厌的代理问题。
这个"不使用接口时出现讨厌的代理问题"是什么呢?思考一分钟。
讨厌的代理问题
假设,我们有一个userserviceimpl
和userservice
类,此时需要在usercontoller
中使用userservice
。在 spring 中通常都习惯这样写代码:
@autowired userservice userservice;
在这种情况下,无论是使用 jdk 动态代理,还是 cglib 都不会出现问题。
但是,如果你的代码是这样的呢:
@autowired userserviceimpl userservice;
这个时候,如果我们是使用 jdk 动态代理,那在启动时就会报错:
因为 jdk 动态代理是基于接口的,代理生成的对象只能赋值给接口变量。
而 cglib 就不存在这个问题。因为 cglib 是通过生成子类来实现的,代理对象无论是赋值给接口还是实现类这两者都是代理对象的父类。
springboot 正是出于这种考虑,于是在 2.x 版本中,将 aop 默认实现改为了 cglib。
更多的细节信息,读者可以自己查阅上述 issue。
总结
- spring 5.x 中 aop 默认依旧使用 jdk 动态代理。
- springboot 2.x 开始,为了解决使用 jdk 动态代理可能导致的类型转化异常而默认使用 cglib。
- 在 springboot 2.x 中,如果需要默认使用 jdk 动态代理可以通过配置项
spring.aop.proxy-target-class=false
来进行修改,proxytargetclass
配置已无效。
延伸阅读
issue:default cglib proxy setting default cannot be overridden by using core framework annotations (@enabletransactionmanagement, @enableaspectjautoproxy) #12194
https://github.com/spring-projects/spring-boot/issues/12194
这个 issue 也聊到了关于proxytargetclass
设置失效的问题,讨论内容包括:@enableaspectjautoproxy
、@enablecaching
和 @enabletransactionmanagement
。感兴趣的读者可以自行查阅该 issue内容。
欢迎关注个人公众号,一起学习成长:
上一篇: 源码包安装转换rpm包
下一篇: Samba与nfs与ftp