分析 inheritableRequestAttributesHolder 失效原因其实标题并不严谨,因为子线程获取不到请求的 header 并不是因为
inheritableRequestAttributesHolder 失效 。这个原因当初我也很奇怪,于是我从网上看到一篇文章,它是这么写的 。
在源码中ThreadLocal对象保存的是RequestAttributes attributes;这个是保存的 对象的引用 。一旦父线程销毁了,那RequestAttributes也会被销毁,那RequestAttributes的引用地址的值就为null **;**虽然子线程也有RequestAttributes的引用,但是引用的值为null了 。
真的是这样吗??我怎么看怎么感觉不对......于是我自己验证了下
@GetMapping("/test")public void test(HttpServletRequest request) {RequestAttributes attr = RequestContextHolder.getRequestAttributes();log.info("父线程:RequestAttributes:{}", attr);RequestContextHolder.setRequestAttributes(attr, true);log.info("父线程:SpringMVC:request:{}",request);log.info("父线程:x-auth-token:{}",request.getHeader("x-auth-token"));ServletRequestAttributes attr1 = (ServletRequestAttributes) attr;HttpServletRequest request1 = attr1.getRequest();log.info("父线程:request:{}",request1);new Thread(() -> {try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}RequestAttributes childAttr = RequestContextHolder.getRequestAttributes();log.info("子线程:RequestAttributes:{}",childAttr);ServletRequestAttributes childServletRequestAttr = (ServletRequestAttributes) childAttr;HttpServletRequest childRequest = childServletRequestAttr.getRequest();log.info("子线程:childRequest:{}",childRequest);String childToken = childRequest.getHeader("x-auth-token");log.info("子线程:x-auth-token:{}",childToken);}).start();}复制代码观察日志
父线程:RequestAttributes:org.Apache.catalina.connector.RequestFacade@ea25271父线程:SpringMVC:request:org.apache.catalina.connector.RequestFacade@ea25271父线程:x-auth-token:null父线程:request:org.apache.catalina.connector.RequestFacade@ea25271子线程:RequestAttributes:org.apache.catalina.connector.RequestFacade@ea25271子线程:childRequest:org.apache.catalina.connector.RequestFacade@ea25271子线程:x-auth-token:{}:null复制代码很明显子线程拿到了 RequestAttitutes 对象,而且和父线程是同一个,这就推翻了上面的说法,并不是引用变为 null 了导致的 。那么到底是什么原因导致父线程结束后,子线程就拿不到 request 对象里面的 header 属性了呢?
我们可以猜测一下,既然父线程和子线程拿到的 request 对象是同一个,并且在子线程代码中 request 对象还不是 null ,但是属性没了,那应该是请求结束之后某个地方对 request 对象进行了属性移除 。我们跟随 RequestFacade 类去寻找真理,寻找寻找再寻找......终于我发现了真相在 org.apache.coyote.Request 类

文章插图
在 Tomcat 内部,请求结束后会对 request 对象重置,把 header 等属性移除,是因为这样如果父线程提前结束,我们在子线程中才无法获取 request 对象的 header 。
或许你可以再思考一下 Tomcat 为什么要这么做?
多线程环境下传递 header(二)既然
RequestContextHolder.setRequestAttributes(attr, true); 也不能完全实现子线程能够获取父线程的 header ,那么我们如何解决呢?
控制主线程在子线程结束后再结束这是最简单的方法,我把父线程挂起来,等子线程任务都执行完了,再结束父线程,这样就不会出现子线程获取不到 header 的情况了 。最简单的,我们可以用 ExecutorCompletionService 实现 。
重新保存 request 的 header上面我们已经知道了获取不到 header 是因为 request 对象的 header 属性被移除了,那么我们只需要自己定义一个数据结构 ThreadLocal 重新在内存中保存一份 header 属性即可 。我们可以定义一个请求拦截器,在拦截器中获取 headers 放到自定义的结构中 。
定义结构
public class RequestHeaderHolder {private static final ThreadLocal<Map<String,String>> REQUEST_HEADER_HOLDER = new InheritableThreadLocal<>(){@Overrideprotected Map<String, String> initialValue() {return new HashMap<>();}};//...省略部分方法}复制代码拦截器public class RequestHeaderInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {Enumeration<String> headerNames = request.getHeaderNames();while (headerNames.hasMoreElements()){String s = headerNames.nextElement();RequestHeaderHolder.set(s,request.getHeader(s));}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {RequestHeaderHolder.remove(); //注意一定要remove}}复制代码
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 10085是什么电话服务显示中国移动 10085是什么电话
- 富婆|案例:深圳小伙服务两个“富婆”,还倒给4000元,真相令人崩溃
- 马尾辫|日本一家酒吧所有服务员都是肌肉女孩,男人都不一定打得过她们
- 中国移动|飞信转型作落幕!中国移动宣布“和飞信”9月停止服务
- 冬瓜|45岁男保姆:服务48岁女雇主两个月后,我们终于成功牵手
- 华为移动服务app下载 服务密码
- 特斯拉|马斯克:特斯拉将装备F1维修站技术优化服务
- 服务器运行一段时间就死机 服务器老死机是什么问题
- 4家店加盟天猫养车,标准店月产值60万 卡尔汽车服务怎么样
- 灵活的手将挽救半调子的服务机器人 beam机器人
