SpringMVC 中 Service 通过 RequestContextHolder 获取 Request 对象

在实际开发中,我们可能需要在 Service 方法中获取 Http 请求的 Request 对象,为了避免每次都从 Controller 里作为方法参数传递。SpringMVC 提供了 RequestContextHolder 类,可以通过静态方法 getRequestAttributes() 获取 Request 对象。

1
2
3
4
5
6
7
8
9
public static HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return requestAttributes.getRequest();
}

public static HttpServletResponse getResponse() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return requestAttributes.getResponse();
}

实现原理

RequestContextHolder 类内部维护了 RequestAttributes 的 ThreadLocal 对象,ThreadLocal 大家应该很熟悉了,用于存放数据到线程上下文中,且线程安全的。

1
2
3
4
5
6
7
8
9
10
11
public abstract class RequestContextHolder  {

private static final boolean jsfPresent = ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());

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

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

// ...

}

那么里面的 requestAttributesHolder 是什么时候设置值的呢?

跟踪代码可以发现,在 DispatcherServlet 的 processRequest() 方法里,在 doService() 方法执行前调用了 initContextHolders() 方法:

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
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);
}

// ...

}

initContextHolders 方法里调用了 RequestContextHolder.setRequestAttributes() 将封装好的 requestAttributes 设置到 ThreadLocal 中。

1
2
3
4
5
6
7
8
9
10
11
private void initContextHolders(HttpServletRequest request, @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
if (localeContext != null) {
LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
}
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
if (logger.isTraceEnabled()) {
logger.trace("Bound request context to thread:" + request);
}
}