Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?

  • 前言
  • 版本约定
  • 正文
    • 例子测试
    • 结论分析
      • proxyTargetClass 标识的校正
    • 哪些接口不是 ReasonableProxyInterface
  • 小结

前言

前面分析 Spring AOP 是如何为 Pointcut 匹配的类生成代理类时,提到 spring 使用 cglib 还是 jdk proxy 来生成动态代理是由两个因素共同决定的:

  1. 第一个因素是 targetClass 的类型(接口 or 实体类);
  2. 第二个因素是 proxyTargetClass 标识的值(true or false)。

其中 proxyTargetClass 标识的值是由用户和 spring 框架共同决定的。

那么 Spring 在为一个类生成代理类时,到底使用的 cglib 还是 jdk proxy 呢?
接下来,我们通过具体的例子来分析一下,通过例子来得出结论。

版本约定

Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)

正文

例子测试

@Component
@Aspect
public class MyAspect {@Around("execution(* com.kvn.aop.proxy.*.*(..))")public Object around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("before...");try {return pjp.proceed();} finally {System.out.println("finally...");}}
}public interface FooInterface2 {String doBiz();
}public interface FooInterface3 {}@RestController
@SpringBootApplication
public class AopApplication {@ResourceApplicationContext applicationContext;public static void main(String[] args) {// 将 proxy-target-class 设置为 falseSystem.setProperty("spring.aop.proxy-target-class", "false");SpringApplication app = new SpringApplication(AopApplication2.class);app.setBannerMode(Banner.Mode.OFF);app.run(args);}@GetMapping("/status")public String status() {return ObjectUtils.identityToString(applicationContext.getBean("fooService")) + "<br/>"+ ObjectUtils.identityToString(applicationContext.getBean("fooService2")) + "<br/>"+ ObjectUtils.identityToString(applicationContext.getBean("fooService3"));}
}

FooService 是一个具体的类,没有实现接口。
FooService2 实现了 FooInterface2 接口。
FooInterface3 实现了一个空接口 FooInterface3。
启动类设置了 proxyTargetClass=false

输出结果:

com.kvn.aop.proxy.FooService$$EnhancerBySpringCGLIB$$a01455ab@4c670453
com.sun.proxy.$Proxy56@4ed737b9
com.kvn.aop.proxy.FooService3$$EnhancerBySpringCGLIB$$cf173812@7eadee2a

可以看出:
FooService、FooService3 都是使用的 cglib,而 FooService2 使用的是 jdk proxy。

结论分析

beanClass 是具体类还是接口类型,这个是可以唯一确定的。变化的是 proxyTargetClass 标识。
Spring AOP 创建代理的源码如下:

可以看出,如果用户指定了 proxyTargetClass 的值的话,会通过 ProxyFactory#copyFrom(ProxyConfig) 方法拷贝过来。
但是,最终 proxyTargetClass 的值还会被 Spring 框架进行校正。

proxyTargetClass 标识的校正

AutoProxyUtils#shouldProxyTargetClass()

public static boolean shouldProxyTargetClass(ConfigurableListableBeanFactory beanFactory, @Nullable String beanName) {if (beanName != null && beanFactory.containsBeanDefinition(beanName)) {BeanDefinition bd = beanFactory.getBeanDefinition(beanName);return Boolean.TRUE.equals(bd.getAttribute(PRESERVE_TARGET_CLASS_ATTRIBUTE));}return false;
}

org.springframework.aop.framework.autoproxy.AutoProxyUtils.preserveTargetClass 这个属性是 spring 内部 bean 使用的,大部分情况都不会走这个分支。

ProxyProcessorSupport#evaluateProxyInterfaces():

protected void evaluateProxyInterfaces(Class<?> beanClass, ProxyFactory proxyFactory) {Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, getProxyClassLoader());boolean hasReasonableProxyInterface = false;for (Class<?> ifc : targetInterfaces) {if (!isConfigurationCallbackInterface(ifc) && !isInternalLanguageInterface(ifc) &&ifc.getMethods().length > 0) {hasReasonableProxyInterface = true;break;}}if (hasReasonableProxyInterface) {// Must allow for introductions; can't just set interfaces to the target's interfaces only.for (Class<?> ifc : targetInterfaces) {proxyFactory.addInterface(ifc);}}else {proxyFactory.setProxyTargetClass(true);}
}

如果 hasReasonableProxyInterface=fasle 的话,也就是:没有合理的(Reasonable)代理接口的话,就会将 proxyTargetClass 设置为 true。

上面的例子中:
FooService2 实现了 FooService2 接口,它是一个合理的代理接口,所以,proxyTargetClass 保持原样为 false,从而 FooService2 使用的是 jdk proxy 代理。
FooService3 实现的是一个空接口,Spring 认为不是一个合理的代理接口,所以,会将 proxyTargetClass 设置为 true,从而 FooService3 使用的是 cglib 代理。

哪些接口不是 ReasonableProxyInterface

根据上面的分析,如果 target object 实现的接口不是 ReasonableProxyInterface 的话,同样不会使用 jdk proxy。

非 ReasonableProxyInterface 的类型如下:

protected boolean isConfigurationCallbackInterface(Class<?> ifc) {return (InitializingBean.class == ifc || DisposableBean.class == ifc || Closeable.class == ifc ||AutoCloseable.class == ifc || ObjectUtils.containsElement(ifc.getInterfaces(), Aware.class));
}protected boolean isInternalLanguageInterface(Class<?> ifc) {return (ifc.getName().equals("groovy.lang.GroovyObject") ||ifc.getName().endsWith(".cglib.proxy.Factory") ||ifc.getName().endsWith(".bytebuddy.MockAccess"));
}

也就是说,如果 target object 只实现了 InitializingBean、DisposableBean、Closeable、AutoCloseable、Aware、bytebuddy.MockAccess、cglib.proxy.Factory、groovy.lang.GroovyObject 等接口的话,就不会使用 jdk proxy。

小结

总的来说,默认情况下 proxyTargetClass=false,Spring 是默认使用 jdk proxy 的。
如果 target object 没有实现任何 ReasonableProxyInterface 接口的话,就会使用 cglib proxy。
如果用户指定了 proxyTargetClass=true 的话,Spring 基本上都是使用 cglib proxy 的。

再细分来看的话:

  • 默认情况下 proxyTargetClass=false:
  1. beanClass 是接口类型,则使用 jdk proxy
  2. beanClass 不是接口类型,且 beanClass 实现了一个合理的代理接口,则使用 jdk proxy 来产生代理
  3. 除了上面两种情况,spring 会将 proxyTargetClass 校正为 true,最后使用 cglib 来产生代理
  • 如果用户指定了 proxyTargetClass=true
  1. beanClass 是接口类型,则使用 jdk proxy
    (通常扫描出来的 beanClass 都不会是接口类型,而是用户定义的一个具体的类)
  2. beanClass 不是接口类型,则使用 cglib

如果本文对你有所帮助,欢迎点赞收藏!

源码测试工程下载:
老王读Spring IoC源码分析&测试代码下载
老王读Spring AOP源码分析&测试代码下载

公众号后台回复:下载IoC 或者 下载AOP 可以免费下载源码测试工程…

文章,请关注公众号: 老王学源码


系列博文:
【老王读Spring AOP-0】SpringAop引入&&AOP概念、术语介绍
【老王读Spring AOP-1】Pointcut如何匹配到 join point
【老王读Spring AOP-2】如何为 Pointcut 匹配的类生成动态代理类
【老王读Spring AOP-3】Spring AOP 执行 Pointcut 对应的 Advice 的过程
【老王读Spring AOP-4】Spring AOP 与Spring IoC 结合的过程 && ProxyFactory 解析
【老王读Spring AOP-5】@Transactional产生AOP代理的原理
【老王读Spring AOP-6】@Async产生AOP代理的原理
【Spring 源码阅读】Spring IoC、AOP 原理小总结

相关阅读:
【Spring源码三千问】Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?
【Spring源码三千问】Advice、Advisor、Advised都是什么接口?
【Spring源码三千问】没有AspectJ,Spring中如何使用SpringAOP、@Transactional?
【Spring源码三千问】Spring AOP 中 AbstractAdvisorAutoProxyCreator、AbstractAdvisingBeanPostProcessor的区别
【Spring 源码三千问】同样是AOP代理bean,为什么@Async标记的bean循环依赖时会报错?

【Spring源码三千问】Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?相关推荐

  1. 【Spring源码三千问】Bean的Scope有哪些?scope=request是什么原理?

    Bean的Scope有哪些?scope=request是什么原理? 前言 版本约定 正文 Scope 接口的类图 RequestScope 在哪里注册的? Scope 在哪里生效的? scope=re ...

  2. 【Spring 源码阅读】Spring IoC、AOP 原理小总结

    Spring IoC.AOP 原理小总结 前言 版本约定 正文 Spring BeanFactory 容器初始化过程 IoC 的过程 bean 完整的创建流程如下 AOP 的过程 Annotation ...

  3. spring源码深度解析---创建AOP代理之获取增强器

    spring源码深度解析-创建AOP代理之获取增强器 在上一篇的博文中我们讲解了通过自定义配置完成了对AnnotationAwareAspectJAutoProxyCreator类型的自动注册,那么这 ...

  4. 人人都能看懂的Spring源码解析,Spring如何解决循环依赖

    人人都能看懂的Spring源码解析,Spring如何解决循环依赖 原理解析 什么是循环依赖 循环依赖会有什么问题? 如何解决循环依赖 问题的根本原因 如何解决 为什么需要三级缓存? Spring的三级 ...

  5. spring源码分析第六天------spring经典面试问题

    spring源码分析第六天------spring经典面试问题 1.Spring5 新特性及应用举例 2.Spring 经典的面试问题 a.什么是 Spring 框架?Spring 框架有哪些主要模块 ...

  6. 【Spring源码学习】Spring Bean的销毁

    [Spring源码学习]Spring Bean的销毁 一.注册bean销毁的类 1.registerDisposableBeanIfNecessary() 2.DisposableBeanAdapte ...

  7. (转)spring源码解析,spring工作原理

    转自:https://www.ibm.com/developerworks/cn/java/j-lo-spring-principle/ Spring 的骨骼架构 Spring 总共有十几个组件,但是 ...

  8. spring源码学习:spring初始化流程

    首先借个图,说明一下spring的bean的整个生命流程. 销毁什么的这个看图就知道怎么回事,使用的话一般都是纯业务,而且我们更关心spring是怎么初始化的,初始化成我们定义的那个样子.我们就是以这 ...

  9. spring源码深度解析—Spring的整体架构和环境搭建

    概述 Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用.Spring是于2003 年兴起的一个轻量级的Java 开发框 ...

最新文章

  1. shell awk 的一些用法
  2. 在WebStorm里配置watcher实现编辑less文件时自动生成.map和压缩后的.css文件
  3. ios_随手篇3_关于宏的使用
  4. python爬虫登陆网页版腾讯课堂
  5. FPGA专有名词的积累
  6. 极客青年说,北京沙龙
  7. ArcGIS之通过Model Builder执行地理处理
  8. Oracle 数据库监听配置
  9. Java中基于TCP通过socket嵌套字连接方式传送文件
  10. cannot deserialize from Object value
  11. JavaScript分割字符串
  12. 博科300 java配置,博科300 光纤交换机如何设置为SSH登录?
  13. 怎样将exe打包成服务运行
  14. Android使用文件管理器打开指定文件夹,浏览里面的内容
  15. 96Boards MIPI CSI Camera Mezzanine V2.1
  16. 关于DebugView无法打印出KdPrint信息
  17. OpenCV极坐标转换函数warpPolar的使用
  18. Oracle使用json后乱码,nodejs读取本地中文json文件出现乱码解决方法
  19. arm5718的ipc-dsp,ipc-ipu通讯
  20. matlab khatri rao积,关于矩阵Khatri-Rao积的一些迹不等式

热门文章

  1. office2010字体包_在Office 2010中使用高级字体连字
  2. element table 合并单元格错位或单元格后移
  3. mysql jena rdf_RDF结合Jena初步基础学习(一)
  4. 人工智能面试总结-正则优化函数
  5. C# 中 EmguCV bitmap 和Image 互转
  6. 文件太大无法复制到U盘
  7. HTTP下载文件校验失败原因分析与解决
  8. 5G无线技术基础自学系列 | 密集组网
  9. GD32F303的SPI初始化完毕,发送SPI数据后。如果想要改变频率SPEED,必须重新初始化SPI时钟。否则SPI将失效
  10. Ubuntu 彻底删除Anaconda3