专栏首页A周立SpringCloudSpring 5 AOP 默认改用 CGLIB 了?从现象到源码的深度分析

Spring 5 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 Framework 5.x 文档

简单翻译一下。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.....()");
    }
}

默认使用Cglib代理了?

UserServiceImpl实现了UserService接口,同时使用UserServiceAspectUserService#work方法进行前置增强拦截。

从运行结果来看,这里的确使用了 CGLIB 代理而不是 JDK 动态代理。

难道真的是文档写错了?!

@EnableAspectJAutoProxy 源码注释

在 Spring Framework 中,是使用@EnableAspectJAutoProxy注解来开启 Spring AOP 相关功能的。

Spring Framework 5.2.0.RELEASE 版本@EnableAspectJAutoProxy注解源码如下:

@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 版本。

proxyTargetClass设置无效了?

通过运行发现,还是使用了 CGLIB 代理。难道@EnableAspectJAutoProxyproxyTargetClass设置无效了?

Spring Framework 5.x

整理一下思路

  1. 有人说 Spring5 开始 AOP 默认使用 CGLIB 了
  2. Spring Framework 5.x 文档和 @EnableAspectJAutoProxy源码注释都说了默认是使用 JDK 动态代理
  3. 程序运行结果说明,即使继承了接口,设置proxyTargetClassfalse,程序依旧使用 CGLIB 代理

等一下,我们是不是遗漏了什么?

示例程序是使用 SpringBoot 来运行的,那如果不用 SpringBoot,只用 Spring Framework 会怎么样呢?

运行环境:Spring Framework 5.2.0.RELEASE 版本。UserServiceImpl 和 UserServiceAspect 类和上文一样,这里不在赘述。

Spring Framework 5.x

Spring Framework 5.x使用CGLIB

运行结果表明:在 Spring Framework 5.x 版本中,如果类实现了接口,AOP 默认还是使用 JDK 动态代理。

再整理思路

  1. Spring5 AOP 默认依旧使用 JDK 动态代理,官方文档和源码注释没有错。
  2. SpringBoot 2.x 版本中,AOP 默认使用 cglib,且无法通过proxyTargetClass进行修改。
  3. 那是不是 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方法:

AutoConfigurationImportSelector#getImportGroup

在这个方法中返回了AutoConfigurationGroup类。这是AutoConfigurationImportSelector中的一个内部类,他实现了DeferredImportSelector.Group接口。

在 SpringBoot 2.x 版本中,就是通过AutoConfigurationImportSelector.AutoConfigurationGroup#process方法来导入自动配置类的。

导入配置类

通过断点调试可以看到,和 AOP 相关的自动配置是通过org.springframework.boot.autoconfigure.aop.AopAutoConfiguration来进行配置的。

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

这里也提一下spring-configuration-metadata.json文件的作用:在使用application.propertiesapplication.yml文件时,IDEA 就是通过读取这些文件信息来提供代码提示的,SpringBoot 框架自己是不会来读取这个配置文件的。

SringBoot 1.5.x 又是怎么样的

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 中,抛出了这样一个问题:

image.png

翻译一下:我们应该使用@EnableTransactionManagement(proxyTargetClass = true)来防止人们不使用接口时出现讨厌的代理问题。

这个"不使用接口时出现讨厌的代理问题"是什么呢?思考一分钟。

讨厌的代理问题

假设,我们有一个UserServiceImplUserService类,此时需要在UserContoller中使用UserService。在 Spring 中通常都习惯这样写代码:

@Autowired
UserService userService;

在这种情况下,无论是使用 JDK 动态代理,还是 CGLIB 都不会出现问题。

但是,如果你的代码是这样的呢:

@Autowired
UserServiceImpl userService;

这个时候,如果我们是使用 JDK 动态代理,那在启动时就会报错:

启动报错

因为 JDK 动态代理是基于接口的,代理生成的对象只能赋值给接口变量。

而 CGLIB 就不存在这个问题。因为 CGLIB 是通过生成子类来实现的,代理对象无论是赋值给接口还是实现类这两者都是代理对象的父类。

SpringBoot 正是出于这种考虑,于是在 2.x 版本中,将 AOP 默认实现改为了 CGLIB。

更多的细节信息,读者可以自己查阅上述 issue。

总结

  1. Spring 5.x 中 AOP 默认依旧使用 JDK 动态代理。
  2. SpringBoot 2.x 开始,为了解决使用 JDK 动态代理可能导致的类型转化异常而默认使用 CGLIB。
  3. 在 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。

最后,欢迎大家在公众号留言,我们一起探讨学习!感谢你的阅读~

文章分享自微信公众号:
IT牧场

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

原始发表时间:2019-10-29
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 当@Transactional遇到@CacheEvict,你的代码是不是有bug!

    如上图所示,当@Transactional 遇到@CacheEvict,缓存放在 redis 中,这样写代码会有什么问题呢?你们的程序中是否写着这样的代码呢?如...

    用户1516716
  • Spring AOP 源码分析 - 创建代理对象

    在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的。现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的...

    田小波
  • SpringAOP

    动态代理分两种 JDK动态代理:只能对实现了接口的类产生代理 Cglib动态代理:第三方代理技术,对没有实现接口的类产生代理对象,生成子类对象,可以动态添加类的...

    用户3112896
  • Spring AOP 原理源码深度剖析

    概述 AOP(Aspect-Oriented Programming) 面向切面编程。Spring Aop 在 Spring框架中的地位举足轻重,主要用于实现...

    方志朋
  • Spring AOP源码分析-代理方式的选择

    在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种...

    编程大道
  • 面试必备:从源码入手,带你一文读懂Spring AOP面向切面编程

    AOP,Aspect Oriented Programming,面向切面编程。在很多时候我们写一些功能的时候,不需要用到继承这么重的方法,例如对每个方法在执行前...

    搜云库技术团队
  • 【小家Spring】面向切面编程之---Spring AOP的原理讲解以及源码分析(Cannot find current proxy: Set 'exposeProxy' property on )

    一说Spring AOP大家肯定不陌生,它作为Spring Framwork的两大基石之一,在Spring的产品线中有着大量的应用。相信小伙伴们在平时工作的项目...

    YourBatman
  • Spring学习(四)AOP基础

    Spring aop的存在,可以帮助我们少些很多与业务无关但是又冗余的代码,比如,我们可以将计算接口执行时间或者一些通用日志打印的代码抽离出来写到spr...

    虞大大
  • 【小家Spring】详解Spring AOP的底层代理JdkDynamicAopProxy和ObjenesisCglibAopProxy的源码分析(介绍CGLIB使用中的坑)

    在前面的博文我们了解到,Spring所有的代理AopProxy的创建最后都是ProxyCreatorSupport#createAopProxy这个方法,而这个...

    YourBatman
  • 听说 Spring AOP 有坑?那就来踩一踩

    在这里,笔者的SpringBoot的版本为2.1.5.RELEASE,对应的Spring版本为5.1.7.RELEASE。

    芋道源码
  • Spring AOP是什么?你都拿它做什么?

    上一篇文章中,我对Spring源码进行了分析讨论,此处不再赘述,有兴趣的同学可以看看向Spring大佬低头——大量源码流出解析,本文是对上一篇文章的一个补充。回...

    Java团长
  • 从源码入手,一文带你读懂Spring AOP面向切面编程

    之前《零基础带你看Spring源码——IOC控制反转》详细讲了Spring容器的初始化和加载的原理,后面《你真的完全了解Java动态代理吗?看这篇就够了》介绍了...

    Zack说码
  • 深入理解Spring系列之十二:@Transactional是如何工作的

    结合Spring框架,在进行数据库操作的时候,经常使用@Transactional注解,工作经历中看到很多人使用方式都是错误的,没有深入理解过其原理,这是很危险...

    JavaQ
  • 面试问烂的 Spring AOP 原理、SpringMVC 过程

    Spring AOP ,SpringMVC ,这两个应该是国内面试必问题,网上有很多答案,其实背背就可以。但今天笔者带大家一起深入浅出源码,看看他的原理。以期让...

    精讲java
  • 面试问烂的 Spring AOP 原理、SpringMVC 过程

    Spring AOP ,SpringMVC ,这两个应该是国内面试必问题,网上有很多答案,其实背背就可以。但今天笔者带大家一起深入浅出源码,看看他的原理。以期让...

    JAVA葵花宝典
  • 逐行阅读Spring5.X源码(十二)AOP源码分析,难!面试官都不一定懂!

    警告:阅读此文前务必先阅读之前写的《spring如何解决循环引用》,本篇文章高度依赖循环引用。

    源码之路
  • 生气!面试官你过来,我给你手写一个Spring Aop实现!

    又是一周过去了,不知上周发的关于Spring循环依赖使用的三级缓存你们掌握到什么样子了呢?这周又是一篇深度好文(自夸一下),作者每天下班肝了好几天赶出来的文章,...

    止术
  • 原荐Spring AOP是什么?你都拿它做什么?

             对于最近博主最近写博客的兴致大发,我也在思考:为什么而写博客?在互联网时代,无论你是牛人大咖,还是小白菜鸟,都有发表自己看法的权利。无论你是对...

    我叫刘半仙

扫码关注腾讯云开发者

领取腾讯云代金券