Spring 的 AOP 在什么场景下会失效?

#Java#面试八股

Spring 的 AOP 在什么场景下会失效?

结论先行:AOP 失效只有一个根因——调用没有经过代理对象,或者代理没能生成/命中。前者包括同类自调用、this 调用、内部类调用;后者包括方法不可覆写(private/final/static)、对象不是 Spring Bean、切点表达式没匹配上、JDK 代理类型不匹配。排查顺序固定:先确认"有没有代理",再确认"代理有没有被调用到"。

一、失效清单

场景原因解决
同类方法自调用this.b() 不经过代理拆到另一个 Bean、自注入、AopContext.currentProxy()
private / final / static 方法CGLIB 无法覆写、静态方法不参与代理改为 public 非 final,或把逻辑挪到可代理方法
new 出来的对象不在容器里,不会被代理交给容器管理(@Component 或 @Bean)
JDK 代理 + 注入实现类代理只实现接口,不是实现类注入接口,或 proxyTargetClass=true 用 CGLIB
切点表达式写错包路径/返回值/参数不匹配@Pointcut 单独抽出来,开 debug 看匹配
注解标在接口上,实现类无注解默认不继承接口方法上的注解注解标在实现类,或用 AspectJ 模式
@Transactional 标在 Controller事务只加在 Service 层才合理,且需代理移到 Service,并确保是外部调用
内部类 / 匿名类调用外部 Bean 方法视引用而定,常是 this 语义用注入的引用调用

二、自调用示意

外部调用 ──▶ 代理(有事务/日志) ──▶ 目标对象 a()
                                      │
                                      └─ this.b()  ← 直接走原始对象,绕开代理
                                           b() 上的 @Transactional 完全不生效
@Service
public class OrderService {

    @Transactional
    public void create() {
        // 反例:自调用,save 上的事务注解不生效
        this.save();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save() { /* ... */ }
}

三、三种修复写法

// 方案一:拆类(最推荐)。把 save 放到独立 Bean,跨 Bean 调用必过代理
@Service
public class OrderSaveService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save() { /* ... */ }
}

// 方案二:自注入。容器注入的是代理,通过它调用就会走通知链
@Service
public class OrderService {
    @Autowired
    @Lazy                       // 避免自注入引发循环依赖告警
    private OrderService self;

    public void create() {
        self.save();            // 走代理
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save() { }
}

// 方案三:暴露代理,用 AopContext 取当前代理对象
// 需开启 @EnableAspectJAutoProxy(exposeProxy = true)
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
public class AopConfig { }

public void create() {
    ((OrderService) AopContext.currentProxy()).save();
}

四、为什么 private / final / static 不行

  • JDK 动态代理:基于接口实现,只能代理接口里声明的方法,实现类的额外方法完全不可见;
  • CGLIB:生成子类并覆写方法,private、final、static 都无法覆写,final 类直接报错;
  • 因此 Spring 的 @Transactional 在 Spring 6 之前要求方法是 public;6.0 起支持 protected 与包可见,但 private 仍然无效。

五、常见追问

  • 问:怎么快速判断是不是代理问题? 打印 bean.getClass(),出现 $Proxy 或 $$EnhancerBySpringCGLIB 说明有代理;再在切面里打断点看是否进入。
  • 问:AopContext.currentProxy() 为什么能拿到代理? exposeProxy=true 时,代理在调用前把自身放入 ThreadLocal,方法内通过它取回,本质是线程绑定的"当前代理"。
  • 问:@Async 失效也是同理吗? 是,@Async 同样靠代理,自调用、非 public 方法、同类调用都会失效。
  • 问:切点表达式对 @Transactional 也适用吗? 事务不是用户切面,它由 BeanFactoryTransactionAttributeSourceAdvisor 参与自动代理,但"是否被代理"的判定逻辑一致。

小结

记一句口诀:代理在,调用过代理,方法可覆写,注解能被看到——四条缺一条就失效。回答时先给根因,再摆表格,最后给出"拆类 / 自注入 / exposeProxy"三种修法,比罗列现象更有说服力。

笔记加载中…