首页 > 其他分享 >RequestContextHolder获取得到Request

RequestContextHolder获取得到Request

时间:2023-03-08 11:14:16浏览次数:38  
标签:RequestContextHolder RequestAttributes Request request 获取 线程 requestAttributes a

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对象了。

标签:RequestContextHolder,RequestAttributes,Request,request,获取,线程,requestAttributes,a
From: https://www.cnblogs.com/likeguang/p/17191266.html

相关文章

  • 获取一个集合出现重复的数据
    1.对象集合://单独String集合List<Student>list=newArrayList<>();list.add(Student.builder().id("1").name("1").build());list......
  • requests库
    1.requests库是用来发送http请求,接收http响应的一个python库requests库经常被用来爬取网站信息用它发起http请求到网站,从响应消息中提取信息例:pipinstallrequestsim......
  • TenantClientHttpRequestInterceptor implements ClientHttpRequestInterceptor
      ClientHttpRequestInterceptor是对RestTemplate的请求进行拦截的,在项目中直接使用restTemplate.getForObject的时候,会对这种请求进行拦截,经常被称为:RestTempalte拦截......
  • java中根据公网IP获取地址
    packagecom.dashan.utils.iputils;importcom.fasterxml.jackson.databind.ObjectMapper;importokhttp3.OkHttpClient;importokhttp3.Request;importokhttp3.Res......
  • java中获取内网IP
    packagecom.dashan.utils.iputils;importorg.apache.commons.lang.StringUtils;importjavax.servlet.http.HttpServletRequest;importjava.net.InetAddress;impo......
  • Request对象和Response对象
    1.请求    2.响应  ......
  • 从 JS 日期对象获取 YYYYMMDD 格式的字符串?
    我正在尝试使用JS将a转换dateobject为格式的字符串YYYYMMDD。有没有比连接Date.getYear(),Date.getMonth()和更简单的方法Date.getDay()?解答http://www.st......
  • Python第三方库request的安装及基本用法
    ​1、安装安装命令:pipinstallrequests豆瓣源安装:pipinstallrequests-ihttps://pypi.douban.com/simple/​2、requests常见参数​url参数:传入的是字符串,请求地址data......
  • [转]C# 获得窗体句柄并发送消息(利用windows API可在不同进程中获取)
     C#使用WindowsAPI获取窗口句柄控制其他程序窗口编写程序模拟鼠标和键盘操作可以方便的实现你需要的功能,而不需要对方程序为你开放接口。比如,操作飞信定时发送短信等......
  • WiFi probe request/response
    proberequest主动扫描通过发送proberequest帧进行STA会在每个信道上发送proberequest进行扫描。proberequest会向广播地址FF:FF:FF:FF:FF:FF发送。发送的STA可以指......