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