SpringMVC HandlerInterceptor 提供 preHandle、postHandle、afterCompletion 三個切入點,支持鏈式組合。本文深度解析三大方法參數語義、多攔截器執行順序規律、配置方式對比、登錄認證/日志/權限 實戰模式,附帶完整可運行代碼。
| 方法 | 簽名 | 執行時機 | 返回值 | 典型用途 | 關鍵參數 |
|---|---|---|---|---|---|
| preHandle | boolean preHandle(req, res, handler) |
Controller 方法執行前 | true 放行 / false 攔截 |
身份認證、權限校驗、簽名驗證、限流、請求預處理 | HttpServletRequest/Response, handler (HandlerMethod) |
| postHandle | void postHandle(req, res, handler, mv) |
Controller 方法執行後,視圖渲染前 | 無 | 公共模型數據注入 (菜單、用戶信息)、統一視圖指定、響應頭設置 | ModelAndView (可修改模型/視圖) |
| afterCompletion | void afterCompletion(req, res, handler, ex) |
視圖渲染完成後 (最終) | 無 | 日志記錄、性能監控、資源清理、異常上報、埋點統計 | Exception (可獲取異常信息) |
HandlerMethod,可反射獲取方法註解、參數信息。假設配置順序:Interceptor1 → Interceptor2
Interceptor1.preHandle → Interceptor2.preHandle → Controller → Interceptor2.postHandle → Interceptor1.postHandle (逆序) → View Render → Interceptor2.afterCompletion → Interceptor1.afterCompletion (逆序)
Interceptor1.preHandle → Interceptor2.preHandle (false) → Interceptor1.afterCompletion → Interceptor2.postHandle/afterCompletion 不執行
Interceptor1.preHandle (false) → Interceptor1.afterCompletion → Interceptor2 完全不執行
<mvc:interceptors>
<!-- 第1順位:日志攔截器 (必須放行) -->
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.example.interceptor.LoggingInterceptor" />
</mvc:interceptor>
<!-- 第2順位:登錄認證攔截器 -->
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/login**" /> <!-- 排除登錄頁 -->
<mvc:exclude-mapping path="/static/**" /> <!-- 排除靜態資源 -->
<bean class="com.example.interceptor.LoginInterceptor" />
</mvc:interceptor>
<!-- 第3順位:權限校驗攔截器 (登錄後) -->
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.example.interceptor.PermissionInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
注意:exclude-mapping 只對當前攔截器生效,鏈中後續攔截器需自行排除或放行。
public class LoginInterceptor implements HandlerInterceptor {
private static final String[] WHITE_LIST = {"/login", "/logout", "/static/**", "/error"};
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
String ctx = request.getContextPath();
String path = uri.substring(ctx.length()); // 相對路徑
// 1. 白名單放行
for (String pattern : WHITE_LIST) {
if (pathMatcher.match(pattern, path)) return true;
}
// 2. 檢查 Session
HttpSession session = request.getSession(false); // 不創建新 session
if (session != null && session.getAttribute("username") != null) {
return true; // 已登錄
}
// 3. 未登錄:區分 AJAX 與頁面請求
String accept = request.getHeader("Accept");
String xRequested = request.getHeader("X-Requested-With");
boolean isAjax = (accept != null && accept.contains("application/json")) ||
"XMLHttpRequest".equals(xRequested);
if (isAjax) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"msg\":\"未登錄或會話過期\"}");
} else {
// 頁面跳轉登錄頁,攜帶目標 URL 登錄後回跳
String targetUrl = request.getRequestURL().toString();
String query = request.getQueryString();
if (query != null) targetUrl += "?" + query;
request.getSession(true).setAttribute("TARGET_URL", targetUrl);
response.sendRedirect(ctx + "/login");
}
return false; // 關鍵:攔截,不進入 Controller
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mv) {
// 可注入公共模型:mv.addObject("menuList", menuService.getMenus());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 記錄請求耗時、異常上報
Long start = (Long) request.getAttribute("REQUEST_START_TIME");
if (start != null) {
long cost = System.currentTimeMillis() - start;
log.info("請求 {} 耗時 {}ms, 狀態 {}", request.getRequestURI(), cost, response.getStatus());
}
}
}
// 自定義註解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission { String value() default ""; }
// 攔截器中判斷
@Override
public boolean preHandle(...) {
HandlerMethod hm = (HandlerMethod) handler;
RequirePermission rp = hm.getMethodAnnotation(RequirePermission.class);
if (rp != null) {
String perm = rp.value();
if (!permissionService.hasPermission(currentUser, perm)) {
// 拒絕訪問
return false;
}
}
return true;
}
exclude-mapping 或 <mvc:resources> 配置靜態資源映射。if (handler instanceof HandlerMethod) 防止資源映射處理器報錯。SpringMVC 攔截器看似簡單,實則蘊含精妙的鏈式調用語義。搞清 preHandle 正序、postHandle/afterCompletion 逆序、false 截斷鏈、異常不阻 afterCompletion 這幾條鐵律,配合 白名單、AJAX 識別、註解驅動權限 等實戰模式,即可構建清晰、可維護、高性能的橫切邏輯層。記住:日志在最前、認證在中間、權限在最後,順序不可亂。