1. 什么是 RequestContextHolder?
RequestContextHolder是 Spring 框架 提供的一个工具类,用于在当前线程中存储和获取与请求相关的上下文信息。- 它是基于 ThreadLocal 实现的,能够保证每个线程独立存储和访问请求信息。
与 HttpServletRequest 的关系:
HttpServletRequest:
- 是标准的 Servlet API 提供的类,用于表示一个 HTTP 请求。
- 在 Controller 方法中,我们通常通过参数注入来获取它:
@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。
示意图
HttpServletRequest ↓
ServletRequestAttributes(封装 HttpServletRequest) ↓
RequestContextHolder(通过 ThreadLocal 保存 ServletRequestAttributes)
- 当 Spring 处理请求时,它会将
HttpServletRequest封装成ServletRequestAttributes,然后绑定到当前线程的 ThreadLocal 中。 RequestContextHolder就是通过 ThreadLocal 拿到ServletRequestAttributes,再从中获取HttpServletRequest。
3. RequestContextHolder 的工作原理
绑定请求上下文
Spring 在处理 HTTP 请求时,会自动把 HttpServletRequest 封装成 ServletRequestAttributes 并绑定到线程中:
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(attributes);
获取请求对象
我们可以通过 RequestContextHolder 拿到请求上下文,进一步获取 HttpServletRequest:
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
总结:RequestContextHolder 是一个中间桥梁,它通过 ServletRequestAttributes 间接地连接到 HttpServletRequest。
4. RequestContextHolder 提供的主要方法
获取请求上下文
getRequestAttributes():获取当前线程保存的请求上下文。
示例:
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 获取:
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;}
}
使用:
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 的请求上下文不会自动传递给新线程。需要手动设置:
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);// 获取 HttpServletRequestHttpServletRequest request =((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();System.out.println("异步线程中获取请求参数:" + request.getParameter("name"));} finally {// 清理请求上下文,防止内存泄漏RequestContextHolder.resetRequestAttributes();}}).start();}
}
场景 3:过滤器或拦截器中设置请求上下文
在自定义的过滤器中手动设置请求上下文:
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 {@Overridepublic 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),可能会导致内存泄漏。
- 在使用线程池时,如果不清理请求上下文(