Spring Boot 异步编程实战:手把手教你写不会“炸”主流程的异步代码

张开发
2026/4/17 17:34:01 15 分钟阅读

分享文章

Spring Boot 异步编程实战:手把手教你写不会“炸”主流程的异步代码
目录1. 核心前置条件两步开启异步第一步开启全局开关第二步标记异步方法2. 实战案例如何编写“黑名单历史记录”异步方法核心代码标准的异步 Service 写法3. 深度解析为什么要这么写面试/装逼必备3.1 事务避坑REQUIRES_NEW 的必要性3.2 异常处理为什么必须自己抓4. 调用姿势如何在 Controller 中使用总结Spring Boot 异步编写 Checklist摘要在 Java 开发中我们经常遇到这样的场景主业务逻辑必须马上执行比如给用户返回结果但一些非核心操作比如记录日志、发短信可以慢点做。这时候就要用到Async。但是如何编写一个既能异步执行又不会因为报错导致主事务回滚还能保证异常被捕获的异步方法本文将通过一个“黑名单历史记录”的例子带你深挖Async的核心用法和避坑指南。1. 核心前置条件两步开启异步在 Spring Boot 中使用异步首先得拿到“入场券”。只有两步缺一不可第一步开启全局开关在你的启动类或者配置类上打上EnableAsync注解。SpringBootApplication EnableAsync // ⭐️ 关键开启异步功能的总开关 public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }第二步标记异步方法在你想要异步执行的方法上打上Async注解。注意这个方法通常要写在Service组件里而且不能是private的。2. 实战案例如何编写“黑名单历史记录”异步方法假设我们的业务是用户做完黑名单筛查后需要把这次操作记下来。这个记录动作很慢要写库但我们不想让用户等。核心代码标准的异步 Service 写法import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Transactional; import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; Service Slf4j public class AsyncRecordService { /** * 1. 方法必须是 public * 2. 加上 Async 注解表示这个方法会扔给线程池去跑 * 3. 加上事务注解如果涉及数据库操作 */ Async Transactional(propagation Propagation.REQUIRES_NEW) public void saveBlacklistRecord(RecordDTO dto) { log.info(异步线程启动线程名{}, Thread.currentThread().getName()); try { // 模拟耗时操作 Thread.sleep(2000); // 1. 执行数据库插入保存历史记录快照 // blacklistMapper.insert(dto); log.info(异步记录保存成功{}, dto.getCustomerName()); } catch (Exception e) { // ⭐️ 关键异步方法里的异常必须自己 try-catch // 因为主程序不等你如果你不抓异常会丢失主程序也感知不到 log.error(异步保存记录失败, e); // 这里可以发告警邮件或者记录到本地文件但绝不能抛给主程序 } } }3. 深度解析为什么要这么写面试/装逼必备为什么上面的代码里我们要特意强调REQUIRES_NEW和try-catch这涉及到 Spring 异步的底层原理。3.1 事务避坑REQUIRES_NEW的必要性现象如果你在主业务里调用了异步方法主业务突然报错回滚了你会发现异步保存的记录也没了原因默认情况下Spring 的异步方法如果没指定事务可能会复用主线程的数据库连接。解法Transactional(propagation Propagation.REQUIRES_NEW)这句话的意思是“不管外面发什么疯我都要开启一个全新的数据库事务。”这样主业务回滚不影响历史记录的保存历史记录报错也不影响主业务的成功。大白话主业务和异步方法跑在两个独立的线程上给异步方法加上REQUIRES_NEW注解就是让它在新线程里开启一个完全独立的新事务从而彻底解决了“主业务黑名单筛查动作一报错回滚异步保存的记录黑名单筛查记录也跟着消失”的现象。3.2 异常处理为什么必须自己抓现象主程序调用异步方法主程序正常返回但后台报了一堆错主程序却不知道。原因异步方法是在另一个线程跑的。Java 的规则是子线程的异常无法抛给主线程跨线程通信限制。解法必须在异步方法内部try-catch。最佳实践抓到异常后记录日志但绝不能让方法抛出未检查异常。大白话由于主线程黑名单筛查中调用了子线程即异步方法记录黑名单筛查记录二者本质是两个线程因此子线程中报错了父线程根本不知道所以子线程需要自己捕获异常、打印日志。4. 调用姿势如何在 Controller 中使用写好了异步方法怎么调用它才不会阻塞主流程RestController public class BlacklistController { Autowired private AsyncRecordService asyncRecordService; PostMapping(/check) public String checkBlacklist() { // 1. 执行核心业务同步用户必须等 System.out.println(主线程正在执行核心筛查逻辑...); // 2. 构造数据 RecordDTO dto new RecordDTO(); dto.setCustomerName(测试用户); // 3. ⭐️ 关键调用 // 调用异步方法Spring 会立即返回不会等待 2 秒 asyncRecordService.saveBlacklistRecord(dto); // 4. 立刻返回此时异步方法可能还在后台跑 System.out.println(主线程立即返回给用户不等待记录保存); return success; } }运行结果主线程正在执行核心筛查逻辑... 主线程立即返回给用户不等待记录保存 -- 用户此时已经看到页面了 异步线程启动线程名task-1 -- 后台默默在写数据库 异步记录保存成功测试用户总结Spring Boot 异步编写 Checklist如果你想在项目中安全地使用异步记住这 3 点开关要开别忘了EnableAsync。异常自留异步方法里的try-catch是必须的不能偷懒抛throws。事务隔离如果涉及数据库写入强烈建议加上REQUIRES_NEW防止主事务“死”了把你也拖下水。这就是 Spring Boot 中编写异步方法的全部核心套路

更多文章