惊人!Spring5 AOP 默认使用 CGLIB ?从现象到源码的深度分析

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> 惊人!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。

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

    END

    Java面试题专栏

    惊人!Spring5 AOP 默认使用 CGLIB ?从现象到源码的深度分析

    欢迎长按下图关注公众号后端技术精选

    惊人!Spring5 AOP 默认使用 CGLIB ?从现象到源码的深度分析

    原文始发于微信公众号(后端技术精选):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> 惊人!Spring5 AOP 默认使用 CGLIB ?从现象到源码的深度分析


     上一篇
    记一次SpringBoot项目启动卡住问题排查记录 记一次SpringBoot项目启动卡住问题排查记录
    点击上方“后端技术精选”,选择“置顶公众号” 技术文章第一时间送达! 作者:陈凯玲 来源:https://url.cn/5UWhBvB 一个spring boot开发的项目,spring boot版本是1.5.7,携带的sp
    下一篇 
    微服务 2.0 技术栈选型手册 微服务 2.0 技术栈选型手册
    点击上方“Java知音”,选择“置顶公众号” 技术文章第一时间送达! 作者:杨波 来源:http://t.cn/R14nyRW 一、前言 二、选型准侧 三、微服务基础架构核心关注点 四、服务框架选型 五、运行时支撑服务选