RequestContextHolder类
当我们在开发时,有时候会使用到请求的request和响应的response,除了可以在controller传入HttpServletRequest和HttpServletResponse,一直传到service层外,还可以使用RequestContextHolder类
1、RequestContextHolder获取request和response
获取request代码如下:
1 | RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); |
获取response的代码如下:
1 | RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); |
2、RequestContextHolder分析
点进RequestContextHolder类里面,可以看到里面有三个成员变量
1 | private static final boolean jsfPresent = |
其中jsfPresent基本很少使用了,不用管,而requestAttributesHolder和inheritableRequestAttributesHolder很明显的是ThreadLocal对象,这两个的区别,按照字面意思是一个是可继承的。不过具体什么意思我也不懂,但这并不影响我们的使用
既然是ThreadLocal对象,那就显然的有get,set等方法,而RequestContextHolder内的主要方法为:
resetRequestAttributes:移除值
1
2
3
4
5// 就是移除ThreadLocal的值
public static void resetRequestAttributes() {
requestAttributesHolder.remove();
inheritableRequestAttributesHolder.remove();
}setRequestAttributes:设置值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 就是设置ThreadLocal的值
public static void setRequestAttributes( RequestAttributes attributes, boolean inheritable) {
if (attributes == null) {
resetRequestAttributes();
}
else {
if (inheritable) {
inheritableRequestAttributesHolder.set(attributes);
requestAttributesHolder.remove();
}
else {
requestAttributesHolder.set(attributes);
inheritableRequestAttributesHolder.remove();
}
}
}getRequestAttributes:获取值
1
2
3
4
5
6
7
8
9// 可以看到,如果requestAttributesHolder的空就获取inheritableRequestAttributesHolder,所以这两个不区分不影响使用
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}currentRequestAttributes:获取值,获取到空对象会报错以及使用
jsfPresent时才和getRequestAttributes方法有区别,所以一般情况下和getRequestAttributes基本没区别1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public static RequestAttributes currentRequestAttributes() throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
if (jsfPresent) {
attributes = FacesRequestAttributesFactory.getFacesRequestAttributes();
}
if (attributes == null) {
throw new IllegalStateException("No thread-bound request found: " +
"Are you referring to request attributes outside of an actual web request, " +
"or processing a request outside of the originally receiving thread? " +
"If you are actually operating within a web request and still receive this message, " +
"your code is probably running outside of DispatcherServlet: " +
"In this case, use RequestContextListener or RequestContextFilter to expose the current request.");
}
}
return attributes;
}
3、RequestContextHolder的存入和移除
上面我们知道了,RequestContextHolder主要是维护两个ThreadLocal对象。按照我们使用ThreadLocal对象的经验来看,应该是有个存入,以及结束时移除的过程。让我们来找找看在哪
这里追调用栈的话比较多,我们可以直接查看setRequestAttributes方法的调用地方

然后可以一个一个打断点尝试,这里就直接说调用过程:
1、首先的进入OncePerRequestFilter#doFilter方法,OncePerRequestFilter是实现了Filter,明显是个过滤器
2、然后是进入到RequestContextFilter#doFilterInternal方法,该方法是在OncePerRequestFilter#doFilter方法执行到doFilterInternal(httpRequest, httpResponse, filterChain)这个语句时进入的。该方法如下:
1 | protected void doFilterInternal( |
由上面的代码可以看出,RequestContextFilter#doFilterInternal主要就是分三部分,存入RequestContextHolder,执行业务代码,移除RequestContextHolder
然而,这里的存入并不是我们在业务中使用的,因为在filterChain.doFilter(request, response)中还会重新存入一次
3、FrameworkServlet#doGet,因为发请求是get请求,所以会到doGet方法,由RequestContextFilter#doFilterInternal方法进入到doGet方法的调用过程如下:

在
HttpServlet#service方法中,会判断请求是哪种类型,从而进入对应的请求处理方法中,如doGet,doPost等
4、FrameworkServlet#processRequest,在FrameworkServlet#doGet方法中,调用了内部的processRequest方法,processRequest方法如下:
1 | protected final void processRequest(HttpServletRequest request, HttpServletResponse response) |
上面的主要步骤是:先获取RequestContextHolde的旧值,然后给RequestContextHolde设置新的值,然后执行业务代码,最后给RequestContextHolde设置回旧值。
总的调用过程为:

4、RequestContextHolde获取和Controller传的区别
现在我们知道,从RequestContextHolde可以获取请求的request和response,而从Controller也能一直传到service层,那这两种获取的结果有区别吗?
先说结论:
response是同一个对象
request在请求参数有附件时,不是同一个对象。请求没有附件时,是同一个对象
可以使用代码验证一下:
请求参数无附件时:

请求参数有附件时:

使用RequestContextHolde获取request和response上面已经说了,而只有找到获取从controller传的req和resp,就能明白这种差异的原因。而从controller传的req和resp,就在上面总调用过程的DispatcherServlet#doService方法中。具体来说,是在DispatcherServlet#doService方法调用的DispatcherServlet#doDispatch方法中。以下是doDispatch方法
1 | protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
在上面的代码中,参数request和response就是RequestContextHolde的请求和响应,而经过processedRequest = checkMultipart(request)这行代码,判断请求是否携带附件,如果携带附件的话,则返回一个新的StandardMultipartHttpServletRequest类作为processedRequest 的值,没有附件的话,request就作为processedRequest 的值。然后在mv = ha.handle(processedRequest, response, mappedHandler.getHandler());这行代码中,真正是去执行业务代码。并且传入的processedRequest和response。
因此,response无论是从RequestContextHolde获取还是从controller获取都是同一个对象,而request则要看请求是否携带附件
值得注意的是,因为
RequestContextHolder获取的值是基于ThreadLocal对象的,所以当使用多线程时,是获取不到request和response的,如图:
5、RequestContextHolde方式和Controller方式获取请求的附件
从上面我们知道,当请求参数携带附件时,RequestContextHolde方式和Controller方式获取的request对象是不同的,其中RequestContextHolde获取的request是RequestFacade实现类,而Controller方式获取的request是StandardMultipartHttpServletRequest实现类,那这两种怎么获取附件?
1、使用request.getPart("file")
因为RequestFacade和StandardMultipartHttpServletRequest都实现了HttpServletRequest,所以都有getPart方法

使用例子如下:
1 | try { |
2、转换为MultipartHttpServletRequest对象
这种方法只适用于Controller方式。从上面的类关系图可以知道,StandardMultipartHttpServletRequest实现了MultipartHttpServletRequest,因此可以可以转为MultipartHttpServletRequest对象。使用方法如下:
1 | public void test(HttpServletRequest req, HttpServletResponse resp) { |






