Skip to content

Spring AOP

1. 大白话:这是什么?解决什么痛点?

Spring AOP 的本质是:不修改核心业务代码,在方法调用前后动态织入一段通用增强逻辑

业务方法只负责业务本身,比如下单、扣库存、查订单;而日志、权限、事务、监控、限流这些能力虽然很重要,但它们不是业务主线。如果每个业务方法里都手写一遍,代码会重复、侵入性强,后期改一处规则还要到处找。

AOP 解决的就是这种“横切逻辑污染业务代码”的问题。它把公共能力抽成切面,在符合条件的方法上统一生效。面试里可以一句话概括:AOP 是用代理机制把通用增强逻辑从业务代码里剥离出来,降低重复代码和模块耦合。

2. 底层机制与高频考点

AOP 的术语要按“在哪里增强、增强什么、怎么织进去”来记:

  • JoinPoint(连接点):目标类里所有可能被增强的方法。
  • Pointcut(切入点):真正被匹配、被增强的方法。切入点一定是连接点,连接点不一定是切入点。
  • Advice(通知):真正执行的增强逻辑,比如开启事务、打印日志。
  • Aspect(切面)Pointcut + Advice,也就是“在哪些方法上执行哪些增强”。
  • Target(目标对象):原始业务对象。
  • Proxy(代理对象):Spring 给目标对象生成的代理,外部实际调用的是它。
  • Weaving(织入):把通知应用到目标对象并生成代理对象的过程。

Spring AOP 的底层是运行时动态代理。Spring 容器创建 Bean 时,如果发现它需要被切面增强,就会创建代理对象放进容器里。后续外部调用 Bean 方法时,先进入代理对象,再由代理对象执行通知逻辑,最后决定是否调用目标方法。

代理选择规则:

场景默认代理方式原理限制
目标类实现接口JDK 动态代理生成接口实现类,调用进入 InvocationHandler#invoke必须有接口
目标类没有接口CGLIB 动态代理生成目标类子类,调用进入方法拦截器final 类、final/private 方法无法增强

常见通知类型:

  1. Before:目标方法执行前触发。
  2. After:目标方法执行后触发,不管成功还是异常都会执行。
  3. AfterReturning:目标方法正常返回后触发。
  4. AfterThrowing:目标方法抛异常后触发。
  5. Around:环绕通知,能力最强,可以在目标方法前后做增强,也可以选择不调用目标方法。

多个切面的顺序通常用 @Order 或实现 Ordered 接口控制。数值越小,优先级越高;环绕通知的进入顺序和退出顺序可以理解成“先进后出”。

Spring AOP 和 AspectJ 的区别:

对比点Spring AOPAspectJ
增强时机运行时增强编译期或类加载期增强
底层方式动态代理字节码织入
能增强什么主要是 Spring Bean 的方法级调用方法、字段、构造器、静态方法等更广
使用复杂度简单,和 Spring 集成好配置更复杂
典型选择常规业务日志、事务、权限非 Spring 对象、更复杂切点、高性能大量切面

最高频坑点是自调用失效。因为 Spring AOP 依赖代理对象,只有外部通过代理对象调用方法时,切面才会生效。如果同一个类内部用 this.method() 调用另一个带 @Transactional 的方法,这次调用直接发生在原始对象内部,没有经过代理对象,所以事务、日志等增强都会失效。

事务为什么依赖 AOP?因为声明式事务 @Transactional 的底层就是 AOP 动态代理。调用事务方法时,代理对象会进入 TransactionInterceptor,在目标方法执行前开启事务,方法异常时回滚,正常结束后提交。也就是说,事务能不能生效,关键看调用链有没有经过 Spring 生成的代理对象。

3. 🎯 实战口径

在我的项目里,Spring AOP 最典型的落点是 [[黑马点评]] 的秒杀异步下单事务。

秒杀请求先通过 Redis Lua 完成库存预扣减和一人一单校验,然后把订单消息写入 Redis Stream,由异步线程消费并真正创建订单。创建订单的方法需要 @Transactional 保证查重、扣库存、落库这些数据库操作的一致性。但这里有一个坑:如果在同一个 Service 内部直接用 this.createVoucherOrder() 调用事务方法,就不会经过 Spring 代理对象,TransactionInterceptor 进不来,事务会失效。

所以我的处理是:在主线程中通过 AopContext.currentProxy() 提前拿到当前 Service 的代理对象,保存后给异步消费线程使用;异步线程里通过 proxy.createVoucherOrder() 调用事务方法,让调用链真正进入 Spring AOP 代理。这样 @Transactional 才能生效。

同时,我把 Redisson 分布式锁包在代理调用外层,而不是写在事务方法内部。这样可以保证事务执行完成后再释放锁,避免“锁释放了,但事务还没提交,其他线程又进来读到旧数据”的并发问题。这个点面试时要讲清楚:我不是为了形式上用代理,而是为了保证 代理拦截、事务边界、锁释放顺序 都在正确的位置。

在 [[苍穹外卖AI客服]] 里,Spring AI 的 Advisor 链也可以类比 AOP 思想来讲。比如 SafeToolCallAdvisor 会在模型继续调用工具前先做拦截,检查工具调用轮次和重复签名;如果发现工具调用死循环,就短路返回兜底响应。它不是传统 Spring AOP,但思想是一致的:把安全控制、缓存、记忆注入、工具过滤这些横切能力放到统一链路里,不污染核心业务逻辑。


相关链接:[[代理模式]] | [[苍穹外卖AI客服]] | [[黑马点评]]