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

Spring5 AOP 默认使用 Cglib 了?我第一次听到这个说法是在一个微信群里:html

群聊天

真的假的?查阅文档

刚看到这个说法的时候,我是保持怀疑态度的。java

你们都知道 Spring5 以前的版本 AOP 在默认状况下是使用 JDK 动态代理的,那是否是 Spring5 版本真的作了修改呢?因而我打开 Spring Framework 5.x 文档,再次确认了一下:git

文档地址:docs.spring.io/spring/docs…github

Spring Framework 5.x 文档

简单翻译一下。Spring AOP 默认使用 JDK 动态代理,若是对象没有实现接口,则使用 CGLIB 代理。固然,也能够强制使用 CGLIB 代理。spring

什么?文档写错了?!

当我把官方文档发到群里以后,又收到了这位同窗的回复:json

文档写错了?!

SpringBoot 2.x 代码示例

为了证实文档写错了,这位同窗还写了一个 DEMO。下面,就由我来重现一下这个 DEMO 程序:微信

运行环境:SpringBoot 2.2.0.RELEASE 版本,内置 Spring Framework 版本为 5.2.0.RELEASE 版本。同时添加 spring-boot-starter-aop 依赖,自动装配 Spring AOP。app

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 动态代理。ide

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

@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注解的详细用法,能够参看笔者以前的文章:mp.weixin.qq.com/s/7arh4sVH1…

@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

github.com/spring-proj…

在这个 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

github.com/spring-proj…

这个 issue 也聊到了关于proxyTargetClass设置失效的问题,讨论内容包括:@EnableAspectJAutoProxy@EnableCaching@EnableTransactionManagement。感兴趣的读者能够自行查阅该 issue内容。


欢迎关注我的公众号,一块儿学习成长:

Coder小黑
相关文章
相关标签/搜索