AsyncTool + SpringBoot:轻量级异步编排最佳实践

0 阅读23分钟

AsyncTool + SpringBoot:轻量级异步编排最佳实践

😄生命不息,写作不止

🔥 继续踏上学习之路,学之分享笔记

👊 总有一天我也能像各位大佬一样

🏆 博客首页   @怒放吧德德  To记录领地 @一个有梦有戏的人

🌝分享学习心得,欢迎指正,大家一起学习成长!

转发请携带作者信息  @怒放吧德德(掘金) @一个有梦有戏的人(CSDN)

image-835954_1_20260209_145632.jpg

前言

在日常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

脚本会自动完成以下操作:

  1. 从 Gitee 克隆 asyncTool 源码
  2. 编译项目
  3. 安装到本地 Maven 仓库
  4. 清理临时文件

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 测试验证

  1. 启动SpringBoot项目,访问接口:http://localhost:8001/goods/detail/G10086

  2. 查看返回结果(可看到汇总的商品信息和总耗时,约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
  1. 查看日志,可看到三个任务并行执行,线程名称为我们自定义的前缀(async-tool-pool-),证明自定义线程池生效。

5 开发注意事项

  1. 必须自定义线程池:生产环境严禁使用asyncTool的默认线程池,否则高并发场景下容易出现线程耗尽、内存溢出,建议结合SpringBoot的ThreadPoolTaskExecutor配置,使用有界队列。

  2. 避免任务循环依赖:不要配置「任务A依赖任务B,任务B依赖任务A」的循环依赖,会导致任务死锁,无法执行。

  3. 结果兜底处理:所有任务结果的获取,必须添加兜底逻辑(如getTaskResultSafely方法),避免任务失败/超时导致null指针异常。

  4. 超时时间合理配置:单个任务的超时时间不宜过短(避免正常任务被误判超时),也不宜过长(避免资源占用过久),一般略高于任务平均执行耗时。

  5. 与Spring @Async的区别:asyncTool专注于任务编排和管控,@Async专注于方法级异步化,两者可协同使用(比如用@Async配置线程池,供asyncTool使用),但不要重复异步化。

  6. 分布式场景回滚:asyncTool本身不支持事务回滚,若需要实现“一个任务失败,所有任务回滚”,需手动实现回滚/补偿方法(如订单创建失败,回滚库存和余额),分布式场景下可结合TCC、Seata等分布式事务方案。

6 总结

asyncTool 作为一款轻量级异步任务协调工具,完美解决了传统异步方案的编排复杂、异常难管控、超时无保障等痛点,尤其适合多任务依赖、接口聚合、复杂流程异步化等场景。

而它与SpringBoot的整合非常便捷,只需“引入依赖→配置线程池→定义任务→编排执行”四个步骤,就能快速落地,代码简洁、可维护性高,新手也能快速上手。

如果你的项目中存在异步任务编排、超时/异常管控的需求,不用再手动封装复杂的逻辑,试试asyncTool,它能帮你节省大量开发时间,提升系统的稳定性和可维护性。

最后,附上asyncTool官方地址,感兴趣的同学可以查看源码和更多高级用法:

官方地址

我的仓库地址:gitee.com/liyongde/ja…


转发请携带作者信息  @怒放吧德德 @一个有梦有戏的人
持续创作很不容易,作者将以尽可能的详细把所学知识分享各位开发者,一起进步一起学习。转载请携带链接,转载到微信公众号请勿选择原创,谢谢!
👍创作不易,如有错误请指正,感谢观看!记得点赞哦!👍
谢谢支持!