在 Spring AOP 的使用过程中,我们经常听说 Spring 会在 JDK 动态代理和 CGLIB 之间自动选择代理方式。但这个选择背后的机制是怎样的?Spring 到底依据什么来决定使用哪种方式?本文带你通过源码一探究竟。
一、JDK 动态代理 vs CGLIB
Spring AOP 实现的两种代理技术:
-
JDK 动态代理:要求目标类实现接口,通过反射代理接口方法,代理类是接口的实现类。
-
CGLIB 代理:直接继承目标类,通过字节码生成子类覆盖方法,适用于无接口场景。
这两种方式各有优劣。JDK 代理对接口友好,结构清晰;CGLIB 则能代理类中所有非 final 的方法,功能更强,但对类结构有更多限制。
二、Spring 是如何选择代理方式的?
2.1 入口类:ProxyFactory
Spring AOP 最核心的代理生成类是 org.springframework.aop.framework.ProxyFactory
,它的创建流程可以简化为:
AdvisedSupport advisedSupport = new AdvisedSupport();
ProxyFactory proxyFactory = new ProxyFactory(advisedSupport);
Object proxy = proxyFactory.getProxy();
真正判断代理方式的逻辑在 DefaultAopProxyFactory#createAopProxy()
:
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("Target class must be available if proxyTargetClass set to true");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
} else {
return new JdkDynamicAopProxy(config);
}
}
2.2 代理选择逻辑精简
Spring 的代理选择逻辑,集中在 DefaultAopProxyFactory#createAopProxy()
方法中,其核心逻辑如下:
if (proxyTargetClass || optimize || 没有接口) {
if (目标类是接口 || 目标类是代理类) {
使用 JDK 动态代理;
} else {
使用 CGLIB;
}
} else {
使用 JDK 动态代理;
}
三、判断逻辑解析
1. 优先使用 JDK 动态代理的情况:
-
proxyTargetClass = false
(默认) -
且有接口
-
且没有设置
optimize = true
☑️ 默认情况下,Spring 倾向于使用 JDK 动态代理以保持接口隔离性。
2. 强制使用 CGLIB 的条件:
-
proxyTargetClass = true
-
或设置了
optimize = true
-
或没有任何接口(接口为空)
此时 Spring 会自动采用 ObjenesisCglibAopProxy
,使用 CGLIB 生成代理。
四、SpringBoot 默认策略
在 Spring Boot 中,代理选择默认由配置项控制:
spring.aop.proxy-target-class=true
-
设置为
true
:强制使用 CGLIB -
设置为
false
:遵循接口优先,使用 JDK 动态代理
五、最佳实践建议
-
如果你实现了接口,建议使用 JDK 动态代理,接口隔离更清晰。
-
如果你想代理类中非接口方法,或使用注解
@Transactional
,建议开启proxyTargetClass = true
。 -
切记:CGLIB 基于继承方式,final 类或 final 方法不能被代理!
六、总结
Spring AOP 的代理选择机制本质上就是对 JDK 动态代理和 CGLIB 字节码增强的平衡选择。了解其判断逻辑不仅能帮助我们更好地调试 Spring 应用,还能避免因配置失误引发的事务失效、AOP 失效等“代理坑”。
如果你遇到某个切面不起作用,不妨第一时间确认它是否被代理了,以及代理方式是否正确。
知识点补充:
Objenesis 是什么?
spring 为了解决 CGLIB 创建对象时必须调用默认构造方法的问题,引入了 Objenesis,它可以 无参构造一个类实例,哪怕没有默认构造方法。这让 Spring 的代理创建更加灵活和强大。
Objenesis 与代理构造
Spring 使用 CGLIB 创建代理对象时,通常依赖 Objenesis 工具。它能在不调用构造函数的前提下创建对象实例,解决了 CGLIB 要求目标类有无参构造函数的问题。
因此即便你没有写构造函数,Spring 也能轻松帮你生成代理类,这正是 Spring AOP 强大的背后支撑之一。