Spring RequestContextHolder 和 RequestContextFilter

叙述

Spring Web 提供了一个工具类 RequestContextHolder用来在当前线程中暴露当前请求及其属性RequestAttributes。这样的话在整个请求处理过程中,在当前线程中通过此工具类就可以获取对象RequestAttributes,从而就可以访问当前请求及其属性。RequestAttributes是一个接口,用于抽象一个请求的所有属性对象。


RequestContextHolder

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.
	 * <p>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对象的情况,它的用法如下:

// 获取相关对象
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
 
// 底层实现:request.getAttribute("userId");
String userId = (String) requestAttributes.getAttribute("userId",RequestAttributes.SCOPE_REQUEST);
 
// 底层实现:session.getAttribute("userId");
String userId = (String) requestAttributes.getAttribute("userId",RequestAttributes.SCOPE_SESSION);
 
// 或者转成具体对象
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();
HttpSession session = request.getSession();
// 当前代码上下文中没有指向当前请求对象的变量,使用RequestContextHolder获取当前请求对象
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());

RequestContextFilter

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();
	}

}

RequestContextFilter是一个Servlet Filter,专门用来将当前请求绑定到当前线程,这样在当前线程中任何没有当前请求对象为参数的地方也可以使用RequestContextHolder获得当前请求对象及其属性。该过滤器还是用类似的模式通过LocaleContextHolder暴露本地化上下文对象LocaleContext。



实现原理 ThreadLocal

ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的变量值完成操作的场景。
在每个线程的内部存在一个数据结构为Map的ThreadLocals变量,以<ThreadLocal,Value>的形式保存着线程变量和其对应的值。
对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

ThreadLocal


 问题

1. request和response怎么和当前请求挂钩?
        getRequestAttributes():直接获取ThreadLocal里面的值,保证了每一次获取到的Request是该请求的request.

2. request和response等是什么时候设置进去的?
        DispatcherServlet继承关系:
DispatcherServlet继承关系

1:Servlet的接口和实现类.
2:SpringMVC具有Spring的一些环境变量和Spring容器,类似的XXXAware接口就是对该类提供Spring感知,简单来说就是如果想使用Spring的XXXX就要实现XXXAware,spring会把需要的东西传送过来。
HttpServletBean:进行初始化工作
FrameworkServlet:初始化WebApplicationContext,并提供service方法预处理请求
DispatcherServlet:具体分发处理

FrameworkServlet 类重写了service()、doGet()、doPost() 等方法,方法里面都有一个预处理方法 processRequest(request, response);
查看 processRequest(request, response) 的实现:获取上一个请求的参数 ➡ 重新建立新的参数 ➡ 设置到XXContextHolder ➡ 父类的service()处理请求 ➡ 恢复request ➡ 发布事件


processRequest 源码

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
 
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        //获取上一个请求保存的LocaleContext
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        //建立新的LocaleContext
        LocaleContext localeContext = buildLocaleContext(request);
        //获取上一个请求保存的RequestAttributes
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        //建立新的RequestAttributes
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
 
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
        //具体设置的方法
        initContextHolders(request, localeContext, requestAttributes);
 
        try {
            doService(request, response);
        }
        catch (ServletException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }
 
        finally {
            //恢复
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }
 
            if (logger.isDebugEnabled()) {
                if (failureCause != null) {
                    this.logger.debug("Could not complete request", failureCause);
                }
                else {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        logger.debug("Leaving response open for concurrent processing");
                    }
                    else {
                        this.logger.debug("Successfully completed request");
                    }
                }
            }
            //发布事件
            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }

initContextHolders(request, localeContext, requestAttributes)
把新的RequestAttributes设置进LocalThread,实际上保存的类型为ServletRequestAttributes,这也是为什么在使用的时候可以把RequestAttributes强转为ServletRequestAttributes。
 

    private void initContextHolders(
            HttpServletRequest request, LocaleContext localeContext, RequestAttributes requestAttributes) {
 
        if (localeContext != null) {
            LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
        }
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Bound request context to thread: " + request);
        }
    }

因此RequestContextHolder里面最终保存的为ServletRequestAttributes,这个类相比RequestAttributes方法是多了很多

RequestContextHolder方法

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值