一、前言

最近在做一个系统的全局日志拦截记录功能,有一个需要记录的IP地址的信息,我是从HttpServletRequest对象中获取的,但是我发现如果使用线程池以后,记录日志信息会报错,主要是获取不到HttpServletRequest对象。

下面使用代码简单演示一下问题和解决方法:

二、代码演示

  1. 创建一个spring boot项目,项目结构如下:


2. 导入maven依赖:

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
  1. 为了方便测试,创建GlobalWebUtils工具类:
package com.learn.util;import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;public class GlobalWebUtils {/*** 获取HttpServletRequest对象** @return*/public static HttpServletRequest getRequest() {HttpServletRequest request = null;RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();if (requestAttributes != null) {request = ((ServletRequestAttributes) requestAttributes).getRequest();}return request;}
}
  1. 创建HelloController进行测试:
@RestController
public class HelloController {@GetMapping("/hello")public void hello(String name) {// 获取HttpServletRequest对象HttpServletRequest request = GlobalWebUtils.getRequest();if (request != null) {String helloName = request.getParameter("name");System.out.println("hello," + name);} else {System.out.println("获取不到request对象");}}}

启动项目,访问:http://localhost:8080/hello?name=张三

测试结果:

hello,张三

下面改造一下hello方法,使用多线程的方法执行该代码:

    @GetMapping("/hello")public void hello(String name) {new Thread(() -> {// 获取HttpServletRequest对象HttpServletRequest request = GlobalWebUtils.getRequest();if (request != null) {String helloName = request.getParameter("name");System.out.println("hello," + name);} else {System.out.println("获取不到request对象");}}).start();}

再次访问,测试结果:

获取不到request对象

可以看到如果使用多线程的话,就获取不到父线程中的HttpServletRequest对象了。

三、解决方法

解决方法其实很简单,调用一下RequestContextHolder的setRequestAttributes方法就行了,代码如下:

    @GetMapping("/hello")public void hello(String name) {RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();RequestContextHolder.setRequestAttributes(requestAttributes,true);new Thread(() -> {// 获取HttpServletRequest对象HttpServletRequest request = GlobalWebUtils.getRequest();if (request != null) {String helloName = request.getParameter("name");System.out.println("hello," + name);} else {System.out.println("获取不到request对象");}}).start();}

再次测试,测试结果:

hello,张三

四、原理

首先看一下setRequestAttributes方法源码:

/*** Bind the given RequestAttributes to the current thread.* @param attributes the RequestAttributes to expose,* or {@code null} to reset the thread-bound context* @param inheritable whether to expose the RequestAttributes as inheritable* for child threads (using an {@link InheritableThreadLocal})*/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();}}}

其实看到源码就茅塞顿开了,主要看一下requestAttributesHolder和inheritableRequestAttributesHolder的类型,就可以知道是怎么实现的了。

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

父类是ThreadLocal类型:

public class NamedThreadLocal<T> extends ThreadLocal<T>
  • inheritableRequestAttributesHolder:
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =new NamedInheritableThreadLocal<>("Request context");

父类是InheritableThreadLocal类型:

public class NamedInheritableThreadLocal<T> extends InheritableThreadLocal<T>

看到这里是不是就很清晰了呢,简单来说就是,调用setRequestAttributes方法以后就把原来放在ThreadLocal对象中的属性放到InheritableThreadLocal对象中了,这就是为什么子线程可以获取到HttpServletRequest 对象的原因。

如果还有不明白的地方,可以参考一下我的另外两篇博客:

  • ThreadLocal的理解与应用

  • InheritableThreadLocal的理解与应用

RequestContextHolder跨线程获取不到request对象,解决方法相关推荐

  1. java中的request对象_java中request对象各种方法的使用实例分析

    本文实例讲述了java中request对象各种方法的使用.分享给大家供大家参考,具体如下: request对象是从客户端向服务器端发出请求,包括用户提交的信息以及客户端的一些信息.request对象是 ...

  2. JQuery this和$(this)的区别及获取$(this)子元素对象的方法

    1.JQuery this和$(this)的区别 // this其实是一个Html 元素. // $this 只是个变量名,加$是为说明其是个jquery对象. // 而$(this)是个转换,将th ...

  3. ajax请求时拒绝访问,ajax跨域请求js拒绝访问的解决方法

    ajax跨域请求js拒绝访问的解决方法 内容精选 换一换 可能原因kubelet服务没有运行或运行异常.kubelet服务没有运行或运行异常.解决方法可以通过systemctl status kube ...

  4. git 提交两次commit到同一分支,被糅合为一次Marge Request的解决方法:cherry-pick

    简 述: git 提交两次commit到同一分支,且也push到同一个远程仓库的分支,会被糅合为一次Marge Request的解决方法:使用cherry-pick解决 文章目录 同步博文: 需求背景 ...

  5. go get 获取被墙依赖包解决方法

    go get 获取被墙依赖包解决方法 参考文章: (1)go get 获取被墙依赖包解决方法 (2)https://www.cnblogs.com/lpfuture/p/11395232.html ( ...

  6. Maven CXF wsdl2Java ListXxx生成ArrayOfXxx包装对象 解决方法

    Maven CXF wsdl2Java List<Xxx>生成ArrayOfXxx包装对象 解决方法 添加-xjc-Xxew解决,同时还要给插件添加相应的jar包,如下: <plug ...

  7. failed to open stream :HTTP request failed 解决方法

    failed to open stream :HTTP request failed 解决方法 参考文章: (1)failed to open stream :HTTP request failed ...

  8. 跨站点脚本编制-XSS 描述及解决方法

    跨站点脚本编制-XSS 描述及解决方法 参考文章: (1)跨站点脚本编制-XSS 描述及解决方法 (2)https://www.cnblogs.com/xinaixia/p/5852422.html ...

  9. xm-select getValue()获取不到值的解决方法

    在使用layui 第三方控件xm-select时发现,getValue()始终获取不到值,在这之前是先调用了setValue()方法的,但是在后续代码中使用getValue()又可以获取到值了!还真是 ...

最新文章

  1. Transform(CTM,Translate,Rotate,Scale)
  2. HDU 6035 Colorful Tree(补集思想+树形DP)
  3. 实验吧 登录一下好吗
  4. NHibernate重要概念的解释和说明
  5. ajax jq 图片上传请求头_全面分析前端的网络请求方式:Ajax ,jQuery ,axios,fetch
  6. python执行shell命令
  7. ZooKeeper 的Web管理工具Shepher介绍
  8. python获取列表长度方法_python - 在Pandas df列中获取有关列表长度(平均长度,最大长度等)的统计信息的大多数pandas-onic方法 - 堆栈内存溢出...
  9. MMU页表的内存消耗
  10. Windws Server 2012 Server Backup(备份与还原)
  11. html和css的编程规范,Bootstrap CSS编码规范
  12. php js轮播图片代码,javascript实现焦点图轮播效果代码示例
  13. 软件项目管理MOOC(北邮)——第十章测试答案
  14. Google浏览器清理缓存快捷键是什么
  15. Calendar根据日期获取年份和周、当前周的所有日期
  16. 泽风大过:改过自新;坎为水:坦然面对
  17. 理解Pointers In C++:第一重
  18. 2017南宁(重温经典)
  19. Spring Boot 2.x的默认日志管理与Logback配置详解
  20. 生物老师(搞笑)鼻子和手

热门文章

  1. Python Class 类继承变量的使用方法
  2. 【数据挖掘】心跳信号分类预测 之 特征工程 —— 学习笔记(三)
  3. 【Unity3d Shader】角色投影与倒影
  4. b站服务器崩溃大会员自动续费,骚操作!B站崩了补偿1天大会员会自动续费 官方回应:全额退款...
  5. 蚂蚁学堂(1):10-Session与Cokile实现原理
  6. 鼠标移入移出改变元素的背景颜色
  7. 这个Github宝藏仓库竟然收藏了这么多好东西?
  8. axios拦截器的原理及应用
  9. 电商工具箱之淘客检测
  10. tableau做类excel的业绩对比表