Spring5 AOP 默认使用 Cglib 了?我第一次听到这个说法是在一个微信群里:html
刚看到这个说法的时候,我是保持怀疑态度的。java
你们都知道 Spring5 以前的版本 AOP 在默认状况下是使用 JDK 动态代理的,那是否是 Spring5 版本真的作了修改呢?因而我打开 Spring Framework 5.x 文档,再次确认了一下:git
文档地址:docs.spring.io/spring/docs…github
简单翻译一下。Spring AOP 默认使用 JDK 动态代理,若是对象没有实现接口,则使用 CGLIB 代理。固然,也能够强制使用 CGLIB 代理。spring
当我把官方文档发到群里以后,又收到了这位同窗的回复:json
为了证实文档写错了,这位同窗还写了一个 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.....()");
}
}
复制代码
UserServiceImpl
实现了UserService
接口,同时使用UserServiceAspect
对UserService#work
方法进行前置加强拦截。框架
从运行结果来看,这里的确使用了 CGLIB 代理而不是 JDK 动态代理。ide
难道真的是文档写错了?!
在 Spring Framework 中,是使用@EnableAspectJAutoProxy
注解来开启 Spring AOP 相关功能的。
Spring Framework 5.2.0.RELEASE 版本@EnableAspectJAutoProxy
注解源码以下:
经过源码注释咱们能够了解到:在 Spring Framework 5.2.0.RELEASE 版本中,proxyTargetClass
的默认取值依旧是false
,默认仍是使用 JDK 动态代理。
难道文档和源码注释都写错了?!
接下来,我尝试使用@EnableAspectJAutoProxy
来强制使用 JDK 动态代理。
运行环境:SpringBoot 2.2.0.RELEASE 版本,内置 Spring Framework 版本为 5.2.0.RELEASE 版本。
经过运行发现,仍是使用了 CGLIB 代理。难道@EnableAspectJAutoProxy
的 proxyTargetClass
设置无效了?
@EnableAspectJAutoProxy
源码注释都说了默认是使用 JDK 动态代理proxyTargetClass
为false
,程序依旧使用 CGLIB 代理等一下,咱们是否是遗漏了什么?
示例程序是使用 SpringBoot 来运行的,那若是不用 SpringBoot,只用 Spring Framework 会怎么样呢?
运行环境:Spring Framework 5.2.0.RELEASE 版本。 UserServiceImpl 和 UserServiceAspect 类和上文同样,这里不在赘述。
运行结果代表: 在 Spring Framework 5.x 版本中,若是类实现了接口,AOP 默认仍是使用 JDK 动态代理。
proxyTargetClass
进行修改。结果上面的分析,颇有多是 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
方法:
在这个方法中返回了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 的实现,须要经过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 框架本身是不会来读取这个配置文件的。
能够看到,在 SpringBoot 1.5.x 版本中,默认仍是使用 JDK 动态代理的。
SpringBoot 2.x 版本为何要默认使用 Cglib 来实现 AOP 呢?这么作的好处又是什么呢?笔者从网上找到了一些资料,先来看一个 issue。
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.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内容。
欢迎关注我的公众号,一块儿学习成长: