RequestContextHolder跨线程获取不到requests请求对象的解决方法 ...

打印 上一主题 下一主题

主题 905|帖子 905|积分 2715

一、前言

最近在做一个项目,有个比较耗时的操作是启用线程进行异步操作,当时在启用的线程时,突然发现子线程无法获取父线程中的HttpServletRequest请求对象,因为是第一次遇到这种问题,所以记录一下解决方案。
二、问题模拟

在这里,我们简单模拟一下出现的问题。我们首先编写一个简单的hello请求,代码如下:
  1. /**
  2.      * 主线程获取
  3.      * @return
  4.      */
  5.     @GetMapping("/hello")
  6.     public String hello() {
  7.         String name = "";
  8.         HttpServletRequest request = RequestUtils.getRequest();
  9.         if (null == request) {
  10.             log.info("未获取到request对象!");
  11.         } else {
  12.             name = request.getParameter("name");
  13.             log.info("获取到的内容为{}", name);
  14.         }
  15.         return "hello";
  16.     }
复制代码
这是一个正常的请求,我们启动项目,访问接口地址。


从上图中,我们不难发现,我们成功的拿到了HttpServletRequest中的参数。
接着,我们稍微修改一下我们的代码,另起一个线程,在子线程中获取HttpServletRequest中的name属性,代码如下:
  1. /**
  2.      * 主线程获取
  3.      * @return
  4.      */
  5.     @GetMapping("/hello")
  6.     public String hello() {
  7.         new Thread(() -> {
  8.             HttpServletRequest request = RequestUtils.getRequest();
  9.             if (null == request) {
  10.                 log.info("未获取到request对象!");
  11.             } else {
  12.                 String name = request.getParameter("name");
  13.                 log.info("获取到的内容为{}", name);
  14.             }
  15.         }).start();
  16.         return "hello";
  17.     }
复制代码
我们再次启动项目并访问接口地址:


我们发现,这时候的request对象已经变为空,我们根本没办法获取请求中的name属性。
结论:如果采用多线程,我们就获取不到父线程中的HttpServletRequest对象了。
三、解决方法

解决上面的问题其实很简单,只需要在开启子线程时,调用一下  RequestContextHolder.setRequestAttributes(requestAttributes, true);方法,将第二个参数设为true就可以了。
我们修改上面的代码如下:
  1. /**
  2.      * 主线程获取
  3.      * @return
  4.      */
  5.     @GetMapping("/hello")
  6.     public String hello() {
  7.         /**
  8.          * 解决子线程无法获取HttpServletRequest请求对象中数据的问题
  9.          */
  10.         RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
  11.         RequestContextHolder.setRequestAttributes(requestAttributes, true);
  12.         new Thread(() -> {
  13.             HttpServletRequest request = RequestUtils.getRequest();
  14.             if (null == request) {
  15.                 log.info("未获取到request对象!");
  16.             } else {
  17.                 String name = request.getParameter("name");
  18.                 log.info("获取到的内容为{}", name);
  19.             }
  20.         }).start();
  21.         return "hello";
  22.     }
复制代码
启动项目,访问接口地址,结果如下:


可以发现,我们可以在子线程中获取HttpServletRequest对象了。
四、原理

点开RequestContextHolder.setRequestAttributes(requestAttributes, true)方法,查看源码:
  1.         /**
  2.          * Bind the given RequestAttributes to the current thread.
  3.          * @param attributes the RequestAttributes to expose,
  4.          * or {@code null} to reset the thread-bound context
  5.          * @param inheritable whether to expose the RequestAttributes as inheritable
  6.          * for child threads (using an {@link InheritableThreadLocal})
  7.          */
  8.         public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
  9.                 if (attributes == null) {
  10.                         resetRequestAttributes();
  11.                 }
  12.                 else {
  13.                         if (inheritable) {
  14.                                 inheritableRequestAttributesHolder.set(attributes);
  15.                                 requestAttributesHolder.remove();
  16.                         }
  17.                         else {
  18.                                 requestAttributesHolder.set(attributes);
  19.                                 inheritableRequestAttributesHolder.remove();
  20.                         }
  21.                 }
  22.         }
复制代码
这个方法很简单,主要只要继续查看requestAttributesHolder与inheritableRequestAttributesHolder这两个类的继承关系就可以了。
requestAttributesHolder
  1. private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
  2.                         new NamedThreadLocal<>("Request attributes");
  3. public class NamedThreadLocal<T> extends ThreadLocal<T> {}
复制代码
我们发现,requestAttributesHolder对象类型为NamedThreadLocal,NamedThreadLocal父类是ThreadLocal。
inheritableRequestAttributesHolder
  1. private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
  2.                         new NamedInheritableThreadLocal<>("Request context");
  3. public class NamedInheritableThreadLocal<T> extends InheritableThreadLocal<T> {}
复制代码
我们发现inheritableRequestAttributesHolder的类型为NamedInheritableThreadLocal,NamedInheritableThreadLocal是InheritableThreadLocal的子类。
看到这里,就很清晰了。调用RequestContextHolder.setRequestAttributes(requestAttributes, true)这个方法,将原本放在ThreadLocal对象中的属性放到了类型为InheritableThreadLocal的对象中了,所以我们启动的子线程可以获取到父线程中的属性。
五、总结

当子线程中无法获取父线程中的HttpServletRequest的方法时,我们可以通过调用RequestContextHolder.setRequestAttributes(requestAttributes, true)方法,使得子线程也可以获取父线程中HttpServletRequest对象。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天空闲话

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表