自调用/内部方法为何不生效?如何解决

结论先行:@Transactional/@Async/@Cacheable 与自定义 @Aspect 都依赖 AOP 代理——只有调用方拿着"代理对象"发起调用,拦截器链才会生效。而方法体内的 this 指向目标对象本身(JDK 动态代理把真实目标包在代理内部;CGLIB 代理虽生成目标类的子类,Spring 仍把调用转发给真实目标实例执行),所以 this.method() 的自调用永远绕开代理,注解随之失效。解决手段四条:注入自身代理(@Autowired + @Lazy)、AopContext.currentProxy()(exposeProxy=true)、拆类让调用跨 Bean、改用 TransactionTemplate 编程式事务。

一、失效现象与原理

@Service
public class OrderService {
    public void createOrder() {
        this.deductStock();     // ✗ 自调用:this 是目标对象,不经过代理 → @Transactional 失效
    }

    @Transactional
    public void deductStock() {
        // 扣库存 + 写库
    }
}
  • 原因 1:容器注入给外部调用方的是代理对象,只有经代理发起的调用才被事务/缓存/异步拦截器处理;
  • 原因 2:无论 JDK 代理还是 CGLIB 代理,方法体内 this 都是"真实目标对象",this.xxx() 不会再回到代理的拦截逻辑;
  • 推论:自调用失效与代理类型无关,是 AOP 代理模型的本质边界;失效时异常也到不了事务拦截器,回滚自然不触发。

二、四种解决方案

// 方案 1:注入自身代理(@Lazy 避免构造器循环依赖),改动最小、最常用
@Service
public class OrderService {
    @Autowired
    @Lazy
    private OrderService self;

    public void createOrder() {
        self.deductStock();      // ✓ 经代理调用
    }

    @Transactional
    public void deductStock() {
        // 扣库存 + 写库
    }
}

// 方案 2:AopContext 取当前代理(需开启 exposeProxy)
@EnableAspectJAutoProxy(exposeProxy = true)
// ((OrderService) AopContext.currentProxy()).deductStock();

// 方案 3:拆类——把 deductStock 移到另一个被 Spring 管理的 Bean,跨 Bean 调用天然走代理
// 方案 4:方法内部改用 TransactionTemplate 编程式事务,不依赖代理

三、哪些能力会受影响

  • 受影响:@Transactional、@Async、@Cacheable、@Retryable、自定义 @Aspect 切面等一切"代理实现"的声明式能力;
  • 反推设计:需要被代理的步骤尽量拆成独立 Bean 方法(也便于单测与复用),避免在长方法里 this 连环调用。

常见追问 / 记忆点

  • 追问 1:哪些注解最容易踩这个坑?答:@Transactional、@Async、@Cacheable、@Retryable 以及自定义切面,凡是代理实现的能力都可能因自调用失效。
  • 追问 2:为什么 Spring 不自动修正?答:代理模型无法感知"方法内 this 调用"的意图,这是 AOP 的本质边界,官方建议拆类或自注入显式处理。
  • 追问 3:自调用时传播行为还生效吗?答:拦截器根本没执行,等同该方法没有事务注解——既不加入外层事务,异常也不触发回滚。
  • 记忆点:"外部调用走代理、内部 this 走自己;要内部也生效——注入自己、AopContext、拆类或编程式事务"。
笔记加载中…