#yyds干货盘点# Spring源码三千问Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?
Posted 老王学源码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#yyds干货盘点# Spring源码三千问Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?相关的知识,希望对你有一定的参考价值。
@[TOC](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; 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:
- beanClass 是接口类型,则使用 jdk proxy
- beanClass 不是接口类型,且 beanClass 实现了一个合理的代理接口,则使用 jdk proxy 来产生代理
- 除了上面两种情况,spring 会将 proxyTargetClass 校正为 true,最后使用 cglib 来产生代理
- 如果用户指定了 proxyTargetClass=true
- beanClass 是接口类型,则使用 jdk proxy
(通常扫描出来的 beanClass 都不会是接口类型,而是用户定义的一个具体的类) - beanClass 不是接口类型,则使用 cglib
- beanClass 是接口类型,则使用 jdk proxy
如果本文对你有所帮助,欢迎点赞收藏!
有关 Spring 源码方面的问题欢迎一起交流,备注:51cto (vx: Kevin-Wang001)
博主好课推荐:
课程 | 地址 |
---|---|
Dubbo源码解读——通向高手之路 | https://edu.51cto.com/course/23382.html |
正则表达式基础与提升 | https://edu.51cto.com/course/16391.html |
以上是关于#yyds干货盘点# Spring源码三千问Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?的主要内容,如果未能解决你的问题,请参考以下文章
#yyds干货盘点#Spring源码三千问@Lazy延迟加载与延迟注入有什么区别?
#yyds干货盘点# Spring源码三千问Bean的Scope有哪些?scope=request是什么原理?
#yyds干货盘点# Spring源码三千问BeanDefinition详解——什么是 RootBeanDefinition?merged bean definition 又是什么鬼?
#yyds干货盘点# Spring源码三千问Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?
#yyds干货盘点# Spring 源码三千问同样是AOP代理bean,为什么@Async标记的bean循环依赖时会报错?