Spring请求作用域Bean传递至子线程的问题求助
问题:异步线程中无法访问请求作用域Bean
我在微服务应用中处理请求作用域Java Bean与异步逻辑时遇到了问题。服务间通过REST API通信,使用当前登录用户的JWT令牌完成服务间授权。我希望异步处理耗时较长的流程,完成后向用户发送通知,但调用NotificationApi时总是抛出以下错误:
Error creating bean with name ''scopedTarget.notificationServiceProxy'': Scope ''request'' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton
相关代码结构如下:
异步任务服务
// async service @Service @RequiredArgsConstructor public class AsyncTaskService { private final AsyncTaskRepository asyncTaskRepository; private final ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(10); public UUID startAsyncTask (Supplier<?> task) { ClassLoader contentClassLoader = this.getClass().getClassLoader(); UUID someId = UUID.randomUUID(); CompletableFuture<?> future = CompletableFuture.supplyAsync(() -> { Thread.currentThread().setContextClassLoader(contentClassLoader); return task.get(); }, executorService); future.whenComplete((result, throwable) -> { if (throwable != null) { log.error("Task with id {} finished unsuccessfully. ", someId, throwable); } else { log.info("Task with id {} finished successfully.", someId); } }); return someId; } }
长流程服务
// long-lasting processes service @Service @RequiredArgsConstructor public class LongLastingProcessesService { private final AsyncTaskService asyncTaskService; private final NotificationApi notificationApi; public UUID handleLongLastingProcess() { RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); return asyncTaskService.startAsyncTask(() -> { RequestContextHolder.setRequestAttributes(requestAttributes, true); var result = someService.someMethod(someData); notificationApi.createNotification(someDataToBuildNotification); RequestContextHolder.resetRequestAttributes(); return result; }); } }
NotificationApi定义
// notificationApi @HttpExchange(url = "someUrlForNotificationMicroservice") public interface NotificationApi { @PostExchange Notification createNotification(@RequestBody NotificationBody notificationBody); }
请求作用域Bean配置
// proxy configuration @Configuration class ProxyConfiguration { @Bean @RequestScope RestClient notificationRestClient () { if (SecurityContextHolder.getContext().getAuthentication() != null) { return createRestClient("notification"); } return RestClient.builder().baseUrl(retrieveInstanceUrl("notification")).defaultHeaders(httpHeaders -> { httpHeaders.setBearerAuth(((JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication()).getToken().getTokenValue()); }).build(); } @Bean @RequestScope NotificationApi notificationServiceProxy () { RestClientAdapter adapter = RestClientAdapter.create(notificationRestClient()); HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); return factory.createClient(NotificationApi.class); } private RestClient createRestClient (String appName) { return RestClient.builder().baseUrl(retrieveInstanceUrl(appName)).defaultStatusHandler( HttpStatusCode::isError, (request, response) -> log.error( "Error occurred when communicating between services: {} {}", request, response ) ).defaultHeaders(httpHeaders -> { httpHeaders.setBearerAuth(((JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication()).getToken().getTokenValue()); }).build(); } }
我尝试通过RequestContextHolder将请求属性传递到子线程,但并未生效,请问如何在异步线程中访问这些请求作用域的Bean?
解决方案
问题核心是请求作用域Bean依赖的SecurityContext和RequestContext在异步线程中未正确传递,且Spring默认的请求作用域代理在跨线程场景下无法复用原有上下文。以下是具体解决步骤:
1. 显式传递JWT令牌,脱离请求作用域依赖
请求作用域的NotificationApi本质是为了获取当前用户的JWT令牌,不如在主线程中提前提取令牌,传递给异步任务,彻底避免依赖请求上下文:
修改LongLastingProcessesService:
@Service @RequiredArgsConstructor public class LongLastingProcessesService { private final AsyncTaskService asyncTaskService; private final ProxyConfiguration proxyConfiguration; public UUID handleLongLastingProcess() { // 主线程中提取JWT令牌 String jwtToken = ((JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication()).getToken().getTokenValue(); return asyncTaskService.startAsyncTask(() -> { // 异步线程中手动构建NotificationApi RestClient restClient = proxyConfiguration.createRestClientWithToken("notification", jwtToken); RestClientAdapter adapter = RestClientAdapter.create(restClient); HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); NotificationApi notificationApi = factory.createClient(NotificationApi.class); var result = someService.someMethod(someData); notificationApi.createNotification(someDataToBuildNotification); return result; }); } }
在ProxyConfiguration中新增令牌传递方法:
@Configuration class ProxyConfiguration { // ... 原有代码 // 根据传入的令牌创建RestClient public RestClient createRestClientWithToken(String appName, String jwtToken) { return RestClient.builder().baseUrl(retrieveInstanceUrl(appName)).defaultStatusHandler( HttpStatusCode::isError, (request, response) -> log.error( "Error occurred when communicating between services: {} {}", request, response ) ).defaultHeaders(httpHeaders -> { httpHeaders.setBearerAuth(jwtToken); }).build(); } }
2. 配置线程池支持Spring上下文传递
如果必须保留请求作用域Bean的使用,需要让自定义线程池支持Spring上下文的传递:
修改AsyncTaskService,使用Spring管理的TaskExecutor:
@Service @RequiredArgsConstructor public class AsyncTaskService { private final AsyncTaskRepository asyncTaskRepository; private final TaskExecutor taskExecutor; public UUID startAsyncTask (Supplier<?> task) { UUID someId = UUID.randomUUID(); CompletableFuture<?> future = CompletableFuture.supplyAsync(task, taskExecutor); future.whenComplete((result, throwable) -> { if (throwable != null) { log.error("Task with id {} finished unsuccessfully. ", someId, throwable); } else { log.info("Task with id {} finished successfully.", someId); } }); return someId; } }
配置支持上下文传递的TaskExecutor:
@Configuration public class AsyncConfig { @Bean public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(10); executor.setQueueCapacity(20); executor.setThreadNamePrefix("async-task-"); // 关键:启用上下文传递,让异步线程继承主线程的请求与安全上下文 executor.setTaskDecorator(new ContextDecorator()); executor.initialize(); return executor; } // 自定义装饰器,传递Request和Security上下文 static class ContextDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); return () -> { try { RequestContextHolder.setRequestAttributes(requestAttributes); SecurityContextHolder.getContext().setAuthentication(authentication); runnable.run(); } finally { RequestContextHolder.resetRequestAttributes(); SecurityContextHolder.clearContext(); } }; } } }
3. 确保请求作用域Bean的代理模式正确
修改NotificationApi的Bean配置,指定代理模式为TARGET_CLASS:
@Bean @RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS) NotificationApi notificationServiceProxy () { RestClientAdapter adapter = RestClientAdapter.create(notificationRestClient()); HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(adapter).build(); return factory.createClient(NotificationApi.class); }
原有方法失效原因
- 手动创建的
ThreadPoolExecutor未处理Spring上下文传递,即使通过RequestContextHolder设置属性,Spring的请求作用域代理仍无法识别异步线程中的上下文。 - 请求作用域Bean的初始化依赖活跃的请求上下文,异步线程默认无此上下文,导致Bean创建失败。
内容的提问来源于stack exchange,提问作者rh3h
相关产品推荐
相关产品推荐

