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
),可能會導致內存泄漏。
- 在使用線程池時,如果不清理請求上下文(