1. 概述
本教程將重點介紹如何理解和正確使用 Spring MVC 中的 HandlerInterceptor。
2. Spring MVC 處理程序 (Handler)
為了理解 Spring 攔截器的工作原理,我們先退後一步,看看 HandlerMapping。
HandlerMapping 的目的是將處理程序 (handler method) 映射到 URL。這樣,DispatcherServlet 就能在處理請求時調用它。
事實上,DispatcherServlet 使用 HandlerAdapter 來實際調用方法。
簡而言之,攔截器攔截請求並對其進行處理。它們有助於避免重複的處理程序代碼,例如日誌記錄和授權檢查。
現在我們已經理解了整體上下文,讓我們看看如何使用 HandlerInterceptor 來執行一些預處理和後處理操作。
3. Maven 依賴
為了使用攔截器,我們需要在我們的 <em ref="pom.xml"</em>> 中包含 `<em ref="spring-web"> 依賴:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.2.1</version>
</dependency>
4. Spring Handler 攔截器
在 spring-web 5.3 版本之前,我們可以通過擴展 HandlerInterceptorAdapter 類或實現 HandlerInterceptor 接口來創建 Spring 攔截器類。
需要注意的是,由於 spring-web 5.3 版本之後,HandlerInterceptorAdapter 類已被棄用,因此 為了創建攔截器,我們應該直接實現 HandlerInterceptor 接口。
HandlerInterceptor 接口包含三個主要方法:
- prehandle() – 在實際處理器執行之前調用
- postHandle() – 在處理器執行之後調用
- afterCompletion() – 在完整的請求完成並生成視圖之後調用
這三個方法提供了靈活的預處理和後處理功能。
在繼續之前,請注意:為了跳過理論,直接跳轉到第 5 節的示例。
以下是一個簡單的 preHandle() 實現:
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// your code
return true;
}請注意,該方法返回一個布爾值。它指示 Spring 繼續處理請求 (true) 還是不處理 (false)。
接下來,我們有一個 postHandle() 的實現:
@Override
public void postHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// your code
}攔截器會在處理請求後立即調用此方法,但在生成視圖之前。
例如,我們可以使用此方法將登錄用户的頭像添加到模型中。
我們需要實現的最終方法是 afterCompletion():
@Override
public void afterCompletion(
HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
// your code
}此方法允許我們在請求處理完成後執行自定義邏輯。
此外,值得一提的是,我們可以註冊多個自定義攔截器。為此,我們可以使用 DefaultAnnotationHandlerMapping
5. 自定義日誌攔截器
在本示例中,我們將重點關注在我們的 Web 應用程序中進行日誌記錄。
首先,我們的類需要實現 HandlerInterceptor:
public class LoggerInterceptor implements HandlerInterceptor {
...
}我們還需要在攔截器中啓用日誌記錄:
private static Logger log = LoggerFactory.getLogger(LoggerInterceptor.class);這使得 Log4J 能夠顯示日誌,並指示當前哪個類正在將信息輸出到指定的輸出。
接下來,讓我們重點關注我們的自定義攔截器實現。
5.1. <em >preHandle() </em > 方法
正如其名稱所示,攔截器在處理請求之前會調用 <em >preHandle() </em > 方法。
默認情況下,此方法返回 <em >true </em > 以將請求進一步發送到處理方法。但是,我們可以通過返回false 來告訴 Spring 停止執行。
我們可以利用鈎子來記錄有關請求參數的信息,例如請求來自何處。
在我們的示例中,我們使用簡單的 `Log4J 日誌記錄器來記錄此信息:
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
log.info("[preHandle][" + request + "]" + "[" + request.getMethod()
+ "]" + request.getRequestURI() + getParameters(request));
return true;
}
如我們所見,我們正在記錄請求的一些基本信息。
如果我們在密碼中遇到問題,當然需要確保我們不記錄這些信息。一個簡單的選擇是在密碼和其他敏感類型的數據中用星號替換它們。
以下是實現此功能的快速示例:
private String getParameters(HttpServletRequest request) {
StringBuffer posted = new StringBuffer();
Enumeration<?> e = request.getParameterNames();
if (e != null) {
posted.append("?");
}
while (e.hasMoreElements()) {
if (posted.length() > 1) {
posted.append("&");
}
String curr = (String) e.nextElement();
posted.append(curr + "=");
if (curr.contains("password")
|| curr.contains("pass")
|| curr.contains("pwd")) {
posted.append("*****");
} else {
posted.append(request.getParameter(curr));
}
}
String ip = request.getHeader("X-FORWARDED-FOR");
String ipAddr = (ip == null) ? getRemoteAddr(request) : ip;
if (ipAddr!=null && !ipAddr.equals("")) {
posted.append("&_psip=" + ipAddr);
}
return posted.toString();
}最後,我們旨在獲取 HTTP 請求的源 IP 地址。
以下是一個簡單的實現:
private String getRemoteAddr(HttpServletRequest request) {
String ipFromHeader = request.getHeader("X-FORWARDED-FOR");
if (ipFromHeader != null && ipFromHeader.length() > 0) {
log.debug("ip from proxy - X-FORWARDED-FOR : " + ipFromHeader);
return ipFromHeader;
}
return request.getRemoteAddr();
}5.2. <em >postHandle() </em > 方法
攔截器在處理程序執行完畢後,但在 DispatcherServlet 渲染視圖之前調用此方法。
我們可以利用它來為 ModelAndView 添加額外的屬性。另一個使用場景是在計算請求的處理時間。
在本例中,我們只需在 DispatcherServlet 渲染視圖之前,便記錄我們的請求:
@Override
public void postHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
log.info("[postHandle][" + request + "]");
}5.3. afterCompletion() 方法
我們可以使用此方法在視圖渲染後獲取請求和響應數據:
@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
if (ex != null){
ex.printStackTrace();
}
log.info("[afterCompletion][" + request + "][exception: " + ex + "]");
}6. 配置
現在我們已經將所有組件整合在一起,接下來我們添加自定義攔截器。
要做到這一點,我們需要覆蓋 addInterceptors() 方法:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoggerInterceptor());
}我們可能通過編輯我們的 XML Spring 配置文件來達到相同的配置:
<mvc:interceptors>
<bean id="loggerInterceptor" class="com.baeldung.web.interceptor.LoggerInterceptor"/>
</mvc:interceptors>啓用此配置後,攔截器將處於活動狀態,並且應用程序中的所有請求都將被正確記錄。
請注意,如果配置了多個 Spring 攔截器,則 preHandle() 方法在配置順序中執行,而 postHandle() 和 afterCompletion() 方法則按相反的順序調用。
請記住,如果使用 Spring Boot 而不是純 Spring,我們就不需要用 @EnableWebMvc 註解我們的配置類。
7. 結論
本文檔提供了一個關於使用 Spring MVC Handler Interceptor 攔截 HTTP 請求的快速介紹。