休言女子非英物,夜夜龙泉壁上鸣。这篇文章主要讲述#yyds干货盘点#Spring源码三千问Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?相关的知识,希望能为你提供帮助。
@[TOC](Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?)
前言前面分析 Spring AOP 是如何为 Pointcut 匹配的类生成代理类时,提到 spring 使用 cglib 还是 jdk proxy 来生成动态代理是由两个因素共同决定的:
- 第一个因素是 targetClass 的类型(接口 or 实体类);
- 第二个因素是 proxyTargetClass 标识的值(true or false)。
那么 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
【#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 |
推荐阅读
- JavaScript错误处理try..catch...finally,涵盖throw,TypeError,RangeError#yyds干货盘点#
- 重重封锁,让你一条数据都拿不到《死磕MySQL系列 十三》
- #私藏项目实操分享#Python模拟登录,selenium模块,Python识别图形验证码实现自动登录
- #yyds干货盘点# 硬核!!教你如何通过脚本自动部署虚拟机并安装操作系统
- #yyds干货盘点#读配置讲原理看面试真题,我只能帮你到这了。。。
- 开源app 控制ESP8266,通过mqtt,app inventor开发
- Flutter 专题47 图解新的状态管理 Provider#yyds干货盘点#
- Linux之locate命令
- #yyds干货盘点#算法开启小码农双链表血脉