天空闲话 发表于 2023-7-11 10:19:47

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

一、前言

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

在这里,我们简单模拟一下出现的问题。我们首先编写一个简单的hello请求,代码如下:
/**
   * 主线程获取
   * @return
   */
    @GetMapping("/hello")
    public String hello() {
      String name = "";
      HttpServletRequest request = RequestUtils.getRequest();
      if (null == request) {
            log.info("未获取到request对象!");
      } else {
            name = request.getParameter("name");
            log.info("获取到的内容为{}", name);
      }

      return "hello";
    }这是一个正常的请求,我们启动项目,访问接口地址。
https://img2023.cnblogs.com/blog/2334435/202307/2334435-20230711095122845-711741801.png
https://img2023.cnblogs.com/blog/2334435/202307/2334435-20230711094150215-9386265.png
从上图中,我们不难发现,我们成功的拿到了HttpServletRequest中的参数。
接着,我们稍微修改一下我们的代码,另起一个线程,在子线程中获取HttpServletRequest中的name属性,代码如下:
/**
   * 主线程获取
   * @return
   */
    @GetMapping("/hello")
    public String hello() {

      new Thread(() -> {
            HttpServletRequest request = RequestUtils.getRequest();
            if (null == request) {
                log.info("未获取到request对象!");
            } else {
                String name = request.getParameter("name");
                log.info("获取到的内容为{}", name);
            }
      }).start();


      return "hello";
    }我们再次启动项目并访问接口地址:
https://img2023.cnblogs.com/blog/2334435/202307/2334435-20230711094808269-313060109.png
https://img2023.cnblogs.com/blog/2334435/202307/2334435-20230711094830265-1788060569.png
我们发现,这时候的request对象已经变为空,我们根本没办法获取请求中的name属性。
结论:如果采用多线程,我们就获取不到父线程中的HttpServletRequest对象了。
三、解决方法

解决上面的问题其实很简单,只需要在开启子线程时,调用一下RequestContextHolder.setRequestAttributes(requestAttributes, true);方法,将第二个参数设为true就可以了。
我们修改上面的代码如下:
/**
   * 主线程获取
   * @return
   */
    @GetMapping("/hello")
    public String hello() {
      /**
         * 解决子线程无法获取HttpServletRequest请求对象中数据的问题
         */
      RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
      RequestContextHolder.setRequestAttributes(requestAttributes, true);
      new Thread(() -> {
            HttpServletRequest request = RequestUtils.getRequest();
            if (null == request) {
                log.info("未获取到request对象!");
            } else {
                String name = request.getParameter("name");
                log.info("获取到的内容为{}", name);
            }
      }).start();


      return "hello";
    }启动项目,访问接口地址,结果如下:
https://img2023.cnblogs.com/blog/2334435/202307/2334435-20230711100604388-677001088.png
https://img2023.cnblogs.com/blog/2334435/202307/2334435-20230711100620512-837135958.png
可以发现,我们可以在子线程中获取HttpServletRequest对象了。
四、原理

点开RequestContextHolder.setRequestAttributes(requestAttributes, true)方法,查看源码:
        /**
       * 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");


public class NamedThreadLocal<T> extends ThreadLocal<T> {}我们发现,requestAttributesHolder对象类型为NamedThreadLocal,NamedThreadLocal父类是ThreadLocal。
inheritableRequestAttributesHolder
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
                        new NamedInheritableThreadLocal<>("Request context");

public class NamedInheritableThreadLocal<T> extends InheritableThreadLocal<T> {}我们发现inheritableRequestAttributesHolder的类型为NamedInheritableThreadLocal,NamedInheritableThreadLocal是InheritableThreadLocal的子类。
看到这里,就很清晰了。调用RequestContextHolder.setRequestAttributes(requestAttributes, true)这个方法,将原本放在ThreadLocal对象中的属性放到了类型为InheritableThreadLocal的对象中了,所以我们启动的子线程可以获取到父线程中的属性。
五、总结

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: RequestContextHolder跨线程获取不到requests请求对象的解决方法