适用读者:写过
@Aspect但被"切面不生效"坑过的人。
前置:S07-B01、S07-B02。
实测环境:Spring Boot 3.3.6 / JDK 17(2026-11-05)。
覆盖旧文:968(100) 1737(62) 970(72) 325(54) 474(35) 971(83) = 406 点。
覆盖扩展:E-S07-005(JDK 代理 vs CGLIB)、E-S07-006(自调用失效)、E-S07-020(设计模式)。
上一篇:S07-B02 · Bean 生命周期与作用域
下一篇:S07-B04 · MVC 请求链路。
1. 一句话结论
AOP 的本质是"在 Bean 初始化最后一步(postProcessAfterInitialization)把对象包成代理"。所以:代理之外的调用不走切面——包括"同类内部调用"。
实测(切面日志):
[AOP] 进入 controller 方法
[AOP] controller 返回
(@Before 与 @AfterReturning 都命中,因为请求是从外部进入代理的。)
2. 五种通知与切点
| 通知 | 时机 |
|---|---|
@Before |
方法执行前 |
@AfterReturning |
正常返回后 |
@AfterThrowing |
抛异常后 |
@After |
无论如何(finally 语义) |
@Around |
包住整个调用(可改返回值/异常) |
切点常用写法:
@Pointcut("execution(* demo.service..*.*(..))") // 包及子包
@Pointcut("@annotation(com.demo.Log)") // 注解切点
@Pointcut("within(demo.service.*)") // 类型内
3. JDK 动态代理 vs CGLIB(E-S07-005)
| 维度 | JDK 动态代理 | CGLIB |
|---|---|---|
| 前提 | 必须有接口 | 可代理类(无接口) |
| 实现 | InvocationHandler + 生成实现类 |
生成子类(字节码增强) |
| 限制 | 只能代理接口方法 | 不能代理 final 类/方法 |
| 性能 | 调用略快(JDK 8+) | 创建略快 |
Spring Boot 2.x+ 默认 proxyTargetClass=true(CGLIB)——所以"没有接口也能被代理",但 final 方法不会被增强。
判据:"这个方法是不是 final/static/private?" 是 → 切不到。
4. 六种"切面不生效"(A-970)
| # | 原因 | 表现 |
|---|---|---|
| 1 | 同类内部调用(this.method()) |
不走代理 |
| 2 | 方法是 private / final / static |
无法代理 |
| 3 | 切点表达式写错(包名/类名不匹配) | 完全没命中 |
| 4 | 对象不是 Spring 管的(new 出来的) |
无代理 |
| 5 | 注解不在代理可见的类上(如注解在实现类而代理接口) | 不命中 |
| 6 | 异步/多线程里调用(跨线程丢了代理上下文) | 视情况 |
第 1 条最常踩(E-S07-006):自调用必须走 AopContext.currentProxy() 或拆到另一个 Bean。
5. AOP 的三个真实用途
| 用途 | 说明 |
|---|---|
| 日志/链路(A-325/A-474) | 统一记录入参、耗时、异常 |
| 权限校验 | 注解 + 切面(比拦截器更细) |
| 声明式事务 | @Transactional 本身就是 AOP(J01 的失效原因同源) |
这解释了一件事:"事务失效"和"AOP 失效"是同一类问题——都是"没走代理"。
6. Spring 里的设计模式(A-971)
| 模式 | 在 Spring 里的体现 |
|---|---|
| 工厂 | BeanFactory |
| 单例 | 默认作用域(容器内单例) |
| 代理 | AOP |
| 模板方法 | JdbcTemplate、RestTemplate |
| 观察者 | ApplicationEvent(J03) |
| 策略 | Resource/HandlerMapping 的多实现 |
| 适配器 | HandlerAdapter |
7. 五个误区
| 误区 | 真相 |
|---|---|
"加了 @Aspect 就一定生效" |
切点、代理方式、调用路径都要对 |
| "同类调用也走切面" | 不走(self-invocation) |
| "CGLIB 能代理 final 方法" | 不能 |
| "AOP 只有日志用途" | 事务、权限、缓存都是它 |
| "AOP 是运行时代码注入" | 是代理(对象替换),不是改字节码 |
8. 自检
- AOP 代理在生命周期哪一步生成?
- JDK 代理与 CGLIB 的前提差异?
- 为什么同类内部调用不走切面?怎么修?
- 六种失效原因里,哪三种最常见?
- 为什么说"事务失效与 AOP 失效同源"?
9. 下一篇
S07-B04 · Spring MVC:一个请求走过哪些组件
AOP 讲完,下一篇跟一个 HTTP 请求走完全程:DispatcherServlet → 拦截器 → Controller → 返回值处理 → 异常解析。
本篇属于系列《S07 · Spring 框架》。旧文来源:A-968/1737/970/325/474/971;扩展点 E-S07-005/006/020。AOP 日志为 Spring Boot 3.3.6 实测。