- 前言
- 版本约定
- 正文
- 例子测试
- 结论分析
- 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 来生成动态代理是由两个因素共同决定的:
- 第一个因素是 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")) + "
"
+ 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 框架进行校正。
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 代理。
根据上面的分析,如果 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
如果本文对你有所帮助,欢迎 点赞收藏!



