Spring 将请求范围的 bean 提升到子线程 (HttpServletRequest)
Spring promoting request scoped bean to child threads (HttpServletRequest)
我现在尝试了很多东西,但我似乎漏掉了一块拼图。故事是这样的:我有一个请求范围的 bean,它从 HttpServletRequest 读取一些 SessionContext。该属性在过滤器中设置。所以当代码在正确的线程上运行时,这绝对可以正常工作。
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES)
public class SessionContextProviderImpl implements SessionContextProvider<SessionContext> {
private final HttpServletRequest _request;
@Autowired
public SessionContextProviderImpl(HttpServletRequest request) {
_request = request;
}
@Override
public SessionContext get() {
return (SessionContext) _request.getAttribute(Constants.SESSION_CONTEXT_IDENTIFIER);
}
}
现在我开始使用 java 8s 新功能 CompletableFuture 并且我有其中三个功能在请求线程等待结果时并行计算内容。我想要做的是 promote/hand over/propagate bean 或请求以一种可用于从原始 http 线程生成的子线程的方式。特别是我想从异步提供的 CompletableFuture 中的 HttpServletRequest 获取 SessionContext。
我试过的是这个(替换了 get 的实现):
final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
request.getAttribute(Constants.SESSION_CONTEXT_IDENTIFIER);
但这显然与请求作用域 bean 的结果相同。那么 "getRequest" returns null 而不是抛出异常。
作为第三种方法,我尝试了这个 original post:
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
org.springframework.beans.factory.config.Scope simpleThreadScope = new SimpleThreadScope();
cbf.registerScope("simpleThreadScope", simpleThreadScope);
并且我将 SessionContextProviderImpl 的范围设置为 "simpleThreadScope"。不幸的是,这也不起作用,并引发了一个异常,即它在请求范围之外使用。
我使用的环境:Jersey 加上spring注入。
也许有人有想法?
问候
对于任何未来的冒险家:
我花了一些时间研究 Spring 代码,发现 RequestContextHolder that has a inheritableRequestAttributesHolder. If you look at the documentation of what that is (inheriting from: InheritableThreadLocal) 可以阅读以下内容:
当变量中维护的每线程属性(例如,用户 ID、事务 ID)必须自动传输到任何子线程时,可继承的线程局部变量优先于普通线程局部变量使用已创建。
所以 RequestContextHolder has a field for that and actually setRequestAttributes supports a flag to use inheritableRequestAttributesHolder. Furthermore if you look at RequestContextListener -> requestInitialized 你会发现它被调用时没有标志 (= false)。所以我最后做的是:
public class InheritableRequestContextListener extends RequestContextListener {
private static final String REQUEST_ATTRIBUTES_ATTRIBUTE =
InheritableRequestContextListener.class.getName() + ".REQUEST_ATTRIBUTES";
@Override
public void requestInitialized(ServletRequestEvent requestEvent) {
if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
throw new IllegalArgumentException(
"Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
}
HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
LocaleContextHolder.setLocale(request.getLocale());
RequestContextHolder.setRequestAttributes(attributes, true);
}
}
瞧,我可以在子线程中访问 SessionContextProvider。
在我的例子中,使用 OrderedRequestContextFilter 解决了这个问题。您还必须像这样将 threadContextInheritable 标志设置为 true :
@Bean
public RequestContextFilter requestContextFilter() {
OrderedRequestContextFilter filter = new OrderedRequestContextFilter();
filter.setThreadContextInheritable(true);
return filter;
}
我现在尝试了很多东西,但我似乎漏掉了一块拼图。故事是这样的:我有一个请求范围的 bean,它从 HttpServletRequest 读取一些 SessionContext。该属性在过滤器中设置。所以当代码在正确的线程上运行时,这绝对可以正常工作。
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES)
public class SessionContextProviderImpl implements SessionContextProvider<SessionContext> {
private final HttpServletRequest _request;
@Autowired
public SessionContextProviderImpl(HttpServletRequest request) {
_request = request;
}
@Override
public SessionContext get() {
return (SessionContext) _request.getAttribute(Constants.SESSION_CONTEXT_IDENTIFIER);
}
}
现在我开始使用 java 8s 新功能 CompletableFuture 并且我有其中三个功能在请求线程等待结果时并行计算内容。我想要做的是 promote/hand over/propagate bean 或请求以一种可用于从原始 http 线程生成的子线程的方式。特别是我想从异步提供的 CompletableFuture 中的 HttpServletRequest 获取 SessionContext。
我试过的是这个(替换了 get 的实现):
final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
request.getAttribute(Constants.SESSION_CONTEXT_IDENTIFIER);
但这显然与请求作用域 bean 的结果相同。那么 "getRequest" returns null 而不是抛出异常。
作为第三种方法,我尝试了这个 original post:
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
org.springframework.beans.factory.config.Scope simpleThreadScope = new SimpleThreadScope();
cbf.registerScope("simpleThreadScope", simpleThreadScope);
并且我将 SessionContextProviderImpl 的范围设置为 "simpleThreadScope"。不幸的是,这也不起作用,并引发了一个异常,即它在请求范围之外使用。
我使用的环境:Jersey 加上spring注入。
也许有人有想法?
问候
对于任何未来的冒险家:
我花了一些时间研究 Spring 代码,发现 RequestContextHolder that has a inheritableRequestAttributesHolder. If you look at the documentation of what that is (inheriting from: InheritableThreadLocal) 可以阅读以下内容:
当变量中维护的每线程属性(例如,用户 ID、事务 ID)必须自动传输到任何子线程时,可继承的线程局部变量优先于普通线程局部变量使用已创建。
所以 RequestContextHolder has a field for that and actually setRequestAttributes supports a flag to use inheritableRequestAttributesHolder. Furthermore if you look at RequestContextListener -> requestInitialized 你会发现它被调用时没有标志 (= false)。所以我最后做的是:
public class InheritableRequestContextListener extends RequestContextListener {
private static final String REQUEST_ATTRIBUTES_ATTRIBUTE =
InheritableRequestContextListener.class.getName() + ".REQUEST_ATTRIBUTES";
@Override
public void requestInitialized(ServletRequestEvent requestEvent) {
if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
throw new IllegalArgumentException(
"Request is not an HttpServletRequest: " + requestEvent.getServletRequest());
}
HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
LocaleContextHolder.setLocale(request.getLocale());
RequestContextHolder.setRequestAttributes(attributes, true);
}
}
瞧,我可以在子线程中访问 SessionContextProvider。
在我的例子中,使用 OrderedRequestContextFilter 解决了这个问题。您还必须像这样将 threadContextInheritable 标志设置为 true :
@Bean
public RequestContextFilter requestContextFilter() {
OrderedRequestContextFilter filter = new OrderedRequestContextFilter();
filter.setThreadContextInheritable(true);
return filter;
}