聊一聊過濾器與攔截器

過濾器 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 個區別,別再傻傻分不清了