当我们在开发时,有时候会使用到请求的request和响应的response,除了可以在controller传入HttpServletRequestHttpServletResponse,一直传到service层外,还可以使用RequestContextHolder

1、RequestContextHolder获取request和response

获取request代码如下:

1
2
3
4
5
6
7
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();

String name = request.getParameter("name");
System.out.println(name);

// 当请求传入name参数时,结果输出name参数的值

获取response的代码如下:

1
2
3
4
5
6
7
8
9
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletResponse response = ((ServletRequestAttributes) requestAttributes).getResponse();

try{
OutputStream outputStream = response.getOutputStream();
outputStream.write("hello".getBytes());
}catch (Exception ignored){}

// 响应返回hello的字节流

2、RequestContextHolder分析

点进RequestContextHolder类里面,可以看到里面有三个成员变量

1
2
3
4
5
6
7
8
private static final boolean jsfPresent =
ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());

private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");

private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");

其中jsfPresent基本很少使用了,不用管,而requestAttributesHolderinheritableRequestAttributesHolder很明显的是ThreadLocal对象,这两个的区别,按照字面意思是一个是可继承的。不过具体什么意思我也不懂,但这并不影响我们的使用

既然是ThreadLocal对象,那就显然的有getset等方法,而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(@Nullable 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,所以这两个不区分不影响使用
    @Nullable
    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
    17
    public 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方法的调用地方

image-20240229120507943

然后可以一个一个打断点尝试,这里就直接说调用过程:

1、首先的进入OncePerRequestFilter#doFilter方法,OncePerRequestFilter是实现了Filter,明显是个过滤器

2、然后是进入到RequestContextFilter#doFilterInternal方法,该方法是在OncePerRequestFilter#doFilter方法执行到doFilterInternal(httpRequest, httpResponse, filterChain)这个语句时进入的。该方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {

ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
// 初始化RequestContextHolder,也就是存入
initContextHolders(request, attributes);

try {
// 这个最终会执行到你的业务代码
filterChain.doFilter(request, response);
}
finally {
// 移除RequestContextHolder的内容
resetContextHolders();
if (logger.isTraceEnabled()) {
logger.trace("Cleared thread-bound request context: " + request);
}
attributes.requestCompleted();
}
}

由上面的代码可以看出,RequestContextFilter#doFilterInternal主要就是分三部分,存入RequestContextHolder,执行业务代码,移除RequestContextHolder

然而,这里的存入并不是我们在业务中使用的,因为在filterChain.doFilter(request, response)中还会重新存入一次

3、FrameworkServlet#doGet,因为发请求是get请求,所以会到doGet方法,由RequestContextFilter#doFilterInternal方法进入到doGet方法的调用过程如下:

image-20240229142249516

HttpServlet#service方法中,会判断请求是哪种类型,从而进入对应的请求处理方法中,如doGetdoPost

image-20240229142831593

4、FrameworkServlet#processRequest,在FrameworkServlet#doGet方法中,调用了内部的processRequest方法,processRequest方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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);

// 获取RequestContextHolder内的值,作为旧值
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

// 给RequestContextHolder设置新值
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 {
// 给RequestContextHolde设置回旧值
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}

上面的主要步骤是:先获取RequestContextHolde的旧值,然后给RequestContextHolde设置新的值,然后执行业务代码,最后给RequestContextHolde设置回旧值。

总的调用过程为:

image-20240229161020887

4、RequestContextHolde获取和Controller传的区别

现在我们知道,从RequestContextHolde可以获取请求的requestresponse,而从Controller也能一直传到service层,那这两种获取的结果有区别吗?

先说结论:

response是同一个对象

request在请求参数有附件时,不是同一个对象。请求没有附件时,是同一个对象

可以使用代码验证一下:

请求参数无附件时:

image-20240229163853296

请求参数有附件时:

image-20240229163959071

使用RequestContextHolde获取requestresponse上面已经说了,而只有找到获取从controller传的reqresp,就能明白这种差异的原因。而从controller传的reqresp,就在上面总调用过程的DispatcherServlet#doService方法中。具体来说,是在DispatcherServlet#doService方法调用的DispatcherServlet#doDispatch方法中。以下是doDispatch方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
ModelAndView mv = null;
Exception dispatchException = null;

try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
return;
}

applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

在上面的代码中,参数requestresponse就是RequestContextHolde的请求和响应,而经过processedRequest = checkMultipart(request)这行代码,判断请求是否携带附件,如果携带附件的话,则返回一个新的StandardMultipartHttpServletRequest类作为processedRequest 的值,没有附件的话,request就作为processedRequest 的值。然后在mv = ha.handle(processedRequest, response, mappedHandler.getHandler());这行代码中,真正是去执行业务代码。并且传入的processedRequestresponse

因此,response无论是从RequestContextHolde获取还是从controller获取都是同一个对象,而request则要看请求是否携带附件

值得注意的是,因为RequestContextHolder获取的值是基于ThreadLocal对象的,所以当使用多线程时,是获取不到requestresponse的,如图:

image-20240229171905037

5、RequestContextHolde方式和Controller方式获取请求的附件

从上面我们知道,当请求参数携带附件时,RequestContextHolde方式和Controller方式获取的request对象是不同的,其中RequestContextHolde获取的requestRequestFacade实现类,而Controller方式获取的requestStandardMultipartHttpServletRequest实现类,那这两种怎么获取附件?

1、使用request.getPart("file")

因为RequestFacadeStandardMultipartHttpServletRequest都实现了HttpServletRequest,所以都有getPart方法

image-20240229174202106

使用例子如下:

1
2
3
4
5
6
7
8
9
try {
Part file = request.getPart("file");
InputStream inputStream = file.getInputStream();

ServletOutputStream outputStream = resp.getOutputStream();

IoUtil.copy(inputStream, outputStream);

}catch (Exception ignored){}

2、转换为MultipartHttpServletRequest对象

这种方法只适用于Controller方式。从上面的类关系图可以知道,StandardMultipartHttpServletRequest实现了MultipartHttpServletRequest,因此可以可以转为MultipartHttpServletRequest对象。使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void test(HttpServletRequest req, HttpServletResponse resp) {

try {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) req;
Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
MultipartFile file = fileMap.get("file");
InputStream inputStream = file.getInputStream();

ServletOutputStream outputStream = resp.getOutputStream();

IoUtil.copy(inputStream, outputStream);

}catch (Exception ignored){}
}