在实际Web请求之外使用请求范围的bean

Die*_*nia 23 spring spring-integration spring-aop

我有一个Web应用程序,它在一个独立的线程中运行Spring Integration逻辑.问题是,在某些时候,我的Spring Integration逻辑尝试使用请求范围的bean,然后我得到以下错误:

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.
Run Code Online (Sandbox Code Playgroud)

我设置了ContextLoaderListener:

<listener>
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
Run Code Online (Sandbox Code Playgroud)

我的Scoped Bean是这样注释的(因为我听说代理我的bean会有帮助):

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

我正在做什么?如果是的话,我在这里错过了什么?如果不是,关于如何实现这一点的任何其他建议?

Dee*_*pak 13

对于Spring 4 Frameworks,添加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());
    }**
}
Run Code Online (Sandbox Code Playgroud)

  • 答案被引用了好几次......我没有尝试过,但我认为没有理由为什么它应该适用于衍生线程,因为我完全同意[这里]提供的论证(http://stackoverflow.com/a/21400914/267197). (2认同)

Gar*_*ell 11

您只能在运行请求的Web容器线程上使用请求(和会话)-scoped bean.

我假设线程正在等待SI流的异步回复?

如果是这样,您可以将请求范围的bean绑定到消息,可能在标头中或有效负载中的某个位置.

  • 在该上下文中,"当前线程"表示正在处理HTTP请求的servlet容器线程,而不是您将请求移交给的任意线程. (2认同)

Thi*_*lak 10

使用RequestContextFilter并将属性threadContextInheritable设置为true.这使子线程继承父级的上下文,其中包含请求对象本身.还要确保执行程序不重用池中的线程,因为请求对象非常特定于该请求,并且不能跨各种请求共享.一个这样的执行器是SimpleAsyncTaskExecutor.

有关更多信息,请参阅范围'会话'对当前线程无效; IllegalStateException:未找到线程绑定请求.


uqb*_*uqb 8

对于春季启动2.4和Spring框架5,两者的RequestContextFilterRequestContextListener没有为我工作。

挖掘到代码后,我发现DispatcherServlet将改写inheritableRequestContextHolder通过设置RequestContextFilter或任何其他人,看到DispatcherServlet.processRequestDispatcherServlet.initContextHolders

所以解决方案非常简单,没有任何其他组件:

@Configuration
class whateverNameYouLike {
   @Bean
   DispatcherServlet dispatcherServlet() {
       DispatcherServlet srvl = new DispatcherServlet();
       srvl.setThreadContextInheritable(true);
       return srvl;
   }
}
Run Code Online (Sandbox Code Playgroud)

但请注意,该解决方案仅适用于当前请求线程创建的新线程,而不适用于任何线程池。

对于线程池情况,您可以依赖额外的包装器类吗?

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);
                }
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它?

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();

Run Code Online (Sandbox Code Playgroud)