攔截器 vs 過濾器
| 維度 | Interceptor (攔截器) | Filter (過濾器) |
| 依賴容器 | Spring 容器 | Servlet 容器 (Tomcat) |
| 實現機制 | AOP 動態代理 (反射) | 回調函數 (鏈式調用) |
| 攔截範圍 | 僅 Controller 方法 | 所有請求 (靜態資源、JSP、Servlet) |
| 獲取 Bean | 可注入 Spring Bean | 無法直接注入 |
| 配置方式 | Spring MVC 配置類/XML | web.xml 或 @WebFilter |
| 執行時機 | Controller 前後、視圖渲染後 | Request 進入/離開 Servlet 容器 |
HandlerInterceptor 三大核心方法
public interface HandlerInterceptor {
/**
* Controller 執行前
* @return true: 繼續執行; false: 中斷 (需手動響應)
*/
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
/**
* Controller 執行後,視圖渲染前
* 可修改 ModelAndView (視圖、模型數據)
*/
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
/**
* 視圖渲染完成後
* 資源清理、耗時統計、異常處理
* 類似析構函數
*/
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;
}
實戰案例:綜合業務攔截器
@Component
public class BusinessInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(BusinessInterceptor.class);
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 統一編碼 (雖然 Filter 更適合,但這裡演示)
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
// 2. 跨域支持 (簡易版)
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return false; // 截斷預檢請求
}
// 3. 權限驗證 (排除登錄、靜態資源)
String uri = request.getRequestURI();
if (uri.contains("/login") || uri.contains("/register") || uri.startsWith("/static/")) {
return true;
}
HttpSession session = request.getSession(false);
String token = request.getHeader("X-Token");
if (session == null || session.getAttribute("user") == null) {
// Token 模式驗證
if (token != null && redisTemplate.hasKey("token:" + token)) {
return true;
}
// 未登錄
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"code\":401,\"msg\":\"未登錄或會話過期\"}");
return false;
}
// 4. 請求耗時統計開始
request.setAttribute("startTime", System.currentTimeMillis());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 可修改視圖或模型
if (modelAndView != null) {
modelAndView.addObject("serverTime", System.currentTimeMillis());
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 耗時統計
Long startTime = (Long) request.getAttribute("startTime");
if (startTime != null) {
long cost = System.currentTimeMillis() - startTime;
log.info("請求: {} | 耗時: {}ms | 狀態: {}", request.getRequestURI(), cost, response.getStatus());
// 慢查詢預警
if (cost > 1000) {
log.warn("慢請求預警: {} 耗時 {}ms", request.getRequestURI(), cost);
}
}
// 異常記錄
if (ex != null) {
log.error("請求異常: {}", request.getRequestURI(), ex);
}
// 清理 ThreadLocal 等資源
RequestContextHolder.resetRequestAttributes();
}
}
配置註冊:Java Config 方式 (Spring Boot 推薦)
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private BusinessInterceptor businessInterceptor;
@Autowired
private ApiLogInterceptor apiLogInterceptor; // 另一個攔截器示例
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 1. 業務攔截器:攔截所有 /api/** 路徑
registry.addInterceptor(businessInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/**", "/api/public/**");
// 2. API 日誌攔截器:攔截所有請求
registry.addInterceptor(apiLogInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/static/**", "/favicon.ico", "/health");
// 順序:addInterceptor 調用順序決定執行順序 (preHandle 正序,postHandle/afterCompletion 逆序)
}
}
XML 配置方式 (傳統項目)
<!-- spring-mvc.xml -->
<mvc:interceptors>
<!-- 方式1: 攔截特定路徑 -->
<mvc:interceptor>
<mvc:mapping path="/courses/view2/*"/>
<bean class="com.cc.mvcdemo.interceptor.TestInterceptor"/>
</mvc:interceptor>
<!-- 方式2: 攔截所有經過 DispatcherServlet 的請求 -->
<bean class="com.cc.mvcdemo.interceptor.TestInterceptorTwo"/>
</mvc:interceptors>
常見應用場景
| 場景 | 實現方法 | 關鍵點 |
| 登錄權限驗證 | preHandle 檢查 Session/Token | 排除登錄頁、靜態資源;返回 401 JSON |
| 統一編碼設置 | preHandle 設置 request/response 編碼 | Filter 更適合 (更早執行) |
| 請求日誌/耗時統計 | preHandle 記錄開始,afterCompletion 計算耗時 | 異常也會進 afterCompletion |
| 參數預處理/解密 | preHandle 解密參數,封裝到 Request | 配合 RequestWrapper |
| 響應後處理/加密 | postHandle 修改 ModelAndView | 僅適用於視圖渲染模式 |
| 資源清理 | afterCompletion 關閉連接、清理 ThreadLocal | 無論正常/異常都執行 |
| 國際化 Locale 解析 | preHandle 從 Header/Cookie 解析設置 Locale | 配合 LocaleChangeInterceptor |
避坑指南
- preHandle 返回 false:必須手動寫入響應 (response.getWriter().write(...)),否則瀏覽器無內容
- 攔截靜態資源:配置
excludePathPatterns("/static/**", "/css/**", "/js/**")
- 多攔截器順序:preHandle 按註冊順序正序執行,postHandle/afterCompletion 逆序
- 異常傳播:Controller 拋異常 → 進入 afterCompletion (ex 不為 null) → 全局異常處理器
- 不要在攔截器做重業務邏輯:屬於橫切關注點,業務邏輯放 Service
總結
Spring MVC 攔截器基於 AOP 實現,核心三方法對應前置、後置、最終增強。優於 Filter 的地方在於可注入 Spring Bean、僅攔截 Controller、支持 ModelAndView 修改。實戰中用於權限驗證、日誌統計、編碼處理、資源清理等橫切關注點。配置時注意路徑匹配精確、排除靜態資源、preHandle 返回 false 要手動響應。