在 Spring 編程中,主要配合如下注解構建過濾器:
- @ServletComponentScan
- @WebFilter
那這看起來只是用上這倆注解就能繼續摸魚了呀,但上了生產后,還是能遇到花式問題:
- 作業不起來
- 順序不對
- 執行多次等
大多因為想當然覺得使用簡單,沒有上心,還是有必要精通過濾器執行的流程和原理,
@WebFilter 過濾器無法被自動注入
為統計介面耗時,實作一個過濾器:

該過濾器標記了 @WebFilter,所以啟動程式加上掃描注解 @ServletComponentScan 讓其生效:

然后,提供一個 UserController:

發現應用啟動失敗

TimeCostFilter 看起來是個普通 Bean啊,為何不能被自動注入?
原始碼決議
本質上,過濾器被 @WebFilter 修飾后,TimeCostFilter 只會被包裝為 FilterRegistrationBean,而 TimeCostFilter 本身只會作為一個 InnerBean 被實體化,這意味著 TimeCostFilter 實體并不會作為 Bean 注冊到 Spring 容器,

所以當我們想自動注入 TimeCostFilter 時,就會失敗,知道這個結論后,我們可以帶著兩個問題去理清一些關鍵的邏輯:
FilterRegistrationBean 是什么?它是如何被定義的
javax.servlet.annotation.WebFilter

所以它不屬 Spring,而是 Servlet 規范,
Spring Boot 專案使用它時,Spring Boot 使用了 org.springframework.boot.web.servlet.FilterRegistrationBean 包裝 @WebFilter 標記的實體,
實作上來說,即 FilterRegistrationBean#Filter 屬性就是 @WebFilter 標記的實體,這點我們可以從之前給出的截圖中看出端倪,
定義一個 Filter 類時,我們可能想的是,會自動生成它的實體,然后以 Filter 的名稱作為 Bean 名來指向它,
但除錯發現,在 Spring Boot 中,Bean 名字確實是對的,只是 Bean 實體其實是 FilterRegistrationBean,
這 FilterRegistrationBean 最早是如何獲取的呢?
得追溯到 @WebFilter 注解是如何被處理的,
@WebFilter 是如何作業的
使用 @WebFilter 時,Filter 被加載有兩個條件:
- 宣告了 @WebFilter
- 在能被 @ServletComponentScan 掃到的路徑下
直接搜索對 @WebFilter 的使用,可發現 WebFilterHandler 使用了它:

因此,我們選擇在 doHandle() 打斷點

debug啟動,觀察呼叫堆疊:

可見對 @WebFilter 的處理是在SB啟動時,在ServletComponentRegisteringPostProcessor被觸發,實作對如下注解的的掃描和處理:
- @WebFilter
- @WebListener
- @WebServlet
WebFilterHandler則負責處理 @WebFilter 的使用:
最后,WebServletHandler 通過父類 ServletComponentHandler 的模版方法模式,處理了所有被 @WebFilter 注解的類:
可見最終注冊的 FilterRegistrationBean就是自定義的WebFilter,
看第二個問題:
何時實體化TimeCostFilter
TimeCostFilter 是何時實體化的呢?為什么它沒有成為一個普通 Bean?
可在 TimeCostFilter 構造器中加斷點,便于快速定位初始化時機:

結合原始碼,可發現:
- Tomcat 啟動時(onstartUp),才會創建 FilterRegistrationBean
- FilterRegistrationBean 在被創建時(createBean)會創建 TimeCostFilter 裝配自身,而 TimeCostFilter 是通過 ResolveInnerBean 創建的
- TimeCostFilter 實體最終是一種 InnerBean

所以最終 TimeCostFilter 實體是一種 InnerBean,也就無法自動注入了,
修正
找到根因,就知道如何解決了,
前文決議可知,使用 @WebFilter 修飾過濾器時,TimeCostFilter 型別的 Bean 并沒有注冊至 Spring 容器,真正注冊的是 FilterRegistrationBean,
考慮到還可能存在多個 Filter,可這樣修改:

- 注入 FilterRegistrationBean 型別,而非 TimeCostFilter 型別
- 注入的名稱是包含包名的全限定名,不能直接用
TimeCostFilter,以便存在多個過濾器時,能精確匹配,
總結
@WebFilter 這種方式構建的 Filter 無法直接根據過濾器定義型別自動注入,因為這種Filter本身是以內部Bean呈現,最終是通過FilterRegistrationBean呈現給Spring,
所以可通過自動注入FilterRegistrationBean型別完成自動裝配,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/323429.html
標籤:java
上一篇:【JavaSE】 - 運算子
