You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中CompletableFuture调用API触发请求上下文异常排查

问题描述

我尝试使用CompletableFuture同时发起多个API调用以减少用户等待时间,代码如下:

@Async
public CompletableFuture<CommonDataDTO> commonData() throws JsonProcessingException, ExecutionException, InterruptedException {
    CompletableFuture<UserDTO[]> completableFutureUser = CompletableFuture.supplyAsync(() -> {
        try {
            return  apiHelper.getEmployee();
        } catch (JsonProcessingException e) {
            return new UserDTO[0];
        }
    });
    CompletableFuture<ContactDTO[]> completableFutureContact = CompletableFuture.supplyAsync(() -> {
        try {
            return  apiHelper.getTravelAgent();
        } catch (JsonProcessingException e) {
            return new ContactDTO[0];
        }
    });
    CompletableFuture<CurrencyRateDTO[]> completableFutureCurrency = CompletableFuture.supplyAsync(() -> apiHelper.getCurrencyRate("site"));

    return CompletableFuture.allOf(completableFutureUser, completableFutureContact, completableFutureCurrency).thenApply(v -> {
        return new CommonDataDTO(Arrays.stream(completableFutureUser.join()).toList(), Arrays.stream(completableFutureContact.join()).toList(), Arrays.stream(completableFutureCurrency.join()).toList());
    });
}

运行代码时出现如下错误:

java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

我已在Application类添加了@EnableAsync注解,也曾尝试将allOf替换为thenCombine,但问题依旧。请问我哪里操作有误?

问题原因及解决方法

核心原因

  1. CompletableFuture.supplyAsync()默认使用ForkJoinPool.commonPool(),该线程池的线程不属于Spring请求线程上下文,而apiHelper的内部方法大概率依赖了请求上下文信息(比如RequestAttributes、Session、请求头参数等),导致异步线程无法获取绑定的请求对象,触发异常。
  2. 方法上标注的@Async与内部手动创建的CompletableFuture形成双重异步,进一步脱离了请求上下文。

解决步骤

  • 移除方法上的@Async注解
    你已经通过CompletableFuture实现了并行异步调用,@Async会让整个方法在额外的异步线程执行,叠加后更容易引发上下文丢失问题,因此需要移除该注解。

  • 配置异步线程继承请求上下文
    如果apiHelper的方法必须依赖请求上下文,需自定义Spring异步线程池并配置任务装饰器传递上下文:

    @Configuration
    @EnableAsync
    public class AsyncConfig implements AsyncConfigurer {
        @Override
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(4);
            executor.setMaxPoolSize(10);
            executor.setQueueCapacity(20);
            executor.setThreadNamePrefix("Async-");
            // 任务装饰器:传递请求上下文到异步线程
            executor.setTaskDecorator(runnable -> {
                RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
                return () -> {
                    try {
                        RequestContextHolder.setRequestAttributes(attributes);
                        runnable.run();
                    } finally {
                        RequestContextHolder.resetRequestAttributes();
                    }
                };
            });
            executor.initialize();
            return executor;
        }
    }
    

    之后在supplyAsync中指定该自定义线程池:

    @Autowired
    private Executor asyncExecutor;
    
    // 使用自定义线程池执行异步任务
    CompletableFuture<UserDTO[]> completableFutureUser = CompletableFuture.supplyAsync(() -> {
        try {
            return apiHelper.getEmployee();
        } catch (JsonProcessingException e) {
            return new UserDTO[0];
        }
    }, asyncExecutor);
    
  • 避免异步线程依赖请求上下文(推荐)
    尽量在主线程中提前取出请求上下文里的必要参数,直接传递给apiHelper的方法,而非让apiHelper在异步线程中获取上下文:

    // 主线程中获取请求参数
    String site = request.getParameter("site");
    // 传递参数到异步任务
    CompletableFuture<CurrencyRateDTO[]> completableFutureCurrency = CompletableFuture.supplyAsync(() -> apiHelper.getCurrencyRate(site), asyncExecutor);
    

内容的提问来源于stack exchange,提问作者Nguyen Andrew

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 02:34:56