在实际 Web 请求之外使用请求范围的 bean
Posted
技术标签:
【中文标题】在实际 Web 请求之外使用请求范围的 bean【英文标题】:Using a request scoped bean outside of an actual web request 【发布时间】:2013-06-29 18:29:37 【问题描述】:我有一个 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
我正在做的事情可能吗? 如果是,我在这里错过了什么? 如果没有,关于我如何实现这一目标的任何其他建议?
【问题讨论】:
您是在请求服务后尝试运行代码,还是让请求等待一些异步处理? @OrangeDog 问题已得到回答,并且已被接受。早在 2013 年,我对很多事情一无所知,现在我明白这是一个菜鸟的错误,但还是谢谢。 我应该在那里阅读您的 cmets 以获得答案。你不希望我回答你如何实际做到这一点? 【参考方案1】:您可以像这样在新线程中发布请求:
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()
-method 内部,您会发现一个保存请求的ThreadLocal-variable。现在您可以成功地自动装配您的请求了。
【讨论】:
【参考方案2】:对于 spring-boot 2.4 和 spring framework 5,RequestContextFilter
和 RequestContextListener
都不适用于我。
深入代码后发现DispatcherServlet
会覆盖RequestContextHolder
中RequestContextFilter
或其他任何一个设置的inheritable
,见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);
;
然后这样使用:
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();
【讨论】:
【参考方案3】:使用 RequestContextFilter 并将属性 threadContextInheritable 设置为 true。这使得子线程继承父的上下文,其中包含请求对象本身。还要确保执行程序不会重用池中的线程,因为请求对象非常特定于该请求并且不能在各种请求之间共享。 SimpleAsyncTaskExecutor 就是一个这样的执行器。
更多信息请参考Scope 'session' is not active for the current thread; IllegalStateException: No thread-bound request found。
【讨论】:
【参考方案4】:对于 Spring 4 框架添加 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());
**
【讨论】:
对于 Spring 3 在你的 web.xml 添加这个 org.springframework.web.context.request.RequestContextListener 答案被引用了好几次......我没有尝试过,但我认为它没有理由适用于衍生线程,因为我完全同意here提供的论点。【参考方案5】:您只能在运行请求的 Web 容器线程上使用请求(和会话)范围的 bean。
我认为线程正在等待来自您的 SI 流的异步回复?
如果是这样,您可以将请求范围的 bean 绑定到消息,可能在标头中,或者在负载中的某个位置。
【讨论】:
感谢您的回答。实际上我不认为该线程正在等待回复,因为我只是想保留一个对象并且我需要来自该 TenantContext bean 的信息才能这样做。 那么你根本不能使用请求范围的bean,因为根据定义,请求不再存在。 非常感谢 Gary,那我会另找办法的。 文档将RequestContextListener
描述为[A] listener that exposes the request to the current thread
。这似乎与您在回答中所描述的相矛盾。你能澄清一下吗?
在这种情况下,“当前线程”是指正在处理 HTTP 请求的 servlet 容器线程,而不是您将请求交给的任意线程。以上是关于在实际 Web 请求之外使用请求范围的 bean的主要内容,如果未能解决你的问题,请参考以下文章
如何在 JSF 中正确使用组件绑定? (会话范围 bean 中的请求范围组件)
JSF 请求范围的 bean 不断在每个请求上重新创建新的有状态会话 bean?