Skip to content

Spring AOP

AOP 负责处理横切逻辑。横切逻辑是指那些不属于某个业务动作本身,却在很多业务动作前后都要重复发生的处理,例如事务、日志、审计、权限、限流、幂等、性能统计。

Spring AOP 的实现方式是代理。调用方拿到的是代理对象,代理对象先执行通用逻辑,再把调用交给真实业务对象。可以把它理解成进入方法前的一道门禁:该检查的检查,该记录的记录,该开启事务的开启事务,然后才放行到真正的方法。

Spring AOP 代理调用路径

详细位置

  • 本页说明 AOP 的适用边界、代理机制、事务失效、切点设计和排查方式。
  • Bean 创建与注入见 IoC 容器
  • HTTP 请求进入 Service 前的链路见 Spring MVC
  • 自动配置与启动装配见 Spring Boot

适用边界

适合 AOP 的逻辑通常有三个特征:

  • 重复出现:很多方法都需要同类处理。
  • 与业务主线可分离:去掉后,业务流程仍然能被清楚描述。
  • 触发规则稳定:可以通过注解、包路径、类名或方法签名准确识别。

不适合 AOP 的逻辑:

  • 关键业务分支,例如订单是否可取消、库存是否可扣减。
  • 只服务于一两个方法的普通步骤。
  • 会改变调用者对方法语义理解的隐藏逻辑。
  • 只能依赖脆弱命名规则匹配的方法。

判断方式:如果这段逻辑写成 AOP 后,读业务方法的人会误判业务结果,就不应该用 AOP。

基本术语

术语含义直观理解
Aspect切面一组横切逻辑
Pointcut切点哪些方法会被拦住
Advice通知拦住之后执行什么
Target目标对象真正处理业务的对象
Proxy代理对象调用方实际访问的外层对象
Join Point连接点Spring AOP 中主要是方法执行点

业务开发中最常见的是“用注解标记方法,再由切面处理”。例如某个方法需要审计,就加审计注解;某个方法需要幂等,就加幂等注解。注解是业务代码和横切逻辑之间的契约。

代理机制

代理机制的关键不是“JDK 动态代理还是 CGLIB”,而是“调用有没有经过代理对象”。

会经过代理的调用:

  • Controller 调用容器注入的 Service。
  • 一个 Service 调用另一个容器注入的 Service。
  • 外部调用从 Spring 容器拿到的 Bean。

不会经过代理的调用:

  • 对象内部使用 this 调用自己的另一个方法。
  • 手动 new 出来的对象。
  • 私有方法、构造方法这类不是外部代理入口的方法。

这就是很多 @Transactional、缓存注解、自定义切面失效的根本原因:注解在方法上,但调用没有走到代理门口。

事务与 AOP

@Transactional 可以看作 Spring 提供的内置切面。它在方法调用前开启事务,在方法正常返回后提交事务,在异常符合回滚规则时回滚事务。

事务失效常见原因:

现象原因处理方式
同类内部调用事务方法不生效this 调用绕过代理把事务边界放到外部入口,或拆到另一个 Service
异常发生但没有回滚异常被捕获后吞掉记录后继续抛出,或明确设置回滚规则
手动创建对象后事务不生效对象不是 Spring Bean由容器注入对象
方法不可见或被限制代理无法作为外部入口增强把事务放在清晰的 public 应用服务方法上

事务边界通常放在应用服务层,而不是 Controller 或 Repository。Controller 太靠近协议,Repository 太靠近数据访问细节;Service 更适合表达“一次业务操作要保持一致”。

切点设计

切点设计的目标是“准”。过宽会误伤,过窄会漏掉。

常见方式:

方式适合场景风险
按注解匹配审计、幂等、操作日志、业务开关需要开发者显式标记
按包路径匹配某一层统一治理,例如 Service 层日志包结构变动会影响范围
按方法签名匹配命名规范非常稳定的项目命名变化可能误伤或漏掉
组合切点需要精确限定范围表达式复杂后可读性下降

优先使用注解表达业务意图。比如“这个方法需要审计”比“所有 save* 方法都审计”更稳定。

通知类型

通知执行时机适合做什么
前置通知方法执行前权限检查、参数快速校验
后置通知方法结束后,无论成功失败清理上下文
返回通知方法正常返回后成功审计、成功指标
异常通知方法抛出异常后异常统计、失败审计
环绕通知包住整个调用事务、耗时、限流、幂等

能用更窄通知时,不要默认使用环绕通知。环绕通知能力最大,也最容易隐藏调用行为。

工程场景

操作审计

审计记录“谁在什么时候做了什么”。它适合 AOP,因为审计不应混进每个业务方法的主流程。业务方法只标记需要审计,切面统一读取用户、动作、结果和异常。

需要注意:

  • 审计失败是否影响业务成功,要有明确规则。
  • 审计内容不要记录敏感明文,例如密码、完整 Token。
  • 审计切点应尽量用注解,不要靠方法名猜测。

幂等控制

幂等控制用于防止重复提交。它适合 AOP 的前提是 Key 规则清晰、失败处理清晰、重复请求返回策略清晰。

需要注意:

  • Key 应来自业务唯一信息,例如订单号、请求号、用户 ID 和动作组合。
  • 占位或锁必须有过期时间。
  • 方法失败后是否释放 Key,要按业务语义决定。
  • 幂等不是简单加锁,它影响用户重复请求的最终语义。

日志与耗时

日志和耗时统计适合 AOP,但应该克制:

  • 不要打印大对象和敏感字段。
  • 不要在高频方法上做复杂序列化。
  • 日志要包含可关联的请求 ID 或业务 ID。
  • 耗时统计应能区分接口耗时、数据库耗时、外部调用耗时。

排查路径

注解不生效

按顺序检查:

  1. 目标类是否是 Spring Bean。
  2. 调用方拿到的是否是代理对象。
  3. 调用是否从外部进入,而不是 this 内部调用。
  4. 切点是否能匹配方法。
  5. 方法可见性和类结构是否适合代理。
  6. 异常是否按预期继续传播。

切面执行顺序不对

多个切面同时命中时,应显式设置顺序。通常权限、限流、幂等这类“可能拒绝调用”的逻辑更靠前;事务包住需要一致性的业务操作;日志和指标负责记录结果。

性能异常

检查切点是否过宽,是否在高频方法中做了参数深拷贝、JSON 序列化、远程写日志等重操作。AOP 越靠底层、命中越频繁,越需要控制成本。

总结

AOP 的核心是代理边界。横切逻辑应该像方法外层的统一检查站,而不是隐藏业务流程的暗门。排查时先确认对象是否由容器管理,再确认调用是否经过代理,最后检查切点、顺序、异常传播和性能成本。

别急,先让缓存热一下。