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

Spring MVC 攔截器實現原理與最佳實踐:權限驗證、編碼統一、日誌記錄

     編輯:Java 相關知識

攔截器 vs 過濾器

維度Interceptor (攔截器)Filter (過濾器)
依賴容器Spring 容器Servlet 容器 (Tomcat)
實現機制AOP 動態代理 (反射)回調函數 (鏈式調用)
攔截範圍僅 Controller 方法所有請求 (靜態資源、JSP、Servlet)
獲取 Bean可注入 Spring Bean無法直接注入
配置方式Spring MVC 配置類/XMLweb.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 要手動響應。

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