#yyds干货盘点#Spring源码三千问Spring动态代理(什么时候使用的 cglib,什么时候使用的是 jdk proxy())

休言女子非英物,夜夜龙泉壁上鸣。这篇文章主要讲述#yyds干货盘点#Spring源码三千问Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?相关的知识,希望能为你提供帮助。
@[TOC](Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?)
前言前面分析 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 @Resource ApplicationContext applicationContext; public static void main(String[] args) // 将 proxy-target-class 设置为 false System.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 创建代理的源码如下:
#yyds干货盘点#Spring源码三千问Spring动态代理(什么时候使用的 cglib,什么时候使用的是 jdk proxy())

文章图片

可以看出,如果用户指定了 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; cant just set interfaces to the targets 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
如果本文对你有所帮助,欢迎点赞收藏!
【#yyds干货盘点#Spring源码三千问Spring动态代理(什么时候使用的 cglib,什么时候使用的是 jdk proxy())】有关 Spring 源码方面的问题欢迎一起交流,备注:51cto (vx: Kevin-Wang001)
博主好课推荐:
课程 地址
Dubbo源码解读——通向高手之路 https://edu.51cto.com/course/23382.html
正则表达式基础与提升 https://edu.51cto.com/course/16391.html

    推荐阅读