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
),可能会导致内存泄漏。
- 在使用线程池时,如果不清理请求上下文(