RequestContextHolder获取得到Request

RequestContextHolder获取得到Request

一、问题

有时我们需要在非controller层,如service层而不通过Controller层传参方式而获得HttpServletRequest,HttpServletResponse,在service获取request和response。正常来说在service层是没有request的,然而直接从controlller传过来的话解决方法太粗暴,但是提供工具类又显得太过于麻烦。

后来发现了SpringMVC提供的提供的可以获取HttpServletRequest的一个工具类RequestContextHolder,可以获取得到Servlet。

二、使用

从RequestContextHolder中我们可以获取到requestresponsesession等对象。

//两个方法在没有使用JSF的项目中是没有区别的
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
//RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

//从session里面获取对应的值
String name = (String) requestAttributes.getAttribute("name", RequestAttributes.SCOPE_SESSION);

获取得到HttpServletRequest、HttpServletResponse、HttpSession对象

//类型转换
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)requestAttributes;

//获取到Request对象
HttpServletRequest request = servletRequestAttributes.getRequest();
//获取到Response对象
HttpServletResponse response = servletRequestAttributes.getResponse();
//获取到Session对象
HttpSession session = request.getSession();

因为在HttpServlet类中的service方法中在调用service方法中已经将ServletRequest和ServletResponse转换成了HttpServletRequest和HttpServletResponse类。

三、RequestContextHolder初始化

request、response和session是在哪里被设置进去的呢?

首先看一下RequestContextHolder类中的两个静态属性:

//得到存储进去的request
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
//可被子线程继承的request
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");

再看getRequestAttributes()方法,相当于直接获取ThreadLocal里面的值,这样就保证了每一次获取到的Request是该请求的request.

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

因为DispatcherServlet是来处理请求的,在DispatcherServlet的父类FrameworkServlet类中初始化WebApplicationContext,并提供service方法预处理请求

在service方法中会调用processRequest方法,那么直接来到org.springframework.web.servlet.FrameworkServlet#processRequest中:

查看processRequest(request, response);的实现,具体可以分为三步:

  1. 获取上一个请求的参数
  2. 重新建立新的参数
  3. 设置到XXContextHolder
  4. 父类的service()处理请求
  5. 恢复request
  6. 发布事件
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!包装request和response
  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 (Exception ex) {
    // 简化处理:我将异常缩减成一个
    throw 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);
  }
}

因此RequestContextHolder里面最终保存的为ServletRequestAttributes,而在ServletRequestAttributes中封装了HttpServletRequest、HttpServletResponse和HttpSession对象。

四、特殊情况:子线程获取得到request

在来看下这两个变量:

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

private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
  new NamedInheritableThreadLocal<>("Request context");

对于inheritableRequestAttributesHolder来说,可以给子线程使用。

刚刚从源码中可以看到,在初始化的时候,如下所示:

private void initContextHolders(HttpServletRequest request,
                                @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {

  // threadContextInheritable默认为false

  if (localeContext != null) {
    LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
  }
  if (requestAttributes != null) {
    RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
  }
}

然后看下setRequestAttributes方法:

public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
  // attributes不为空
  if (attributes == null) {
    resetRequestAttributes();
  }
  else {
    // 如果为true,那么将设置到子线程中去
    if (inheritable) {
      // 将RequestAttributes对象设置到ThreadLocal中去
      inheritableRequestAttributesHolder.set(attributes);
      // 移除掉当前线程中的RequestAttributes
      requestAttributesHolder.remove();
    }
    else {
      // 为false的时候,只会存当前线程
      requestAttributesHolder.set(attributes);
      // 将另外一个ThreadLocal中的RequestAttributes移除掉
      inheritableRequestAttributesHolder.remove();
    }
  }
}

从这里可以看到,如果在子线程获取得到了当前request对象,那么当前线程只有在执行完转发之后,才可能获取得到当前request对象;

那么这个时候就应该可以理解什么叫做恢复了。

resetContextHolders(request, previousLocaleContext, previousAttributes);
private void resetContextHolders(HttpServletRequest request,LocaleContext prevLocaleContext,RequestAttributes previousAttributes) { 
  LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);
  // 重点看下这个方法!!threadContextInheritable默认为false
  RequestContextHolder.setRequestAttributes(previousAttributes, 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);
      // 移除掉子线程中的request
      inheritableRequestAttributesHolder.remove();
    }
  }
}

子线程使用

在子线程启动前,加入下面的代码,可以使 requestAttributes 被子线程继承。

// 使子线程也能获取到 requestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
RequestContextHolder.setRequestAttributes(requestAttributes, true);

然后就可以在子线程中获取得到request对象了。

posted @ 2023-03-08 10:58  写的代码很烂  阅读(959)  评论(0编辑  收藏  举报