深入详解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接口异步化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot 文件或图片上传与下载功能的实现

    SpringBoot 文件或图片上传与下载功能的实现

    这篇文章主要介绍了SpringBoot 文件或图片上传与下载功能的实现,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-02-02
  • Spring加载properties文件的方法

    Spring加载properties文件的方法

    这篇文章主要为大家详细介绍了Spring加载properties文件的两种方法,一是通过xml方式,另一种方式是通过注解方式,感兴趣的小伙伴们可以参考一下
    2016-06-06
  • IDEA提示 add *** to custom tags问题及解决

    IDEA提示 add *** to custom tags问题及解决

    文章介绍了如何在文档注释中添加自定义注解(@xxx),并提供了添加和删除注解的方法,总结了个人经验,希望对大家有所帮助
    2024-12-12
  • Spring Boot详细打印启动时异常堆栈信息详析

    Spring Boot详细打印启动时异常堆栈信息详析

    这篇文章主要给大家介绍了关于Spring Boot详细打印启动时异常堆栈信息的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring Boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-10-10
  • Java转换流(InputStreamReader/OutputStreamWriter)的使用

    Java转换流(InputStreamReader/OutputStreamWriter)的使用

    本文主要介绍了Java转换流(InputStreamReader/OutputStreamWriter)的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • springboot实现增加黑名单和白名单功能

    springboot实现增加黑名单和白名单功能

    本文主要介绍了springboot实现增加黑名单和白名单功能,就是单纯的实现filter,然后注册到springboot里面,在filter里面进行黑白名单的筛选,感兴趣的可以了解一下
    2024-05-05
  • IDEA 单元测试报错:Class not found:xxxx springboot的解决

    IDEA 单元测试报错:Class not found:xxxx springb

    这篇文章主要介绍了IDEA 单元测试报错:Class not found:xxxx springboot的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • Java后端Tomcat实现WebSocket实例教程

    Java后端Tomcat实现WebSocket实例教程

    WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成握手。本文给大家介绍Java后端Tomcat实现WebSocket实例教程,感兴趣的朋友一起学习吧
    2016-05-05
  • Maven配置阿里云仓库/国内镜像的详细步骤

    Maven配置阿里云仓库/国内镜像的详细步骤

    在国内使用Maven时,很多时候会遇到下载依赖较慢的问题,主要是因为Maven的默认中央仓库位于国外,网络延迟较高,为了解决这个问题,我们可以配置国内的Maven镜像源,如阿里云提供的镜像,在这篇博客中,我们将详细介绍如何配置Maven使用阿里云仓库,需要的朋友可以参考下
    2025-04-04
  • Java Bean与Map转换的几种方式

    Java Bean与Map转换的几种方式

    Java Bean与Map转换的几种方式,包括反射,内省,cglib,huTool,cglib动态代理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-10-10

最新评论