聊一聊過濾器與攔截器

John同學發表於2022-05-09

過濾器 Filter

面試官:用過過濾器吧,介紹一下過濾器。
John同學(心中竊喜):用過,我經常用它來淨化水 ?...
面試官:今天的面試到此結束,回去等通知吧。
John同學:?...

Filter 基本介紹

過濾器 Filter 是 Sun 公司在 Servlet 2.3 規範中新增的新功能,其作用是對客戶端傳送給 Servlet 的請求以及對 Servlet 返回給客戶端的響應做一些定製化的處理,例如校驗請求的引數、設定請求/響應的 Header、修改請求/響應的內容等。

Filter 引入了過濾鏈(Filter Chain)的概念,一個 Web 應用可以部署多個 Filter,這些 Filter 會組成一種鏈式結構,客戶端的請求在到達 Servlet 之前會一直在這個鏈上傳遞,不同的 Filter 負責對請求/響應做不同的處理。 Filter 的處理流程如下圖所示:

AOP 程式設計思想

在深入理解 Filter 之前,我們先聊一聊面向切面程式設計(Aspect Oriented Programming,AOP)。AOP 不是一種具體的技術,而是一種程式設計思想,它允許我們在不修改原始碼的基礎上實現方法邏輯的增強,也就是在方法執行前後新增一些自定義的處理。

Filter 是 AOP 程式設計思想的一種體現,其作用可認為是對 Servlet 功能的增強。Filter 可以對使用者的請求做預處理,也可以對返回的響應做後處理,且這些處理邏輯與 Servlet 的處理邏輯是分隔開的,這使得程式中各部分業務邏輯之間的耦合度降低,從而提高了程式的可維護性和可擴充套件性。

建立 Filter

建立 Filter 需要實現 javax.servlet.Filter 介面,或者繼承實現了 Filter 介面的父類。Filter 介面中定義了三個方法:

  • init:在 Web 程式啟動時被呼叫,用於初始化 Filter。

  • doFilter:在客戶端的請求到達時被呼叫,doFilter 方法中定義了 Filter 的主要處理邏輯,同時該方法還負責將請求傳遞給下一個 Filter 或 Servlet。

  • destroy:在 Web 程式關閉時被呼叫,用於銷燬一些資源。

下面我們通過實現 Filter 介面來建立一個自定義的 Filter:

public class TestFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println(filterConfig.getFilterName() + " 被初始化");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        System.out.println("Filter 攔截到了請求: " + request.getRequestURL());
        System.out.println("Filter 對請求做預處理...");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("Filter 修改響應的內容...");
    }

    @Override
    public void destroy() {
        System.out.println("Filter 被回收");
    }
}
  • init 方法的 filterConfig 引數封裝了當前 Filter 的配置資訊,在 Filter 初始化時,我們將 Filter 的名稱列印在控制檯。

  • doFilter 方法定義了 Filter 攔截到使用者請求後的處理邏輯,filterChain.doFilter(servletRequest, servletResponse); 指的是將請求傳遞給一下個 Filter 或 Servlet,如果不新增該語句,那麼請求就不會向後傳遞,自然也不會被處理。在該語句之後,可以新增對響應的處理邏輯(如果要修改響應的 Header,可直接在該語句之前修改;如果要修改響應的內容,則需要在該語句之後,且需要自定義一個 response)。

  • destroy 方法中,我們輸出 "Filter 被回收" 的提示資訊。

配置 Filter

Spring 專案中,我們可以使用 @Configuration + @Bean + FilterRegistrationBean 對 Filter 進行配置:

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<TestFilter> registryFilter() {
        FilterRegistrationBean<TestFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new TestFilter());
        registration.addUrlPatterns("/*");
        registration.setName("TestFilter");
        registration.setOrder(0);
        return registration;
    }
}

上述程式碼中,setFilter 方法用於設定 Filter 的型別;addUrlPatterns 方法用於設定攔截的規則;setName 方法用於設定 Filter 的名稱;setOrder 方法用於設定 Filter 的優先順序,數字越小優先順序越高。

測試 Filter

接下來,我們定義一個簡單的 Web 服務,測試 Filter 是否生效:

@RestController
public class UserController {

    @RequestMapping(path = "/hello", method = RequestMethod.GET)
    public String sayHello() {
        System.out.println("正在處理請求...");
        System.out.println("請求處理完成~");
        return "I'm fine, thank you.";
    }
}

啟動專案,在瀏覽器中訪問 localhost:8080/hello,等待請求處理完成,然後關閉專案。整個過程中,控制檯依次列印瞭如下資訊:

可以看到,自定義的 TestFilter 實現了攔截請求、處理響應的目標。

建立 Filter 的其它方式

1. @WebFilter 註解 + 包掃描

除了 FilterRegistrationBean 外,Servlet 3.0 引入的註解 @WebFilter 也可用於配置 Filter。我們只需要在自定義的 Filter 類上新增該註解,就可以設定 Filter 的名稱和攔截規則:

@WebFilter(urlPatterns = "/*", filterName = "TestFilter")
public class TestFilter implements Filter {
    // 省略部分程式碼
}

由於@WebFilter 並非 Spring 提供,因此若要使自定義的 Filter 生效,還需在配置類上新增 @ServletComponetScan 註解,並指定掃描的包:

@SpringBootApplication
@ServletComponentScan("com.example.filter")
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

需要注意的是,@WebFilter 註解並不允許我們設定 Filter 的執行順序,且在 Filter 類上新增 @Order 註解也是無效的。如果專案中有多個被 @WebFilter 修飾的 Filter,那麼這些 Filter 的執行順序由其 "類名的字典序" 決定,例如類名為 "Axx" 的 Filter 的執行順序要先於類名為 "Bxx" 的 Filter。

新增了 @WebFilter 註解後就不要再新增 @Component 註解了,如果都新增,那麼系統會建立兩個 Filter。

2. @Component 註解

Spring 專案中,我們可以通過新增 @Component 註解將自定義的 Bean 交給 Spring 容器管理。同樣的,對於自定義的 Filter,我們也可以直接新增 @Component 註解使其生效,而且還可以新增 @Order 註解來設定不同 Filter 的執行順序。

@Component
@Order(1)
public class TestFilter implements Filter {
    // 省略部分程式碼
}

此種配置方式一般不常使用,因為其無法設定 Filter 的攔截規則,預設的攔截路徑為 /*。雖然不能配置攔截規則,但我們可以在 doFilter 方法中定義請求的放行規則,例如當請求的 URL 匹配我們設定的規則時,直接將該請求放行,也就是立即執行 filterChain.doFilter(servletRequest, servletResponse);

3. 繼承 OncePerRequestFilter

OncePerRequestFilter 是一個由 Spring 提供的抽象類,在專案中,我們可以採用繼承 OncePerRequestFilter 的方式建立 Filter,然後重寫 doFilterInternal 方法定義 Filter 的處理邏輯,重寫 shouldNotFilter 方法設定 Filter 的放行規則。對於多個 Filter 的執行順序,我們也可以通過新增 @Order 註解進行設定。當然,若要使 Filter 生效,還需新增 @Component 註解將其註冊到 Spring 容器。

@Component
@Order(1)
public class CSpringFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 處理邏輯
    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        // 放行規則
    }
}

實際上,方式 2 和方式 3 本質上並沒有什麼區別,因為 OncePerRequestFilter 底層也是通過實現 Filter 介面來達到過濾請求/響應的目的,只不過 Spring 在 OncePerRequestFilter 中幫我們封裝了許多功能,因此更推薦採用此種方式建立 Filter。

Filter 的優先順序

上文中提到,使用配置類或新增 @Order 註解可以顯式的設定 Filter 的執行順序,修改類名可以隱式的設定 Filter 的執行順序。如果專案中存在多個 Filter,且這些 Filter 由不同的方式建立,那麼它們的執行順序是怎樣的呢?

能夠確定的是,Spring 根據 Filter 的 order 決定其優先順序,如果我們通過配置類或者通過 @Order 註解設定了 Filter 的 order,那麼 order 值越小的 Filter 的優先順序越高,無論 Filter 由何種方式建立。如果多個 Filter 的優先順序相同,那麼執行順序為:

  1. 配置類中配置的 Filter 優先執行,如果配置類中存在多個 Filter,那麼 Spring 按照其在配置類中配置的順序依次執行。

  2. @WebFilter 註解修飾的 Filter 之後執行,如果存在多個 Filter,那麼 Spring 按照其類名的字典序依次執行。

  3. @Component 註解修飾的 Filter 最後執行,如果存在多個 Filter,那麼 Spring 按照其類名的字典序依次執行。

注意,以上優先順序順序僅適用於 order 相同的特殊情況。如果我們不配置 Filter 的 order,那麼 Spring 預設將其 order 設定為 LOWEST_PRECEDENCE = Integer.MAX_VALUE,也就是最低優先順序。由於被 @WebFilter 註解修飾的 Filter 無法顯式配置優先順序,因此其 order 為 Integer.MAX_VALUE。本文所說的 Filter 的優先順序指的是 Filter 對請求做預處理的優先順序,對響應做後處理的優先順序與之相反。

以上結論由筆者經過測試以及閱讀原始碼得出,如有理解錯誤,歡迎批評指正 ?。關於原始碼部分,有興趣的小夥伴可以看看 ServletContextInitializerBeans 類和 AnnotationAwareOrderComparator 類的原始碼,筆者在這裡就不具體分析了 ?。

Filter 的應用場景

Filter 的常見應用場景包括:

  • 解決跨域訪問:前後端分離的專案往往存在跨域訪問的問題,Filter 允許我們在 response 的 Header 中設定 "Access-Control-Allow-Origin"、"Access-Control-Allow-Methods" 等頭域,以此解決跨域失敗問題。

  • 設定字元編碼:字元編碼 Filter 可以在 request 提交到 Servlet 之前或者在 response 返回給客戶端之前為請求/響應設定特定的編碼格式,以解決請求/響應內容亂碼的問題。

  • 記錄日誌:日誌記錄 Filter 可以在攔截到請求後,記錄請求的 IP、訪問的 URL,攔截到響應後記錄請求的處理時間。當不需要記錄日誌時,也可以直接將 Filter 的配置註釋掉。

  • 校驗許可權:Web 服務中,客戶端在傳送請求時會攜帶 cookie 或者 token 進行身份認證,許可權校驗 Filter 可以在 request 提交到 Servlet 之前對 cookie 或 token 進行校驗,如果使用者未登入或者許可權不夠,那麼 Filter 可以對請求做重定向或返回錯誤資訊。

  • 替換內容:內容替換 Filter 可以對網站的內容進行控制,防止輸入/輸出非法內容和敏感資訊。例如在請求到達 Servlet 之前對請求的內容進行轉義,防止 XSS 攻擊;在 Servlet 將內容輸出到 response 時,使用 response 將內容快取起來,然後在 Filter 中進行替換,最後再輸出到客戶瀏覽器(由於預設的 response 並不能嚴格的快取輸出內容,因此需要自定義一個具備快取功能的 response)。

Filter 應用場景的相關內容參考自《Java Web 整合開發之王者歸來》,好中二的書名 ?,關於自定義具備快取功能的 response 可參考該書的 P175。

攔截器 Interceptor

Interceptor 基本介紹

本文所說的攔截器指的是 Spring MVC 中的攔截器。

攔截器 Interceptor 是 Spring MVC 中的高階元件之一,其作用是攔截使用者的請求,並在請求處理前後做一些自定義的處理,如校驗許可權、記錄日誌等。這一點和 Filter 非常相似,但不同的是,Filter 在請求到達 Servlet 之前對請求進行攔截,而 Interceptor 則是在請求到達 Controller 之前對請求進行攔截,響應也同理。

與 Filter 一樣,Interceptor 也是 AOP 程式設計思想的體現,且 Interceptor 也具備鏈式結構,我們在專案中可以配置多個 Interceptor,當請求到達時,每個 Interceptor 根據其宣告的順序依次執行。

建立 Interceptor

建立 Interceptor 需要實現 org.springframework.web.servlet.HandlerInterceptor 介面,HandlerInterceptor 介面中定義了三個方法:

  • preHandle:在 Controller 方法執行前被呼叫,可以對請求做預處理。該方法的返回值是一個 boolean 變數,只有當返回值為 true 時,程式才會繼續向下執行。

  • postHandle:在 Controller 方法執行結束,DispatcherServlet 進行檢視渲染之前被呼叫,該方法內可以操作 Controller 處理後的 ModelAndView 物件。

  • afterCompletion:在整個請求處理完成(包括檢視渲染)後被呼叫,通常用來清理資源。

注意,postHandle 方法和 afterCompletion 方法執行的前提條件是 preHandle 方法的返回值為 true。如果 Controller 丟擲異常,那麼 postHandle 方法將不會執行,afterCompletion 方法則一定執行,詳見 DispatcherServlet 類中的 doDispatch 方法。

下面我們建立一個 Interceptor:

@Component
public class TestInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Interceptor 攔截到了請求: " + request.getRequestURL());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        System.out.println("Interceptor 操作 modelAndView...");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("Interceptor 清理資源...");
    }
}

配置 Interceptor

Interceptor 需要註冊到 Spring 容器才能夠生效,註冊的方法是在配置類中實現 WebMvcConfigurer 介面,並重寫 addInterceptors 方法:

@Configuration
public class TestInterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private TestInterceptor testInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(testInterceptor)
                .addPathPatterns("/*")
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg")
                .order(1);
    }
}

上述程式碼中,addInterceptor 方法用於註冊 Interceptor;addPathPatterns 方法用於設定攔截規則;excludePathPatterns 方法用於設定放行規則,order 方法用於設定 Interceptor 的優先順序,數字越小優先順序越高。

測試 Interceptor

下面我們通過一個簡單的 Web 服務,來測試 Interceptor 是否生效:

@RestController
public class UserController {

    @RequestMapping(path = "/hello", method = RequestMethod.GET)
    public String sayHello() {
        System.out.println("正在處理請求...");
        System.out.println("請求處理完成~");
        return "I'm fine, thank you.";
    }
}

啟動專案,在瀏覽器中訪問 localhost:8080/hello,請求處理完成後,控制檯列印瞭如下資訊:

可以看到,Interceptor 成功攔截到了訪問 Controller 的 /hello 請求和訪問靜態資源的 /favicon.ico 請求,並在請求處理前後執行了相應的處理邏輯。

當需要設定多個 Interceptor 時,可以直接在配置類中新增 Interceptor 的配置規則,例如增加 TestInterceptor2:

@Configuration
public class TestInterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private TestInterceptor testInterceptor;

    @Autowired
    private TestInterceptor2 testInterceptor2;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(testInterceptor)
                .addPathPatterns("/*")
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg")
                .order(1);

        registry.addInterceptor(testInterceptor2)
                .addPathPatterns("/*")
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg")
                .order(2);
    }
}

Interceptor 的執行順序由其配置的 order 決定,order 越小越先執行,注意這裡指的是 preHandle 方法的執行順序,postHandle 和 afterCompletion 的執行順序與 preHandle 相反,例如在上述示例中,執行順序為:

如果我們不配置 order,那麼 Spring 預設將 order 設定為 0(可以檢視 InterceptorRegistration 類的原始碼)。如果不同 Interceptor 具有相同的 order,那麼其執行順序為配置類中的註冊順序

Interceptor 的應用場景

Interceptor 的應用場可以參考上文中介紹的 Filter 的應用場景,可以說 Filter 能做到的事 Interceptor 都能做。由於 Filter 在 Servlet 前後起作用,而 Interceptor 可以在 Controller 方法前後起作用,例如操作 Controller 處理後的 ModelAndView,因此 Interceptor 更加靈活,在 Spring 專案中,如果能使用 Interceptor 的話儘量使用 Interceptor。

Filter 和 Interceptor 的區別

Filter 和 Interceptor 都是 AOP 程式設計思想的提現,且都能實現許可權檢查、日誌記錄等功能,但二者也有許多不同之處:

1. 規範不同

Filter 在 Servlet 規範中定義,依賴於 Servlet 容器(如 Tomcat);Interceptor 由 Spring 定義,依賴於 Spring 容器(IoC 容器)。

2. 適用範圍不同

Filter 僅可用於 Web 程式,因為其依賴於 Servlet 容器;Interceptor 不僅可以用於 Web 程式,還可以用於 Application、Swing 等程式。

3. 實現原理不同

Filter 是基於函式回撥來實現的,Interceptor 則是基於 Java 的反射機制(動態代理)來實現的。

下文中我們重點介紹一下 Filter 的回撥機制。

4. 觸發時機不同

Filter 在請求進入 Servlet 容器,且到達 Servlet 之前對請求做預處理;在 Servlet 處理完請求後對響應做後處理。

Interceptor 在請求進入 Servlet,且到達 Controller 之前對請求做預處理;在 Controller 處理完請求後對 ModelAndView 做後處理,在檢視渲染完成後再做一些收尾工作。

下圖展示了二者的觸發時機:

當 Filter 和 Interceptor 同時存在時,Filter 對請求的預處理要先於 Interceptor 的 preHandle 方法;Filter 對響應的後處理要後於 Interceptor 的 postHandle 方法和 afterCompletion 方法。

關於 Filter 和 Interceptor 的補充說明

1. Filter 的回撥機制

在介紹 Filter 的回撥機制之前,我們先了解一下回撥函式的概念。如果將函式(C++ 中的函式指標,Java 中的匿名函式、方法引用等)作為引數傳遞給主方法,那麼這個函式就稱為回撥函式,主方法會在某一時刻呼叫回撥函式。

為了便於區分,我們使用 "主方法" 和 "函式" 來分辨主函式和回撥函式。

使用回撥函式的好處是能夠實現函式邏輯的解耦,主方法內可以定義通用的處理邏輯,部分特定的操作則交給回撥函式來完成。例如 Java 中 Arrays 類的 sort(T[] a, Comparator<? super T> c) 方法允許我們傳入一個比較器來自定義排序規則,這個比較器的 compare 方法就屬於回撥函式,sort 方法會在排序時呼叫 compare 方法。

接下來介紹 Filter 的回撥機制,上文中提到,我們自定義的 xxFilter 類需要實現 Filter 介面,且需要重寫 doFilter 方法:

public class TestFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // ...
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

Filter 介面的 doFilter 方法接收一個 FilterChain 型別的引數,這個 FilterChain 物件可認為是傳遞給 doFilter 方法的回撥函式,嚴格來說應該是這個 FilterChain 物件的 doFilter 方法,注意這裡提到了兩個 doFilter 方法。Filter 介面的 doFilter 方法在執行結束或執行完某些步驟後會呼叫 FilterChain 物件的 doFilter 方法,即呼叫回撥函式。

FilterChain 物件的實際型別為 ApplicationFilterChain,其 doFilter() 方法的處理邏輯如下(省略部分程式碼):

public final class ApplicationFilterChain implements FilterChain {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        // ...
        internalDoFilter(request,response);
    }
    
    private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        if (pos < n) {
            // 獲取第 pos 個 filter, 即 xxFilter  
            ApplicationFilterConfig filterConfig = filters[pos++];       
            Filter filter = filterConfig.getFilter();
            // ...
            // 呼叫 xxFilter 的 doFilter 方法
            filter.doFilter(request, response, this);
        }
    }
}

可見,ApplicationFilterChain 的 doFilter 方法首先根據索引查詢到我們定義的 xxFilter,然後呼叫 xxFilter 的 doFilter 方法,在呼叫時,ApplicationFilterChain 會將自己作為引數傳遞進去。xxFilter 的 doFilter 方法執行完某些步驟後,會呼叫回撥函式,即 ApplicationFilterChain 的 doFilter 方法,這樣 ApplicationFilterChain 就可以獲取到下一個 xxFilter,並呼叫下一個 xxFilter 的 doFilter 方法,如此迴圈下去,直到所有的 xxFilter 全部被呼叫。整個流程如下圖所示:

xxFilter 執行回撥函式的過程就像是給了 ApplicationFilterChain 一個通知,即通知 ApplicationFilterChain 可以執行下一個 xxFilter 的處理邏輯了。

2. 在 Filter 和 Interceptor 注入 Bean 的注意事項

有些文章在介紹 Filter 和 Interceptor 的區別時強調 Filter 不能通過 IoC 注入 Bean,如果我們採用本文中的第一種建立 Filter,那麼確實不能注入成功:

// 自定義的 Filter, 未新增 @Component 註解
public class TestFilter implements Filter {

    @Autowired
    private UserService userService;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        System.out.println(userService);
        filterChain.doFilter(servletRequest, servletResponse);
    }
    // ...
}

// 配置類
@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<TestFilter> registryFilter() {
        FilterRegistrationBean<TestFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new TestFilter());
        registration.addUrlPatterns("/*");
        registration.setName("TestFilter");
        registration.setOrder(0);
        return registration;
    }
}

上述程式碼執行後,userService 輸出為 null,因為註冊到 IoC 容器中的是 new 出來的一個 TestFilter 物件(registration.setFilter(new TestFilter());),並不是 Spring 自動裝配的。若要使 userService 注入成功,可改為如下寫法:

// 自定義的 Filter, 未新增 @Component 註解
@Component
public class TestFilter implements Filter {

    @Autowired
    private UserService userService;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        System.out.println(userService);
        filterChain.doFilter(servletRequest, servletResponse);
    }
    // ...
}

// 配置類
@Configuration
public class FilterConfig {

    @Autowired
    private TestFilter testFilter;

    @Bean
    public FilterRegistrationBean<TestFilter> registryFilter() {
        FilterRegistrationBean<TestFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(testFilter);
        registration.addUrlPatterns("/*");
        registration.setName("TestFilter");
        registration.setOrder(0);
        return registration;
    }
}

與第一種寫法的區別在於,TestFilter 類上新增了 @Component 註解,且配置類中通過 @Autowired 注入 TestFilter 物件。除了使用配置類外,本文介紹的其它幾種方式(新增 @Component 註解或 @WebFilter 註解)都可以直接注入 Bean。

所以還是採用繼承 OncePerRequestFilter 的方式建立 Filter 比較方便。

另外,使用本文介紹的建立 Interceptor 的寫法是可以直接注入 Bean 的,該寫法也是先在自定義的 Interceptor 上新增 @Component 註解,然後在配置類中使用 @Autowired 注入自定義的 Interceptor。

3. Interceptor 攔截靜態請求

有文章提到 Interceptor 不能攔截靜態請求,其實在 Spring 1.x 的版本中確實是這樣的,但 Spring 2.x 對靜態資源也進行了攔截,例如上文中我們在測試 TestInterceptor 是否生效時,發現其攔截到了 /favicon.ico 請求,該請求是一個由瀏覽器自動傳送的靜態請求。

參考資料

書籍:《Java web 整合開發王者歸來》
Spring Boot 實戰:攔截器與過濾器
過濾器和攔截器的 6 個區別,別再傻傻分不清了

相關文章