自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入解析:Spring中Filter與Interceptor的區(qū)別及正確用法

開發(fā) 前端
Filter 用于Tomcat 等 Web 容器中的 Servlet 相關(guān)處理,而并非 Spring 原生的工具。這一發(fā)現(xiàn)有助于我們理解為什么 Spring 中的過濾器和攔截器具有相似的功能。

自從我們開始使用 Spring,我們經(jīng)常聽到過濾器(Filter)和攔截器(Interceptor)。然而,當(dāng)真正需要使用它們時(shí),可能會(huì)對(duì)它們的區(qū)別和相似點(diǎn)感到困惑。產(chǎn)生這種困惑的主要原因是它們的用途相似(例如,授權(quán)檢查、日志處理、數(shù)據(jù)壓縮/解壓等)。

使用過濾器可以實(shí)現(xiàn)的場(chǎng)景同樣可以用攔截器實(shí)現(xiàn),因此它們的邊界變得模糊不清。為了解釋它們的差異和相似之處,我們將深入探討兩者的起源和設(shè)計(jì)理念。

本文基于 SpringBoot 2.7.5 版本進(jìn)行講解。

過濾器:外來引入的概念

基本概念

仔細(xì)研究源代碼,我們會(huì)發(fā)現(xiàn)過濾器的概念實(shí)際上是從 Servlet 引入的外來概念,它遵循 Servlet 規(guī)范??梢钥匆幌?Filter 類的全限定名稱:

javax.servlet.Filter

可以看出,F(xiàn)ilter 用于Tomcat 等 Web 容器中的 Servlet 相關(guān)處理,而并非 Spring 原生的工具。這一發(fā)現(xiàn)有助于我們理解為什么 Spring 中的過濾器和攔截器具有相似的功能。

由于它們分別由不同的作者為各自的系統(tǒng)創(chuàng)建,因此出現(xiàn)了類似的思想和實(shí)現(xiàn)方法也是可以理解的。畢竟,英雄所見略同。

隨后,Spring 引入并兼容了 Tomcat 容器的處理邏輯,使得兩個(gè)相似的概念可以存在于同一應(yīng)用上下文中(注意,Spring 并沒有將它們合并,而只是使其兼容),這也導(dǎo)致開發(fā)人員容易產(chǎn)生困惑。

為了更好地理解 Filter 的作用,讓我們引入官方的注釋進(jìn)行說明:

過濾器是一個(gè)對(duì)象,它可以對(duì)對(duì)資源的請(qǐng)求(如 servlet 或靜態(tài)內(nèi)容)或資源的響應(yīng)或兩者執(zhí)行過濾任務(wù)。

從這個(gè)定義中,我們可以提取兩條有用的信息:

  • 執(zhí)行時(shí)機(jī):Filter 的執(zhí)行時(shí)機(jī)有兩個(gè),在請(qǐng)求處理前和在響應(yīng)返回前。
  • 執(zhí)行內(nèi)容:過濾器本質(zhì)上執(zhí)行的是過濾任務(wù),而過濾條件基于對(duì)資源的請(qǐng)求或?qū)Y源的響應(yīng)。

除了上述信息外,結(jié)合 Tomcat 中 Servlet 容器的結(jié)構(gòu)設(shè)計(jì),我們可以推導(dǎo)出 Filter 的執(zhí)行流程圖:

圖片圖片

在實(shí)際開發(fā)場(chǎng)景中,資源請(qǐng)求的預(yù)處理或資源響應(yīng)的后處理可能并不限于單一類型的過濾任務(wù)。

因此,Tomcat 設(shè)計(jì)中使用了責(zé)任鏈模式來處理需要多種不同類型過濾器處理請(qǐng)求或響應(yīng)的場(chǎng)景。

這一概念也體現(xiàn)在前面提到的流程圖中。需要注意的是,由于采用了線性數(shù)據(jù)結(jié)構(gòu)(鏈結(jié)構(gòu)),在實(shí)際的過濾器操作過程中存在固有的執(zhí)行順序。這意味著在實(shí)現(xiàn)自定義過濾器時(shí),必須確保過濾器之間不存在依賴反轉(zhuǎn)。

當(dāng)然,如果過濾器之間沒有依賴關(guān)系,那么執(zhí)行順序就不是問題。Tomcat 使用 org.apache.catalina.core.ApplicationFilterChain 來實(shí)現(xiàn)上述的責(zé)任鏈模式。可以通過以下代碼更好地理解這一概念:

publicfinalclassApplicationFilterChainimplementsFilterChain{

publicvoiddoFilter(ServletRequest request,ServletResponse response)
throwsIOException,ServletException{

if(Globals.IS_SECURITY_ENABLED){
finalServletRequest req = request;
finalServletResponse res = response;
try{
java.security.AccessController.doPrivileged(
(java.security.PrivilegedExceptionAction<Void>)()->{
// 實(shí)際執(zhí)行過濾操作
internalDoFilter(req,res);
returnnull;
}
);
}catch(PrivilegedActionException pe){
...
}
}else{
// 實(shí)際執(zhí)行過濾操作
internalDoFilter(request,response);
}
}


privatevoidinternalDoFilter(ServletRequest request,
ServletResponse response)
throwsIOException,ServletException{

// 如果存在下一個(gè)過濾器,則調(diào)用它
if(pos < n){
ApplicationFilterConfig filterConfig = filters[pos++];
try{
Filter filter = filterConfig.getFilter();
...
if(Globals.IS_SECURITY_ENABLED){
...
}else{
// 結(jié)合 Filter 類進(jìn)行分析,實(shí)際上是執(zhí)行回調(diào)函數(shù),
// 該方法的第三個(gè)參數(shù)傳遞了當(dāng)前的 applicationFilterChain 對(duì)象,結(jié)合上面的 pos 指針確定過濾鏈?zhǔn)欠褚淹耆珗?zhí)行
                    filter.doFilter(request, response,this);
}
}catch(IOException|ServletException|RuntimeException e){
throw e;
}catch(Throwable e){
...
}
return;
}

// 執(zhí)行到鏈的末端——調(diào)用 servlet 實(shí)例
try{
...
// 實(shí)際執(zhí)行 servlet 服務(wù),注意這僅是進(jìn)入 servlet 實(shí)例,而未真正進(jìn)入具體處理器
            servlet.service(request, response);
...
}catch(IOException|ServletException|RuntimeException e){
throw e;
}catch(Throwable e){
...
}finally{
...
}
}
}

從上述代碼可以看出,Tomcat 使用 pos 指針來記錄過濾器鏈中過濾器的執(zhí)行位置。只有在鏈中的所有過濾器都執(zhí)行完畢并通過后,request 和 response 對(duì)象才會(huì)提交給 servlet 實(shí)例進(jìn)行相應(yīng)的服務(wù)處理。

需要注意的是,此時(shí)尚未涉及具體的 handler,意味著過濾器的處理無法細(xì)化到具體處理器類的請(qǐng)求/響應(yīng),而只能較為模糊地處理整個(gè) servlet 實(shí)例級(jí)別的請(qǐng)求/響應(yīng)。

當(dāng)然,從上述代碼中還可以看出一個(gè)問題,即似乎僅對(duì)資源請(qǐng)求進(jìn)行過濾處理,而沒有對(duì)資源響應(yīng)進(jìn)行過濾處理。

實(shí)際上,資源響應(yīng)的過濾處理隱藏在每個(gè)過濾器的 doFilter 方法中。當(dāng)實(shí)現(xiàn)自定義過濾器時(shí),需要遵循以下邏輯來處理資源響應(yīng):

@Override
publicvoiddoFilter(ServletRequest request,ServletResponse response,FilterChain chain)throwsIOException,ServletException{
// TODO 前置處理
// 調(diào)用 applicationFilterChain 對(duì)象的 doFilter 方法(這實(shí)際上是回調(diào)邏輯)。必須包含這一步,否則鏈?zhǔn)浇Y(jié)構(gòu)會(huì)在此處中斷。
    chain.doFilter(request, response);
// TODO 后置處理
}

結(jié)合 ApplicationFilterChain 中的 internalDoFilter 方法,可以發(fā)現(xiàn)隱含的入棧和出棧邏輯(本質(zhì)上是方法堆棧)。資源請(qǐng)求的前置處理實(shí)際上是一個(gè)入棧過程,當(dāng)所有前置處理過濾器入棧完畢后,servlet.service(request, response) 開始執(zhí)行。

在 servlet 服務(wù)處理完成后,出棧過程開始,逐個(gè)按順序執(zhí)行后置處理邏輯,直至方法結(jié)束退出。

必須指出,這種邏輯對(duì)初學(xué)者來說不太友好。由于 Filter 只是一個(gè)接口,無法像抽象類那樣提供模板方法,初學(xué)者在沒有參考示例的情況下可能很難使用,若只是查看源碼可能會(huì)有類似疑問。

還要提醒大家,實(shí)現(xiàn)自定義過濾器時(shí)必須遵循上述模板,否則可能會(huì)導(dǎo)致鏈?zhǔn)搅鞒瘫黄茐幕蚝笾眠壿嫙o法實(shí)現(xiàn)。

在 Spring 中的使用

雖然提到了 Spring,但這里實(shí)際討論的是 Spring Boot 中的使用方法。要在 Spring Boot 中實(shí)現(xiàn)自定義過濾器,只需添加注入邏輯將其放入 Spring 容器。Spring Boot 提供了兩種方式來完成此操作:

  • 在自定義過濾器上使用 @Component 注解;
  • 在自定義過濾器上使用 @WebFilter 注解,并在啟動(dòng)類上使用 @ServletComponentScan 注解;

推薦使用第二種方法注入過濾器,因?yàn)?Spring 提供了 Tomcat 原生處理不具備的額外功能,即 URL 匹配功能。

結(jié)合 @WebFilter 注解中的 urlPattern 字段,Spring 能進(jìn)一步細(xì)化過濾器處理的粒度,使開發(fā)者更靈活。此外,可通過 Spring 提供的 @Order 注解來自定義過濾器的注入順序。

攔截器:Spring 原生功能

基本概念

探討完過濾器后,我們將目光轉(zhuǎn)向攔截器。此時(shí)發(fā)現(xiàn),攔截器的概念源自 Spring,對(duì)應(yīng)的接口類為 HandlerInterceptor(還有一個(gè)異步攔截器接口類,此處不展開,有興趣的同學(xué)可自行閱讀源碼)。

查看相應(yīng)源碼后發(fā)現(xiàn),HandlerInterceptor 提供了三個(gè)與執(zhí)行時(shí)機(jī)相關(guān)的方法,而不同于 Filter 僅提供一個(gè)簡(jiǎn)單的 doFilter 方法:

  • preHandle:在執(zhí)行相應(yīng)處理程序之前執(zhí)行,進(jìn)行前置處理;
  • postHandle:在請(qǐng)求處理完成后但在渲染 ModelAndView 對(duì)象之前執(zhí)行,進(jìn)行與 ModelAndView 對(duì)象相關(guān)的后置處理;
  • afterCompletion:在渲染 ModelAndView 對(duì)象后且在返回響應(yīng)前執(zhí)行,對(duì)結(jié)果進(jìn)行后置處理。

與 Filter 類僅提供的 doFilter 方法相比,HandlerInterceptor 的方法定義更為精準(zhǔn)和易用。無需閱讀源碼或參考示例,便可大致猜測(cè)如何實(shí)現(xiàn)自定義攔截器。

結(jié)合 org.springframework.web.servlet.DispatcherServlet#doDispatch 的源碼,可以繪制出以下流程圖(此處不貼出具體代碼,有興趣的同學(xué)可自行查看):

圖片圖片

可以看到,攔截器的執(zhí)行邏輯全部包含在 servlet 實(shí)例中。結(jié)合前述過濾器的執(zhí)行流程說明,不難發(fā)現(xiàn)過濾器就像夾心餅干的兩片餅干,將 servlet 和攔截器包在中間,攔截器的執(zhí)行時(shí)機(jī)在過濾器前置處理之后、后置處理之前。

此外,通過閱讀源碼還可發(fā)現(xiàn),Spring 在使用攔截器時(shí)同樣使用了責(zé)任鏈模式。在不同任務(wù)和邏輯需順序執(zhí)行的場(chǎng)景中,這種模式十分有用。

需要注意的是,由于 Spring 在設(shè)計(jì)攔截器時(shí)已明確定義了不同階段的方法,因此攔截器的實(shí)際執(zhí)行過程并未采用與過濾器相同的推棧和彈棧方式。

在 Spring 中的使用

要在 Spring Boot 中使用攔截器,除了實(shí)現(xiàn) HandlerInterceptor 接口外,還需要顯式地在 Spring 的 Web 配置中進(jìn)行注冊(cè),如下所示:

@Configuration
publicclassWebConfigimplementsWebMvcConfigurer{

@Override
publicvoidaddInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(newDemoInterceptor()).addPathPatterns("/api/*").excludePathPatterns("/api/ok");
}
}

從上述代碼可以看到,Spring 也為自定義攔截器提供了與過濾器相同的路徑匹配功能。借助該功能,自定義攔截器可以更細(xì)致地處理請(qǐng)求和響應(yīng)。這一點(diǎn)再次重疊了過濾器的功能,但這當(dāng)然是 Spring 內(nèi)部提供的功能。

常見使用場(chǎng)景

確實(shí),在文章開頭我們已介紹了一些兩者的功能。這里再簡(jiǎn)單總結(jié)一下。

從以上分析可以看出,過濾器和攔截器的設(shè)計(jì)初衷是將請(qǐng)求的前置處理和響應(yīng)的后置處理從業(yè)務(wù)代碼中分離出來,作為通用處理邏輯供開發(fā)者擴(kuò)展實(shí)現(xiàn)。這一設(shè)計(jì)思想類似于 AOP。

在實(shí)際開發(fā)中,自定義過濾器或攔截器常用于實(shí)現(xiàn)以下操作:

  • 用戶登錄驗(yàn)證;
  • 權(quán)限檢查;
  • 日志攔截;
  • 數(shù)據(jù)壓縮/解壓;
  • 加解密處理;

這里不展示各場(chǎng)景的編碼實(shí)現(xiàn),有興趣的同學(xué)可以自行搜索學(xué)習(xí)。

一點(diǎn)建議:雖然上述場(chǎng)景看似繁多,但其實(shí)本質(zhì)都是在處理請(qǐng)求參數(shù)或響應(yīng)結(jié)果。理解這一點(diǎn)后,設(shè)計(jì)和實(shí)現(xiàn)這些場(chǎng)景就會(huì)相對(duì)容易。

總結(jié)

通過以上分析可見,過濾器和攔截器在Spring Boot中的核心區(qū)別在于執(zhí)行時(shí)機(jī)、應(yīng)用場(chǎng)景及使用便捷性。過濾器圍繞請(qǐng)求的全流程運(yùn)行,適合系統(tǒng)級(jí)通用邏輯處理(如數(shù)據(jù)壓縮、編碼設(shè)置),而攔截器位于控制器層面,更適合業(yè)務(wù)邏輯擴(kuò)展(如權(quán)限校驗(yàn)、日志記錄)。

設(shè)計(jì)上,過濾器通過“推入-彈出”機(jī)制延續(xù)過濾鏈,邏輯較為復(fù)雜;而攔截器提供明確的接口方法,執(zhí)行流程更為直觀。此外,二者均采用職責(zé)鏈模式,體現(xiàn)AOP思想,幫助實(shí)現(xiàn)請(qǐng)求的分層處理。

因此,開發(fā)中根據(jù)需求選擇工具即可:系統(tǒng)級(jí)處理優(yōu)先過濾器,業(yè)務(wù)級(jí)處理優(yōu)先攔截器。

責(zé)任編輯:武曉燕 來源: 路條編程
相關(guān)推薦

2010-08-23 10:57:14

CSSclassid

2024-05-16 12:24:53

2010-08-30 10:52:39

CSSclassid

2021-09-15 16:20:02

Spring BootFilterJava

2010-08-30 10:37:54

DIVSPAN

2024-03-07 13:30:44

Java對(duì)象true

2022-01-13 10:04:21

攔截器Interceptor過濾器

2024-06-17 10:45:07

C++編程操作符

2024-04-30 08:38:31

C++

2024-03-11 15:32:50

C++開發(fā)

2024-10-10 14:43:54

LambdaSpring編程

2024-12-27 08:09:04

2024-01-24 08:31:13

extends?接口規(guī)范

2024-04-09 08:57:25

SizeofC++字符串

2023-11-26 18:02:00

ReactDOM

2010-07-12 12:32:35

UML用例圖

2025-01-07 13:48:57

2010-09-25 15:30:10

虛擬化

2021-12-28 20:06:43

JavaScript開發(fā)數(shù)組

2025-02-27 08:30:10

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)