深入详解SpringBoot中接口异步化实战指南

 更新时间:2026年03月03日 15:19:15   作者:一勺菠萝丶  
这篇文章主要为大家详细介绍了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接口异步化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 利用javadoc注释自动生成Swagger注解

    利用javadoc注释自动生成Swagger注解

    由于现在controller方法上面没有swagger注解,只能拿到接口url地址,无法获得接口功能描述,所以本文为大家介绍一下如何利用javadoc注释自动生成Swagger注解,感兴趣的可以了解下
    2023-08-08
  • Java面试之如何实现10亿数据判重

    Java面试之如何实现10亿数据判重

    当数据量比较大时,使用常规的方式来判重就不行了,所以这篇文章小编主要来和大家介绍一下Java实现10亿数据判重的相关方法,希望对大家有所帮助
    2024-02-02
  • java使用jacob.jar将word转pdf

    java使用jacob.jar将word转pdf

    这篇文章主要为大家详细介绍了java利用jacob.jar将word转pdf,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-12-12
  • RabbitMQ消息队列中多路复用Channel信道详解

    RabbitMQ消息队列中多路复用Channel信道详解

    这篇文章主要介绍了RabbitMQ消息队列中多路复用Channel信道详解,消息Message是指在应用间传送的数据,消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象,需要的朋友可以参考下
    2023-08-08
  • MyBatisPlus 主键策略的实现(4种)

    MyBatisPlus 主键策略的实现(4种)

    MyBatis Plus 集成了多种主键策略,帮助用户快速生成主键,本文主要介绍了MyBatisPlus主键策略的实现,具有一定的参考价值,感兴趣的可以了解一下
    2023-10-10
  • Retrofit+RxJava实现带进度条的文件下载

    Retrofit+RxJava实现带进度条的文件下载

    这篇文章主要为大家详细介绍了Retrofit+RxJava实现带进度条的文件下载,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-06-06
  • RocketMQ消息队列实现随机消息发送当做七夕礼物

    RocketMQ消息队列实现随机消息发送当做七夕礼物

    这篇文章主要为大家介绍了RocketMQ消息队列实现随机消息发送当做七夕礼物,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • spring缓存代码详解

    spring缓存代码详解

    这篇文章主要介绍了spring缓存代码详解,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • Eclipse引用XSD实现XML配置文件提示标签的方法

    Eclipse引用XSD实现XML配置文件提示标签的方法

    今天小编就为大家分享一篇关于Eclipse引用XSD实现XML配置文件提示标签的方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • java 中file.encoding的设置详解

    java 中file.encoding的设置详解

    这篇文章主要介绍了java 中file.encoding的设置详解的相关资料,需要的朋友可以参考下
    2017-04-04

最新评论