How to enable request scope in async task executor

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" />

Solution 1:

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();
    }
}

Solution 2:

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.

Solution 3:

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.

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?

Solution 4:

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

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.

Executor (Same as in @Armadillo's post):

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.

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:

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.
    }

Solution 5:

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.

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:

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("cachedThreadPoolExecutor")    
  public void asyncMethod() {     
     anyService.execute() //This Service execution uses request scoped bean
  }