我有一个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.
我设置了ContextLoaderListener:
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
我的Scoped Bean是这样注释的(因为我听说代理我的bean会有所帮助):
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class TenantContext implements Serializable {
我正在做什么? 如果是的话,我在这里错过了什么? 如果不是,关于我如何实现这一点的任何其他建议?
答案 0 :(得分:10)
使用RequestContextFilter并将属性threadContextInheritable设置为true。这使得子线程继承父上下文,该上下文包含请求对象本身。还要确保执行程序不重用池中的线程,因为请求对象非常特定于该请求,并且不能跨各种请求共享。一个这样的执行器是SimpleAsyncTaskExecutor。
答案 1 :(得分:10)
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());
}**
}
答案 2 :(得分:7)
您只能在运行请求的Web容器线程上使用请求(和会话)-scoped bean。
我假设线程正在等待SI流的异步回复?
如果是这样,您可以将请求范围的bean绑定到消息,可能在标头中或有效负载中的某个位置。
答案 3 :(得分:5)
对于spring-boot 2.4和spring框架5,RequestContextFilter
和RequestContextListener
都不适合我。
深入研究代码后,我发现DispatcherServlet
将覆盖inheritable
或其他任何人设置的RequestContextHolder
的{{1}},请参见RequestContextFilter
和DispatcherServlet.processRequest
。
因此解决方案非常简单,没有任何其他组件:
DispatcherServlet.initContextHolders
但是请注意,仅解决方案仅适用于当前请求线程创建的新线程,不适用于任何线程池。
对于线程池,您可以依赖一个额外的包装器类:
@Configuration
class whateverNameYouLike {
@Bean
DispatcherServlet dispatcherServlet() {
DispatcherServlet srvl = new DispatcherServlet();
srvl.setThreadContextInheritable(true);
return srvl;
}
}
然后像这样使用它:
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);
}
}
};
}
}
答案 4 :(得分:0)
您可以像这样在新线程中发布请求:
import org.springframework.web.context.request.RequestContextListener;
...
ServletRequestEvent requestEvent = new ServletRequestEvent(req.getServletContext(), req);
RequestContextListener requestContextListener = new RequestContextListener();
requestContextListener.requestInitialized(requestEvent);
...
requestContextListener.requestDestroyed(requestEvent);
如果您查看requestInitialized()
方法内部,您会发现一个保存请求的ThreadLocal变量。现在,您可以成功自动连接请求了。