深入詳解SpringBoot中接口異步化實戰(zhàn)指南
背景
在一個智慧課堂系統(tǒng)中,有一個"生成講稿"的接口,內(nèi)部需要調(diào)用 AI 大模型(Coze),整個過程耗時可能在數(shù)十秒甚至更長。
原始的調(diào)用鏈?zhǔn)沁@樣的:
前端
│
├─ 1. 調(diào) updateCdGenStatus(status=1) → 把狀態(tài)設(shè)為"生成中"
│
├─ 2. 調(diào) generateSpeech → 同步等待 AI 生成(可能等幾十秒)
│
└─ 3. 調(diào) updateCdGenStatus(status=2) → 把狀態(tài)設(shè)為"已生成"
這個設(shè)計存在兩個明顯問題:
- 前端長時間阻塞:第 2 步接口響應(yīng)極慢,用戶體驗差。
- 狀態(tài)不一致:如果用戶在第 2 步等待期間刷新了頁面,第 3 步永遠(yuǎn)不會執(zhí)行,狀態(tài)永遠(yuǎn)卡在"生成中",無法恢復(fù)。
解決思路
將兩個問題合并解決:
- 狀態(tài)管理下沉到后端:generateSpeech 內(nèi)部在成功后自動更新狀態(tài)為"已生成",失敗時回滾為"暫存",前端不再負(fù)責(zé)狀態(tài)收尾。
- 接口改為異步:generateSpeech 立即返回,AI 生成任務(wù)由后臺線程池執(zhí)行,前端輪詢狀態(tài)字段即可感知進(jìn)度。
改造后的調(diào)用鏈:
前端 后端
│ │
├─ updateCdGenStatus(1) ────────?│ 同步,立即返回,狀態(tài)="生成中"
│?──────── 200 OK ───────────────┤
│ │
├─ generateSpeech ──────────────?│ 立即返回 200 OK
│?──────── 200 OK ───────────────┤ │
│ │ ▼ 后臺線程池異步執(zhí)行
│ (前端可正常操作/刷新頁面) │ 調(diào)用 AI 大模型(耗時)
│ │ 成功 → cdGenStatus = 2(已生成)
│ │ 失敗 → cdGenStatus = 0(暫存,可重試)
│
前端定時輪詢查詢接口,檢查 cdGenStatus 字段
實現(xiàn)步驟
第一步:創(chuàng)建異步線程池配置類
Spring Boot 默認(rèn)的異步線程池配置較為簡陋,建議為耗時任務(wù)單獨配置一個命名線程池,便于監(jiān)控和隔離。
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 調(diào)用屬于 IO 密集型,核心線程數(shù)不需要很大。
* 使用 CallerRunsPolicy 作為拒絕策略,保證任務(wù)不丟失。
*/
@Bean("speechExecutor")
public Executor speechExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2); // 核心線程數(shù)
executor.setMaxPoolSize(5); // 最大線程數(shù)
executor.setQueueCapacity(20); // 隊列容量
executor.setThreadNamePrefix("speech-async-");
executor.setKeepAliveSeconds(60);
// 隊列滿且線程達(dá)到上限時,由調(diào)用方線程執(zhí)行,避免任務(wù)丟失
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
為什么不用 @SpringBootApplication 上加 @EnableAsync?
將 @EnableAsync 放在獨立的 @Configuration 類中,職責(zé)更清晰,也方便后續(xù)擴(kuò)展其他線程池。
第二步:在 Service 實現(xiàn)方法上加 @Async
/**
* 異步生成講稿。方法立即返回,由后臺線程池(speechExecutor)執(zhí)行耗時的 AI 調(diào)用。
* 生成成功后自動將 cdGenStatus 更新為"講稿已生成"(2),
* 生成失敗則回滾為"暫存"(0),無需前端二次調(diào)用更新狀態(tài)接口。
*
* @param speechDto 生成講稿所需參數(shù),包含 courseDetailId、classId 等
*/
@Async("speechExecutor") // 指定使用 speechExecutor 線程池
@Override
public void generateSpeech(SpeechDto speechDto) {
try {
// --- 耗時操作:調(diào)用 AI 大模型 ---
String genWord = cozeUtil.pptCreate(cozeDto);
// 其他業(yè)務(wù)邏輯...
// 生成成功 → 后端直接更新狀態(tài)為"已生成"
this.updateCdGenStatus("2", speechDto.getCourseDetailId(), speechDto.getClassId());
} catch (Exception e) {
// 生成失敗 → 回滾狀態(tài)為"暫存",前端可重試
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 已標(biāo)注 @Async,Spring 會在調(diào)用時直接提交任務(wù)到線程池并返回,Controller 完全感知不到異步細(xì)節(jié)。
關(guān)鍵注意事項
@Async 的自調(diào)用陷阱
@Async 基于 Spring AOP 代理實現(xiàn),同一個類內(nèi)部的方法互相調(diào)用無法觸發(fā)異步。
// ? 錯誤:在同一個 Service 內(nèi)部調(diào)用,@Async 不生效
public void someMethod() {
this.generateSpeech(dto); // 直接調(diào)用,不走代理,不會異步
}
// ? 正確:從另一個 Spring Bean(如 Controller)注入后調(diào)用
@Autowired
private IBizCourseDetailService bizCourseDetailService;
public void someMethod() {
bizCourseDetailService.generateSpeech(dto); // 走代理,異步生效
}異步方法的返回值
@Async 方法的返回值只能是 void 或 Future<T> / CompletableFuture<T>。
本場景使用 void 即可,狀態(tài)通過數(shù)據(jù)庫字段通知前端。
異步方法中的事務(wù)
@Async 方法運行在新線程中,@Transactional 的事務(wù)上下文不會從調(diào)用方傳播過來。如果異步方法內(nèi)需要事務(wù),需要在異步方法本身上加 @Transactional。
SecurityContext(Shiro/Spring Security)不傳播
異步線程中拿不到調(diào)用方的登錄用戶信息,如需使用,需在調(diào)用前手動傳入,或通過參數(shù)傳遞。
前端配合改造
去掉 generateSpeech 成功回調(diào)里調(diào)用 updateCdGenStatus 的邏輯,改為輪詢查詢接口:
// 調(diào)用生成講稿接口后,定時輪詢狀態(tài)
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);
}總結(jié)
| 改造點 | 方式 |
|---|---|
| 開啟 Spring 異步 | @Configuration 類上加 @EnableAsync |
| 配置專用線程池 | @Bean 注冊 ThreadPoolTaskExecutor |
| 方法異步化 | Service 實現(xiàn)方法上加 @Async("poolName") |
| 狀態(tài)管理內(nèi)聚 | try 塊末尾更新成功狀態(tài),catch 中回滾失敗狀態(tài) |
| 前端感知進(jìn)度 | 去掉二次狀態(tài)更新調(diào)用,改為輪詢查詢接口 |
到此這篇關(guān)于深入詳解SpringBoot中接口異步化實戰(zhàn)指南的文章就介紹到這了,更多相關(guān)SpringBoot接口異步化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot 文件或圖片上傳與下載功能的實現(xiàn)
這篇文章主要介紹了SpringBoot 文件或圖片上傳與下載功能的實現(xiàn),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-02-02
IDEA提示 add *** to custom tags問題及解決
文章介紹了如何在文檔注釋中添加自定義注解(@xxx),并提供了添加和刪除注解的方法,總結(jié)了個人經(jīng)驗,希望對大家有所幫助2024-12-12
Spring Boot詳細(xì)打印啟動時異常堆棧信息詳析
這篇文章主要給大家介紹了關(guān)于Spring Boot詳細(xì)打印啟動時異常堆棧信息的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10
Java轉(zhuǎn)換流(InputStreamReader/OutputStreamWriter)的使用
本文主要介紹了Java轉(zhuǎn)換流(InputStreamReader/OutputStreamWriter)的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
IDEA 單元測試報錯:Class not found:xxxx springb
這篇文章主要介紹了IDEA 單元測試報錯:Class not found:xxxx springboot的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
Java后端Tomcat實現(xiàn)WebSocket實例教程
WebSocket protocol 是HTML5一種新的協(xié)議。它實現(xiàn)了瀏覽器與服務(wù)器全雙工通信(full-duplex)。一開始的握手需要借助HTTP請求完成握手。本文給大家介紹Java后端Tomcat實現(xiàn)WebSocket實例教程,感興趣的朋友一起學(xué)習(xí)吧2016-05-05
Maven配置阿里云倉庫/國內(nèi)鏡像的詳細(xì)步驟
在國內(nèi)使用Maven時,很多時候會遇到下載依賴較慢的問題,主要是因為Maven的默認(rèn)中央倉庫位于國外,網(wǎng)絡(luò)延遲較高,為了解決這個問題,我們可以配置國內(nèi)的Maven鏡像源,如阿里云提供的鏡像,在這篇博客中,我們將詳細(xì)介紹如何配置Maven使用阿里云倉庫,需要的朋友可以參考下2025-04-04

