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?
A request-scoped bean is an object managed by Spring, for which the framework creates a new instance for every HTTP request. The app can use the instance only for the request that created it. Any new HTTP request (from the same or other clients) creates and uses a different instance of the same class (figure 2).
As singleton beans are injected only once per their lifetime you need to provide scoped beans as proxies which takes care of that. @RequestScope is a meta-annotation on @Scope that 1) sets the scope to "request" and 2) sets the proxyMode to ScopedProxyMode.
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.
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());
}**
}
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.
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();
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With