Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enable request scope in async task executor

Tags:

java

spring

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:

@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

my async processing of request

taskExecutor.execute(new Runnable() {      @Override     public void run() {         asyncRequest(request);     } }); 

where taskExecutor is:

<bean id="taskExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor" /> 
like image 284
hudi Avatar asked May 19 '14 07:05

hudi


People also ask

How do you enable the use of @async annotation?

To enable the asynchronous processing, add the @EnableAsync annotation to the configuration class. The @EnableAsync annotation switches on Spring's ability to run @Async methods in a background thread pool.

What is a request scope?

If a component is marked with request scope, simultaneous requests each see a different instance of the component. This is true even when the same session sends two requests simultaneously; each request gets a pointer to a separate object.

What does @async annotation do?

Simply put, annotating a method of a bean with @Async will make it execute in a separate thread. In other words, the caller will not wait for the completion of the called method. One interesting aspect in Spring is that the event support in the framework also has support for async processing if necessary.

What is ThreadPoolTaskExecutor in spring?

ThreadPoolTaskExecutor is a java bean that allows for configuring a ThreadPoolExecutor in a bean style by setting up the values for the instance variables like corePoolSize, maxPoolSize, keepAliveSeconds, queueCapacity and exposing it as a Spring TaskExecutor.


2 Answers

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:

  • 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

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.

Note2: For brevity, only shown the Callable and submit() implementation. You can do the same for the Runnable and 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();     } } 
like image 81
Armadillo Avatar answered Oct 05 '22 13:10

Armadillo


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.


A small update for 2021: Using current versions of Spring Boot, the mere existence of a bean of type TaskDecorator will suffice. Upon creating the context, the task decorator will be used to decorate the executors that Spring Boot creates.

like image 26
Michael Piefel Avatar answered Oct 05 '22 13:10

Michael Piefel