別小看“Spring過濾器”蔫巩,這些知識點你必須得掌握!

容器啟動時過濾器初始化以及排序注冊相關(guān)邏輯快压。

1 @WebFilter過濾器使用@Order無效

啟動程序:

image

Controller:

image

實現(xiàn)倆過濾器:

  • AuthFilter
image
  • TimeCostFilter
image

使用 @Order圆仔,期望 TimeCostFilter 先被執(zhí)行,因為TimeCostFilter原本設(shè)計是統(tǒng)計這個接口性能蔫劣,所以需要統(tǒng)計AuthFilter執(zhí)行的授權(quán)過程坪郭。

全部代碼實現(xiàn)完畢,執(zhí)行結(jié)果如下:

image

觀察日志脉幢,執(zhí)行時間并不包含授權(quán)過程歪沃,所以這違背預(yù)期,明明加了 @Order呀嫌松!

但若交換Order指定的值绸罗,發(fā)現(xiàn)也沒效果,why豆瘫?Order不能排序WebFilter珊蟀?

源碼解析

當(dāng)請求來時,會執(zhí)行到 StandardWrapperValve#invoke()外驱,創(chuàng)建 ApplicationFilterChain育灸,并通過ApplicationFilterChain#doFilter() 觸發(fā)過濾器執(zhí)行,并最終執(zhí)行到內(nèi)部私有方法internalDoFilter()昵宇,嘗試在internalDoFilter()中尋找一些啟示:

image

可知磅崭,過濾器執(zhí)行順序由實例變量Filters維護(hù),F(xiàn)ilters是createFilterChain()在容器啟動時順序遍歷StandardContext中的成員變量FilterMaps所獲:

image

發(fā)現(xiàn)對 StandardContext#FilterMaps 的寫入是在

addFilterMapBefore()

image

過濾器的執(zhí)行順序由StandardContext#FilterMaps順序決定瓦哎,而FilterMaps則是一個包裝過的數(shù)組砸喻,所以只要進(jìn)一步弄清FilterMaps中各元素的排列順序即可柔逼。

addFilterMapBefore()中加入斷點:

image

Spring從selfInitialize()一直調(diào)用到addFilterMapBefore()。

selfInitialize()通過調(diào)用getServletContextInitializerBeans()獲取所有ServletContextInitializer類型Bean割岛,并調(diào)用該Bean的onStartup()愉适,最終調(diào)用到addFilterMapBefore()。

image

上述的selfInitialize()又從何處調(diào)用過來呢癣漆?

selfInitialize()

getServletContextInitializerBeans()

返回的ServletContextInitializer類型Beans順序=》

決定了addFilterMapBefore()調(diào)用順序=》

決定FilterMaps內(nèi)元素順序=》

最終決定了過濾器的執(zhí)行順序维咸。

getServletContextInitializerBeans()

僅返回了ServletContextInitializerBeans類的一個實例,參考代碼如下:

image

ServletContextInitializerBeans是個集合類

image

所以惠爽,selfInitialize()能遍歷 ServletContextInitializerBeans癌蓖,查看其iterator()所遍之物:

image

對外暴露的集合遍歷元素為sortedList成員變量,即selfInitialize()最終遍歷sortedList婚肆。
綜上租副,sortedList中的過濾器Bean元素順序決定最終過濾器的執(zhí)行順序。
繼續(xù)查看ServletContextInitializerBeans構(gòu)造器:

image

第87行可知:sortedList元素順序由this.initializers.values通過AnnotationAwareOrderComparator排序而得较性。

AnnotationAwareOrderComparator通過兩種方式獲取比較器需要的order值以決定sortedInitializers的排列順序:

  • 待排序的對象元素實現(xiàn)Order接口用僧,則通過 getOrder() 獲取order值
  • 否則執(zhí)行 OrderUtils.findOrder() 獲取該對象類 @Order 的屬性

因為this.initializers.values類型為ServletContextInitializer,其實現(xiàn)了Ordered接口两残,所以此處比較器使用 getOrder() 獲取order值,對應(yīng)實例變量order把跨。

如下方法均構(gòu)建了ServletContextInitializer子類的實例人弓,并添加到了this.initializers:

  • addAdaptableBeans()
  • addServletContextInitializerBeans()

這里只看addServletContextInitializerBeans,因為使用 @WebFilter 標(biāo)記添加過濾器的方案最終只會通過該方法生效:實例化并注冊了所有的ServletRegistrationBean着逐、FilterRegistrationBean以及ServletListenerRegistrationBean崔赌。

在這個方法中,Spring通過 getOrderedBeansOfType() 實例化了所有ServletContextInitializer子類:

image

根據(jù)不同類型耸别,調(diào)用addServletContextInitializerBean()健芭,ServletContextInitializer子類包括:

  • ServletRegistrationBean
  • FilterRegistrationBean
  • ServletListenerRegistrationBean

正好對應(yīng)了Servlet三大要素。

現(xiàn)在只關(guān)心對應(yīng)Filter的FilterRegistrationBean秀姐,顯然慈迈,F(xiàn)ilterRegistrationBean是ServletContextInitializer的子類(實現(xiàn)了Ordered接口),同樣由成員變量order的值決定其執(zhí)行的優(yōu)先級省有。

private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer,
      ListableBeanFactory beanFactory) {
      ...
   if (initializer instanceof FilterRegistrationBean) {
      Filter source = ((FilterRegistrationBean<?>) initializer).getFilter();
      addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
   }
   ...
}

最終添加到this.initializers:

image

我們沒有定義FilterRegistrationBean痒留,那這里FilterRegistrationBean在哪里被定義出的?其order類成員變量是否有特定取值邏輯蠢沿?

WebFilterHandler#doHandle()

image

通過 BeanDefinitionBuilder動態(tài)構(gòu)建了FilterRegistrationBean類型BeanDefinition伸头。然而這里并未設(shè)置order值,也沒設(shè)置 @Order 指定值舷蟀。

至此恤磷,也就知道問題根因面哼,所有被@WebFilter注解的類,最終都會在此處被包裝為FilterRegistrationBean類的BeanDefinition扫步。

雖FilterRegistrationBean也實現(xiàn)了Ordered接口

image

但在這并未填充值魔策,因為:

  • 這里所有屬性都是從 @WebFilter 對應(yīng)的屬性獲取
  • @WebFilter 本身沒有指定可以輔助排序的屬性

過濾器執(zhí)行順序

  • RegistrationBean中order屬性的值
  • ServletContextInitializerBeans類成員變量sortedList中元素的順序
  • ServletWebServerApplicationContext 中selfInitialize()遍歷FilterRegistrationBean的順序
  • addFilterMapBefore()調(diào)用的順序
  • filterMaps內(nèi)元素的順序
  • 過濾器的執(zhí)行順序

RegistrationBean中order屬性的值最終可以決定過濾器的執(zhí)行順序。

然而锌妻,使用 @WebFilter 時代乃,構(gòu)建的FilterRegistrationBean并未依據(jù) @Order 的值去設(shè)置order屬性,所以 @Order 失效仿粹。

修正

實現(xiàn)自己的FilterRegistrationBean配置添加過濾器搁吓,不再使用 @WebFilter

image

由于WebFilterHandler#doHandle()雖構(gòu)建FilterRegistrationBean類型BeanDefinition,但未設(shè)置order值吭历。

所以堕仔,考慮直接手動實例化FilterRegistrationBean實例且設(shè)置其setOrder()。

切記去掉AuthFilter和TimeCostFilter類中的@WebFilter 即可晌区。

2 竟然重復(fù)執(zhí)行了過濾器

解決排序問題摩骨,可能有人就想了是不是有其他解決方案?

比如在兩個過濾器中使用 @Component朗若,讓@Order生效恼五?

代碼如下。

AuthFilter:

@WebFilter
@Slf4j
@Order(2)
@Component
public class AuthFilter implements Filter {
    @SneakyThrows
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){
        if(isPassAuth()){
            System.out.println("通過授權(quán)");
            chain.doFilter(request, response);
        }else{
            System.out.println("未通過授權(quán)");
            ((HttpServletResponse)response).sendError(401);
        }
    }
    private boolean isPassAuth() throws InterruptedException {
        System.out.println("執(zhí)行檢查權(quán)限");
        Thread.sleep(1000);
        return true;
    }
}

TimeCostFilter類如下:

@WebFilter
@Slf4j
@Order(1)
@Component
public class TimeCostFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("#開始計算接口耗時");
        long start = System.currentTimeMillis();
        chain.doFilter(request, response);
        long end = System.currentTimeMillis();
        long time = end - start;
        System.out.println("#執(zhí)行時間(ms):" + time);
    }
}

最終執(zhí)行結(jié)果如下:

#開始計算接口耗時
執(zhí)行檢查權(quán)限
通過授權(quán)
執(zhí)行檢查權(quán)限
通過授權(quán)
#開始計算接口耗時
......用戶注冊成功
#執(zhí)行時間(ms):73
#執(zhí)行時間(ms):2075

更改 AuthFilter 類中的Order值為0哭懈,繼續(xù)測試灾馒,得到結(jié)果如下:

執(zhí)行檢查權(quán)限
通過授權(quán)
#開始計算接口耗時
執(zhí)行檢查權(quán)限
通過授權(quán)
#開始計算接口耗時
......用戶注冊成功
#執(zhí)行時間(ms):96
#執(zhí)行時間(ms):1100

看來控制Order值可以調(diào)整Filter執(zhí)行順序了,但過濾器本身卻被執(zhí)行2次遣总,why睬罗?

源碼解析

被@WebFilter的過濾器,會在WebServletHandler類中被重新包裝為FilterRegistrationBean類的BeanDefinition旭斥,而非Filter類型容达。

而自定義過濾器增加 @Component 時,懷疑Spring會根據(jù)當(dāng)前類再次包裝一個新過濾器垂券,因而doFIlter()被執(zhí)行兩次花盐。

ServletContextInitializerBeans構(gòu)造器:

image

addAdaptableBeans()

實例化并注冊了所有實現(xiàn)Servlet、Filter及EventListener接口的類菇爪,重點看Filter實現(xiàn)類卒暂,然后再逐一包裝為FilterRegistrationBean。

于是娄帖,可知Spring能直接實例化FilterRegistrationBean類型過濾器的原因:

WebFilterHandler相關(guān)類通過掃描 @WebFilter也祠,動態(tài)構(gòu)建了FilterRegistrationBean類型的BeanDefinition,并注冊到Spring近速;

或我們自己使用 @Bean 顯式實例化FilterRegistrationBean并注冊到Spring诈嘿,如案例1中的解決方案堪旧。

但Filter類型的過濾器如何才能被Spring直接實例化呢?

任何通過 @Component 修飾的的類奖亚,都可自動注冊到Spring淳梦,被Spring直接實例化。

調(diào)用了addAsRegistrationBean()昔字,其beanType為Filter.class:

protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
   // ...
   addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
   // ...
}

繼續(xù)查看最終調(diào)用到的方法addAsRegistrationBean():

private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
      Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
   // 創(chuàng)建所有 Filter 子類的實例爆袍,即所有實現(xiàn)Filter接口且被@Component修飾的類
   List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
   // 依次遍歷這些Filter類實例
   for (Entry<String, B> entry : entries) {
      String beanName = entry.getKey();
      B bean = entry.getValue();
      if (this.seen.add(bean)) {
         // 通過RegistrationBeanAdapter將這些類包裝為RegistrationBean
         RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
         // 獲取Filter類實例的Order值
         int order = getOrder(bean);
         // 設(shè)置到包裝類 RegistrationBean
         registration.setOrder(order);
         // 將RegistrationBean添加到this.initializers
         this.initializers.add(type, registration);
         if (logger.isTraceEnabled()) {
            logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
                  + order + ", resource=" + getResourceDescription(beanName, beanFactory));
         }
      }
   }
}

當(dāng)過濾器同時被 @WebFilter、@Component 修飾時作郭,會導(dǎo)致兩個FilterRegistrationBean實例產(chǎn)生:

  • addServletContextInitializerBeans()
  • addAdaptableBeans()

最終都會創(chuàng)建FilterRegistrationBean的實例陨囊,但不同的是:

  • @WebFilter 會讓addServletContextInitializerBeans()實例化,并注冊所有動態(tài)生成的FilterRegistrationBean類型的過濾器
  • @Component 會讓addAdaptableBeans()實例化所有實現(xiàn)Filter接口的類夹攒,然后再逐一包裝為FilterRegistrationBean類型的過濾器蜘醋。

修正

參考案例1的問題修正部分。
也可去掉@WebFilter保留@Component:

//@WebFilter
@Slf4j
@Order(1)
@Component
public class TimeCostFilter implements Filter {
   //省略非關(guān)鍵代碼
}

總結(jié)

  • @WebFilter和@Component的相同點是:

它們最終都被包裝并實例化成為了FilterRegistrationBean咏尝;

它們最終都是在 ServletContextInitializerBeans的構(gòu)造器中開始被實例化压语。

  • @WebFilter和@Component的不同點:

被@WebFilter修飾的過濾器會被提前在BeanFactoryPostProcessors擴展點包裝成FilterRegistrationBean類型的BeanDefinition,然后在ServletContextInitializerBeans.addServletContextInitializerBeans() 進(jìn)行實例化

@Component修飾的過濾器類编检,在ServletContextInitializerBeans.addAdaptableBeans() 中被實例化成Filter類型后胎食,再包裝為RegistrationBean類型

被@WebFilter修飾的過濾器不會注入Order屬性

被@Component修飾的過濾器會在ServletContextInitializerBeans.addAdaptableBeans() 中注入Order屬性

作者:JavaEdge.
原文鏈接:https://blog.csdn.net/qq_33589510/article/details/120937141

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市允懂,隨后出現(xiàn)的幾起案子厕怜,更是在濱河造成了極大的恐慌,老刑警劉巖累驮,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酣倾,死亡現(xiàn)場離奇詭異舵揭,居然都是意外死亡谤专,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門午绳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來置侍,“玉大人,你說我怎么就攤上這事拦焚±唬” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵赎败,是天一觀的道長秕衙。 經(jīng)常有香客問我,道長僵刮,這世上最難降的妖魔是什么据忘? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任鹦牛,我火速辦了婚禮,結(jié)果婚禮上勇吊,老公的妹妹穿的比我還像新娘曼追。我一直安慰自己,他們只是感情好汉规,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布礼殊。 她就那樣靜靜地躺著,像睡著了一般针史。 火紅的嫁衣襯著肌膚如雪晶伦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天悟民,我揣著相機與錄音坝辫,去河邊找鬼。 笑死射亏,一個胖子當(dāng)著我的面吹牛近忙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播智润,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼及舍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了窟绷?” 一聲冷哼從身側(cè)響起锯玛,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兼蜈,沒想到半個月后攘残,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡为狸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年歼郭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辐棒。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡病曾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出漾根,到底是詐尸還是另有隱情泰涂,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布辐怕,位于F島的核電站逼蒙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏寄疏。R本人自食惡果不足惜是牢,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一顶考、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧妖泄,春花似錦驹沿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至罚渐,卻和暖如春却汉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背荷并。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工合砂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人源织。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓翩伪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谈息。 傳聞我的和親對象是個殘疾皇子缘屹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容