一、前言
最近在做一个项目,有个比较耗时的操作是启用线程进行异步操作,当时在启用的线程时,突然发现子线程无法获取父线程中的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";
- }
复制代码 这是一个正常的请求,我们启动项目,访问接口地址。


从上图中,我们不难发现,我们成功的拿到了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";
- }
复制代码 我们再次启动项目并访问接口地址:


我们发现,这时候的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";
- }
复制代码 启动项目,访问接口地址,结果如下:


可以发现,我们可以在子线程中获取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对象。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |