多线程下的 RequestContextHolder

8,300 阅读1分钟

RequestContextHolder 是 Spring 提供的一个用来暴露 Request 对象的工具,利用 RequestContextHolder,可以在一个请求线程中获取到 Request,避免了 Request 从头传到尾的情况。一般项目中,会对这个类再次进行封装,便于获取请求的相关的信息,常见的比如用户信息。

RequestContextHolder 基于 ThreadLocal 实现。

public abstract class RequestContextHolder  {
​
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
         new NamedThreadLocal<>("Request attributes");
​
   private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
         new NamedInheritableThreadLocal<>("Request context");

FrameworkServlet#processRequest 中,会在进入处理请求前,将 Request 封装为 RequestAttributes,放到 RequestContextHolder 中。

```java
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
​
   RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
   ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
​
   initContextHolders(request, localeContext, requestAttributes);
​
   try {
      doService(request, response);
   }
}
​
private void initContextHolders(HttpServletRequest request,
      @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
​
    if (requestAttributes != null) {
      RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
    }
}

RequestContextHolder 会根据 threadContextInheritable 选择将 RequestAttributes 放入 inheritableRequestAttributesHolder 或者 inheritableRequestAttributesHolder 中。

public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
   if (attributes == null) {
      resetRequestAttributes();
   }
   else {
      if (inheritable) {
         inheritableRequestAttributesHolder.set(attributes);
         requestAttributesHolder.remove();
      }
      else {
         requestAttributesHolder.set(attributes);
         inheritableRequestAttributesHolder.remove();
      }
   }
}

而取出 RequestAttributes 时则会先从 requestAttributesHolder 中取,取不到再到 inheritableRequestAttributesHolder 中取。

public static RequestAttributes getRequestAttributes() {
   RequestAttributes attributes = requestAttributesHolder.get();
   if (attributes == null) {
      attributes = inheritableRequestAttributesHolder.get();
   }
   return attributes;
}

那么 RequestContextHolder 为什么需要 inheritableRequestAttributesHolder 和 requestAttributesHolder 两个 ThreadLocal 成员变量,这两个变量又有什么区别呢?RequestContextHolder 默认从 requestAttributesHolder 存取,但是在多线程的情况下,子线程无法访问父线程中的数据,即 RequestContextHolder#getRequestAttributes 返回 null,此时就需要用到 inheritableRequestAttributesHolder。inheritableRequestAttributesHolder 是 NamedInheritableThreadLocal 类型,NamedInheritableThreadLocal 继承于 InheritableThreadLocal,InheritableThreadLocal 实现了子线程从父线程继承数据,这样在子线程也可以访问父线程中 InheritableThreadLocal 的数据。

要使用 inheritableRequestAttributesHolder 替代 requestAttributesHolder,关键在于 FrameworkServlet 中的 threadContextInheritable,该值默认为 false,即默认使用 requestAttributesHolder,将其设为 true,则会使用 inheritableRequestAttributesHolder。

@Bean
public ServletRegistrationBean apiServlet(DispatcherServlet dispatcherServlet, MultipartConfigElement multipartConfigElement) {
    dispatcherServlet.setThreadContextInheritable(true);
    ServletRegistrationBean bean = new ServletRegistrationBean(dispatcherServlet);
    return bean;
}

InheritableThreadLocal 解决了父线程向子线程传递数据的问题,但传递数据发生在创建 Thread 阶段,如果使用了线程池,线程被复用,子线程的数据仍然是创建时传递的数据,而不是执行任务时父线程的数据。这种情况下,就需要重写 RequestContextHolder,使用 TransmittableThreadLocal 代替 ThreadLocal。TransmittableThreadLocal 用于解决使用线程池时,父线程向子线程传递数据的问题,详见 blog.csdn.net/qq_26012495…