16

I have a web application running in tomcat where I'm using a ThreadPool (Java 5 ExecutorService) to run IO intensive operations in parallel to improve performance. I would like to have some of the beans used within each pooled thread be in the request scope, but the Threads in the ThreadPool do not have access to the spring context and get a proxy failure. Any ideas on how to make the spring context available to the threads in the ThreadPool to resolve the proxy failures?

I'm guessing there must be a way to register/unregister each thread in the ThreadPool with spring for each task, but haven't had any luck finding how to do this.

Thanks!

Perulish8
  • 223
  • 1
  • 2
  • 7

4 Answers4

48

I am using the following super class for my tasks that need to have access to request scope. Basically you can just extend it and implement your logic in onRun() method.

import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

/**
 * @author Eugene Kuleshov
 */
public abstract class RequestAwareRunnable implements Runnable {
  private final RequestAttributes requestAttributes;
  private Thread thread;

  public RequestAwareRunnable() {
    this.requestAttributes = RequestContextHolder.getRequestAttributes();
    this.thread = Thread.currentThread();
  }

  public void run() {
    try {
      RequestContextHolder.setRequestAttributes(requestAttributes);
      onRun();
    } finally {
      if (Thread.currentThread() != thread) {
        RequestContextHolder.resetRequestAttributes();
      }
      thread = null;
    }
  }

  protected abstract void onRun();
}
Eugene Kuleshov
  • 31,461
  • 5
  • 66
  • 67
12

I also wish I had 1000 votes to give to the currently accepted answer. I had been stumped on how to do this for some time. Based on it, here is my solution using the Callable interface in case you want to use some of the new @Async stuff in Spring 3.0.

public abstract class RequestContextAwareCallable<V> implements Callable<V> {

    private final RequestAttributes requestAttributes;
    private Thread thread;

    public RequestContextAwareCallable() {
        this.requestAttributes = RequestContextHolder.getRequestAttributes();
        this.thread = Thread.currentThread();
    }

    public V call() throws Exception {
        try {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            return onCall();
        } finally {
            if (Thread.currentThread() != thread) {
                RequestContextHolder.resetRequestAttributes();
            }
            thread = null;
        }
    }

    public abstract V onCall() throws Exception;
}
csamuel
  • 820
  • 2
  • 8
  • 11
  • gr8 !! I was looking for this :) – hop Sep 04 '12 at 03:36
  • ok, I have method with `@Async` annotation. it is returnig `Future` result. I bealive that inside of proxy-generated class there are `ThreadExecutor` and `Callabbe`, but how to force it use such kind of Callable? As a temporary solution I just pass app context reference into method, It works for me but it not looking good. – msangel Jul 03 '13 at 23:58
  • I think this code may have a problem. The RequestContextHolder.getRequestAttributes() will return the same instance used in the current request, not a copy. Therefore, you may be accessing a ServletRequestAttributes object outside of the span of the servlet request... – Francisco Lozano Dec 20 '13 at 11:38
  • It's worth mentioning that the `RequestAttributes` shared across multiple threads are usually not thread-safe: this means that if any of the consumer threads need a request-scoped bean never instantiated before in the current customer request, it might happen to have this bean instantiated twice or multiple times, causing unexpected behaviours. If the threads are instead consuming request-scoped beans already "warmed-up" by the main request thread, this problem doesn't happen. I'm currently looking for a solution to make `RequestAttributes` atomic in the "find or create bean" operation. – GianluKa Feb 25 '20 at 11:56
0

Could you try it the other way round? Use a data container that's stored in request scope and give it to the thread pool (perhaps put it into a queue, so that the thread pool can take one data container at a time, work on it, mark it as "done" and continue with the next one).

tangens
  • 39,095
  • 19
  • 120
  • 139
0

Spring has a ThreadPoolTaskExecutor class that you can use to manage your thread pool from Spring. However, it looks like you'd have to do some work to make the Spring context available to each thread.

I'm not sure if it will work even if you do wire it up this way though. Spring uses a token in thread local to locate objects in request (or session) scope, so if you're trying to access a request scope bean from a different thread, it's likely that token won't be there.

Jason Gritman
  • 5,251
  • 4
  • 30
  • 38
  • 2
    this is just a way to create executor. the real problem here is that `but the Threads in the ThreadPool do not have access to the spring context and get a proxy failure.`. – msangel Jul 03 '13 at 23:53