深入详解SpringBoot中接口异步化实战指南
背景
在一个智慧课堂系统中,有一个"生成讲稿"的接口,内部需要调用 AI 大模型(Coze),整个过程耗时可能在数十秒甚至更长。
原始的调用链是这样的:
前端
│
├─ 1. 调 updateCdGenStatus(status=1) → 把状态设为"生成中"
│
├─ 2. 调 generateSpeech → 同步等待 AI 生成(可能等几十秒)
│
└─ 3. 调 updateCdGenStatus(status=2) → 把状态设为"已生成"
这个设计存在两个明显问题:
- 前端长时间阻塞:第 2 步接口响应极慢,用户体验差。
- 状态不一致:如果用户在第 2 步等待期间刷新了页面,第 3 步永远不会执行,状态永远卡在"生成中",无法恢复。
解决思路
将两个问题合并解决:
- 状态管理下沉到后端:generateSpeech 内部在成功后自动更新状态为"已生成",失败时回滚为"暂存",前端不再负责状态收尾。
- 接口改为异步:generateSpeech 立即返回,AI 生成任务由后台线程池执行,前端轮询状态字段即可感知进度。
改造后的调用链:
前端 后端
│ │
├─ updateCdGenStatus(1) ────────►│ 同步,立即返回,状态="生成中"
│◄──────── 200 OK ───────────────┤
│ │
├─ generateSpeech ──────────────►│ 立即返回 200 OK
│◄──────── 200 OK ───────────────┤ │
│ │ ▼ 后台线程池异步执行
│ (前端可正常操作/刷新页面) │ 调用 AI 大模型(耗时)
│ │ 成功 → cdGenStatus = 2(已生成)
│ │ 失败 → cdGenStatus = 0(暂存,可重试)
│
前端定时轮询查询接口,检查 cdGenStatus 字段
实现步骤
第一步:创建异步线程池配置类
Spring Boot 默认的异步线程池配置较为简陋,建议为耗时任务单独配置一个命名线程池,便于监控和隔离。
package org.jeecg.modules.business.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author lxy
*/
@Configuration
@EnableAsync // 开启 Spring 异步支持
public class AsyncConfig {
/**
* 生成讲稿专用异步线程池。
* AI 调用属于 IO 密集型,核心线程数不需要很大。
* 使用 CallerRunsPolicy 作为拒绝策略,保证任务不丢失。
*/
@Bean("speechExecutor")
public Executor speechExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2); // 核心线程数
executor.setMaxPoolSize(5); // 最大线程数
executor.setQueueCapacity(20); // 队列容量
executor.setThreadNamePrefix("speech-async-");
executor.setKeepAliveSeconds(60);
// 队列满且线程达到上限时,由调用方线程执行,避免任务丢失
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
为什么不用 @SpringBootApplication 上加 @EnableAsync?
将 @EnableAsync 放在独立的 @Configuration 类中,职责更清晰,也方便后续扩展其他线程池。
第二步:在 Service 实现方法上加 @Async
/**
* 异步生成讲稿。方法立即返回,由后台线程池(speechExecutor)执行耗时的 AI 调用。
* 生成成功后自动将 cdGenStatus 更新为"讲稿已生成"(2),
* 生成失败则回滚为"暂存"(0),无需前端二次调用更新状态接口。
*
* @param speechDto 生成讲稿所需参数,包含 courseDetailId、classId 等
*/
@Async("speechExecutor") // 指定使用 speechExecutor 线程池
@Override
public void generateSpeech(SpeechDto speechDto) {
try {
// --- 耗时操作:调用 AI 大模型 ---
String genWord = cozeUtil.pptCreate(cozeDto);
// 其他业务逻辑...
// 生成成功 → 后端直接更新状态为"已生成"
this.updateCdGenStatus("2", speechDto.getCourseDetailId(), speechDto.getClassId());
} catch (Exception e) {
// 生成失败 → 回滚状态为"暂存",前端可重试
this.updateCdGenStatus("0", speechDto.getCourseDetailId(), speechDto.getClassId());
throw new RuntimeException(e);
}
}第三步:Controller 无需任何改动
@PostMapping(value = "/generateSpeech")
public Result<?> generateSpeech(@RequestBody SpeechDto speechDto) {
bizCourseDetailService.generateSpeech(speechDto); // 立即返回
return Result.OK();
}
由于 generateSpeech 已标注 @Async,Spring 会在调用时直接提交任务到线程池并返回,Controller 完全感知不到异步细节。
关键注意事项
@Async 的自调用陷阱
@Async 基于 Spring AOP 代理实现,同一个类内部的方法互相调用无法触发异步。
// ❌ 错误:在同一个 Service 内部调用,@Async 不生效
public void someMethod() {
this.generateSpeech(dto); // 直接调用,不走代理,不会异步
}
// ✅ 正确:从另一个 Spring Bean(如 Controller)注入后调用
@Autowired
private IBizCourseDetailService bizCourseDetailService;
public void someMethod() {
bizCourseDetailService.generateSpeech(dto); // 走代理,异步生效
}异步方法的返回值
@Async 方法的返回值只能是 void 或 Future<T> / CompletableFuture<T>。
本场景使用 void 即可,状态通过数据库字段通知前端。
异步方法中的事务
@Async 方法运行在新线程中,@Transactional 的事务上下文不会从调用方传播过来。如果异步方法内需要事务,需要在异步方法本身上加 @Transactional。
SecurityContext(Shiro/Spring Security)不传播
异步线程中拿不到调用方的登录用户信息,如需使用,需在调用前手动传入,或通过参数传递。
前端配合改造
去掉 generateSpeech 成功回调里调用 updateCdGenStatus 的逻辑,改为轮询查询接口:
// 调用生成讲稿接口后,定时轮询状态
async function generateSpeech(params) {
await api.generateSpeech(params); // 立即返回
// 轮询,每 3 秒检查一次
const timer = setInterval(async () => {
const res = await api.queryCourseDetail({ id: params.courseDetailId, classId: params.classId });
const status = res.result.cdGenStatus;
if (status === '2') {
clearInterval(timer);
// 生成成功,刷新页面
} else if (status === '0') {
clearInterval(timer);
// 生成失败,提示用户重试
}
}, 3000);
}总结
| 改造点 | 方式 |
|---|---|
| 开启 Spring 异步 | @Configuration 类上加 @EnableAsync |
| 配置专用线程池 | @Bean 注册 ThreadPoolTaskExecutor |
| 方法异步化 | Service 实现方法上加 @Async("poolName") |
| 状态管理内聚 | try 块末尾更新成功状态,catch 中回滚失败状态 |
| 前端感知进度 | 去掉二次状态更新调用,改为轮询查询接口 |
到此这篇关于深入详解SpringBoot中接口异步化实战指南的文章就介绍到这了,更多相关SpringBoot接口异步化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!


最新评论