使用CompletableFuture调用外部API时RequestContextHolder为空的配置方案问询
解决CompletableFuture异步线程中RequestContextHolder为空导致OAuth2授权异常的问题
这个问题我之前也踩过坑,核心原因就是Spring的RequestContextHolder默认不会把请求上下文传递到异步线程中——你主线程里的请求属性在supplyAsync开启的独立线程里完全拿不到,自然就会抛出servlet request cannot be null异常,毕竟你贴的DefaultOAuth2AuthorizedClientManager代码里,明确会尝试从RequestContextHolder中获取当前请求对象,拿不到就会返回null。
下面给你两种实用的解决方案:
方案一:手动在异步线程中传递RequestAttributes
如果只是单个异步任务需要处理,这种方式最直接快速:
// 主线程先把当前请求上下文存下来 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); CompletableFuture<UserDto> userDetailsFuture = CompletableFuture .supplyAsync(() -> { try { // 将主线程的请求上下文设置到当前异步线程中 RequestContextHolder.setRequestAttributes(requestAttributes); // 现在调用辅助方法时,RequestContextHolder就有可用属性了 return clientHelper.getUserDetails(userId); } finally { // 务必清理上下文,避免线程池复用线程时污染后续任务 RequestContextHolder.resetRequestAttributes(); } });
方案二:配置全局TaskExecutor自动传递上下文
如果项目里有大量异步任务都需要请求上下文,推荐全局配置一个带上下文传递能力的线程池,一劳永逸:
@Configuration public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(25); executor.setThreadNamePrefix("Async-Task-"); // 通过TaskDecorator自动复制请求上下文到异步线程 executor.setTaskDecorator(runnable -> { RequestAttributes currentRequestAttr = RequestContextHolder.getRequestAttributes(); return () -> { try { RequestContextHolder.setRequestAttributes(currentRequestAttr); runnable.run(); } finally { RequestContextHolder.resetRequestAttributes(); } }; }); executor.initialize(); return executor; } }
之后使用这个自定义Executor来创建CompletableFuture即可:
@Autowired private Executor asyncExecutor; CompletableFuture<UserDto> userDetailsFuture = CompletableFuture .supplyAsync(() -> clientHelper.getUserDetails(userId), asyncExecutor);
额外提醒
- 如果你项目里用了Spring的
@Async注解,这个自定义Executor也会自动生效,无需额外配置 - 一定要在finally块中重置RequestAttributes,否则线程池里的线程复用上下文,会导致后续请求出现数据污染的奇怪问题
内容的提问来源于stack exchange,提问作者ark
相关产品推荐
相关产品推荐

