说说 Spring AOP
面向切面编程,在我们的应用中,经常需要做一些事情,但是这些事情与核心业务无关,比如,要记录所有 update 方法的执行时间时间,操作人等等信息,记录到日志,
通过 Spring 的 AOP 技术,就可以在不修改 update 的代码的情况下完成该需求。
日志这个例子点出了 AOP 的动机(横切关注点模块化),但要用好它还需要:术语体系、通知类型与执行顺序、切入点表达式写法、以及最著名的自调用失效问题。本文把”说说 AOP”说到能直接写切面的程度。
术语速建
- 切面(Aspect):横切逻辑的模块化单元(@Aspect 类)
- 连接点(Joinpoint):可被增强的执行点(Spring AOP 中只有方法执行)
- 切入点(Pointcut):连接点的筛选表达式(哪些方法被增强)
- 通知(Advice):增强逻辑本身及其执行时机
- 织入(Weaving):把切面应用到目标对象创建代理的过程(Spring 在运行期织入)
五种通知与执行顺序
|
正常路径顺序:Around 前段 → Before → 目标方法 → Around 后段 → After → AfterReturning;异常路径:Around 前段 → Before → 目标抛异常 → After → AfterThrowing(Around 后段不执行,除非内部 catch)。Around 是唯一能控制”是否执行目标、修改入参返回值”的通知,性能监控/重试/缓存首选它。
切入点表达式实战
execution(* com.x.service..*.update*(..)) |
常用组合:
@annotation(com.x.OpLog):按注解切入(最推荐,意图显式)within(com.x.web..*):按类范围bean(*Service):按 bean 名- 组合:
execution(...) && @annotation(...),||/&&/!布尔组合
注解切入优于包名匹配的原因:新增类自动遵守包名规则可能”误伤”,而注解是开发者显式声明的增强契约。
自调用失效:AOP 最著名的坑
|
原因:调用方持有的是代理对象,但 create 内部的 this 是目标对象本身,this.validate 绕过代理直达裸方法,增强链不执行。解法按推荐序:
- 拆类:validate 移到独立 bean,走容器注入调用
- 注入自身:@Lazy 自注入或 ObjectProvider 拿代理
- AopContext.currentProxy()(需 exposeProxy=true,侵入性强,最后手段)
同类坑:@Async/@Cacheable 自调用同样失效——所有基于代理的增强都受此约束。
AOP 能做什么与不能做什么
能做:日志、审计、权限校验、事务、缓存、限流、重试、指标埋点、参数校验兜底——共同特征是”与业务正交、横跨多方法”。
不能/不宜:
- 增强非 public 方法(代理语义只承诺 public)
- 增强 final 类/方法(CGLIB 继承限制)
- 替代业务逻辑本身(切面里写业务 = 把耦合从明处搬到暗处)
- 对未被容器管理的对象生效(new 出来的对象无代理)
与 IoC 的关系
AOP 的增强发生在 Bean 生命周期的初始化后阶段(BeanPostProcessor 生成代理),代理对象入单例池——没有 IoC 的生命周期托管,AOP 没有织入时机。两者是”容器提供挂载点、切面提供横切逻辑”的共生关系,这也是 Spring 把 AOP 做进容器而非独立库的原因。
AOP 的价值一句话:把”每个方法都要写但与方法无关”的代码收敛到一处声明;用好它的关键是切入点显式化(注解驱动)与牢记代理边界(自调用、public、final)。











