Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?
前言
前面分析 Spring AOP 是如何为 Pointcut 匹配的类生成代理类时,提到 spring 使用 cglib 还是 jdk proxy 来生成动态代理是由两个因素共同决定的:
- 第一个因素是 targetClass 的类型(接口 or 实体类);
- 第二个因素是 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 创建代理的源码如下:
可以看出,如果用户指定了 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:
- beanClass 是接口类型,则使用 jdk proxy
- beanClass 不是接口类型,且 beanClass 实现了一个合理的代理接口,则使用 jdk proxy 来产生代理
- 除了上面两种情况,spring 会将 proxyTargetClass 校正为 true,最后使用 cglib 来产生代理
- 如果用户指定了 proxyTargetClass=true
- beanClass 是接口类型,则使用 jdk proxy
(通常扫描出来的 beanClass 都不会是接口类型,而是用户定义的一个具体的类) - 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循环依赖时会报错?