【面试突击七】深度进阶:Spring AOP 代理机制、失效场景与切面顺序全解析
如果说 IOC 是 Spring 的骨架,那么 AOP(面向切面编程)就是 Spring 的灵魂。它让我们能够在不修改源代码的情况下,优雅地解耦日志、事务、权限等通用业务。
本文将带你攻克 AOP 的三大难点:代理选择机制、失效场景判定、多切面执行顺序。
一、 AOP 底层双雄:JDK vs CGLIB
Spring AOP 的本质是动态代理。根据目标对象是否实现接口,Spring 会在运行期动态选择以下两种技术之一。
1. 核心对比
| 特性 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 实现原理 | 基于 Java 反射 机制 | 基于 ASM 字节码 框架 |
| 代理形式 | 生成接口的实现类(匿名类) | 生成目标类的子类(继承) |
| 目标要求 | 目标类 必须实现接口 | 目标类不能是 final |
| 关系比喻 | 代理类与目标类是 兄弟 关系 | 代理类是目标类的 儿子 |
| 配置参数 | proxyTargetClass = false (默认) | proxyTargetClass = true |
2. Spring 怎么选?
- 默认策略:如果目标 Bean 实现了接口,用 JDK;否则用 CGLIB。
- 现代趋势:Spring Boot 2.x 开始,默认配置
spring.aop.proxy-target-class=true,即默认全量使用 CGLIB。这样做是为了减少因接口缺失导致的代理失败,同时也避免了 JDK 代理在某些场景下的类型强转异常。
二、 避坑指南:为什么我的 AOP 失效了?
在开发中,明明写了切面,逻辑却没执行,通常是以下四个原因导致的:
1. “内部调用”陷阱(最常见)
场景:类 A 里面有方法 method1 和 method2,method2 上有切面。当你在 method1 中通过 this.method2() 调用时,切面失效。
- 原因:AOP 拦截的是代理对象的调用。在方法内部,
this指向的是原始对象,而不是增强后的代理对象。 - 图解:
外部调用 -> [代理对象.method1] -> [原始对象.method1] | |--直接调--> [原始对象.method2] (切面跳过!) - 解决:使用
AopContext.currentProxy()获取当前代理对象调用,或者重构代码将方法拆分到不同的 Bean。
2. 方法权限问题
- 原因:JDK 代理只能代理接口中的
public方法;CGLIB 代理无法重写private、final或static方法。
3. 对象不是 Spring Bean
- 原因:AOP 代理是在 Bean 初始化阶段(后置处理器)生成的。如果你直接
new了一个对象,Spring 根本无法介入。
三、 切面执行顺序:洋葱模型
当一个方法被多个切面(如事务、日志、缓存)同时拦截时,谁先执行?
1. 顺序控制
@Order(n)注解:数值n越小,优先级越高(越在洋葱的最外层)。Ordered接口:重写getOrder()方法,返回小数值者优先。
2. 执行流程图解
想象一个洋葱,高优先级的切面(Order 越小)包裹在最外面:
进入方法 (Request) >>> <<< 退出方法 (Response)
___________________________________________________________
| Aspect 1 (Order=1) |
| [ @Before ] |
| _____________________________________________________ |
| | Aspect 2 (Order=10) | |
| | [ @Before ] | |
| | _______________________________________________ | |
| | | 目标业务方法执行 (Target) | | |
| | |_______________________________________________| | |
| | | |
| | [ @After / @AfterReturning ] | |
| |_____________________________________________________| |
| |
| [ @After / @AfterReturning ] |
|___________________________________________________________|
- 入站阶段:
Order越小,@Before越先执行。 - 出站阶段:
Order越小,@After越后执行(最后离开战场)。
四、 AOP 术语极简记忆
为了方便面试复述,请牢记这五个词:
- Aspect (切面):你要写的“通用逻辑”类(如日志类)。
- Pointcut (切入点):你要拦截的“具体范围”(如
execution(* ...))。 - Advice (通知):你打算“什么时候”干活(
Before/After/Around)。 - Join Point (连接点):程序运行中的“拦截位置”(Spring 中仅支持方法调用)。
- Proxy (代理):Spring 最终为你生成的那个“全能助手”。
五、 总结建议
- 优先考虑职责分离:如果遇到内部调用失效,考虑业务逻辑是否需要拆分。
- 掌握代理原理:JDK 看接口(兄弟),CGLIB 看继承(父子)。
- 关注顺序逻辑:涉及事务和日志时,通常日志的
Order应该比事务更小,以便记录事务开启和结束的完整过程。
作者提示:AOP 的强大在于无侵入,但过度使用会增加调试难度。建议仅在处理横切关注点时使用。


被折叠的 条评论
为什么被折叠?



