Java 如何在异步任务执行器中启用请求范围
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23732089/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to enable request scope in async task executor
提问by hudi
In my app I have some async web services. Server accept request, return OK response and start processing request with AsyncTaskExecutor. My question is how to enable request scope here because in this processing I need to get class which is annotated by:
在我的应用程序中,我有一些异步网络服务。服务器接受请求,返回 OK 响应并使用 AsyncTaskExecutor 开始处理请求。我的问题是如何在此处启用请求范围,因为在此处理中我需要获取由以下内容注释的类:
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
Now I get exception:
现在我得到了例外:
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.
because it runs in SimpleAsyncTaskExecutor
and not in DispatcherServlet
因为它运行在SimpleAsyncTaskExecutor
而不是在DispatcherServlet
my async processing of request
我对请求的异步处理
taskExecutor.execute(new Runnable() {
@Override
public void run() {
asyncRequest(request);
}
});
where taskExecutor is:
其中 taskExecutor 是:
<bean id="taskExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
采纳答案by Armadillo
We ran into the same problem - needed to execute code in the background using @Async, so it was unable to use any Session- or RequestScope beans. We solved it the following way:
我们遇到了同样的问题 - 需要使用 @Async 在后台执行代码,所以它无法使用任何 Session- 或 RequestScope bean。我们通过以下方式解决了它:
- Create a custom TaskPoolExecutor that stores scoped information with the tasks
- Create a special Callable (or Runnable) that uses the information to set and clear the context for the background thread
- Create an override configuration to use the custom executor
- 创建一个自定义 TaskPoolExecutor 来存储任务的范围信息
- 创建一个特殊的 Callable(或 Runnable),它使用信息来设置和清除后台线程的上下文
- 创建覆盖配置以使用自定义执行程序
Note: this will only work for Session and Request scoped beans, and not for security context (as in Spring Security). You'd have to use another method to set the security context if that is what you're after.
注意:这仅适用于会话和请求范围的 bean,而不适用于安全上下文(如 Spring Security)。如果这是您所追求的,则必须使用另一种方法来设置安全上下文。
Note2: For brevity, only shown the Callable and submit() implementation. You can do the same for the Runnable and execute().
注2:为简洁起见,仅显示了 Callable 和 submit() 实现。您可以对 Runnable 和 execute() 执行相同的操作。
Here is the code:
这是代码:
Executor:
执行人:
public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
}
}
Callable:
可调用:
public class ContextAwareCallable<T> implements Callable<T> {
private Callable<T> task;
private RequestAttributes context;
public ContextAwareCallable(Callable<T> 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:
配置:
@Configuration
public class ExecutorConfig extends AsyncConfigurerSupport {
@Override
@Bean
public Executor getAsyncExecutor() {
return new ContextAwarePoolExecutor();
}
}
回答by Thilak
There is no way to get a request scoped object in an child async thread, since the original parent request processing thread may have already committed the response to the client and all the request objects are destroyed. One way to handle such scenarios is to use custom scope, like SimpleThreadScope.
无法在子异步线程中获取请求范围的对象,因为原始的父请求处理线程可能已经将响应提交给客户端,并且所有请求对象都被销毁。处理此类场景的一种方法是使用自定义范围,例如SimpleThreadScope。
one problem with SimpleThreadScope is that the child threads will not inherit parents scope variables, because it uses simple ThreadLocal internally. To overcome that implement a custom scope which is exactly similar to SimpleThreadScope but uses InheritableThreadLocal internally. For more info reg this Spring MVC: How to use a request-scoped bean inside a spawned thread?
SimpleThreadScope 的一个问题是子线程不会继承父作用域变量,因为它在内部使用简单的 ThreadLocal。为了克服这个问题,实现了一个与 SimpleThreadScope 完全相似但在内部使用 InheritableThreadLocal 的自定义范围。有关更多信息 reg 这个 Spring MVC: How to use a request-scoped bean inside a spawned thread?
回答by Michael Piefel
The easiest way is to use a task decorator like this:
最简单的方法是使用这样的任务装饰器:
static class ContextCopyingDecorator implements TaskDecorator {
@Nonnull
@Override
public Runnable decorate(@Nonnull Runnable runnable) {
RequestAttributes context =
RequestContextHolder.currentRequestAttributes();
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
RequestContextHolder.setRequestAttributes(context);
MDC.setContextMap(contextMap);
runnable.run();
} finally {
MDC.clear();
RequestContextHolder.resetRequestAttributes();
}
};
}
}
To add this decorator to the task executor, all you need is to add it in the configuration routine:
要将这个装饰器添加到任务执行器,您只需将它添加到配置例程中:
@Override
@Bean
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor();
poolExecutor.setTaskDecorator(new ContextCopyingDecorator());
poolExecutor.initialize();
return poolExecutor;
}
There is no need for an additional holder or a custom thread-pool task executor.
不需要额外的持有者或自定义线程池任务执行器。
回答by Arun A
The solutions mentioned before were not working for me. The reason why the solution not working is, as mentioned in @Thilak's post, as soon as the original parent thread committed response to the client, the request objects may be garbage collected. But with some tweak to the solution provided by @Armadillo I was able to get it working. I am using spring boot 2.2
之前提到的解决方案对我不起作用。解决方案不起作用的原因是,正如@Thilak 的帖子中所述,一旦原始父线程提交对客户端的响应,请求对象可能会被垃圾收集。但是通过对@Armadillo 提供的解决方案进行一些调整,我能够让它工作。我正在使用弹簧靴 2.2
Here is what I followed.
这是我遵循的。
- Create a custom TaskPoolExecutor that stores(after cloning) scoped information with the tasks.
- Create a special Callable (or Runnable) that uses the cloned information to set the current context values and clear the context for the async thread.
- 创建一个自定义 TaskPoolExecutor,用于存储(克隆后)任务范围的信息。
- 创建一个特殊的 Callable(或 Runnable),它使用克隆的信息来设置当前上下文值并清除异步线程的上下文。
Executor (Same as in @Armadillo's post):
执行者(与@Armadillo 的帖子相同):
public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
}
}
Callable:
可调用:
public class ContextAwareCallable<T> implements Callable<T> {
private Callable<T> task;
private final RequestAttributes requestAttributes;
public ContextAwareCallable(Callable<T> task, RequestAttributes requestAttributes) {
this.task = task;
this.requestAttributes = cloneRequestAttributes(requestAttributes);
}
@Override
public T call() throws Exception {
try {
RequestContextHolder.setRequestAttributes(requestAttributes);
return task.call();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
private RequestAttributes cloneRequestAttributes(RequestAttributes requestAttributes){
RequestAttributes clonedRequestAttribute = null;
try{
clonedRequestAttribute = new ServletRequestAttributes(((ServletRequestAttributes) requestAttributes).getRequest(), ((ServletRequestAttributes) requestAttributes).getResponse());
if(requestAttributes.getAttributeNames(RequestAttributes.SCOPE_REQUEST).length>0){
for(String name: requestAttributes.getAttributeNames(RequestAttributes.SCOPE_REQUEST)){
clonedRequestAttribute.setAttribute(name,requestAttributes.getAttribute(name,RequestAttributes.SCOPE_REQUEST),RequestAttributes.SCOPE_REQUEST);
}
}
if(requestAttributes.getAttributeNames(RequestAttributes.SCOPE_SESSION).length>0){
for(String name: requestAttributes.getAttributeNames(RequestAttributes.SCOPE_SESSION)){
clonedRequestAttribute.setAttribute(name,requestAttributes.getAttribute(name,RequestAttributes.SCOPE_SESSION),RequestAttributes.SCOPE_SESSION);
}
}
if(requestAttributes.getAttributeNames(RequestAttributes.SCOPE_GLOBAL_SESSION).length>0){
for(String name: requestAttributes.getAttributeNames(RequestAttributes.SCOPE_GLOBAL_SESSION)){
clonedRequestAttribute.setAttribute(name,requestAttributes.getAttribute(name,RequestAttributes.SCOPE_GLOBAL_SESSION),RequestAttributes.SCOPE_GLOBAL_SESSION);
}
}
return clonedRequestAttribute;
}catch(Exception e){
return requestAttributes;
}
}
}
The change I made is to introduce cloneRequestAttributes() to copy and set the RequestAttribute, so that the values remain available even after the original parent thread commits response to the client.
我所做的更改是引入 cloneRequestAttributes() 来复制和设置 RequestAttribute,以便即使在原始父线程向客户端提交响应之后,这些值仍然可用。
Configuration: Since there are other async configuration and I didn't want the behavior to be applicable in other async executors I've created its own task executor configuration.
配置:由于还有其他异步配置,并且我不希望该行为适用于其他异步执行程序,因此我创建了自己的任务执行程序配置。
@Configuration
@EnableAsync
public class TaskExecutorConfig {
@Bean(name = "contextAwareTaskExecutor")
public TaskExecutor getContextAwareTaskExecutor() {
ContextAwarePoolExecutor taskExecutor = new ConAwarePoolExecutor();
taskExecutor.setMaxPoolSize(20);
taskExecutor.setCorePoolSize(5);
taskExecutor.setQueueCapacity(100);
taskExecutor.setThreadNamePrefix("ContextAwareExecutor-");
return taskExecutor;
}
}
And finally on the async method, I use the executor name.
最后在异步方法上,我使用了执行程序名称。
@Async("contextAwareTaskExecutor")
public void asyncMethod() {
}
Alternate Solution:
替代解决方案:
We ended up in this trouble by trying to reuse an existing component class. Though the solution made it look like it is convenient. Its much less hassle (cloning objects and reserving thread pool) if we could have referred the relevant request scoped values as method parameters. In our case, we are planning to refactor the code in such a way that the component class which is using the request scoped bean, and being reused from the async method, to accept the values as method parameters. Request scoped bean is removed from the reusable component and moved to the component class which invokes its method. To put what I just described it in code:
通过尝试重用现有的组件类,我们最终遇到了这个麻烦。尽管解决方案使它看起来很方便。如果我们可以将相关的请求范围值作为方法参数引用,那么它的麻烦(克隆对象和保留线程池)就会少得多。在我们的例子中,我们计划以这样一种方式重构代码,即使用请求作用域 bean 并从异步方法重用的组件类接受值作为方法参数。请求范围的 bean 从可重用组件中删除,并移动到调用其方法的组件类中。把我刚刚描述的内容放在代码中:
Our current state is :
我们目前的状态是:
@Async("contextAwareTaskExecutor")
public void asyncMethod() {
reUsableCompoment.executeLogic() //This component uses the request scoped bean.
}
Refactored code:
重构代码:
@Async("taskExecutor")
public void asyncMethod(Object requestObject) {
reUsableCompoment.executeLogic(requestObject); //Request scoped bean is removed from the component and moved to the component class which invokes it menthod.
}
回答by Chamin Wickramarathna
@Armadillo's answer motivated me to write the implementation for Runnable.
@Armadillo 的回答激励我为 Runnable 编写实现。
Custom implementation for TaskExecutor:
TaskExecutor 的自定义实现:
/**
* This custom ThreadPoolExecutor stores scoped/context information with the tasks.
*/
public class ContextAwareThreadPoolExecutor extends ThreadPoolTaskExecutor {
@Override
public Future<?> submit(Runnable task) {
return super.submit(new ContextAwareRunnable(task, RequestContextHolder.currentRequestAttributes()));
}
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
return super.submitListenable(new ContextAwareRunnable(task, RequestContextHolder.currentRequestAttributes()));
}
}
Custom implementation for Runnable:
Runnable 的自定义实现:
/**
* This custom Runnable class can use to make background threads context aware.
* It store and clear the context for the background threads.
*/
public class ContextAwareRunnable implements Runnable {
private Runnable task;
private RequestAttributes context;
public ContextAwareRunnable(Runnable task, RequestAttributes context) {
this.task = task;
// Keeps a reference to scoped/context information of parent thread.
// So original parent thread should wait for the background threads.
// Otherwise you should clone context as @Arun A's answer
this.context = context;
}
@Override
public void run() {
if (context != null) {
RequestContextHolder.setRequestAttributes(context);
}
try {
task.run();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
}
回答by Gayathri Krishnan
None of the above Solution works for me because in my case the parent thread responded for request back to the client and the request scoped object can't be referred in any worker threads.
以上解决方案都不适合我,因为在我的情况下,父线程响应请求返回给客户端,并且请求范围的对象不能在任何工作线程中引用。
I just made a work around to make above things work. I am using Spring Boot 2.2 and using customTaskExecutor with ContextAwareCallable just specified above.
我只是做了一个工作来使上述事情起作用。我正在使用 Spring Boot 2.2 并使用 customTaskExecutor 和上面刚刚指定的 ContextAwareCallable。
Async Configuration:
异步配置:
@Bean(name = "cachedThreadPoolExecutor")
public Executor cachedThreadPoolExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ContextAwarePoolExecutor();
threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true);
threadPoolTaskExecutor.setThreadNamePrefix("ThreadName-");
threadPoolTaskExecutor.initialize();
return threadPoolTaskExecutor;
}
ContextAwarePoolExecutor:
ContextAwarePoolExecutor:
public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
@Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(new ContextAwareCallable(task, RequestContextHolder.currentRequestAttributes()));
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(new ContextAwareCallable(task,
RequestContextHolder.currentRequestAttributes()));
}
}
}
Created Custom Context Aware Callable:
创建自定义上下文感知可调用:
public class ContextAwareCallable<T> implements Callable<T> {
private Callable<T> task;
private CustomRequestScopeAttributes customRequestScopeAttributes;
private static final String requestScopedBean =
"scopedTarget.requestScopeBeanName";
public ContextAwareCallable(Callable<T> task, RequestAttributes context) {
this.task = task;
if (context != null) {
//This is Custom class implements RequestAttributes class
this.customRequestScopeAttributes = new CustomRequestScopeAttributes();
//Add the request scoped bean to Custom class
customRequestScopeAttributes.setAttribute
(requestScopedBean,context.getAttribute(requestScopedBean,0),0);
//Set that in RequestContextHolder and set as Inheritable as true
//Inheritable is used for setting the attributes in diffrent ThreadLocal objects.
RequestContextHolder.setRequestAttributes
(customRequestScopeAttributes,true);
}
}
@Override
public T call() throws Exception {
try {
return task.call();
} finally {
customRequestScopeAttributes.removeAttribute(requestScopedBean,0);
}
}
}
Custom class:
自定义类:
public class CustomRequestScopeAttributes implements RequestAttributes {
private Map<String, Object> requestAttributeMap = new HashMap<>();
@Override
public Object getAttribute(String name, int scope) {
if(scope== RequestAttributes.SCOPE_REQUEST) {
return this.requestAttributeMap.get(name);
}
return null;
}
@Override
public void setAttribute(String name, Object value, int scope) {
if(scope== RequestAttributes.SCOPE_REQUEST){
this.requestAttributeMap.put(name, value);
}
}
@Override
public void removeAttribute(String name, int scope) {
if(scope== RequestAttributes.SCOPE_REQUEST) {
this.requestAttributeMap.remove(name);
}
}
@Override
public String[] getAttributeNames(int scope) {
if(scope== RequestAttributes.SCOPE_REQUEST) {
return this.requestAttributeMap.keySet().toArray(new String[0]);
}
return new String[0];
}
//Override all methods in the RequestAttributes Interface.
}
Finally add the Async annotation in the method needed.
最后在需要的方法中添加Async注解。
@Async("cachedThreadPoolExecutor")
public void asyncMethod() {
anyService.execute() //This Service execution uses request scoped bean
}
回答by user3487063
With Spring-boot-2.0.3.REALEASE / spring-web-5.0.7, I've come up with below code working for @Async
使用 Spring-boot-2.0.3.REALEASE / spring-web-5.0.7,我想出了以下适用于@Async 的代码
Class that holds the ThreadLocalcontext.
持有ThreadLocal上下文的类。
import java.util.Map;
public class ThreadContextHolder {
private ThreadContextHolder() {}
private static final ThreadLocal<Map<String, Object>> ctx = new ThreadLocal<>();
public static Map<String, Object> getContext() {
return ctx.get();
}
public static void setContext(Map<String, Object> attrs) {
ctx.set(attrs);
}
public static void removeContext() {
ctx.remove();
}
}
Async config :
异步配置:
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
...
...
executor.setTaskDecorator(
runnable -> {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); // or currentRequestAttributes() if you want to fall back to JSF context.
Map<String, Object> map =
Arrays.stream(requestAttributes.getAttributeNames(0))
.collect(Collectors.toMap(r -> r, r -> requestAttributes.getAttribute(r, 0)));
return () -> {
try {
ThreadContextHolder.setContext(map);
runnable.run();
} finally {
ThreadContextHolder.removeContext();
}
};
});
executor.initialize();
return executor;
}
And from the async method :
从异步方法:
@Async
public void asyncMethod() {
logger.info("{}", ThreadContextHolder.getContext().get("key"));
}
回答by cbaez
I solved this issue adding the following bean configuration
我解决了这个问题,添加了以下 bean 配置
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="request">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
Update:the above solution does not clean up any objects associated with the threads as mention in spring's documentation. This alternative works for me: https://www.springbyexample.org/examples/custom-thread-scope-module.html
更新:上述解决方案不会像 spring 文档中提到的那样清理与线程关联的任何对象。这个替代方案对我有用:https: //www.springbyexample.org/examples/custom-thread-scope-module.html
回答by Степин Сергей
@Armadillo
@犰狳
Worked for me, many thanks.
As for Spring Security Context, there is more out-of-box solution and it worked for me either (found here How to set up Spring Security SecurityContextHolder strategy?)
为我工作,非常感谢。
至于 Spring Security Context,有更多开箱即用的解决方案,它也对我有用(在此处找到如何设置 Spring Security SecurityContextHolder 策略?)
In order to use SecurityContextHolder in child threads:
为了在子线程中使用 SecurityContextHolder:
@Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean() {
MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
methodInvokingFactoryBean.setTargetClass(SecurityContextHolder.class);
methodInvokingFactoryBean.setTargetMethod("setStrategyName");
methodInvokingFactoryBean.setArguments(new String[]{SecurityContextHolder.MODE_INHERITABLETHREADLOCAL});
return methodInvokingFactoryBean;
}