源代码版本 : spring-webmvc-5.1.4.RELEASE
Spring Web
提供了一个工具类 RequestContextHolder
用来在当前线程中暴露当前请求及其属性RequestAttributes
。这样的话在整个请求处理过程中,在当前线程中通过此工具类就可以获取对象RequestAttributes
,从而就可以访问当前请求及其属性。RequestAttributes
是一个接口,用于抽象一个请求的所有属性对象。
RequestContextHolder
RequestContextHolder
中的信息RequestAttributes
可以使用RequestContextListener
或者org.springframework.web.filter.RequestContextFilter
来设置。
package org.springframework.web.context.request;
import javax.faces.context.FacesContext;
import org.springframework.core.NamedInheritableThreadLocal;
import org.springframework.core.NamedThreadLocal;
import org.springframework.util.ClassUtils;
public abstract class RequestContextHolder {
private static final boolean jsfPresent =
ClassUtils.isPresent("javax.faces.context.FacesContext",
RequestContextHolder.class.getClassLoader());
// ThreadLocal 对象, 用于在当前线程中保存 RequestAttributes,inheritable=false时使用
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
// ThreadLocal 对象, 用于在当前线程中保存 RequestAttributes,inheritable=true时使用
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");
/**
* Reset the RequestAttributes for the current thread.
* 清除当前线程中的 RequestAttributes 对象。
*/
public static void resetRequestAttributes() {
requestAttributesHolder.remove();
inheritableRequestAttributesHolder.remove();
}
/**
*将给定的 RequestAttributes 对象绑定到当前线程,并且此属性不能传播到当前线程的子线程。
* @param attributes the RequestAttributes to expose
* @see #setRequestAttributes(RequestAttributes, boolean)
*/
public static void setRequestAttributes(RequestAttributes attributes) {
setRequestAttributes(attributes, false);
}
/**
* 将给定的 RequestAttributes 对象绑定到当前线程,并且inheritable=false时此属性不能传播到当前线程
* 的子线程 ,inheritable=true时此属性不能传播到当前线程的子线程。
* @param attributes 要绑定的对象 RequestAttributes, null 的时候从当前线程中删除相应的绑定。
* @param inheritable 指定是否可以将RequestAttributes传播到子线程
*/
public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) {
if (attributes == null) {
resetRequestAttributes();
}
else {
if (inheritable) {
inheritableRequestAttributesHolder.set(attributes);
requestAttributesHolder.remove();
}
else {
requestAttributesHolder.set(attributes);
inheritableRequestAttributesHolder.remove();
}
}
}
/**
* 返回绑定到当前线程的RequestAttributes
* @return 返回绑定到当前线程的RequestAttributes或者null(未绑定的情况)
*/
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
/**
* Return the RequestAttributes currently bound to the thread.
* Exposes the previously bound RequestAttributes instance, if any.
* Falls back to the current JSF FacesContext, if any.
* @return the RequestAttributes currently bound to the thread
* @throws IllegalStateException if no RequestAttributes object
* is bound to the current thread
* @see #setRequestAttributes
* @see ServletRequestAttributes
* @see FacesRequestAttributes
* @see javax.faces.context.FacesContext#getCurrentInstance()
*/
public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
if (jsfPresent) {
attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
}
if (attributes == null) {
throw new 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.");
}
}
return attributes;
}
/**
* Inner class to avoid hard-coded JSF dependency.
*/
private static class FacesRequestAttributesFactory {
public static RequestAttributes getFacesRequestAttributes() {
FacesContext facesContext = FacesContext.getCurrentInstance();
return (facesContext != null ? new FacesRequestAttributes(facesContext) : null);
}
}
}
有了该工具类RequestContextHolder
,在一个请求处理过程中访问不到request
对象的地方,就可以通过RequestContextHolder.getRequestAttributes()
或者RequestContextHolder.currentRequestAttributes()
来获取当前request
的属性了。比如在ContentNegotiatingViewResolver.resolveViewName
中就出现了这种没有当前request
对象但又需要访问当前request
对象的情况,它的用法如下:
// 当前代码上下文中没有指向当前请求对象的变量,使用RequestContextHolder获取当前请求对象
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
RequestContextFilter
RequestContextFilter
是一个Servlet Filter
,专门用来将当前请求绑定到当前线程,这样在当前线程中任何没有当前请求对象为参数的地方也可以使用RequestContextHolder
获得当前请求对象及其属性。该过滤器还是用类似的模式通过LocaleContextHolder
暴露本地化上下文对象LocaleContext
。
package org.springframework.web.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
public class RequestContextFilter extends OncePerRequestFilter {
// 是否要将 LocaleContext 和 RequestAttributes 设置为可以被子线程继承,缺省值为 false
private boolean threadContextInheritable = false;
/**
* 设置是否要将 LocaleContext 和 RequestAttributes 设置为可以被子线程继承,缺省值为 false 以避免
* 生成后台线程时的副作用。
* Switch this to "true" to enable inheritance for custom child threads which
* are spawned during request processing and only used for this request
* (that is, ending after their initial task, without reuse of the thread).
* 警告: Do not use inheritance for child threads if you are
* accessing a thread pool which is configured to potentially add new threads
* on demand (e.g. a JDK java.util.concurrent.ThreadPoolExecutor),
* since this will expose the inherited context to such a pooled thread.
*/
public void setThreadContextInheritable(boolean threadContextInheritable) {
this.threadContextInheritable = threadContextInheritable;
}
/**
* Returns "false" so that the filter may set up the request context in each
* asynchronously dispatched thread.
*/
@Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
/**
* Returns "false" so that the filter may set up the request context in an
* error dispatch.
*/
@Override
protected boolean shouldNotFilterErrorDispatch() {
return false;
}
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 构造 RequestAttributes 对象并绑定到当前请求处理线程
ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
initContextHolders(request, attributes);
try {
filterChain.doFilter(request, response);
}
finally {
// 当前请求处理完成,清除当前线程中的 RequestAttributes 属性,
// 因为此时当前线程可能会被容器用于处理下一个请求,如果没有该清除动作,
// 可能会泄露当前请求信息。
resetContextHolders();
if (logger.isDebugEnabled()) {
logger.debug("Cleared thread-bound request context: " + request);
}
attributes.requestCompleted();
}
}
private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
if (logger.isDebugEnabled()) {
logger.debug("Bound request context to thread: " + request);
}
}
private void resetContextHolders() {
LocaleContextHolder.resetLocaleContext();
RequestContextHolder.resetRequestAttributes();
}
}