-->

Java/Spring MVC: provide request context to child

2020-08-26 10:43发布

问题:

I have the Problem, that I want to outsource some processes of my Spring WebMVC application into separate Threads. That was easy enough and works, until I want to use a class, userRightService, which uses the global request. That's not available in the threads, and we get a problem, that's pretty much understandable.

This is my Error:

java.lang.RuntimeException:
org.springframework.beans.factory.BeanCreationException: Error creating bean
with name 'scopedTarget.userRightsService': 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: Cannot ask for request attribute - 
request is not active anymore!

Okay, clear enough. I am trying to keep the request context by implementing this solution:

How to enable request scope in async task executor

This is my runnable class:

@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class myThread implements Runnable {

  private RequestAttributes context;

  public DataExportThread(RequestAttributes context) {
    this.context = context;
  }

  public void run() {
    RequestContextHolder.setRequestAttributes(context);

And this where it gets spawned:

final DataExportThread dataExportThread = 
   new myThread(RequestContextHolder.currentRequestAttributes());

final Thread thread = new Thread(myThread);
thread.setUncaughtExceptionHandler((t, e) -> {...});
thread.start();

As far as I understood, we store the currentRequestAttributes in the thread and then, when running, we restore them currentRequestAttributes... sounded solid to me, but the error is still there. I think I made some mistake adapting the solution for my case. maybe someone can help me finding the error.

Before I went through a lot of stackoverflow-threads with different solutions (see below), so I could try something else next, but this one seemed the clearest and simplest to me, so I hope someone could help me finding the mistake in the implementation or explain why it's the wrong approach.

  • Scope 'session' is not active for the current thread; IllegalStateException: No thread-bound request found
  • Accessing request scoped beans in a multi-threaded web application
  • Using a request scoped bean outside of an actual web request
  • InheritableThreadLocal value not inherited by ExecutorService threads
  • How to enable request scope in async task executor
  • Setting ThreadContext for all threads in application
  • Spring mvc request scope context across threads

I already tried this one without success:

  • How to enable request scope in async task executor

If it's matters:

<org.springframework-version>4.3.4.RELEASE</org.springframework-version>

BTW: I know that it would be better to restructure the application in a way, that the request is not needed in the thread but that's very complicated in that case and I really hope I could avoid this.

--

Edit1:

The Bean which can not be created in the thread starts like this:

@Service("userRightsService")
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserRightsService {

--

Edit2:

I also tried this one:

  • Setting ThreadContext for all threads in application

But context is always empty...

回答1:

I couldn't reproduce the problem as I am not sure how are you creating/injecting the UserRightsService but I have a couple of suggestions that you may try.

I guess that the problem is that the RequestAttributes is invalidated as the request is over (that's why the exception says Cannot ask for request attribute - request is not active anymore), which happens as your task is running.

Instead, you could try injecting the UserRightsService where your thread is spawned and pass this instance as an argument to the thread. That way the UserRightsService should be created without problem as the request should be still available.

Even so, trying to access the RequestAttributes after the request is over will probably fail. In that case I propose to make a copy of all the values that you need before the request is over, i.e. before your run the thread.

If that doesn't work for you please provide some more info regarding how you initialize the UserRightsService inside the task.

Good luck!

P.S.: I think that the scope annotation in your thread class is useless as the task object is created manually and not managed by spring.



回答2:

For those, who are searching. With the help of Master_Ex's hints I found a solution:

In the runnable:

private HttpServletRequest request;

public void run() {

    final RequestContextListener rcl = new RequestContextListener();
    final ServletContext sc = request.getServletContext();
    rcl.requestInitialized(new ServletRequestEvent(sc, request));

And in the UserRightService I make a call to a function which does the following:

    SecurityContext context = SecurityContextHolder.getContext();
    Authentication auth = context.getAuthentication();

    context.setAuthentication(getDataExportAuthentication(exportingUser));

@Master_Ex's Thank You, your post was very helpful. So sorry that I am too late to give you the bounty, otherwise I would have marked it as the correct one.