我招了一个实习生,让他写个异步处理任务。他用 Spring 的@Async写了一个 Service,怎么调都不生效。
代码长这样:
@ServicepublicclassOrderService{publicvoidprocess(LongorderId){this.sendNotification(orderId);// 希望异步执行}@AsyncpublicvoidsendNotification(LongorderId){// 发通知...}}他说「我加了 @Async 了啊,为什么还是同步执行的?」
我一看就笑了——自己调自己,AOP 管不了你。
为什么 @Async 不生效
Spring 的@Async、@Transactional、@Cacheable这些注解都是通过 AOP 代理实现的。
Spring 会给 Bean 创建一个代理对象。你调用orderService.process()时,实际上调用的是代理对象的方法。代理对象在方法前后织入事务、异步等逻辑。
但问题来了:process()方法里this.sendNotification()——这个this是原始对象,不是代理对象。原始对象调用sendNotification()时,Spring 根本没有机会拦截,直接执行了原始方法。
@Transactional自调用不生效,也是同一个道理。
三种解法
解法一:注入自己(最简单)
@ServicepublicclassOrderService{@AutowiredprivateOrderServiceself;// 注入代理对象publicvoidprocess(LongorderId){self.sendNotification(orderId);// 通过代理调用}@AsyncpublicvoidsendNotification(LongorderId){// 现在真正异步了}}注意:Spring 4.3+ 之后,如果构造器注入会循环依赖报错,用@Lazy解决:
@Lazy@AutowiredprivateOrderServiceself;解法二:拆成两个 Service(最推荐)
@ServicepublicclassOrderService{@AutowiredprivateNotificationServicenotificationService;publicvoidprocess(LongorderId){notificationService.sendNotification(orderId);}}@ServicepublicclassNotificationService{@AsyncpublicvoidsendNotification(LongorderId){// 独立的异步 Service}}这样结构清晰,职责单一,测试也好写。
解法三:用 ApplicationContext 拿代理
@ServicepublicclassOrderService{@AutowiredprivateApplicationContextctx;publicvoidprocess(LongorderId){OrderServiceproxy=ctx.getBean(OrderService.class);proxy.sendNotification(orderId);}}这种偏 Hack,能不用就不用。
不只是 @Async
同样的坑,@Transactional也一样:
@ServicepublicclassUserService{@TransactionalpublicvoidcreateUser(Useruser){userMapper.insert(user);this.updateStats();// 事务不会生效!}@Transactional(propagation=Propagation.REQUIRES_NEW)publicvoidupdateStats(){// 你以为会开启新事务,其实没有}}自调用 = 没有事务、没有异步、没有缓存。记住这一条就够了。
核心要点:
- ✅
@Async/@Transactional自调用不生效,因为this不是代理对象 - ✅ 解法一:注入自己
@Autowired self - ✅ 解法二:拆成两个 Service(推荐)
- ✅ 解法三:
ApplicationContext.getBean()(不推荐) - ✅ 不是 Bug,是 Spring AOP 代理机制决定的
关于作者:无羡,独立开发者,全栈工程师,专注 AI 应用与微服务架构。
📌 分类:技术复盘
🔗 个人博客 — 更多技术文章
✨ 开发者福利整理 — 云服务资源汇总
📂 软件工坊 — 我的技术分享
🌐 个人门户 — 独立开发作品全集