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接口,同时使用UserServiceAspectUserService#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 代理。难道@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 版本中,如果类实现了接口,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方法:

在这个方法中返回了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.propertiesapplication.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)来防止人们不使用接口时出现讨厌的代理问题。

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

讨厌的代理问题

假设,我们有一个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内容。


欢迎关注个人公众号,一起学习成长:

惊人!Spring5 AOP 默认使用Cglib ?从现象到源码深度分析的更多相关文章

  1. Spring5源码深度分析(二)之理解@Conditional,@Import注解

    代码地址: 1.源码分析二主要分析的内容 1.使用@Condition多条件注册bean对象2.@Import注解快速注入第三方bean对象3.@EnableXXXX 开启原理4.基于ImportBe ...

  2. spring5 源码深度解析----- 被面试官给虐懵了,竟然是因为我不懂@Configuration配置类及@Bean的原理

    @Configuration注解提供了全新的bean创建方式.最初spring通过xml配置文件初始化bean并完成依赖注入工作.从spring3.0开始,在spring framework模块中提供 ...

  3. spring源码深度解析— IOC 之 默认标签解析(上)

    概述 接前两篇文章  spring源码深度解析—Spring的整体架构和环境搭建  和  spring源码深度解析— IOC 之 容器的基本实现 本文主要研究Spring标签的解析,Spring的标签 ...

  4. spring源码深度解析— IOC 之 默认标签解析(下)

    在spring源码深度解析— IOC 之 默认标签解析(上)中我们已经完成了从xml配置文件到BeanDefinition的转换,转换后的实例是GenericBeanDefinition的实例.本文主 ...

  5. spring5 源码深度解析----- Spring事务 是怎么通过AOP实现的?(100%理解Spring事务)

    此篇文章需要有SpringAOP基础,知道AOP底层原理可以更好的理解Spring的事务处理. 自定义标签 对于Spring中事务功能的代码分析,我们首先从配置文件开始人手,在配置文件中有这样一个配置 ...

  6. spring5 源码深度解析----- @Transactional注解的声明式事物介绍(100%理解事务)

    面的几个章节已经分析了spring基于@AspectJ的源码,那么接下来我们分析一下Aop的另一个重要功能,事物管理. 事务的介绍 1.数据库事物特性 原子性多个数据库操作是不可分割的,只有所有的操作 ...

  7. spring默认启动位置以及contextConfigLocation设置源码解析

    这几天在看spring的源码,涉及到spring启动位置的部分,下面就看看spring到底是从哪儿开始加载的.本文使用的是spring3.0M3 首先spring的加载会借助一个监听器ContextL ...

  8. MongoDB 默认写入关注保存数据丢失问题与源码简单分析

    MongoDB 默认写入关注可能保存数据丢失问题分析 问题描述: EDI服务进行优化,将原有MQ发送成功并且DB写入成功,两个条件都达成,响应接收订单数据成功,修改为只有有一个条件成功就响应接收数据成 ...

  9. 控制台程序的中文输出乱码问题(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中显示出错 ...

随机推荐

  1. python最基本的数据掌握

    python初学者可能会对list数据类型和int或者是字符串数据类型比较迷茫  list是引用,是指向的一个内存地址, 变量不是引用的 啥也不说上解释: a = 1 b = a a = 2 prin ...

  2. asp.net core IdentityServer4 实现 resource owner password credentials(密码凭证)

    前言 OAuth 2.0默认四种授权模式(GrantType) 授权码模式(authorization_code) 简化模式(implicit) 密码模式(resource owner passwor ...

  3. 基于Spark的电影推荐系统(实战简介)

    写在前面 一直不知道这个专栏该如何开始写,思来想去,还是暂时把自己对这个项目的一些想法 和大家分享 的形式来展现.有什么问题,欢迎大家一起留言讨论. 这个项目的源代码是在https://github. ...

  4. mybatis 启用延迟加载和按需加载配置

    启用延迟加载和按需加载 Mybatis配置文件中通过两个属性lazyLoadingEnabled和aggressiveLazyLoading来控制延迟加载和按需加载. lazyLoadingEnabl ...

  5. 使用Hexo开源博客系统,轻松搭建你的个人博客(2)- 配置篇

    上一章节,我们介绍了Hexo的基础搭建,搭建完大家一定发现,是英文版本的,并且页面有点丑陋.这一章节,就来跟大家介绍Hexo的配置和主题的设置. 站点信息 上一章有跟大家提到过_config.yml这 ...

  6. Nodejs 发送邮件 激活邮箱

    1. 安装nodemailer npm install nodemailer 项目中引入nodemailer var nodemailer = require('nodemailer'); 2.QQ邮 ...

  7. 如何让谷歌浏览器支持小于12px的字体

    CSS3有个新的属性transform,而我们用到的就是transform:scale() 书写一段代码 <body> <p>我是一个小于12PX的字体</p> & ...

  8. kubeadm部署高可用集群Kubernetes 1.14.1版本

    Kubernetes高可用集群部署 部署架构: Master 组件: kube-apiserver Kubernetes API,集群的统一入口,各组件协调者,以HTTP API提供接口服务,所有对象 ...

  9. Java 学习笔记之 Error和Exception的联系

    Error和Exception的联系: Error和Exception的联系 继承结构:Error和Exception都是继承于Throwable,RuntimeException继承自Excepti ...

  10. MongoDB 学习笔记之 $or与索引关系

    $or与索引关系: 对leftT集合的timestamp创建索引 执行$or语句:db.leftT.find({$or: [{ "timestamp" : 5},{"ag ...