SpringMVC 之 RequestContextHolder

简介

在业务编写中,常常会出现将 requestresponse 传来传去的场景,正常来说在 service 层是没有 request 的,然而直接从 Controller 传过来的话方法太粗暴了,而 SpringMVC 提供的 RequestContextHolder 可以在一个请求线程的中获取到 Request 并将其存储在底层的 ThreadLocal,避免了 Request 从头传到尾的情况。一般项目中,会对这个类进行再次封装,便于获取请求的相关信息,常见的比如用户信息。

原理剖析

RequestContextHolder 基于 ThreadLocal 实现。

public abstract class RequestContextHolder  {

   private static final boolean jsfPresent =
         ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());

   private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
         new NamedThreadLocal<>("Request attributes");

   //用于子线程的
   private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
         new NamedInheritableThreadLocal<>("Request context");
}

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

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    ......

   LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
   LocaleContext localeContext = buildLocaleContext(request);

   RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
   ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
   asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

   initContextHolders(request, localeContext, requestAttributes);

    ......

}

private void initContextHolders(HttpServletRequest request,
        @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
    if (localeContext != null) {
        LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
    }
    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 时会先从 requestAttributes 中取,取不到再到 inheritableRequestAttributesHolder 中取。

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

inheritableRequestAttributesHolderrequestAttributesHolder

RequestContextHolder 底层由 ThreadLocal 实现。

通过源码剖析我们可以看到,在 RequestContextHolder 中有两个 ThreadLocal 变量,分别为 inheritableRequestAttributesHolderrequestAttributesHolder 。这两个变量有什么区别吗?

RequestContextHolder 默认从 requestAttributesHolder 存取,但是在多线程的情况下,子线程无法访问父线程中的数据,即 RequestContextHolder#getRequestAttributes 返回 null,此时就需要用到 inheritableRequestAttributesHolderinheritableRequestAttributesHolderNamedInheritableThreadLocal 类型,NamedInheritableThreadLocal 继承于 InheritableThreadLocalInheritableThreadLocal 实现了子线程从父线程继承数据,这样在子线程也可以访问父线程中 InheritableThreadLocal 的数据。

ThreadLocal | Devil的个人博客 (devildyw.github.io)

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

private boolean threadContextInheritable = false;

RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);

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

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

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注