Using a request scoped bean outside of an actual web request
Asked Answered
W

5

35

I have a web application that has a Spring Integration logic running with it in a separated thread. The problem is that at some point my Spring Integration logic tries to use a request scoped bean and then i get the following errors:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.tenantContext': 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.


Caused by: 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.

I have the ContextLoaderListener set:

<listener>
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

My Scoped Bean is annotated like that(since I heard that proxing my bean would help):

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)  
public class TenantContext  implements Serializable {

Is what I'm doing possible? If yes, what am I missing here? If no, any other suggestions on how I can achieve that?

Whitsun answered 2/7, 2013 at 11:45 Comment(3)
Are you trying to run code after the request has been served, or have the request wait for some asynchronous processing?Forgiven
@Forgiven the question was answered and the answer accepted already. Back in 2013 I didn't know a lot of things and now I understand it was a rookie mistake, but thanks anyway.Whitsun
I should have read your comments there to get the answer. You don't want my answer for how you can actually do it then?Forgiven
J
12

You can only use request (and session) -scoped beans on the web container thread on which the request is running.

I presume that thread is waiting for an async reply from your SI flow?

If so, you can bind the request-scoped bean to the message, perhaps in a header, or somewhere in the payload.

Jewelljewelle answered 2/7, 2013 at 12:46 Comment(5)
Thanks for you answer. Actually I don't think that thread is waiting for the reply since I'm just trying to persist a object and i need info from that TenantContext bean to do so.Whitsun
Then you can't use a request scoped bean at all because, by definition, the request no longer exists.Jewelljewelle
Thank you very much Gary, I'm gonna find another way then.Whitsun
The documentation describes a RequestContextListener as [A] listener that exposes the request to the current thread. That seems contradictory to what you've described in your answer. Can you please clarify?Begin
In that context, the "current thread" means the servlet container thread that is processing the HTTP request, not some arbitrary thread that you hand off the request to.Jewelljewelle
V
15

For Spring 4 Frameworks add servletContext.addListener(new RequestContextListener());

public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { RootConfiguration.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { WebMvcConfiguration.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new HiddenHttpMethodFilter() };
    }

    **@Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        servletContext.addListener(new RequestContextListener());
    }**
}
Vigilantism answered 4/6, 2015 at 9:29 Comment(2)
For Spring 3 add This in your web.xml <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener>Vigilantism
The answer was quoted several times... I haven't tried it, but I see no reason why it should work for spawned thread as I totally agree with argumentation provided here.Geometrize
C
15

For spring-boot 2.4 and spring framework 5, both of the RequestContextFilter and RequestContextListener did not work for me.

After digging into the code, I found the DispatcherServlet will overwrite the inheritable of RequestContextHolder set by RequestContextFilter or any one else, see DispatcherServlet.processRequest and DispatcherServlet.initContextHolders.

So the solution is quite simple, without any other components:

@Configuration
class whateverNameYouLike {
   @Bean
   DispatcherServlet dispatcherServlet() {
       DispatcherServlet srvl = new DispatcherServlet();
       srvl.setThreadContextInheritable(true);
       return srvl;
   }
}

But notice that the solution alone only applies to new threads created by the current request thread, not regarding to any thread pool.

For the thread pool cases, you can depend on an extra wraper class:

public class InheritableRequestContextTaskWrapper {
    private Map parentMDC = MDC.getCopyOfContextMap();
    private RequestAttributes parentAttrs = RequestContextHolder.currentRequestAttributes();

    public <T, R> Function<T, R> lambda1(Function<T, R> runnable) {
        return t -> {
            Map orinMDC = MDC.getCopyOfContextMap();
            if (parentMDC == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(parentMDC);
            }

            RequestAttributes orinAttrs = null;
            try {
                orinAttrs = RequestContextHolder.currentRequestAttributes();
            } catch (IllegalStateException e) {
            }
            RequestContextHolder.setRequestAttributes(parentAttrs, true);
            try {
                return runnable.apply(t);
            } finally {
                if (orinMDC == null) {
                    MDC.clear();
                } else {
                    MDC.setContextMap(orinMDC);
                }
                if (orinAttrs == null) {
                    RequestContextHolder.resetRequestAttributes();
                } else {
                    RequestContextHolder.setRequestAttributes(orinAttrs, true);
                }
            }
        };
    }
}

And then use it like this:

InheritableRequestContextTaskWrapper wrapper = new InheritableRequestContextTaskWrapper();
List<String> res = pool.submit(() -> ids.parallelStream().map(
    wrapper.lambda1((String id) -> {
        try {
           // do something and return the result string
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Error occurred in async tasks", e);
        }
    })).collect(Collectors.toList())).get();

Clod answered 16/7, 2020 at 7:52 Comment(1)
this worked with me until the main thread is finished, then the background thread start to throw the IllegalState Exception again. Any idea why? I tried the easier solution which is starting a thread from the main rest api thread: Thread t = new Thread(runnable); t.start() "But notice that the solution alone only applies to new threads created by the current request thread, not regarding to any thread pool." by saing this, that should work, right? btw, what is MDC? how to import it? Thank you for your helpful feedbackPunish
J
12

You can only use request (and session) -scoped beans on the web container thread on which the request is running.

I presume that thread is waiting for an async reply from your SI flow?

If so, you can bind the request-scoped bean to the message, perhaps in a header, or somewhere in the payload.

Jewelljewelle answered 2/7, 2013 at 12:46 Comment(5)
Thanks for you answer. Actually I don't think that thread is waiting for the reply since I'm just trying to persist a object and i need info from that TenantContext bean to do so.Whitsun
Then you can't use a request scoped bean at all because, by definition, the request no longer exists.Jewelljewelle
Thank you very much Gary, I'm gonna find another way then.Whitsun
The documentation describes a RequestContextListener as [A] listener that exposes the request to the current thread. That seems contradictory to what you've described in your answer. Can you please clarify?Begin
In that context, the "current thread" means the servlet container thread that is processing the HTTP request, not some arbitrary thread that you hand off the request to.Jewelljewelle
W
9

Use RequestContextFilter with the property threadContextInheritable set to true. This makes the child thread to inherit the parent's context, which contains the request object itself. Also make sure that the executor doesn't reuse the threads in the pool, because the request object is very specific to that request and cannot be shared across various requests. One such executor is SimpleAsyncTaskExecutor.

For more info refer Scope 'session' is not active for the current thread; IllegalStateException: No thread-bound request found.

Wavemeter answered 15/10, 2014 at 9:49 Comment(0)
E
1

You could publish the request in the new Thread like this:

import org.springframework.web.context.request.RequestContextListener;
 ...
ServletRequestEvent requestEvent = new ServletRequestEvent(req.getServletContext(), req);
RequestContextListener requestContextListener = new RequestContextListener();
requestContextListener.requestInitialized(requestEvent);
 ...
requestContextListener.requestDestroyed(requestEvent);

If you look inside requestInitialized()-method you will find a ThreadLocal-variable holding the request. Now you can autowire your request successfully.

Elated answered 1/10, 2020 at 20:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.