📌 阅读前提示 :AOP 阶段的收官篇。前两篇我们走通了「BPP 在初始化后调用
wrapIfNecessary → createProxy」。本篇回答最后两个硬核问题:(1)代理类型怎么选?(2)一次方法调用时,多个通知是怎么「排着队」依次执行的?并彻底讲清「同类自调用 AOP 失效」的底层原因。
一、引子:为什么面试必问 JDK 还是 CGLIB
因为这背后藏着 Spring 对「性能 / 兼容性 / 能力边界」的取舍:
- JDK 动态代理 :基于接口,生成实现类,要求目标必须实现接口 ;调用快、但不能代理类本身的方法(非接口方法切不到)。
- CGLIB :基于继承,生成子类重写方法,能代理普通类;无需接口,但不能代理 final 类 / final 方法,且生成字节码略慢。
Spring 的 DefaultAopProxyFactory 就是在这两者间做裁决。
二、源码追踪(一):代理类型裁决逻辑
java
// DefaultAopProxyFactory.java
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// optimize:激进优化;proxyTargetClass:强制 CGLIB;isProxyTargetClass
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) throw new AopConfigException("TargetSource 无法确定目标类");
// 目标类是接口,或已经是 JDK 代理类 → 只能用 JDK 动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 其余 → CGLIB
return new ObjenesisCglibAopProxy(config);
} else {
// 没强制 CGLIB,且有用户提供的接口 → JDK 动态代理
return new JdkDynamicAopProxy(config);
}
}
2.1 裁决规则一览表
| 条件 | 结果 |
|---|---|
proxyTargetClass=true(或 Boot 默认) |
CGLIB |
optimize=true |
CGLIB |
| 目标类无接口、且非强制 JDK | CGLIB |
目标类实现了接口、且 proxyTargetClass=false |
JDK 动态代理 |
| 目标类是接口本身 / 已是 JDK 代理 | 强制 JDK 动态代理 |
⚠️ 易错点 :「有接口就一定用 JDK 代理」------错 。只要
proxyTargetClass=true(Boot 默认就是),即使有接口也走 CGLIB。规则优先级是:强制 CGLIB 配置 > 接口存在。
三、源码追踪(二):调用时的拦截器责任链
无论哪种代理,最终调用都会进入「责任链」。以 JDK 代理为例:
3.1 JdkDynamicAopProxy.invoke 织入责任链
java
// JdkDynamicAopProxy.java
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ① 取出该方法的拦截器链(已按 @Order 排序)
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
if (chain.isEmpty()) {
// ② 无通知 → 直接反射调用目标方法,不走代理逻辑
return AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
// ③ 包装成 MethodInvocation,递归推进责任链
MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
return invocation.proceed(); // ← 责任链启动
}
3.2 ReflectiveMethodInvocation.proceed:递归式「逐个执行」
java
// ReflectiveMethodInvocation.java
@Override
public Object proceed() throws Throwable {
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// 链走完 → 调用真实目标方法
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof MethodInterceptor) {
MethodInterceptor mi = (MethodInterceptor) interceptorOrInterceptionAdvice;
return mi.invoke(this); // ← 每个拦截器处理完后,再调 proceed() 推进下一个
}
// 动态匹配拦截器
return proceed();
}
📌 责任链本质 :这是经典的「递归 + 索引推进」。
@Around对应的MethodInterceptor.invoke(this)内部会调用invocation.proceed()进入下一个拦截器;当所有拦截器走完,才执行真实目标方法。出栈顺序天然形成「环绕」效果。
3.3 通知执行顺序(单切面前)
| 通知 | 在链路中的位置 | 执行特点 |
|---|---|---|
@Around(proceed 之前) |
最外 | 最先执行,包住一切 |
@Before |
靠前 | proceed 前打印/校验 |
| 目标方法 | 链底 | invokeJoinpoint() |
@AfterReturning |
返回后 | 拿到返回值 |
@After |
finally | 必执行 |
@AfterThrowing |
异常分支 | 仅异常时 |
@Around(proceed 之后) |
最外返回 | 最后收尾 |
四、终极问题:同类自调用为什么 AOP 失效
java
@Service
public class OrderService {
public void create() {
this.validate(); // ← this 指向【原始对象】,不是代理!
}
@Transactional
public void validate() { /* ... */ }
}
原因 :create() 是由代理对象 调入的,但方法体内的 this 是原始目标对象 (Spring 注入的是原始 Bean,代理只是外层壳)。this.validate() 绕过了代理,责任链根本没机会执行,@Transactional / 切面全部失效。
两种解法:
java
// 解法一:exposeProxy=true 后从 AopContext 取代理再调
@EnableAspectJAutoProxy(exposeProxy = true)
public class AopConfig {}
public void create() {
((OrderService) AopContext.currentProxy()).validate(); // 走代理,AOP 生效
}
// 解法二:拆到另一个 Bean(推荐,解耦更清晰)
@Autowired OrderValidator validator;
public void create() { validator.validate(); } // 跨 Bean 调用,代理正常介入
💡 结论 :自调用失效的根因是「代理是包装,不是替换」------
this永远是原始对象。理解这点,所有「AOP 没生效」的诡异问题都能定位。

五、常见误区
| 误区 | 正解 |
|---|---|
| 有接口就一定 JDK 代理 | 错,proxyTargetClass=true 时强制 CGLIB |
| CGLIB 不能代理任何类 | 只能代理非 final 类、非 final 方法 |
@Around 不调 proceed 也能返回 |
不调 proceed 目标不执行,需自己定返回值 |
| 自调用时 AOP 也生效 | 失效,因 this 指向原始对象(见第四节) |
| 无匹配通知的 Bean 走代理也慢 | 空链直接反射调目标,几乎无开销 |
🧪 面试题自测
DefaultAopProxyFactory选 JDK 还是 CGLIB 的完整规则?- 为什么
proxyTargetClass=true时即使有接口也走 CGLIB? ReflectiveMethodInvocation.proceed()的递归推进原理?- 同类自调用 AOP 失效的根因?两种解法?
- CGLIB 代理有什么限制(final / 构造器等)?
- 为什么
@Around能完全控制目标方法是否执行?
🔧 Debug 小技巧
在 JdkDynamicAopProxy.invoke 断点,观察 chain 列表的元素顺序(对应通知执行顺序);再单步进入 ReflectiveMethodInvocation.proceed,用「Step Over 走到 mi.invoke(this)、再 Step Into」的方式,逐拦截器感受责任链的递归推进。最后在 create() 里加 this == AopContext.currentProxy() 的条件断点,亲眼看到「自调用时两边不等」。
下一篇预告
AOP 阶段完结。下一篇进入 Spring MVC 阶段(第 15 篇) :DispatcherServlet 如何初始化------onRefresh → initStrategies 九大组件,以及一次 HTTP 请求在 Spring 里的「总调度」是如何搭起来的。
如果这篇对你有帮助,欢迎 点赞 · 收藏 · 关注 三连支持。
Spring 源码系列共 30 篇,由浅入深持续更新中。有疑问或想深挖的源码点,评论区告诉我,下篇见。