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(boolean inheritable) { RequestAttributes attributes,
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) { |