你有没有遇到过这样的问题?明明给Spring Bean加了@Transactional注解,事务却没有生效?排查许久,最后发现问题的根源竟是代理类型选择错误——JDK动态代理只代理接口方法,而你实现的方法在类上并未定义接口。本文将深入DefaultAopProxyFactory的源码,详细剖析Spring框架在“选择JDK动态代理还是CGLIB代理”背后的决策逻辑,掌握这一核心机制,未来处理代理相关问题便能游刃有余。
1. 前置知识:理解AdvisedSupport
在深入DefaultAopProxyFactory之前,必须先理解AdvisedSupport这个核心类。它是Spring AOP的配置中心,封装了创建代理对象所需的所有关键信息,包括:目标对象(target)、通知器(advisors,如@Before、@After等逻辑)、要代理的接口(interfaces)、是否进行优化(optimize)、是否强制使用CGLIB代理(proxyTargetClass)等。DefaultAopProxyFactory的createAopProxy方法,正是通过接收这个AdvisedSupport配置对象,才能智能地决策并创建相应的代理实例。
2. 核心决策逻辑:createAopProxy方法详解
DefaultAopProxyFactory的灵魂在于其createAopProxy(AdvisedSupport config)方法。下面我们通过简化后的核心源码,来厘清其决策路径:
// DefaultAopProxyFactory.java
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 三个关键开关:优化、强制代理类、无用户自定义接口
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("Target class must be available for creating a CGLIB proxy");
}
// 若目标类本身是接口或已经是JDK代理类,则回退使用JDK代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 否则,使用CGLIB代理(通过Objenesis解决无默认构造器的问题)
return new ObjenesisCglibAopProxy(config);
} else {
// 三个开关均未开启,默认使用基于接口的JDK动态代理
return new JdkDynamicAopProxy(config);
}
}
// 判断是否“无用户自定义接口”(即接口列表为空或仅包含Spring内部定义的SpringProxy接口)
private boolean hasNoUserSuppliedInterfaces(AdvisedSupport config) {
Class<?>[] interfaces = config.getProxiedInterfaces();
return interfaces.length == 0 || (interfaces.length == 1 && SpringProxy.class.isAssignableFrom(interfaces[0]));
}
这段代码的逻辑可以清晰地拆解为两个层次:
- 前置条件判断:只要满足
isOptimize()(优化开关)、isProxyTargetClass()(强制使用CGLIB代理)或hasNoUserSuppliedInterfaces()(无用户自定义接口)这三个条件中的任意一个,流程就会进入“CGLIB代理候选区”。
- 目标类类型检查:即使进入了候选区,Spring也会对
targetClass进行检查。如果目标类是一个接口(例如@Service注解的类实现了某个接口)或者本身已经是一个JDK代理类(通过Proxy.isProxyClass()判断),Spring会“回退”到使用JDK动态代理。只有当目标类是一个普通的、非接口的类时,才会最终确定使用CGLIB代理。
举个例子:即使在配置中显式设置了proxyTargetClass=true(强制代理类),但如果目标对象是一个接口(例如UserService接口),Spring依然会选择JDK动态代理——因为CGLIB无法继承一个接口。
3. 分道扬镳:JDK与CGLIB代理的实现细节
理解了“如何选择”,我们再来看“如何创建”。Spring为两种代理方式分别提供了JdkDynamicAopProxy(JDK代理)和ObjenesisCglibAopProxy(CGLIB代理)的实现。
3.1 JDK动态代理:基于InvocationHandler
JDK动态代理要求目标类必须实现至少一个接口。代理类会实现这些接口,并通过InvocationHandler来拦截所有方法调用。JdkDynamicAopProxy的核心是invoke方法:
// JdkDynamicAopProxy.java (简化版)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
// 处理equals、hashCode等Object类的基础方法
if (AopUtils.isEqualsMethod(method)) return equals(args[0]);
if (AopUtils.isHashCodeMethod(method)) return hashCode();
// 处理Spring Advised接口的方法调用(内部管理用)
if (method.getDeclaringClass() == Advised.class) {
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 获取适用于当前方法的拦截器链(通知链,如@Before + @After)
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
if (chain.isEmpty()) {
// 无拦截链,直接反射调用目标方法
return AopUtils.invokeJoinpointUsingReflection(target, method, args);
} else {
// 创建方法调用链,并依次执行通知逻辑
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
return invocation.proceed();
}
} finally {
if (target != null) {
targetSource.releaseTarget(target);
}
}
}
JDK代理的流程非常标准:拦截方法调用 → 获取配置好的拦截器链 → 沿链执行各个通知(Advice)→ 最终调用原始目标方法。其中,ReflectiveMethodInvocation.proceed()方法负责以链式方式执行通知,例如先执行@Before通知,再调用目标方法,最后执行@After通知。
3.2 CGLIB动态代理:基于MethodInterceptor
CGLIB通过继承目标类来生成子类作为代理对象(因此目标类不能是final,方法也不能是final)。Spring使用ObjenesisCglibAopProxy包装了CGLIB,其主要目的是利用Objenesis库解决CGLIB要求目标类必须有无参构造器的限制。它的核心是intercept方法(实现了MethodInterceptor接口):
// ObjenesisCglibAopProxy.java (简化版)
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
// 如果需要暴露代理对象到ThreadLocal(@EnableAspectJAutoProxy(exposeProxy=true)时生效)
if (this.advised.exposeProxy) {
AopContext.setCurrentProxy(proxy);
}
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// 获取拦截器链,逻辑与JDK代理一致
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
if (chain.isEmpty()) {
// 无拦截链,使用CGLIB的MethodProxy直接调用父类(目标)方法,性能优于反射
return methodProxy.invoke(target, args);
} else {
// 执行拦截链(CglibMethodInvocation是ReflectiveMethodInvocation的子类)
MethodInvocation invocation = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy);
return invocation.proceed();
}
} finally {
if (target != null) {
targetSource.releaseTarget(target);
}
if (this.advised.exposeProxy) {
AopContext.setCurrentProxy(null);
}
}
}
CGLIB代理的执行流程与JDK代理在逻辑上高度一致,但存在两个关键区别:
- 代理机制:CGLIB通过继承生成子类代理,JDK通过实现接口生成代理。
- 方法调用:在直接调用目标方法时(无拦截链),CGLIB使用
MethodProxy.invoke(),其性能通常比JDK代理使用的反射调用(AopUtils.invokeJoinpointUsingReflection)快3-5倍。
4. 核心决策流程图示
为了更直观地理解整个决策过程,可以通过下面的时序图梳理DefaultAopProxyFactory的工作流程:

5. 实战中的典型问题:为什么@Transactional会失效?
许多开发者在实际使用中遇到过“添加了@Transactional注解但事务未回滚”的问题,这很大程度上与代理机制有关,特别是在复杂的Java应用和Spring事务管理中:
- 目标方法是private的:无论是JDK代理还是CGLIB代理,都无法拦截
private方法。JDK代理基于接口,接口中无private方法;CGLIB通过继承,子类无法覆盖父类的private方法。
- 内部方法调用:在同一个Service类中,方法A直接调用方法B(
this.b())。此时的this是原始的目标对象,而非被Spring增强后的代理对象,因此调用不会经过代理逻辑,导致定义在方法B上的切面(如@Transactional)失效。
- 目标类是final的:CGLIB通过继承生成代理。如果目标类被声明为
final,则无法继承,Spring将抛出Cannot proxy final class异常,导致代理创建失败。
总结
深入源码后不难发现,Spring选择代理的策略非常务实——优先尝试基于接口的JDK动态代理,如果条件不满足则降级使用基于继承的CGLIB代理。而isOptimize、proxyTargetClass和hasNoUserSuppliedInterfaces这三个开关,本质上是为开发者提供的、用于“微调”或“强制”这一决策过程的手段。
掌握这个核心逻辑后,当再次面对代理相关的问题时,你可以快速聚焦于几个关键检查点:目标类是否实现了接口?全局或特定Bean的proxyTargetClass配置是什么?目标类或方法是否为final?将这些问题与DefaultAopProxyFactory的源码逻辑一一对应,绝大多数代理谜题都能迎刃而解。