Using a request scoped bean outside of an actual web request

前端 未结 5 802
我在风中等你
我在风中等你 2020-12-09 15:39

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

相关标签:
5条回答
  • 2020-12-09 15:55

    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.

    0 讨论(0)
  • 2020-12-09 16:03

    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.

    0 讨论(0)
  • 2020-12-09 16: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.

    0 讨论(0)
  • 2020-12-09 16: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();
    
    
    0 讨论(0)
  • 2020-12-09 16:21

    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());
        }**
    }
    
    0 讨论(0)
提交回复
热议问题