自调用失效与代理对象获取

引言

最常见的“@Transactional / @Async / AOP 没生效”现场:方法明明标注了注解,也确实是 public,却毫无效果。排查后发现调用发生在同一个类内部(this 自调用)。本章讲清失效原理与四种修复手段。

核心概念

  • 失效原理:注解式 AOP/事务基于代理。外部调用 bean.method() 穿过代理 → 通知生效;类内部 this.method() 直接落在原始对象上,绕过代理,通知自然不触发。
  • 官方定义:代理模式(默认)下,只有“经由代理的外部调用”才会被拦截,自调用不构成代理调用(以官方文档为准)。
  • 修复思路分四类:
    1. 拆 Bean:把被调方法移到另一个 Spring Bean,跨 Bean 调用天然走代理——最推荐;
    2. 自注入:Bean 注入自己的代理(ObjectProvider<Self> / @Lazy 自注入 / 从 ApplicationContext.getBean 取),用代理调用自己;
    3. AopContext.currentProxy():开启 exposeProxy = true 后,在方法内取当前代理再调用;
    4. 重构:把“自调用”改成外部编排,消除对自调用的依赖。

代码示例

@Service
public class OrderService {
    private final ObjectProvider<OrderService> self; // 方式二:自注入代理

    public OrderService(ObjectProvider<OrderService> self) { this.self = self; }

    public void placeOrder(Order order) {
        // ❌ this.ship(order) —— 自调用,ship 上的事务/AOP 注解不生效
        // ✅ 改用注入的自身代理:
        self.getObject().ship(order);
    }

    @Transactional          // 由代理调用时才会真正开启事务
    public void ship(Order order) { /* 扣库存、改状态 */ }
}

// 方式三(片段):开启 exposeProxy 后
// @Configuration 上:@EnableAspectJAutoProxy(exposeProxy = true)
// 方法内:((OrderService) AopContext.currentProxy()).ship(order);

注意点

  • 自调用失效同样影响 @Transactional@Async@Cacheable@EventListener 前的切面等一切代理型能力。
  • 拆 Bean 是首选:自注入虽能用,却让类依赖自己的代理,测试与阅读都别扭;@Lazy 自注入可避免构造期循环。
  • AopContext.currentProxy() 依赖线程本地且要 exposeProxy,拿到前先判空,别滥用。
  • 修复后验证:给方法打日志或用代理类名断言,确认调用确实穿过代理。
  • 即使方法在别的 Bean 里,若调用方直接 new 目标类(不经容器),同样不生效——代理只存在于容器管理的关系中。

小结

代理模式默认只拦“穿过代理的外部调用”,this.method() 自调用必然失效。修复按顺序选:拆 Bean(推荐)→ 自注入代理 → AopContext(兜底)。凡是注解型横切能力,都遵循“容器 + 代理调用”这个前提。

笔记加载中…