今天我们分析多线程场景下异步操作子线程获取不到主线程request信息问题解决及源码解析:
一,共享request问题解决:
1、先说使用,主线程核心设置代码:
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
RequestContextHolder.setRequestAttributes(servletRequestAttributes, true);//设置子线程共享
2、主线程设置后,子线程就可以直接使用了,可以断定或打印出来相关信息,子线程相关代码:
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
String userName = request.getHeader("userName");
String userId = request.getHeader("userId ");
如果设置成功,request不为空,且能查到相关参数。如图:
继续看:
二、源码解析:
1、关键入口RequestContextHolder类,意思是以线程绑定的的形式保存web请求的相关信息的holder类,通过设置inheritable属性来决定是否能被子线程继承;该类里面包含两个ThreadLocal全局属性;进入 setRequestAttributes(servletRequestAttributes, true);方法源码
//类上共享变量
private static final boolean jsfPresent =
ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
//控制主线程的 ThreadLocal
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
//控制子线程的 ThreadLocal
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
//核心源码
public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
if (attributes == null) {
resetRequestAttributes();
}
else {
if (inheritable) {//传true
// 就是把request的属性放入到inheritableRequestAttributesHolder 中,供子类继承
inheritableRequestAttributesHolder.set(attributes);
requestAttributesHolder.remove();//子线程中删除主线程的信息
}
else {//传false
requestAttributesHolder.set(attributes);//设置主线程使用
inheritableRequestAttributesHolder.remove();//不共享设置
}
}
}
2、子线程获取主线程的request的属性 请看下图,子线程中 requestAttributesHolder.get() 获取属性为空,就会从inheritableRequestAttributesHolder 中去获取属性:
点击 RequestContextHolder.getRequestAttributes();方法:
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {//负责主线程的信息为空,就去负责子线程的作用域中取
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
到此你就会豁然开朗了。
3、延伸问题:上一步 requestAttributesHolder 属性是在什么时候放进去的呢,从类的说明中看看到 DispatcherServlet 类已经默认把web request的属性放到了 requestAttributesHolder 中,DispatcherServlet 继承自 FrameworkServlet,在FrameworkServlet 的 processRequest()方法中的 resetContextHolders会把web request的属性放入到requestAttributesHolder 中,具体源码如下:public class DispatcherServlet extends FrameworkServlet 点击:FrameworkServlet类
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);//处理请求核心代码,进入
}
else {
super.service(request, response);
}
}
点击:processRequest 方法:
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
//核心代码
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
点击:resetContextHolders 方法:
private void resetContextHolders(HttpServletRequest request,
@Nullable LocaleContext prevLocaleContext, @Nullable RequestAttributes previousAttributes) {
LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);
//主线程设置request信息
RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable);
}
4、到此异步子线程共享主线程request属性的源码剖析已经结束。需要说明的是这种共享方式适合用于主线程等待子线程完成任务后在结束的情况,否则主线程调用主线程先与子线程结束的话主线程的request 会被销毁,子线程还是共享不了主线程的request属性。针对后面的情况给出一个解决方案,那就是子线程都结束后通知主线程结束。