[面试题] Spring的AOP在什么场景下会失效?
📖 版块 A:知识讲解版 (温故知新)
-
Spring AOP 的核心原理:Spring AOP 是基于代理模式实现的。默认情况下,如果目标对象实现了接口,使用 JDK 动态代理;如果没实现接口,使用 CGLIB 字节码增强。所有的增强逻辑(通知)都是通过代理对象调用的。
-
AOP 失效的常见场景:
- 类内部调用 (Self-invocation):同一个类中,一个无增强逻辑的方法调用另一个带 AOP 注解(如 @Transactional, @Cacheable)的方法,此时调用的是目标对象本身的方法,而不是代理对象。
- 非 public 方法:Spring AOP 默认只支持 public 方法。对于 CGLIB,无法代理 private 方法(无法重写);对于 JDK 代理,只能代理接口定义的 public 方法。
- 方法被声明为 final 或 static:CGLIB 通过继承生成子类实现代理,final 方法无法被子类重写,static 方法属于类不属于对象,均无法被拦截。
- 对象未被 Spring 管理:如果对象是通过
new关键字创建的,而不是通过 Spring IOC 容器加载的,Spring 无法为其生成代理。 - 错误的配置:如切点表达式(Pointcut)编写错误,或者使用了不正确的代理模式设置。
🎤 版块 B:面试实战版 (高手逐字稿)
Q:Spring 的 AOP 在什么场景下会失效?
💬 高手逐字稿:
关于 Spring AOP 失效的问题,我认为最核心的原因在于:AOP 是基于代理对象(Proxy)工作的,只要调用链绕过了代理对象,直接作用于目标对象(Target),AOP 就会失效。
最经典的场景就是类内部自调用。比如我在 Service 类里有两个方法,A 方法没有事务,B 方法加了 @Transactional。如果在 A 中直接调用 this.B(),这时候执行的是目标对象的方法,没有经过代理对象的拦截,事务增强就不会生效。这是开发中最容易踩的坑。
第二类是方法修饰符的限制。Spring AOP 默认要求目标方法必须是 public 的。如果是 private 方法,由于 CGLIB 无法重写私有方法,或者由于 JDK 代理只能看到接口方法,导致拦截器无法切入。同样的道理,final 方法和 static 方法也无法被动态代理增强。
第三类是对象脱离了 IOC 容器的控制。如果一个对象是我们手动 new 出来的,或者是在配置中漏掉了扫描,它就不是一个 Spring Bean,Spring 自然没法在初始化阶段为其创建代理对象,AOP 也就无从谈起。
这里可以深入聊一个细节:如何解决内部调用失效的问题?
通常我们有三种方案。第一种最简单,就是把方法拆到两个不同的 Service 里。第二种是利用 AopContext.currentProxy() 强制获取当前的代理对象来调用,但这需要开启 exposeProxy=true,而且对代码有侵入。
但我个人最推荐的是第三种,就是在类里通过 @Autowired 注入自己。Spring 能够处理这种循环依赖(在 Setter 或 Field 注入下),这样在执行内部调用时,通过注入的那个“自己”来调用,走的就是代理对象了。这样既解决了失效问题,也保持了���码的可读性。