Spring Web : RequestContextHolder 和 RequestContextFilter

源代码版本 : 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();
	}

}

你可能感兴趣的:(spring,Spring,Web)