首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

SpringMVC 攔截器完全指南:HandlerInterceptor 三大方法、鏈式執行順序、登錄認證實戰與最佳實踐

     編輯:Java 綜合教學

前言:攔截器是 SpringMVC 橫切關注點的利器,掌握執行鏈順序是正確使用的前提

SpringMVC HandlerInterceptor 提供 preHandle、postHandle、afterCompletion 三個切入點,支持鏈式組合。本文深度解析三大方法參數語義、多攔截器執行順序規律、配置方式對比、登錄認證/日志/權限 實戰模式,附帶完整可運行代碼。

一、 HandlerInterceptor 三大方法詳解

方法 簽名 執行時機 返回值 典型用途 關鍵參數
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 (可獲取異常信息)

關鍵區別

  • preHandle 返回 false:直接中斷鏈,postHandle 不執行,afterCompletion 執行 (需手動響應,如重定向登錄頁)。
  • 拋異常:preHandle/controller 拋異常 → afterCompletion 執行 (ex 非 null),postHandle 不執行。
  • handler 參數:SpringMVC 3.1+ 為 HandlerMethod,可反射獲取方法註解、參數信息。

二、 鏈式執行順序鐵律 (多攔截器)

假設配置順序:Interceptor1 → Interceptor2

場景 1:全部放行

Interceptor1.preHandle → Interceptor2.preHandle
→ Controller
→ Interceptor2.postHandle → Interceptor1.postHandle (逆序)
→ View Render
→ Interceptor2.afterCompletion → Interceptor1.afterCompletion (逆序)

場景 2:Interceptor1 放行,Interceptor2 攔截 (preHandle 返回 false)

Interceptor1.preHandle → Interceptor2.preHandle (false)
→ Interceptor1.afterCompletion
→ Interceptor2.postHandle/afterCompletion 不執行

場景 3:Interceptor1 攔截

Interceptor1.preHandle (false)
→ Interceptor1.afterCompletion
→ Interceptor2 完全不執行

總結規律

  1. preHandle 正序,任一返回 false 終止後續 preHandle。
  2. postHandle 逆序,僅當所有 preHandle 均 true 且 Controller 正常返回時執行。
  3. afterCompletion 逆序,始終執行 (只要 preHandle 執行過),ex 參數攜帶異常信息。

三、 配置方式:全局攔截器 (推薦)


<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;
}

六、 避坑清單

  1. preHandle 返回 false 必須自行響應:重定向、寫 JSON、設置狀態碼,否則瀏覽器白屏無響應。
  2. 靜態資源別攔截:exclude-mapping 或 <mvc:resources> 配置靜態資源映射。
  3. session.getSession(false):避免攔截器強制創建大量無用 Session。
  4. 異常傳遞給 afterCompletion:利用 ex 參數記錄完整錯誤堆棧。
  5. HandlerMethod 轉型前判斷:if (handler instanceof HandlerMethod) 防止資源映射處理器報錯。

結語

SpringMVC 攔截器看似簡單,實則蘊含精妙的鏈式調用語義。搞清 preHandle 正序、postHandle/afterCompletion 逆序、false 截斷鏈、異常不阻 afterCompletion 這幾條鐵律,配合 白名單、AJAX 識別、註解驅動權限 等實戰模式,即可構建清晰、可維護、高性能的橫切邏輯層。記住:日志在最前、認證在中間、權限在最後,順序不可亂。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved