AsyncTool + SpringBoot:轻量级异步编排最佳实践
😄生命不息,写作不止
🔥 继续踏上学习之路,学之分享笔记
👊 总有一天我也能像各位大佬一样
🏆 博客首页 @怒放吧德德 To记录领地 @一个有梦有戏的人
🌝分享学习心得,欢迎指正,大家一起学习成长!
转发请携带作者信息 @怒放吧德德(掘金) @一个有梦有戏的人(CSDN)
前言
在日常Java开发中,异步编程是提升系统性能、优化用户体验的关键手段——比如电商详情页需要并行调用多个接口、后台任务需要按依赖顺序执行、高并发场景下需要管控异步任务的超时与异常。但传统异步方案(Java原生 Future、CompletableFuture、Spring @Async)要么编排复杂、要么管控能力不足,手动封装又容易出现线程耗尽、异常扩散等问题。
今天给大家推荐一款京东开源的轻量级工具——asyncTool,它专门解决复杂异步任务的编排、依赖、异常和超时管控问题,而且整合 SpringBoot 非常便捷,新手也能快速上手。下面我们从「是什么、有什么好处、适用于什么场景、SpringBoot 怎么整合」四个核心维度,一次性讲透它的用法。具体代码可以看我的仓库:gitee.com/liyongde/ja…
1 asyncTool 是什么?
asyncTool 是京东平台开源的一款轻量级异步任务协调工具,核心定位是「简化复杂异步任务的编排与管控」,无需复杂的链式调用或手动线程管理,就能轻松实现异步任务的并行、串行、混合依赖执行。
它既不是替代 Java 原生异步 API,也不是替代 Spring @Async,而是对它们的补充和增强——相比CompletableFuture 的链式调用冗余、@Async 的管控能力薄弱,asyncTool 更专注于「流程化」和「可管控性」,尤其适合多任务有依赖、需要统一管控超时/异常的场景。
核心特点总结:轻量级(无强依赖,核心包体积小)、上手简单(API简洁)、功能强大(编排、异常、超时、重试一站式支持),且完全兼容 SpringBoot 生态,是企业级开发中异步任务编排的优选工具之一。
2 为什么要用asyncTool?
在使用 asyncTool 之前,我们先看看传统异步方案的痛点,以及asyncTool是如何针对性解决的——这也是它能成为开发效率利器的关键。
2.1 简化复杂任务编排,告别代码臃肿
传统痛点:如果需要实现「先串行校验参数 → 再并行查询商品/订单/用户信息 → 最后串行汇总结果」,用 CompletableFuture 需要写大量链式调用(thenApply、thenCombine),用 @Async 则需要手动处理线程等待和依赖传递,代码可读性差、维护成本高,还容易出现线程死锁。
asyncTool优势:通过 WorkerWrapper#dependsOn() 方法,一行代码即可配置任务依赖关系,编排逻辑直观可见,无需手动处理线程协调。比如“任务B依赖任务A的结果”,只需给任务B配置dependsOn(A),就能自动实现串行执行,并行任务更是只需批量提交即可。
2.2 异常隔离,避免单个任务失败拖垮全局
传统痛点:异步任务中如果单个任务未捕获异常,可能导致线程终止,甚至扩散影响其他任务;并行任务中一个任务报错,容易导致整个流程卡壳,无法获取其他正常任务的结果。
asyncTool 优势:内置异常隔离机制,自动捕获单个任务的异常,不会中断其他任务的执行。失败任务的异常会被封装到 TaskResult 中,同时支持自定义 onFailure() 回调记录详情,其他正常任务仍能正常返回结果,便于业务兜底处理(比如返回默认值)。
2.3 精细化超时管控,避免资源泄露
传统痛点:Java 原生 Future 需要手动调用 get(long, TimeUnit) 设置超时,多个任务则需要逐个配置;@Async默认没有超时管控,容易导致任务长期挂起,消耗系统资源。
asyncTool 优势:支持「全局超时+单个任务独立超时」双向管控,通过 TaskConfig 只需简单配置超时时间,超时任务会自动终止并触发 onTimeout() 回调,无需手动编写超时判断逻辑,有效避免资源泄露。
2.4 内置重试机制,提升任务执行成功率
传统痛点:对于网络抖动、微服务临时不可用等偶发失败场景,传统方案需要手动编写重试逻辑(循环+延迟+重试次数),代码冗余且难以统一管控。
asyncTool 优势:通过 TaskConfig 可直接配置单个任务的重试次数、重试间隔,无需手动编写重试代码,偶发失败可自动重试,大幅提升异步任务的执行成功率,尤其适合分布式接口调用场景。
2.5 轻量级接入,无缝兼容SpringBoot
传统痛点:一些复杂的异步框架(如XX任务调度框架)接入繁琐,需要额外配置大量XML或注解,且与 SpringBoot 整合存在兼容性问题。
asyncTool 优势:只需引入一个 Maven 依赖,无需额外配置,就能快速整合 SpringBoot;支持SpringBoot 的线程池配置,可与 Spring 的 ThreadPoolTaskExecutor 无缝结合,统一管理线程资源。
3 适用场景
asyncTool的核心价值是「任务编排+管控」,因此更适合多任务、有依赖、需管控的异步场景,以下是企业级开发中最常见的4类场景,覆盖大部分业务需求:
3.1 接口聚合查询(最常用)
场景描述:电商商品详情页、用户中心页、订单详情页等,需要并行调用多个微服务接口(如商品基础信息、库存、评价、物流),聚合结果后返回给前端。
核心价值:将串行调用的“耗时总和”优化为“最长接口耗时”,大幅提升接口响应速度(比如3个各耗时500ms的接口,串行需1500ms,并行仅需500ms),优化用户体验。
3.2 复杂流程异步编排
场景描述:后台复杂业务流程,比如“订单创建”流程:先串行执行「参数校验 → 订单入库」,再并行执行「扣减库存 → 扣减余额 → 发送短信通知」,最后串行执行「更新订单状态 → 记录操作日志」。
核心价值:通过依赖配置实现“串行+并行”混合编排,逻辑清晰,无需手动处理线程等待,降低开发复杂度。
3.3 分布式接口调用(需重试/超时管控)
场景描述:跨服务调用场景,比如调用第三方支付接口、物流接口、短信接口,这类接口容易出现网络抖动、临时不可用等问题,需要配置超时和重试。
核心价值:通过TaskConfig配置超时时间和重试次数,自动处理偶发失败,同时通过异常回调记录接口调用异常,便于问题排查。
3.4 后台非实时任务(需异步回调)
场景描述:后台报表生成、数据导出、日志归档、消息推送等非实时任务,不需要立即获取结果,只需任务完成后执行回调(如通知前端、更新任务状态)。
核心价值:通过invokeAllAsync()方法实现异步执行,主线程无需阻塞,可继续处理其他业务,任务完成后自动触发回调,提升系统吞吐量。
4 重点实操:SpringBoot 整合 asyncTool(附可复用代码)
前面讲了这么多理论,接下来进入最核心的实操环节——SpringBoot整合asyncTool,全程手把手,代码可直接复制到项目中运行(基于SpringBoot 2.x/3.x,兼容asyncTool 2.0.5稳定版)。
4.1 引入Maven依赖
在pom.xml中添加asyncTool依赖,推荐使用2.0.5稳定版(无特殊需求优先选择该版本,避免快照版的未知Bug):
<!-- 京东 asyncTool 异步任务协调工具 -->
<dependency>
<groupId>com.jd.platform</groupId>
<artifactId>asyncTool</artifactId>
<version>1.4.1-SNAPSHOT</version>
</dependency>
补充:如果中央仓库无法拉取依赖,因为其并没有上传到中央仓库,可以通过 git 仓库底下的 sh 命令进行操作,主要是将其源码拉取下来,并进行 install 到 maven 中。
直接运行项目根目录下的安装脚本:
# Windows 系统
install-asynctool.bat
# Linux/Mac 系统
chmod +x install-asynctool.sh
./install-asynctool.sh
脚本会自动完成以下操作:
- 从 Gitee 克隆 asyncTool 源码
- 编译项目
- 安装到本地 Maven 仓库
- 清理临时文件
4.2 配置自定义线程池
asyncTool有默认线程池,但核心线程数和最大线程数的默认配置的不适合高并发场景(容易出现线程耗尽)。因此在SpringBoot中,我们自定义线程池,与asyncTool绑定,统一管理线程资源。
先创建线程池配置类(结合SpringBoot的配置文件,可灵活调整参数):
/**
* asyncTool 线程池配置类(SpringBoot整合核心)
* @author: lyd
* @date: 2026/2/11
*/
@Configuration
@ConfigurationProperties(prefix = "async-tool.thread-pool")
@Data
public class AsyncToolThreadPoolConfig {
// 核心线程数(可在application.yml中配置)
private int corePoolSize = 10;
// 最大线程数(可在application.yml中配置)
private int maxPoolSize = 20;
// 任务队列容量(可在application.yml中配置)
private int queueCapacity = 100;
// 空闲线程存活时间(秒)
private int keepAliveSeconds = 60;
/**
* 自定义线程池(Spring管理,供asyncTool使用)
* 返回 ExecutorService 类型,供 Async.beginWork() 使用
*/
@Bean("asyncToolExecutor")
public ExecutorService asyncToolExecutor() {
return new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveSeconds,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(queueCapacity),
r -> {
Thread thread = new Thread(r);
thread.setName("async-tool-pool-" + thread.getId());
return thread;
},
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
}
然后在application.yml中添加线程池参数配置(可根据项目并发量调整):
async-tool:
thread-pool:
core-pool-size: 10
max-pool-size: 20
queue-capacity: 100
keep-alive-seconds: 60
thread-name-prefix: async-tool-pool-
4.3 定义异步任务
所有需要异步执行的业务逻辑,都需要实现asyncTool的IWorker<T, V>接口,实现 action 方法:
-
T:任务入参类型(比如商品ID、用户ID)
-
V:任务返回结果类型(比如自定义DTO、String)
我们以「电商商品详情页聚合查询」为例,定义3个异步任务(查询商品基础信息、库存、评价),并交给Spring管理(添加@Component注解):
这里只拿基础信息为例,其他见仓库代码
/**
* 商品基础信息查询任务
* <p>
* 查询商品的基础信息(ID、名称、价格)
* </p>
*
* @author lyd
* @date 2026/2/13
*/
@Component
@Slf4j
public class GoodsBaseInfoTask implements IWorker<String, GoodsBaseInfoDTO> {
/**
* 核心业务方法,所有业务逻辑、异常处理均在此实现
*
* @param goodsId 任务入参
* @param allWrappers 所有任务包装器(可获取其他任务信息)
* @return 商品基础信息 DTO
*/
@Override
public GoodsBaseInfoDTO action(String goodsId, Map<String, WorkerWrapper> allWrappers) {
long startTime = System.currentTimeMillis();
log.info("开始查询商品基础信息,任务名称: GoodsBaseInfoTask, goodsId: {}", goodsId);
try {
// 模拟耗时操作(如Feign调用、数据库查询)
Thread.sleep(500);
// 构建返回结果
GoodsBaseInfoDTO dto = new GoodsBaseInfoDTO();
dto.setGoodsId(goodsId);
dto.setGoodsName("华为Mate 60 Pro");
dto.setPrice(new BigDecimal("6999.00"));
long executionTime = System.currentTimeMillis() - startTime;
log.info("查询商品基础信息完成,执行时间: {}ms", executionTime);
return dto;
} catch (InterruptedException e) {
log.error("查询商品基础信息异常,goodsId: {}", goodsId, e);
Thread.currentThread().interrupt();
// 返回兜底结果
return createDefaultBaseInfo(goodsId);
} catch (Exception e) {
log.error("查询商品基础信息发生未知异常,goodsId: {}", goodsId, e);
// 返回兜底结果
return createDefaultBaseInfo(goodsId);
}
}
/**
* 创建默认的商品基础信息(兜底数据)
*
* @param goodsId 商品ID
* @return 默认的商品基础信息 DTO
*/
private GoodsBaseInfoDTO createDefaultBaseInfo(String goodsId) {
GoodsBaseInfoDTO dto = new GoodsBaseInfoDTO();
dto.setGoodsId(goodsId);
dto.setGoodsName("商品信息暂时无法获取");
dto.setPrice(BigDecimal.ZERO);
return dto;
}
}
action 方法中,第一个参数为输入的参数,第二参数是所有的任务数组,在这里可以看到其他任务的信息,包括获取结果。
4.4 封装任务包装器
为了避免重复创建WorkerWrapper(任务包装器),我们封装一个工具类,统一构建任务包装器,结合Spring注入的TaskConfig(自定义线程池配置):
/**
* 任务包装器构建工具
* <p>
* 负责构建 AsyncTool 的 WorkerWrapper,封装任务的依赖关系和执行顺序
* </p>
*
* @author lyd
* @date 2026/2/13
*/
@Component
@Slf4j
public class TaskWrapperBuilder {
// 忽略导入的bean
/**
* 构建商品基础信息任务包装器(场景1:并行执行)
*
* @param goodsId 商品ID
* @return 商品基础信息任务包装器
*/
public WorkerWrapper<String, GoodsBaseInfoDTO> buildBaseInfoWrapper(String goodsId) {
return new WorkerWrapper.Builder<String, GoodsBaseInfoDTO>()
.worker(goodsBaseInfoTask)
.param(goodsId)
.build();
}
/**
* 构建商品库存任务包装器(场景1:并行执行)
*
* @param goodsId 商品ID
* @return 商品库存任务包装器
*/
public WorkerWrapper<String, GoodsStockDTO> buildStockWrapper(String goodsId) {
return new WorkerWrapper.Builder<String, GoodsStockDTO>()
.worker(goodsStockTask)
.param(goodsId)
.build();
}
/**
* 构建商品评价任务包装器(场景1:并行执行)
*
* @param goodsId 商品ID
* @return 商品评价任务包装器
*/
public WorkerWrapper<String, GoodsCommentDTO> buildCommentWrapper(String goodsId) {
return new WorkerWrapper.Builder<String, GoodsCommentDTO>()
.worker(goodsCommentTask)
.param(goodsId)
.build();
}
/**
* 构建依赖任务包装器(场景2:任务依赖)
* <p>
* 场景说明:
* 1. 先执行基础信息任务
* 2. 基础信息任务完成后,库存任务和评价任务并行执行
* </p>
*
* @param goodsId 商品ID
* @return 任务包装器数组 [baseWrapper, stockWrapper, commentWrapper]
*/
public WorkerWrapper<String, ?>[] buildDependentWrappers(String goodsId) {
// 1. 构建库存任务包装器(先创建,后面设置依赖)
WorkerWrapper<String, GoodsStockDTO> stockWrapper =
new WorkerWrapper.Builder<String, GoodsStockDTO>()
.worker(goodsStockDependTask)
.param(goodsId)
.build();
// 2. 构建评价任务包装器(先创建,后面设置依赖)
WorkerWrapper<String, GoodsCommentDTO> commentWrapper =
new WorkerWrapper.Builder<String, GoodsCommentDTO>()
.worker(goodsCommentDependTask)
.param(goodsId)
.build();
// 3. 构建基础信息任务包装器(设置ID和next,供后续任务获取结果)
WorkerWrapper<String, GoodsBaseInfoDTO> baseWrapper =
new WorkerWrapper.Builder<String, GoodsBaseInfoDTO>()
.worker(goodsBaseInfoDependTask)
.param(goodsId)
.id("baseTask") // 设置任务ID
.next(stockWrapper, commentWrapper) // 设置后续任务
.build();
return new WorkerWrapper[]{baseWrapper, stockWrapper, commentWrapper};
}
/**
* 构建结果传递任务包装器(场景3:结果传递)
* <p>
* 场景说明:
* 1. 先执行基础信息任务
* 2. 库存任务和评价任务使用基础信息任务的返回结果作为输入参数
* </p>
*
* @param goodsId 商品ID
* @return 任务包装器数组 [baseWrapper, stockWrapper, commentWrapper]
*/
public WorkerWrapper<String, ?>[] buildResultPassingWrappers(String goodsId) {
// 1. 构建库存任务包装器(先创建,后面设置依赖)
WorkerWrapper<String, GoodsStockDTO> stockWrapper =
new WorkerWrapper.Builder<String, GoodsStockDTO>()
.worker(goodsStockDependTask)
.param(goodsId)
.build();
// 2. 构建评价任务包装器(先创建,后面设置依赖)
WorkerWrapper<String, GoodsCommentDTO> commentWrapper =
new WorkerWrapper.Builder<String, GoodsCommentDTO>()
.worker(goodsCommentDependTask)
.param(goodsId)
.build();
// 3. 构建基础信息任务包装器(设置next关系)
WorkerWrapper<String, GoodsBaseInfoDTO> baseWrapper =
new WorkerWrapper.Builder<String, GoodsBaseInfoDTO>()
.worker(goodsBaseInfoDependTask)
.param(goodsId)
.id("baseTask")
.next(stockWrapper, commentWrapper)
.build();
return new WorkerWrapper[]{baseWrapper, stockWrapper, commentWrapper};
}
}
任务执行器
/**
* AsyncTool 任务执行器
* <p>
* 封装 AsyncTool 的任务执行逻辑,提供三种场景的任务执行方法
* </p>
*
* @author lyd
* @date 2026/2/13
*/
@Component
@Slf4j
public class AsyncToolTaskExecutor {
@Autowired
@Qualifier("asyncToolExecutor")
private ExecutorService asyncToolExecutor;
@Autowired
private TaskWrapperBuilder wrapperBuilder;
/**
* 执行并行任务(场景1:三个任务并行执行,互不依赖)
* <p>
* 场景说明:
* - 商品基础信息、库存信息、评价信息三个任务并行执行
* - 任务之间没有依赖关系
* - 所有任务完成后返回结果
* </p>
*
* @param goodsId 商品ID
* @return 任务执行结果 Map,key 为任务类型(base/stock/comment),value 为对应的 DTO
* @throws ExecutionException 任务执行异常
* @throws InterruptedException 任务中断异常
*/
public Map<String, Object> executeParallelTasks(String goodsId)
throws ExecutionException, InterruptedException {
log.info("开始执行并行任务(场景1),goodsId: {}", goodsId);
long startTime = System.currentTimeMillis();
try {
// 1. 构建三个独立的任务包装器
WorkerWrapper<String, GoodsBaseInfoDTO> baseWrapper =
wrapperBuilder.buildBaseInfoWrapper(goodsId);
WorkerWrapper<String, GoodsStockDTO> stockWrapper =
wrapperBuilder.buildStockWrapper(goodsId);
WorkerWrapper<String, GoodsCommentDTO> commentWrapper =
wrapperBuilder.buildCommentWrapper(goodsId);
// 2. 执行任务(超时时间3000ms)
Async.beginWork(3000, asyncToolExecutor, baseWrapper, stockWrapper, commentWrapper);
// 3. 获取任务执行结果
Map<String, Object> results = new HashMap<>();
if (baseWrapper.getWorkResult() != null) {
results.put("base", baseWrapper.getWorkResult().getResult());
}
if (stockWrapper.getWorkResult() != null) {
results.put("stock", stockWrapper.getWorkResult().getResult());
}
if (commentWrapper.getWorkResult() != null) {
results.put("comment", commentWrapper.getWorkResult().getResult());
}
long executionTime = System.currentTimeMillis() - startTime;
log.info("并行任务执行完成(场景1),总执行时间: {}ms", executionTime);
return results;
} catch (ExecutionException | InterruptedException e) {
log.error("并行任务执行异常(场景1),goodsId: {}", goodsId, e);
throw e;
} catch (Exception e) {
log.error("并行任务执行发生未知异常(场景1),goodsId: {}", goodsId, e);
throw new ExecutionException("并行任务执行失败", e);
}
}
/**
* 执行依赖任务(场景2:任务之间有依赖关系)
* <p>
* 场景说明:
* - 先执行基础信息任务
* - 基础信息任务完成后,库存任务和评价任务并行执行
* - 库存和评价任务依赖基础信息任务的结果
* </p>
*
* @param goodsId 商品ID
* @return 任务执行结果 Map
* @throws ExecutionException 任务执行异常
* @throws InterruptedException 任务中断异常
*/
public Map<String, Object> executeDependentTasks(String goodsId)
throws ExecutionException, InterruptedException {
log.info("开始执行依赖任务(场景2),goodsId: {}", goodsId);
long startTime = System.currentTimeMillis();
try {
// 1. 构建依赖任务包装器
WorkerWrapper<String, ?>[] wrappers = wrapperBuilder.buildDependentWrappers(goodsId);
WorkerWrapper<String, GoodsBaseInfoDTO> baseWrapper =
(WorkerWrapper<String, GoodsBaseInfoDTO>) wrappers[0];
WorkerWrapper<String, GoodsStockDTO> stockWrapper =
(WorkerWrapper<String, GoodsStockDTO>) wrappers[1];
WorkerWrapper<String, GoodsCommentDTO> commentWrapper =
(WorkerWrapper<String, GoodsCommentDTO>) wrappers[2];
// 2. 执行任务(超时时间3000ms)
Async.beginWork(3000, asyncToolExecutor, baseWrapper, stockWrapper, commentWrapper);
// 3. 获取任务执行结果
Map<String, Object> results = new HashMap<>();
if (baseWrapper.getWorkResult() != null) {
results.put("base", baseWrapper.getWorkResult().getResult());
}
if (stockWrapper.getWorkResult() != null) {
results.put("stock", stockWrapper.getWorkResult().getResult());
}
if (commentWrapper.getWorkResult() != null) {
results.put("comment", commentWrapper.getWorkResult().getResult());
}
long executionTime = System.currentTimeMillis() - startTime;
log.info("依赖任务执行完成(场景2),总执行时间: {}ms", executionTime);
return results;
} catch (ExecutionException | InterruptedException e) {
log.error("依赖任务执行异常(场景2),goodsId: {}", goodsId, e);
throw e;
} catch (Exception e) {
log.error("依赖任务执行发生未知异常(场景2),goodsId: {}", goodsId, e);
throw new ExecutionException("依赖任务执行失败", e);
}
}
/**
* 执行结果传递任务(场景3:后续任务使用前置任务的返回结果)
* <p>
* 场景说明:
* - 先执行基础信息任务
* - 库存任务和评价任务使用基础信息任务的返回结果作为输入
* - 演示任务之间的结果传递
* </p>
*
* @param goodsId 商品ID
* @return 任务执行结果 Map
* @throws ExecutionException 任务执行异常
* @throws InterruptedException 任务中断异常
*/
public Map<String, Object> executeResultPassingTasks(String goodsId)
throws ExecutionException, InterruptedException {
log.info("开始执行结果传递任务(场景3),goodsId: {}", goodsId);
long startTime = System.currentTimeMillis();
try {
// 1. 构建结果传递任务包装器
WorkerWrapper<String, ?>[] wrappers = wrapperBuilder.buildResultPassingWrappers(goodsId);
WorkerWrapper<String, GoodsBaseInfoDTO> baseWrapper =
(WorkerWrapper<String, GoodsBaseInfoDTO>) wrappers[0];
WorkerWrapper<String, GoodsStockDTO> stockWrapper =
(WorkerWrapper<String, GoodsStockDTO>) wrappers[1];
WorkerWrapper<String, GoodsCommentDTO> commentWrapper =
(WorkerWrapper<String, GoodsCommentDTO>) wrappers[2];
// 2. 执行任务(超时时间3000ms)
Async.beginWork(3000, asyncToolExecutor, baseWrapper, stockWrapper, commentWrapper);
// 3. 获取任务执行结果
Map<String, Object> results = new HashMap<>();
if (baseWrapper.getWorkResult() != null) {
results.put("base", baseWrapper.getWorkResult().getResult());
}
if (stockWrapper.getWorkResult() != null) {
results.put("stock", stockWrapper.getWorkResult().getResult());
}
if (commentWrapper.getWorkResult() != null) {
results.put("comment", commentWrapper.getWorkResult().getResult());
}
long executionTime = System.currentTimeMillis() - startTime;
log.info("结果传递任务执行完成(场景3),总执行时间: {}ms", executionTime);
return results;
} catch (ExecutionException | InterruptedException e) {
log.error("结果传递任务执行异常(场景3),goodsId: {}", goodsId, e);
throw e;
} catch (Exception e) {
log.error("结果传递任务执行发生未知异常(场景3),goodsId: {}", goodsId, e);
throw new ExecutionException("结果传递任务执行失败", e);
}
}
}
4.5 Service层编排执行任务
在SpringBoot的Service层,注入任务包装器构建工具类,使用asyncTool的AsyncTools类执行任务(支持同步执行和异步回调),这里演示最常用的「同步执行(阻塞等待结果,适合接口聚合)」:
package com.example.asyncTool.service;
import com.example.asyncTool.util.GoodsTaskWrapperBuilder;
import com.jd.platform.async.AsyncTools;
import com.jd.platform.async.TaskResult;
import com.jd.platform.async.wrapper.WorkerWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 商品详情服务(asyncTool 任务编排核心层)
*/
@Service
@Slf4j
public class GoodsDetailService {
@Autowired
private GoodsTaskWrapperBuilder taskWrapperBuilder;
/**
* 聚合查询商品详情(同步执行,阻塞等待所有任务完成,适合接口调用)
*/
public String getGoodsDetail(String goodsId) {
log.info("开始聚合查询商品详情,goodsId: {}", goodsId);
// 1. 构建所有任务包装器(通过工具类简化创建)
WorkerWrapper<String, String> baseWrapper = taskWrapperBuilder.buildGoodsBaseWrapper(goodsId);
WorkerWrapper<String, String> stockWrapper = taskWrapperBuilder.buildGoodsStockWrapper(goodsId);
WorkerWrapper<String, String> commentWrapper = taskWrapperBuilder.buildGoodsCommentWrapper(goodsId);
// 2. 执行异步任务(并行执行,阻塞等待所有任务完成)
// invokeAll():同步执行,等待所有任务完成后返回
AsyncTools.invokeAll(baseWrapper, stockWrapper, commentWrapper);
// 3. 安全获取任务结果(兜底处理,避免null指针)
String baseResult = getTaskResultSafely(baseWrapper, "商品基础信息查询失败");
String stockResult = getTaskResultSafely(stockWrapper, "商品库存查询失败");
String commentResult = getTaskResultSafely(commentWrapper, "商品评价查询失败");
// 4. 汇总结果(实际项目中可封装为 GoodsDetailDTO 返回)
StringBuilder resultBuilder = new StringBuilder();
resultBuilder.append("===== 商品详情汇总 =====\n");
resultBuilder.append(baseResult).append("\n");
resultBuilder.append(stockResult).append("\n");
resultBuilder.append(commentResult).append("\n");
resultBuilder.append("查询耗时:").append(getMaxTaskTime(baseWrapper, stockWrapper, commentWrapper)).append("ms");
log.info("商品详情查询完成,goodsId: {}", goodsId);
return resultBuilder.toString();
}
/**
* 安全获取任务结果(兜底处理)
*/
private <V> V getTaskResultSafely(WorkerWrapper<?, V> wrapper, V defaultValue) {
if (wrapper == null || wrapper.getTaskResult() == null || !wrapper.getTaskResult().isSuccess()) {
return defaultValue;
}
return wrapper.getTaskResult().getResult();
}
/**
* 获取所有任务的最长耗时(并行任务的总耗时 = 最长任务耗时)
*/
private long getMaxTaskTime(WorkerWrapper<?, ?>... wrappers) {
long maxTime = 0;
for (WorkerWrapper<?, ?> wrapper : wrappers) {
TaskResult<?> taskResult = wrapper.getTaskResult();
if (taskResult != null && taskResult.getTime() > maxTime) {
maxTime = taskResult.getTime();
}
}
return maxTime;
}
}
补充:异步回调场景(适合后台非实时任务)
如果不需要立即获取结果(如报表生成、消息推送),可使用invokeAllAsync()方法,传入全局回调ICallback,任务完成后自动触发回调:
/**
* 异步执行任务(无需阻塞,任务完成后回调,适合后台任务)
*/
public void asyncGenerateGoodsReport(String goodsId) {
// 1. 构建任务包装器
WorkerWrapper<String, String> baseWrapper = taskWrapperBuilder.buildGoodsBaseWrapper(goodsId);
WorkerWrapper<String, String> stockWrapper = taskWrapperBuilder.buildGoodsStockWrapper(goodsId);
WorkerWrapper<String, String> commentWrapper = taskWrapperBuilder.buildGoodsCommentWrapper(goodsId);
// 2. 定义全局回调(所有任务完成后执行)
ICallback callback = new ICallback() {
@Override
public void begin() {
log.info("商品报表生成任务开始执行,goodsId: {}", goodsId);
}
@Override
public void result(boolean success, List<WorkerWrapper> workerWrappers) {
log.info("所有任务执行完成,开始生成商品报表");
// 汇总结果,生成报表(如写入数据库、导出文件)
for (WorkerWrapper wrapper : workerWrappers) {
log.info("{}:{}", wrapper.getTaskName(), getTaskResultSafely(wrapper, "执行失败"));
}
log.info("商品报表生成完成,goodsId: {}", goodsId);
}
};
// 3. 异步执行任务(主线程无需阻塞)
AsyncTools.invokeAllAsync(callback, baseWrapper, stockWrapper, commentWrapper);
log.info("主线程继续处理其他业务...");
}
主要的是任务的创建以及编排,通过new WorkerWrapper.Builder创建任务,通过 Async.beginWork执行任务,参数中的 worker 就是实现了 IWorker的任务类,例如:GoodsBaseInfoTask,而任务的编排是通过 Async.beginWork()方法实现。
任务之间的关联通过 .next(xxx 任务)来设置,如果不设置,那么任务间就没有相互关系。其次需要注意的是Async.beginWork()方法对任务的处理。如方法 executeDependentTasks(String)下的 Async.beginWork(3000, asyncToolExecutor, baseWrapper);这段代码,通过 com.storm.jd.asynctool.infrastructure.asynctool.executor.TaskWrapperBuilder#buildDependentWrappers可以看出因为任务的关系是:
-> goodsStockDependTask
baseWrapper ->
-> goodsCommentDependTask
baseWrapper 任务执行之后,在并行执行 goodsStockDependTask 与goodsCommentDependTask。
4.6 Controller层提供接口,测试效果
创建Controller,调用Service层方法,提供HTTP接口,测试asyncTool的执行效果:
package com.example.asyncTool.controller;
import com.example.asyncTool.service.GoodsDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 商品详情Controller(测试asyncTool整合效果)
*/
@RestController
@RequestMapping("/goods")
public class GoodsDetailController {
/**
* 聚合查询商品详情(模拟带依赖关系)
* 测试地址:http://localhost:8001/goods/detail/v2/12345
*/
@GetMapping("/detail/v2/{goodsId}")
public Result<GoodsDetailVO> getGoodsDetailV2(@PathVariable String goodsId) {
log.info("接收商品详情查询请求(任务依赖),goodsId: {}", goodsId);
// 参数验证
if (StringUtils.isBlank(goodsId)) {
log.warn("请求参数无效,goodsId为空");
return Result.error(ResultCode.PARAM_ERROR, "商品ID不能为空");
}
// 调用应用服务
Result<GoodsDetailVO> result = applicationService.getGoodsDetailWithDependency(goodsId);
log.info("商品详情查询请求完成(任务依赖),goodsId: {}", goodsId);
return result;
}
// 其他代码不粘贴,可以看gitee代码。
}
4.7 测试验证
-
启动SpringBoot项目,访问接口:http://localhost:8001/goods/detail/G10086
-
查看返回结果(可看到汇总的商品信息和总耗时,约600ms,即最长任务的耗时):
2026-02-16 14:33:42.189 INFO 17784 --- [nio-8001-exec-2] c.s.j.a.i.w.c.GoodsDetailController : 接收商品详情查询请求,goodsId: G10086
2026-02-16 14:33:42.198 INFO 17784 --- [nio-8001-exec-2] .s.j.a.a.s.GoodsDetailApplicationService : 开始查询商品详情,goodsId: G10086
2026-02-16 14:33:42.198 INFO 17784 --- [nio-8001-exec-2] c.s.j.a.i.a.e.AsyncToolTaskExecutor : 开始执行并行任务(场景1),goodsId: G10086
2026-02-16 14:33:42.213 INFO 17784 --- [nc-tool-pool-61] c.s.j.a.i.a.tasks.GoodsStockTask : 开始查询商品库存,任务名称: GoodsStockTask, goodsId: G10086
2026-02-16 14:33:42.213 INFO 17784 --- [nc-tool-pool-60] c.s.j.a.i.a.tasks.GoodsBaseInfoTask : 开始查询商品基础信息,任务名称: GoodsBaseInfoTask, goodsId: G10086
2026-02-16 14:33:42.213 INFO 17784 --- [nc-tool-pool-62] c.s.j.a.i.a.tasks.GoodsCommentTask : 开始查询商品评价,任务名称: GoodsCommentTask, goodsId: G10086
2026-02-16 14:33:42.724 INFO 17784 --- [nc-tool-pool-61] c.s.j.a.i.a.tasks.GoodsStockTask : 查询商品库存完成,执行时间: 511ms
2026-02-16 14:33:42.725 INFO 17784 --- [nc-tool-pool-60] c.s.j.a.i.a.tasks.GoodsBaseInfoTask : 查询商品基础信息完成,执行时间: 512ms
2026-02-16 14:33:42.817 INFO 17784 --- [nc-tool-pool-62] c.s.j.a.i.a.tasks.GoodsCommentTask : 查询商品评价完成,执行时间: 604ms
2026-02-16 14:33:42.818 INFO 17784 --- [nio-8001-exec-2] c.s.j.a.i.a.e.AsyncToolTaskExecutor : 并行任务执行完成(场景1),总执行时间: 620ms
2026-02-16 14:33:42.819 INFO 17784 --- [nio-8001-exec-2] .s.j.a.d.s.GoodsDetailAggregationService : 开始聚合商品详情,goodsId: G10086
2026-02-16 14:33:42.819 INFO 17784 --- [nio-8001-exec-2] .s.j.a.d.s.GoodsDetailAggregationService : 商品详情聚合完成,goodsId: G10086, 是否有货: true, 是否好评: true
2026-02-16 14:33:42.819 INFO 17784 --- [nio-8001-exec-2] .s.j.a.a.s.GoodsDetailApplicationService : 查询商品详情完成,goodsId: G10086
2026-02-16 14:33:42.821 INFO 17784 --- [nio-8001-exec-2] c.s.j.a.i.w.c.GoodsDetailController : 商品详情查询请求完成,goodsId: G10086
- 查看日志,可看到三个任务并行执行,线程名称为我们自定义的前缀(async-tool-pool-),证明自定义线程池生效。
5 开发注意事项
-
必须自定义线程池:生产环境严禁使用asyncTool的默认线程池,否则高并发场景下容易出现线程耗尽、内存溢出,建议结合SpringBoot的ThreadPoolTaskExecutor配置,使用有界队列。
-
避免任务循环依赖:不要配置「任务A依赖任务B,任务B依赖任务A」的循环依赖,会导致任务死锁,无法执行。
-
结果兜底处理:所有任务结果的获取,必须添加兜底逻辑(如getTaskResultSafely方法),避免任务失败/超时导致null指针异常。
-
超时时间合理配置:单个任务的超时时间不宜过短(避免正常任务被误判超时),也不宜过长(避免资源占用过久),一般略高于任务平均执行耗时。
-
与Spring @Async的区别:asyncTool专注于任务编排和管控,@Async专注于方法级异步化,两者可协同使用(比如用@Async配置线程池,供asyncTool使用),但不要重复异步化。
-
分布式场景回滚:asyncTool本身不支持事务回滚,若需要实现“一个任务失败,所有任务回滚”,需手动实现回滚/补偿方法(如订单创建失败,回滚库存和余额),分布式场景下可结合TCC、Seata等分布式事务方案。
6 总结
asyncTool 作为一款轻量级异步任务协调工具,完美解决了传统异步方案的编排复杂、异常难管控、超时无保障等痛点,尤其适合多任务依赖、接口聚合、复杂流程异步化等场景。
而它与SpringBoot的整合非常便捷,只需“引入依赖→配置线程池→定义任务→编排执行”四个步骤,就能快速落地,代码简洁、可维护性高,新手也能快速上手。
如果你的项目中存在异步任务编排、超时/异常管控的需求,不用再手动封装复杂的逻辑,试试asyncTool,它能帮你节省大量开发时间,提升系统的稳定性和可维护性。
最后,附上asyncTool官方地址,感兴趣的同学可以查看源码和更多高级用法:
我的仓库地址:gitee.com/liyongde/ja…
转发请携带作者信息 @怒放吧德德 @一个有梦有戏的人
持续创作很不容易,作者将以尽可能的详细把所学知识分享各位开发者,一起进步一起学习。转载请携带链接,转载到微信公众号请勿选择原创,谢谢!
👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍
谢谢支持!