惊人!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
在这个 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
这个 issue 也聊到了关于proxyTargetClass设置失效的问题,讨论内容包括:@EnableAspectJAutoProxy、@EnableCaching 和 @EnableTransactionManagement。感兴趣的读者可以自行查阅该 issue内容。
欢迎关注个人公众号,一起学习成长:

惊人!Spring5 AOP 默认使用Cglib ?从现象到源码深度分析的更多相关文章
- Spring5源码深度分析(二)之理解@Conditional,@Import注解
代码地址: 1.源码分析二主要分析的内容 1.使用@Condition多条件注册bean对象2.@Import注解快速注入第三方bean对象3.@EnableXXXX 开启原理4.基于ImportBe ...
- spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理
@Configuration注解提供了全新的bean创建方式.最初spring通过xml配置文件初始化bean并完成依赖注入工作.从spring3.0开始,在spring framework模块中提供 ...
- spring源码深度解析— IOC 之 默认标签解析(上)
概述 接前两篇文章 spring源码深度解析—Spring的整体架构和环境搭建 和 spring源码深度解析— IOC 之 容器的基本实现 本文主要研究Spring标签的解析,Spring的标签 ...
- spring源码深度解析— IOC 之 默认标签解析(下)
在spring源码深度解析— IOC 之 默认标签解析(上)中我们已经完成了从xml配置文件到BeanDefinition的转换,转换后的实例是GenericBeanDefinition的实例.本文主 ...
- spring5 源码深度解析----- Spring事务 是怎么通过AOP实现的?(100%理解Spring事务)
此篇文章需要有SpringAOP基础,知道AOP底层原理可以更好的理解Spring的事务处理. 自定义标签 对于Spring中事务功能的代码分析,我们首先从配置文件开始人手,在配置文件中有这样一个配置 ...
- spring5 源码深度解析----- @Transactional注解的声明式事物介绍(100%理解事务)
面的几个章节已经分析了spring基于@AspectJ的源码,那么接下来我们分析一下Aop的另一个重要功能,事物管理. 事务的介绍 1.数据库事物特性 原子性多个数据库操作是不可分割的,只有所有的操作 ...
- spring默认启动位置以及contextConfigLocation设置源码解析
这几天在看spring的源码,涉及到spring启动位置的部分,下面就看看spring到底是从哪儿开始加载的.本文使用的是spring3.0M3 首先spring的加载会借助一个监听器ContextL ...
- MongoDB 默认写入关注保存数据丢失问题与源码简单分析
MongoDB 默认写入关注可能保存数据丢失问题分析 问题描述: EDI服务进行优化,将原有MQ发送成功并且DB写入成功,两个条件都达成,响应接收订单数据成功,修改为只有有一个条件成功就响应接收数据成 ...
- 控制台程序的中文输出乱码问题(export LC_CTYPE=zh_CN.GBK,或者修改/etc/sysconfig/i18n为zh_CN.GBK。使用setlocale(LC_CTYPE, "");会使用默认办法。编译器会将源码做转换成Unicode格式,或者指定gcc的输入文件的编码参数-finput-charset=GBK。Linux下应该用wprintf(L"%ls/n",wstr))
今天发现用securecrt登陆时,gcc编译出错时会出现乱码,但直接在主机的窗口界面下用Shell编译却没有乱码.查看了一下当时的错误描述,发现它的引号是中文引号,导致在SecureCRT中显示出错 ...
随机推荐
- [LeetCode] 由 “打印机任务队列" 所想
一.这是个基础问题 Ref: Python之队列模拟算法(打印机问题)[首先研究这个问题作为开始] 任务队列 定义一个任务队列,来管理任务,而无需关心队列的”任务类型". # 自定义队列类 ...
- [Linux] Linux中重命名文件和文件夹的方法(mv命令和rename命令)
原文链接 在Linux下重命名文件或目录,可以使用mv命令或rename命令,这里分享下二者的使用方法. mv命令既可以重命名,又可以移动文件或文件夹. 例子:将目录A重命名为B mv A B 例子: ...
- maven仓库 - nexus配置
搭建环境: 腾讯云服务器 CentOS 6.8.jdk7.sonatype nexus.maven.Xshell 5 版本信息: jdk : jdk-7u80-linux-x64.tar.gz nex ...
- pathlib模块
一.pathlib库官方定义 pathlib 是Python内置库,Python 文档给它的定义是 Object-oriented filesystem paths(面向对象的文件系统路径).path ...
- 【译】Kubernetes监控实践(2):可行监控方案之Prometheus和Sensu
本文介绍两个可行的K8s监控方案:Prometheus和Sensu.两个方案都能全面提供系统级的监控数据,帮助开发人员跟踪K8s关键组件的性能.定位故障.接收预警. 拓展阅读:Kubernetes监控 ...
- Cocos Creator实现左右跳游戏
1. 玩法说明 游戏开始后,点击屏幕左右两侧,机器人朝左上方或右上方跳一步,如果下一步有石块,成功得1分,否则游戏结束. 2. 模块介绍 游戏场景分为2个:主页场景(home).游戏场景(game) ...
- vue中关于滚动条的那点事
vue中关于滚动条的那点事 不知道你有没有遇到过这种情况,有时当页面切换时,滚动条不在页面的顶端.最近半路加入一个项目,就遇到这种情况.(若只是为了解决此问题,可直接翻到最下方)下面谈谈解决此问题的过 ...
- CDH 5.9.3 集群配置
-----------------------------------------集群规划------------------------------------------ hostname ip ...
- 读《深入理解Elasticsearch》点滴-改善查询相关性
1.标准查询 query match _all query:"搜索字符串" operator:or 2.多匹配查询+区分权重 query multi_match "que ...
- MySQL 分页查询优化
有时在处理偏移量非常大的分页时候查询时,例如LIMIT 1000,10这样的查询,这时MySQL需要查询1010条记录然后只返回最后10条,前面1000条记录都被抛弃,这样的代价非常高.要优化这种查询 ...