如何在异步任务执行程序中启用请求范围
在我的应用程序中,我有一些异步Web服务。 服务器接受请求,返回OK响应并使用AsyncTaskExecutor启动处理请求。 我的问题是如何在此处启用请求范围,因为在此处理中我需要获取由以下内容注释的类:
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
现在我得到例外:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.requestContextImpl': 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; nested exception is 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/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
因为它在SimpleAsyncTaskExecutor
运行而不在DispatcherServlet
我对请求的异步处理
taskExecutor.execute(new Runnable() { @Override public void run() { asyncRequest(request); } });
taskExecutor的位置是:
我们遇到了同样的问题 – 需要使用@Async在后台执行代码,因此它无法使用任何Session或RequestScope bean。 我们通过以下方式解决了它:
- 创建一个自定义TaskPoolExecutor,用于存储带有任务的范围信息
- 创建一个特殊的Callable(或Runnable),它使用该信息来设置和清除后台线程的上下文
- 创建覆盖配置以使用自定义执行程序
注意 :这仅适用于Session和Request范围的bean,而不适用于安全上下文(如Spring Security)。 如果您正在使用安全上下文,则必须使用其他方法来设置安全上下文。
注2 :为简洁起见,仅显示Callable和submit()实现。 您可以对Runnable和execute()执行相同的操作。
这是代码:
执行人:
public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor { @Override public Future submit(Callable task) { return super.submit(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes())); } @Override public ListenableFuture submitListenable(Callable task) { return super.submitListenable(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes())); } }
赎回:
public class ContextAwareCallable implements Callable { private Callable task; private RequestAttributes context; public ContextAwareCallable(Callable task, RequestAttributes context) { this.task = task; this.context = context; } @Override public T call() throws Exception { if (context != null) { RequestContextHolder.setRequestAttributes(context); } try { return task.call(); } finally { RequestContextHolder.resetRequestAttributes(); } } }
组态:
@Configuration public class ExecutorConfig extends AsyncConfigurerSupport { @Override @Bean public Executor getAsyncExecutor() { return new ContextAwarePoolExecutor(); } }
由于原始父请求处理线程可能已将响应提交到客户端并且所有请求对象都已销毁,因此无法在子异步线程中获取请求范围对象。 处理此类方案的一种方法是使用自定义范围,如SimpleThreadScope 。
SimpleThreadScope的一个问题是子线程不会inheritance父范围变量,因为它在内部使用简单的ThreadLocal。 要克服该实现,自定义范围与SimpleThreadScope完全相似,但在内部使用InheritableThreadLocal。 有关更多信息,请参阅Spring MVC:如何在生成的线程中使用请求范围的bean?
最简单的方法是使用这样的任务装饰器:
static class ContextCopyingDecorator implements TaskDecorator { @Nonnull @Override public Runnable decorate(@Nonnull Runnable runnable) { RequestAttributes context = RequestContextHolder.currentRequestAttributes(); Map contextMap = MDC.getCopyOfContextMap(); return () -> { try { RequestContextHolder.setRequestAttributes(context); MDC.setContextMap(contextMap); runnable.run(); } finally { MDC.clear(); RequestContextHolder.resetRequestAttributes(); } }; } }
要将此装饰器添加到任务执行程序,您只需将其添加到配置例程中:
@Override @Bean public Executor getAsyncExecutor() { ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor(); poolExecutor.setTaskDecorator(new ContextCopyingDecorator()); poolExecutor.initialize(); return poolExecutor; }
不需要额外的持有者或自定义线程池任务执行程序。