RequestContextHolder获取得到Request
RequestContextHolder获取得到Request
一、问题
有时我们需要在非controller层,如service层而不通过Controller层传参方式而获得HttpServletRequest,HttpServletResponse,在service获取request和response。正常来说在service层是没有request的,然而直接从controlller传过来的话解决方法太粗暴,但是提供工具类又显得太过于麻烦。
后来发现了SpringMVC提供的提供的可以获取HttpServletRequest的一个工具类RequestContextHolder,可以获取得到Servlet。
二、使用
从RequestContextHolder中我们可以获取到request
、response
、session
等对象。
//两个方法在没有使用JSF的项目中是没有区别的
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
//RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//从session里面获取对应的值
String name = (String) requestAttributes.getAttribute("name", RequestAttributes.SCOPE_SESSION);
获取得到HttpServletRequest、HttpServletResponse、HttpSession对象
//类型转换
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)requestAttributes;
//获取到Request对象
HttpServletRequest request = servletRequestAttributes.getRequest();
//获取到Response对象
HttpServletResponse response = servletRequestAttributes.getResponse();
//获取到Session对象
HttpSession session = request.getSession();
因为在HttpServlet类中的service方法中在调用service方法中已经将ServletRequest和ServletResponse转换成了HttpServletRequest和HttpServletResponse类。
三、RequestContextHolder初始化
request、response和session是在哪里被设置进去的呢?
首先看一下RequestContextHolder类中的两个静态属性:
//得到存储进去的request
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
//可被子线程继承的request
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");
再看getRequestAttributes()
方法,相当于直接获取ThreadLocal里面的值,这样就保证了每一次获取到的Request是该请求的request.
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
因为DispatcherServlet是来处理请求的,在DispatcherServlet的父类FrameworkServlet类中初始化WebApplicationContext,并提供service方法预处理请求
在service方法中会调用processRequest方法,那么直接来到org.springframework.web.servlet.FrameworkServlet#processRequest中:
查看processRequest(request, response);
的实现,具体可以分为三步:
- 获取上一个请求的参数
- 重新建立新的参数
- 设置到XXContextHolder
- 父类的service()处理请求
- 恢复request
- 发布事件
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
//获取上一个请求保存的LocaleContext
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//建立新的LocaleContext
LocaleContext localeContext = buildLocaleContext(request);
//获取上一个请求保存的RequestAttributes
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//建立新的RequestAttributes!包装request和response
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 (Exception ex) {
// 简化处理:我将异常缩减成一个
throw ex;
}
finally {
//请求处理结束之后重置
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
//发布事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
再看initContextHolders(request, localeContext, requestAttributes)方法,把新的RequestAttributes设置进LocalThread,实际上保存的类型为ServletRequestAttributes,这也是为什么在使用的时候可以把RequestAttributes强转为ServletRequestAttributes。
private void initContextHolders(HttpServletRequest request,
LocaleContext localeContext,
RequestAttributes requestAttributes) {
if (localeContext != null) {
LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
}
// 存入到当前线程中
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
}
因此RequestContextHolder里面最终保存的为ServletRequestAttributes,而在ServletRequestAttributes中封装了HttpServletRequest、HttpServletResponse和HttpSession对象。
四、特殊情况:子线程获取得到request
在来看下这两个变量:
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
对于inheritableRequestAttributesHolder来说,可以给子线程使用。
刚刚从源码中可以看到,在初始化的时候,如下所示:
private void initContextHolders(HttpServletRequest request,
@Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
// threadContextInheritable默认为false
if (localeContext != null) {
LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
}
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
}
然后看下setRequestAttributes方法:
public static void setRequestAttributes(@Nullable RequestAttributes attributes, boolean inheritable) {
// attributes不为空
if (attributes == null) {
resetRequestAttributes();
}
else {
// 如果为true,那么将设置到子线程中去
if (inheritable) {
// 将RequestAttributes对象设置到ThreadLocal中去
inheritableRequestAttributesHolder.set(attributes);
// 移除掉当前线程中的RequestAttributes
requestAttributesHolder.remove();
}
else {
// 为false的时候,只会存当前线程
requestAttributesHolder.set(attributes);
// 将另外一个ThreadLocal中的RequestAttributes移除掉
inheritableRequestAttributesHolder.remove();
}
}
}
从这里可以看到,如果在子线程获取得到了当前request对象,那么当前线程只有在执行完转发之后,才可能获取得到当前request对象;
那么这个时候就应该可以理解什么叫做恢复了。
resetContextHolders(request, previousLocaleContext, previousAttributes);
private void resetContextHolders(HttpServletRequest request,LocaleContext prevLocaleContext,RequestAttributes previousAttributes) {
LocaleContextHolder.setLocaleContext(prevLocaleContext, this.threadContextInheritable);
// 重点看下这个方法!!threadContextInheritable默认为false
RequestContextHolder.setRequestAttributes(previousAttributes, this.threadContextInheritable);
}
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);
// 移除掉子线程中的request
inheritableRequestAttributesHolder.remove();
}
}
}
子线程使用
在子线程启动前,加入下面的代码,可以使 requestAttributes 被子线程继承。
// 使子线程也能获取到 requestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
RequestContextHolder.setRequestAttributes(requestAttributes, true);
然后就可以在子线程中获取得到request对象了。