1. 什么是 RequestContextHolder?
RequestContextHolder
是 Spring 框架 提供的一个工具类,用于在当前线程中存储和获取与请求相关的上下文信息。- 它是基于 ThreadLocal 实现的,能够保证每个线程独立存储和访问请求信息。
与 HttpServletRequest 的关系:
HttpServletRequest:
-
是标准的 Servlet API 提供的类,用于表示一个 HTTP 请求。
-
在 Controller 方法中,我们通常通过参数注入来获取它:
java@GetMapping("/example") public String example(HttpServletRequest request) { String param = request.getParameter("name"); return "Parameter: " + param; }
RequestContextHolder:
- 是 Spring 对请求上下文的封装。
- 它的核心是通过 RequestAttributes (Spring 自定义的接口)来间接操作
HttpServletRequest
,从而获取请求信息。 - 它的最大优势是:在 Service 层、过滤器、拦截器 等不能直接注入
HttpServletRequest
的地方,也能获取请求信息。
2. RequestContextHolder 与 HttpServletRequest 的联系
核心联系
RequestContextHolder
不会直接持有 HttpServletRequest,但它持有与请求相关的上下文信息。- 这个上下文信息是通过
RequestAttributes
实现的,而ServletRequestAttributes
是它的一个实现类,封装了HttpServletRequest
和HttpServletResponse
。
示意图
java
HttpServletRequest
↓
ServletRequestAttributes(封装 HttpServletRequest)
↓
RequestContextHolder(通过 ThreadLocal 保存 ServletRequestAttributes)
- 当 Spring 处理请求时,它会将
HttpServletRequest
封装成ServletRequestAttributes
,然后绑定到当前线程的 ThreadLocal 中。 RequestContextHolder
就是通过 ThreadLocal 拿到ServletRequestAttributes
,再从中获取HttpServletRequest
。
3. RequestContextHolder 的工作原理
绑定请求上下文
Spring 在处理 HTTP 请求时,会自动把 HttpServletRequest
封装成 ServletRequestAttributes
并绑定到线程中:
java
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(attributes);
获取请求对象
我们可以通过 RequestContextHolder
拿到请求上下文,进一步获取 HttpServletRequest
:
java
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
总结 :RequestContextHolder
是一个中间桥梁,它通过 ServletRequestAttributes 间接地连接到 HttpServletRequest
。
4. RequestContextHolder 提供的主要方法
获取请求上下文
**getRequestAttributes():
**获取当前线程保存的请求上下文。
示例:
java
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
if (attributes instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes) attributes).getRequest();
System.out.println("请求参数:" + request.getParameter("name"));
}
- 返回类型是
RequestAttributes
,需要强转为ServletRequestAttributes
才能拿到HttpServletRequest
。
Spring 设计
RequestAttributes
作为一个 通用接口,这样可以支持不同类型的请求上下文,而不仅仅是 HTTP 请求。
RequestAttributes
:定义了请求上下文的通用方法,不依赖于特定类型的请求。ServletRequestAttributes
:RequestAttributes
的一个具体实现,专门封装 Servlet 环境 下的HttpServletRequest
和HttpServletResponse
。为什么需要强制转换?
因为
RequestAttributes
接口 是通用的,它不知道自己具体是什么请求类型(例如 Servlet 请求、Portlet 请求等)。
- RequestAttributes 只提供了通用方法,比如存储和获取请求属性。
- ServletRequestAttributes 提供了专门的方法,比如
getRequest()
和getResponse()
,用于获取HttpServletRequest
和HttpServletResponse
。
currentRequestAttributes()
- 和
getRequestAttributes()
类似,但如果没有请求上下文,它会抛出异常。
RequestContextHolder 提供的方法
方法 | 作用 |
---|---|
getRequestAttributes() |
获取当前线程绑定的 RequestAttributes ,如果没有返回 null 。 |
currentRequestAttributes() |
获取当前线程的 RequestAttributes ,如果没有会抛出异常。 |
setRequestAttributes(RequestAttributes) |
手动将 RequestAttributes 绑定到当前线程。适用于手动设置上下文的场景。 |
resetRequestAttributes() |
清除当前线程绑定的 RequestAttributes ,防止内存泄漏。 |
setRequestAttributes(RequestAttributes, boolean inheritable) |
支持将请求上下文传递给子线程。inheritable = true 表示上下文可继承。 |
5. 使用场景及代码示例
场景 1:在 Service 层获取 HttpServletRequest
通常情况下,Service 层不能直接访问 HttpServletRequest
,但可以通过 RequestContextHolder
获取:
java
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
public class RequestUtil {
public static HttpServletRequest getCurrentRequest() {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return attributes != null ? attributes.getRequest() : null;
}
}
使用:
java
public void printRequestParam() {
HttpServletRequest request = RequestUtil.getCurrentRequest();
if (request != null) {
String name = request.getParameter("name");
System.out.println("请求参数 name:" + name);
} else {
System.out.println("无法获取 HttpServletRequest");
}
}
场景 2:在异步线程中传递请求上下文
默认情况下,Spring 的请求上下文不会自动传递给新线程。需要手动设置:
java
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
public class AsyncRequestExample {
public void processInAsyncThread() {
// 获取当前请求上下文
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
new Thread(() -> {
try {
// 绑定请求上下文到新线程
RequestContextHolder.setRequestAttributes(attributes, true);
// 获取 HttpServletRequest
HttpServletRequest request =
((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
System.out.println("异步线程中获取请求参数:" + request.getParameter("name"));
} finally {
// 清理请求上下文,防止内存泄漏
RequestContextHolder.resetRequestAttributes();
}
}).start();
}
}
场景 3:过滤器或拦截器中设置请求上下文
在自定义的过滤器中手动设置请求上下文:
java
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
public class CustomFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 手动设置上下文
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes((HttpServletRequest) request));
try {
chain.doFilter(request, response);
} finally {
// 清理上下文,防止内存泄漏
RequestContextHolder.resetRequestAttributes();
}
}
}
6. RequestContextHolder 的注意事项
-
必须在请求线程中使用
RequestContextHolder
依赖于 ThreadLocal,只能在处理请求的线程中使用。- 如果在非 Web 环境或没有 HTTP 请求的线程中调用,会返回
null
或抛出异常。
-
异步线程问题
- 异步线程无法自动继承父线程的请求上下文,必须手动通过
setRequestAttributes
传递。
- 异步线程无法自动继承父线程的请求上下文,必须手动通过
-
内存泄漏风险
- 在使用线程池时,如果不清理请求上下文(
resetRequestAttributes
),可能会导致内存泄漏。
- 在使用线程池时,如果不清理请求上下文(