栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?
  • 前言
  • 版本约定
  • 正文
    • 例子测试
    • 结论分析
      • proxyTargetClass 标识的校正
    • 哪些接口不是 ReasonableProxyInterface
  • 小结

系列博文:
【老王读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源码三千问】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")) + "
" + ObjectUtils.identityToString(applicationContext.getBean("fooService2")) + "
" + 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

如果本文对你有所帮助,欢迎 点赞收藏!
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/356008.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号