1. 程式人生 > >過濾器 和 攔截器 6個區別,別再傻傻分不清了

過濾器 和 攔截器 6個區別,別再傻傻分不清了

>本文收錄在個人部落格:[www.chengxy-nds.top](http://www.chengxy-nds.top),技術資料共享,同進步 週末有個小夥伴加我微信,向我請教了一個問題:老哥,**過濾器 (`Filter`) 和 攔截器 (`Interceptor`) 有啥區別啊?** 聽到題目我的第一感覺就是:**簡單**! 畢竟這兩種工具開發中用到的頻率都相當高,應用起來也是比較簡單的,可當我準備回覆他的時候,竟然不知道從哪說起,支支吾吾了半天,場面炒雞尷尬有木有,工作這麼久一個基礎問題答成這樣,丟了大人了。 ![自導自演,別太當真過,哈哈哈](https://img-blog.csdnimg.cn/20200601163732522.png) 平時覺得簡單的知識點,但通常都不會太關注細節,一旦被別人問起來,反倒說不出個所以然來。 歸根結底,還是對這些知識瞭解的不夠,一直停留在會用的階段,以至於現在**一看就會一說就廢**!這是典型基礎不紮實的表現,哎·~,其實我也就是個虛胖! 知恥而後勇,下邊結合實踐,更直觀的來感受一下兩者到底有什麼不同? ### 準備環境 我們在專案中同時配置 `攔截器` 和 `過濾器`。 #### 1、過濾器 (Filter) 過濾器的配置比較簡單,直接實現`Filter` 介面即可,也可以通過`@WebFilter`註解實現對特定`URL`攔截,看到`Filter` 介面中定義了三個方法。 - `init()` :該方法在容器啟動初始化過濾器時被呼叫,它在 `Filter` 的整個生命週期只會被呼叫一次。**注意**:這個方法必須執行成功,否則過濾器會不起作用。 - `doFilter()` :容器中的每一次請求都會呼叫該方法, `FilterChain` 用來呼叫下一個過濾器 `Filter`。 - `destroy()`: 當容器銷燬 過濾器例項時呼叫該方法,一般在方法中銷燬或關閉資源,在過濾器 `Filter` 的整個生命週期也只會被呼叫一次 ```javascript @Component public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("Filter 前置"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("Filter 處理中"); filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { System.out.println("Filter 後置"); } } ``` #### 2、攔截器 (Interceptor) 攔截器它是鏈式呼叫,一個應用中可以同時存在多個攔截器`Interceptor`, 一個請求也可以觸發多個攔截器 ,而每個攔截器的呼叫會依據它的宣告順序依次執行。 首先編寫一個簡單的攔截器處理類,請求的攔截是通過`HandlerInterceptor` 來實現,看到`HandlerInterceptor` 介面中也定義了三個方法。 - `preHandle()` :這個方法將在請求處理之前進行呼叫。**注意**:如果該方法的返回值為`false` ,將視為當前請求結束,不僅自身的攔截器會失效,還會導致其他的攔截器也不再執行。 - `postHandle()`:只有在 `preHandle()` 方法返回值為`true` 時才會執行。會在Controller 中的方法呼叫之後,DispatcherServlet 返回渲染檢視之前被呼叫。 **有意思的是**:`postHandle()` 方法被呼叫的順序跟 `preHandle()` 是相反的,先宣告的攔截器 `preHandle()` 方法先執行,而`postHandle()`方法反而會後執行。 - `afterCompletion()`:只有在 `preHandle()` 方法返回值為`true` 時才會執行。在整個請求結束之後, DispatcherServlet 渲染了對應的檢視之後執行。 ```javascript @Component public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("Interceptor 前置"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("Interceptor 處理中"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("Interceptor 後置"); } } ``` 將自定義好的攔截器處理類進行註冊,並通過`addPathPatterns`、`excludePathPatterns`等屬性設定需要攔截或需要排除的 `URL`。 ```javascript @Configuration public class MyMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**"); } } ``` ### 我們不一樣 過濾器 和 攔截器 均體現了`AOP`的程式設計思想,都可以實現諸如日誌記錄、登入鑑權等功能,但二者的不同點也是比較多的,接下來一一說明。 #### 1、實現原理不同 過濾器和攔截器 底層實現方式大不相同,`過濾器` 是基於函式回撥的,`攔截器` 則是基於Java的反射機制(動態代理)實現的。 這裡重點說下過濾器! 在我們自定義的過濾器中都會實現一個 `doFilter()`方法,這個方法有一個`FilterChain` 引數,而實際上它是一個回撥介面。`ApplicationFilterChain`是它的實現類, 這個實現類內部也有一個 `doFilter()` 方法就是回撥方法。 ```javascript public interface FilterChain { void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException; } ``` ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020060311274589.png?#pic_center) `ApplicationFilterChain`裡面能拿到我們自定義的`xxxFilter`類,在其內部回撥方法`doFilter()`裡呼叫各個自定義`xxxFilter`過濾器,並執行 `doFilter()` 方法。 ```javascript public final class ApplicationFilterChain implements FilterChain { @Override public void doFilter(ServletRequest request, ServletResponse response) { ...//省略 internalDoFilter(request,response); } private void internalDoFilter(ServletRequest request, ServletResponse response){ if (pos < n) { //獲取第pos個filter ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = filterConfig.getFilter(); ... filter.doFilter(request, response, this); } } } ``` 而每個`xxxFilter` 會先執行自身的 `doFilter()` 過濾邏輯,最後在執行結束前會執行`filterChain.doFilter(servletRequest, servletResponse)`,也就是回撥`ApplicationFilterChain`的`doFilter()` 方法,以此迴圈執行實現函式回撥。 ```javascript @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { filterChain.doFilter(servletRequest, servletResponse); } ``` #### 2、使用範圍不同 我們看到過濾器 實現的是 `javax.servlet.Filter` 介面,而這個介面是在`Servlet`規範中定義的,也就是說過濾器`Filter` 的使用要依賴於`Tomcat`等容器,導致它只能在`web`程式中使用。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200602141320121.png?) 而攔截器(`Interceptor`) 它是一個`Spring`元件,並由`Spring`容器管理,並不依賴`Tomcat`等容器,是可以單獨使用的。不僅能應用在`web`程式中,也可以用於`Application`、`Swing`等程式中。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200602172813157.png) #### 3、觸發時機不同 `過濾器` 和 `攔截器`的觸發時機也不同,我們看下邊這張圖。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200602173814901.png?#pic_center) 過濾器`Filter`是在請求進入容器後,但在進入`servlet`之前進行預處理,請求結束是在`servlet`處理完以後。 攔截器 `Interceptor` 是在請求進入`servlet`後,在進入`Controller`之前進行預處理的,`Controller` 中渲染了對應的檢視之後請求結束。 #### 4、攔截的請求範圍不同 在上邊我們已經同時配置了過濾器和攔截器,再建一個`Controller`接收請求測試一下。 ```javascript @Controller @RequestMapping() public class Test { @RequestMapping("/test1") @ResponseBody public String test1(String a) { System.out.println("我是controller"); return null; } } ``` 專案啟動過程中發現,過濾器的`init()`方法,隨著容器的啟動進行了初始化。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200603152935516.png?) 此時瀏覽器傳送請求,F12 看到居然有兩個請求,一個是我們自定義的 `Controller` 請求,另一個是訪問靜態圖示資源的請求。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020060315412216.png) 看到控制檯的列印日誌如下: 執行順序 :`Filter 處理中` ->
`Interceptor 前置` -> `我是controller` -> `Interceptor 處理中` -> `Interceptor 處理後` ```javascript Filter 處理中 Interceptor 前置 Interceptor 處理中 Interceptor 後置 Filter 處理中 ``` 過濾器`Filter`執行了兩次,攔截器`Interceptor`只執行了一次。這是因為過濾器幾乎可以對所有進入容器的請求起作用,而攔截器只會對`Controller`中請求或訪問`static`目錄下的資源請求起作用。 #### 5、注入Bean情況不同 在實際的業務場景中,應用到過濾器或攔截器,為處理業務邏輯難免會引入一些`service`服務。 下邊我們分別在過濾器和攔截器中都注入`service`,看看有什麼不同? ```javascript @Component public class TestServiceImpl implements TestService { @Override public void a() { System.out.println("我是方法A"); } } ``` 過濾器中注入`service`,發起請求測試一下 ,日誌正常打印出`“我是方法A”`。 ```javascript @Autowired private TestService testService; @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("Filter 處理中"); testService.a(); filterChain.doFilter(servletRequest, servletResponse); } ``` ```javascript Filter 處理中 我是方法A Interceptor 前置 我是controller Interceptor 處理中 Interceptor 後置 ``` 在攔截器中注入`service`,發起請求測試一下 ,竟然TM的報錯了,`debug`跟一下發現注入的`service`怎麼是`Null`啊? ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200603163633360.png?) 這是因為載入順序導致的問題,`攔截器`載入的時間點在`springcontext`之前,而`Bean`又是由`spring`進行管理。 >
攔截器:老子今天要進洞房; >Spring:兄弟別鬧,你媳婦我還沒生出來呢! 解決方案也很簡單,我們在註冊攔截器之前,先將`Interceptor` 手動進行注入。**注意**:在`registry.addInterceptor()`註冊的是`getMyInterceptor()` 例項。 ```javascript @Configuration public class MyMvcConfig implements WebMvcConfigurer { @Bean public MyInterceptor getMyInterceptor(){ System.out.println("注入了MyInterceptor"); return new MyInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**"); } } ``` #### 6、控制執行順序不同 實際開發過程中,會出現多個過濾器或攔截器同時存在的情況,不過,有時我們希望某個過濾器或攔截器能優先執行,就涉及到它們的執行順序。 過濾器用`@Order`註解控制執行順序,通過`@Order`控制過濾器的級別,值越小級別越高越先執行。 ```javascript @Order(Ordered.HIGHEST_PRECEDENCE) @Component public class MyFilter2 implements Filter { ``` 攔截器預設的執行順序,就是它的註冊順序,也可以通過`Order`手動設定控制,值越小越先執行。 ```javascript @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2); registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1); registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3); } ``` 看到輸出結果發現,先宣告的攔截器 `preHandle()` 方法先執行,而`postHandle()`方法反而會後執行。 `postHandle()` 方法被呼叫的順序跟 `preHandle()` 居然是相反的!如果實際開發中嚴格要求執行順序,那就需要特別注意這一點。 ```javascript Interceptor1 前置 Interceptor2 前置 Interceptor 前置 我是controller Interceptor 處理中 Interceptor2 處理中 Interceptor1 處理中 Interceptor 後置 Interceptor2 處理後 Interceptor1 處理後 ``` **那為什麼會這樣呢?** 得到答案就只能看原始碼了,我們要知道`controller` 中所有的請求都要經過核心元件`DispatcherServlet`路由,都會執行它的 `doDispatch()` 方法,而攔截器`postHandle()`、`preHandle()`方法便是在其中呼叫的。 ```javascript protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { try { ........... try { // 獲取可以執行當前Handler的介面卡 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } // 注意: 執行Interceptor中PreHandle()方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // 注意:執行Handle【包括我們的業務邏輯,當丟擲異常時會被Try、catch到】 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); // 注意:執行Interceptor中PostHandle 方法【丟擲異常時無法執行】 mappedHandler.applyPostHandle(processedRequest, response, mv); } } ........... } ``` 看看兩個方法`applyPreHandle()`、`applyPostHandle()`具體是如何被呼叫的,就明白為什麼`postHandle()`、`preHandle()` 執行順序是相反的了。 ```javascript boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if(!ObjectUtils.isEmpty(interceptors)) { for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) { HandlerInterceptor interceptor = interceptors[i]; if(!interceptor.preHandle(request, response, this.handler)) { this.triggerAfterCompletion(request, response, (Exception)null); return false; } } } return true; } ``` ```javascript void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { HandlerInterceptor[] interceptors = this.getInterceptors(); if(!ObjectUtils.isEmpty(interceptors)) { for(int i = interceptors.length - 1; i >= 0; --i) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(request, response, this.handler, mv); } } } ``` 發現兩個方法中在呼叫攔截器陣列 `HandlerInterceptor[]` 時,迴圈的順序竟然是相反的。。。,導致`postHandle()`、`preHandle()` 方法執行的順序相反。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200603184307199.png?) ### 總結 我相信大部分人都能熟練使用濾器和攔截器,但兩者的差別還是需要多瞭解下,不然開發中使用不當,時不時就會出現奇奇怪怪的問題,以上內容比較簡單,新手學習老鳥複習,有遺漏的地方還望大家積極補充,如有理解錯誤之處,還望不吝賜教。 --- 原創不易,**燃燒秀髮輸出內容** >整理了幾百本各類技術電子書, 送給小夥伴們, 我的同名公眾號自行領取。和一些小夥伴們建了一個技術交流群,一起探討技術、分享技術資料,旨在共同學習進步,如果感興趣就加入我們吧! ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC8yLzQvMTcwMGU0Mjk1MDQzMjQ0Yg?x-oss-process=image/for