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,但问题依旧。请问我哪里操作有误?
问题原因及解决方法
核心原因
CompletableFuture.supplyAsync()默认使用ForkJoinPool.commonPool(),该线程池的线程不属于Spring请求线程上下文,而apiHelper的内部方法大概率依赖了请求上下文信息(比如RequestAttributes、Session、请求头参数等),导致异步线程无法获取绑定的请求对象,触发异常。- 方法上标注的
@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
相关产品推荐
相关产品推荐

