一、前言
最近在做一个项目,有个比较耗时的操作是启用线程进行异步操作,当时在启用的线程时,突然发现子线程无法获取父线程中的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的对象中了,所以我们启动的子线程可以获取到父线程中的属性。文章来源:https://www.toymoban.com/news/detail-544592.html
五、总结
当子线程中无法获取父线程中的HttpServletRequest的方法时,我们可以通过调用RequestContextHolder.setRequestAttributes(requestAttributes, true)
方法,使得子线程也可以获取父线程中HttpServletRequest对象。文章来源地址https://www.toymoban.com/news/detail-544592.html
到了这里,关于RequestContextHolder跨线程获取不到requests请求对象的解决方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!