叙述
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>的形式保存着线程变量和其对应的值。
对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
问题
1. request和response怎么和当前请求挂钩?
getRequestAttributes():直接获取ThreadLocal里面的值,保证了每一次获取到的Request是该请求的request.
2. request和response等是什么时候设置进去的?
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方法是多了很多