容器啟動時過濾器初始化以及排序注冊相關(guān)邏輯快压。
1 @WebFilter過濾器使用@Order無效
啟動程序:
Controller:
實現(xiàn)倆過濾器:
- AuthFilter
- TimeCostFilter
使用 @Order圆仔,期望 TimeCostFilter 先被執(zhí)行,因為TimeCostFilter原本設(shè)計是統(tǒng)計這個接口性能蔫劣,所以需要統(tǒng)計AuthFilter執(zhí)行的授權(quán)過程坪郭。
全部代碼實現(xiàn)完畢,執(zhí)行結(jié)果如下:
觀察日志脉幢,執(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()中尋找一些啟示:
可知磅崭,過濾器執(zhí)行順序由實例變量Filters維護(hù),F(xiàn)ilters是createFilterChain()在容器啟動時順序遍歷StandardContext中的成員變量FilterMaps所獲:
發(fā)現(xiàn)對 StandardContext#FilterMaps 的寫入是在
addFilterMapBefore()
過濾器的執(zhí)行順序由StandardContext#FilterMaps順序決定瓦哎,而FilterMaps則是一個包裝過的數(shù)組砸喻,所以只要進(jìn)一步弄清FilterMaps中各元素的排列順序即可柔逼。
addFilterMapBefore()中加入斷點:
Spring從selfInitialize()一直調(diào)用到addFilterMapBefore()。
selfInitialize()通過調(diào)用getServletContextInitializerBeans()獲取所有ServletContextInitializer類型Bean割岛,并調(diào)用該Bean的onStartup()愉适,最終調(diào)用到addFilterMapBefore()。
上述的selfInitialize()又從何處調(diào)用過來呢癣漆?
selfInitialize()
getServletContextInitializerBeans()
返回的ServletContextInitializer類型Beans順序=》
決定了addFilterMapBefore()調(diào)用順序=》
決定FilterMaps內(nèi)元素順序=》
最終決定了過濾器的執(zhí)行順序维咸。
getServletContextInitializerBeans()
僅返回了ServletContextInitializerBeans類的一個實例,參考代碼如下:
ServletContextInitializerBeans是個集合類
所以惠爽,selfInitialize()能遍歷 ServletContextInitializerBeans癌蓖,查看其iterator()所遍之物:
對外暴露的集合遍歷元素為sortedList成員變量,即selfInitialize()最終遍歷sortedList婚肆。
綜上租副,sortedList中的過濾器Bean元素順序決定最終過濾器的執(zhí)行順序。
繼續(xù)查看ServletContextInitializerBeans構(gòu)造器:
第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子類:
根據(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:
我們沒有定義FilterRegistrationBean痒留,那這里FilterRegistrationBean在哪里被定義出的?其order類成員變量是否有特定取值邏輯蠢沿?
WebFilterHandler#doHandle()
通過 BeanDefinitionBuilder動態(tài)構(gòu)建了FilterRegistrationBean類型BeanDefinition伸头。然而這里并未設(shè)置order值,也沒設(shè)置 @Order 指定值舷蟀。
至此恤磷,也就知道問題根因面哼,所有被@WebFilter注解的類,最終都會在此處被包裝為FilterRegistrationBean類的BeanDefinition扫步。
雖FilterRegistrationBean也實現(xiàn)了Ordered接口
但在這并未填充值魔策,因為:
- 這里所有屬性都是從 @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 :
由于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)造器:
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