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

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 创建代理的源码如下:
createProxy

可以看出,如果用户指定了 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 可以免费下载源码测试工程…

阅读更多文章,请关注公众号: 老王学源码
gzh


系列博文:
【老王读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循环依赖时会报错?

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老王学源码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值