Skip to content

@Async 如何避免内部调用失效? ​

  • 标签:后端、Spring

🏛 业务架构师 ​

@Async 内部调用失效的根因:异步能力是代理对象提供的,this.method() 走的是原始对象,不经过代理,注解自然不生效。避免方式按我推荐的优先级排:

  1. 重构拆类(根治,首选):把异步方法抽到独立的 Bean(如 NotificationSender),注入后调用——调用的是代理对象,注解生效。顺便修了职责边界:异步逻辑独立成类,测试和复用都更干净;
  2. 注入自身代理:@Autowired @Lazy private AsyncService self; 然后调 self.asyncMethod()——改动小,但语义别扭,我视为临时止血;
  3. AopContext.currentProxy():需 @EnableAspectJAutoProxy(exposeProxy=true),框架味太重,可读性差,评审时我会要求注释;
  4. 不绕,直接正面解决:拿不到代理就别用注解异步——注入 TaskExecutor 手动提交,把方法体提成普通私有方法:
java
executor.submit(() -> doNotify(orderId));

这版反而显式、可靠。

架构师视角的延伸:同一类问题(事务失效、缓存失效)都源于"代理 + 自调用",治理上我把"自调用注解方法"列入静态检查规则(ArchUnit/自定义 lint),用工具替代人的记性——凡是"依赖开发者记住才不踩"的坑,都应该尽量变成 CI 里的自动拦截。

🏗 系统架构师 ​

失效根因:@Async 是代理增强,同类内部 this 调用绕过代理直接执行目标方法,「提交线程池」这一步从未发生。

解法按优先级:

  1. 重构拆分(正解):把异步方法挪到独立 Bean——自调用本身就是「一个类干两件事」的坏味道信号;
  2. 事件解耦:发 ApplicationEvent,用 @Async 的 @EventListener 消费——语义从「方法调用」变成「事实通知」,往往更合理;
  3. 自注入代理(兜底):注入自身代理(ObjectProvider<AsyncService> 或字段自注入)后通过代理调用;AopContext.currentProxy() 也可,但需要 exposeProxy,侵入性强,不作首选。

治理动作:这类失效编译器不报错、单测不易发现。我在平台里做静态扫描——同类内调用带 @Async 的方法直接 CI 拦截;并要求异步任务有监控埋点(提交数、执行数、队列深度),用运行时数据兜底验证「真的异步了」。

💻 全栈开发专家 ​

失效根因:@Async 靠代理实现,同类内部 this.method() 不经过代理,注解等于没写。四个解法:

1. 拆类(首选)——顺带治类膨胀,职责更清晰:

java
@Service
public class MailService {
    @Async("mailExecutor")
    public void sendAsync(String to) { ... }
}

@Service
public class OrderService {
    private final MailService mailService;         // 注入的是代理
    public void pay() { mailService.sendAsync(user); }
}

2. 自注入代理:

java
@Service
public class OrderService {
    @Autowired @Lazy
    private OrderService self;                     // 代理对象

    public void pay() { self.notifyAsync(order); }

    @Async
    public void notifyAsync(Order o) { ... }
}

3. AopContext.currentProxy():开启 @EnableAspectJAutoProxy(exposeProxy = true) 后 ((OrderService) AopContext.currentProxy()).notifyAsync(order)。

4. 编程式线程池:注入 ThreadPoolTaskExecutor 直接 submit,不纠结代理。

团队规约:默认方案 1,遗留代码赶修复用 2/3 过渡。验证是否生效最简单:方法里打 Thread.currentThread().getName(),看到 mailExecutor-x 而不是 http-nio-exec-x 就 OK。