日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
一口氣說出過濾器和攔截器6個區(qū)別,別再傻傻分不清了

本文轉(zhuǎn)載自微信公眾號「程序員內(nèi)點事」,轉(zhuǎn)載本文請聯(lián)系程序員內(nèi)點事公眾號。

公司主營業(yè)務(wù):成都網(wǎng)站設(shè)計、成都網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)、移動網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。創(chuàng)新互聯(lián)建站是一支青春激揚、勤奮敬業(yè)、活力青春激揚、勤奮敬業(yè)、活力澎湃、和諧高效的團隊。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團隊有機會用頭腦與智慧不斷的給客戶帶來驚喜。創(chuàng)新互聯(lián)建站推出保德免費做網(wǎng)站回饋大家。

周末有個小伙伴加我微信,向我請教了一個問題:老哥,「過濾器 (Filter) 和 攔截器 (Interceptor) 有啥區(qū)別啊?」 聽到題目我的第一感覺就是:「簡單」!

畢竟這兩種工具開發(fā)中用到的頻率都相當(dāng)高,應(yīng)用起來也是比較簡單的,可當(dāng)我準(zhǔn)備回復(fù)他的時候,竟然不知道從哪說起,支支吾吾了半天,場面炒雞尷尬有木有,工作這么久一個基礎(chǔ)問題答成這樣,丟了大人了。

平時覺得簡單的知識點,但通常都不會太關(guān)注細(xì)節(jié),一旦被別人問起來,反倒說不出個所以然來。

歸根結(jié)底,還是對這些知識了解的不夠,一直停留在會用的階段,以至于現(xiàn)在「一看就會一說就廢」!這是典型基礎(chǔ)不扎實的表現(xiàn),哎·~,其實我也就是個虛胖!

知恥而后勇,下邊結(jié)合實踐,更直觀的來感受一下兩者到底有什么不同?

準(zhǔn)備環(huán)境

我們在項目中同時配置 攔截器 和 過濾器。

1、過濾器 (Filter)

過濾器的配置比較簡單,直接實現(xiàn)Filter 接口即可,也可以通過@WebFilter注解實現(xiàn)對特定URL攔截,看到Filter 接口中定義了三個方法。

  • init() :該方法在容器啟動初始化過濾器時被調(diào)用,它在 Filter 的整個生命周期只會被調(diào)用一次?!缸⒁狻梗哼@個方法必須執(zhí)行成功,否則過濾器會不起作用。
  • doFilter() :容器中的每一次請求都會調(diào)用該方法, FilterChain 用來調(diào)用下一個過濾器 Filter。
  • destroy():當(dāng)容器銷毀 過濾器實例時調(diào)用該方法,一般在方法中銷毀或關(guān)閉資源,在過濾器 Filter 的整個生命周期也只會被調(diào)用一次
 
 
 
 
  1. @Component 
  2. public class MyFilter implements Filter { 
  3.      
  4.     @Override 
  5.     public void init(FilterConfig filterConfig) throws ServletException { 
  6.  
  7.         System.out.println("Filter 前置"); 
  8.     } 
  9.  
  10.     @Override 
  11.     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 
  12.  
  13.         System.out.println("Filter 處理中"); 
  14.         filterChain.doFilter(servletRequest, servletResponse); 
  15.     } 
  16.  
  17.     @Override 
  18.     public void destroy() { 
  19.  
  20.         System.out.println("Filter 后置"); 
  21.     } 

2、攔截器 (Interceptor)

攔截器它是鏈?zhǔn)秸{(diào)用,一個應(yīng)用中可以同時存在多個攔截器Interceptor, 一個請求也可以觸發(fā)多個攔截器 ,而每個攔截器的調(diào)用會依據(jù)它的聲明順序依次執(zhí)行。

首先編寫一個簡單的攔截器處理類,請求的攔截是通過HandlerInterceptor 來實現(xiàn),看到HandlerInterceptor 接口中也定義了三個方法。

  • preHandle() :這個方法將在請求處理之前進行調(diào)用?!缸⒁狻梗喝绻摲椒ǖ姆祷刂禐閒alse ,將視為當(dāng)前請求結(jié)束,不僅自身的攔截器會失效,還會導(dǎo)致其他的攔截器也不再執(zhí)行。
  • postHandle():只有在 preHandle() 方法返回值為true 時才會執(zhí)行。會在Controller 中的方法調(diào)用之后,DispatcherServlet 返回渲染視圖之前被調(diào)用?!赣幸馑嫉氖恰梗簆ostHandle() 方法被調(diào)用的順序跟 preHandle() 是相反的,先聲明的攔截器 preHandle() 方法先執(zhí)行,而postHandle()方法反而會后執(zhí)行。

afterCompletion():只有在 preHandle() 方法返回值為true 時才會執(zhí)行。在整個請求結(jié)束之后, DispatcherServlet 渲染了對應(yīng)的視圖之后執(zhí)行。

 
 
 
 
  1. @Component 
  2. public class MyInterceptor implements HandlerInterceptor { 
  3.  
  4.     @Override 
  5.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 
  6.  
  7.         System.out.println("Interceptor 前置"); 
  8.         return true; 
  9.     } 
  10.  
  11.     @Override 
  12.     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 
  13.  
  14.         System.out.println("Interceptor 處理中"); 
  15.     } 
  16.  
  17.     @Override 
  18.     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 
  19.  
  20.         System.out.println("Interceptor 后置"); 
  21.     } 

將自定義好的攔截器處理類進行注冊,并通過addPathPatterns、excludePathPatterns等屬性設(shè)置需要攔截或需要排除的 URL。

 
 
 
 
  1. @Configuration 
  2. public class MyMvcConfig implements WebMvcConfigurer { 
  3.  
  4.     @Override 
  5.     public void addInterceptors(InterceptorRegistry registry) { 
  6.         registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); 
  7.         registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**"); 
  8.     } 

我們不一樣

過濾器 和 攔截器 均體現(xiàn)了AOP的編程思想,都可以實現(xiàn)諸如日志記錄、登錄鑒權(quán)等功能,但二者的不同點也是比較多的,接下來一一說明。

1、實現(xiàn)原理不同

過濾器和攔截器 底層實現(xiàn)方式大不相同,過濾器 是基于函數(shù)回調(diào)的,攔截器 則是基于Java的反射機制(動態(tài)代理)實現(xiàn)的。

這里重點說下過濾器!

在我們自定義的過濾器中都會實現(xiàn)一個 doFilter()方法,這個方法有一個FilterChain 參數(shù),而實際上它是一個回調(diào)接口。ApplicationFilterChain是它的實現(xiàn)類, 這個實現(xiàn)類內(nèi)部也有一個 doFilter() 方法就是回調(diào)方法。

 
 
 
 
  1. public interface FilterChain { 
  2.     void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException; 

ApplicationFilterChain里面能拿到我們自定義的xxxFilter類,在其內(nèi)部回調(diào)方法doFilter()里調(diào)用各個自定義xxxFilter過濾器,并執(zhí)行 doFilter() 方法。

 
 
 
 
  1. public final class ApplicationFilterChain implements FilterChain { 
  2.     @Override 
  3.     public void doFilter(ServletRequest request, ServletResponse response) { 
  4.             ...//省略 
  5.             internalDoFilter(request,response); 
  6.     } 
  7.   
  8.     private void internalDoFilter(ServletRequest request, ServletResponse response){ 
  9.     if (pos < n) { 
  10.             //獲取第pos個filter     
  11.             ApplicationFilterConfig filterConfig = filters[pos++];         
  12.             Filter filter = filterConfig.getFilter(); 
  13.             ... 
  14.             filter.doFilter(request, response, this); 
  15.         } 
  16.     } 
  17.   

而每個xxxFilter 會先執(zhí)行自身的 doFilter() 過濾邏輯,最后在執(zhí)行結(jié)束前會執(zhí)行filterChain.doFilter(servletRequest, servletResponse),也就是回調(diào)ApplicationFilterChain的doFilter() 方法,以此循環(huán)執(zhí)行實現(xiàn)函數(shù)回調(diào)。

 
 
 
 
  1. @Override 
  2.    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 
  3.  
  4.        filterChain.doFilter(servletRequest, servletResponse); 
  5.    } 

2、使用范圍不同

我們看到過濾器 實現(xiàn)的是 javax.servlet.Filter 接口,而這個接口是在Servlet規(guī)范中定義的,也就是說過濾器Filter 的使用要依賴于Tomcat等容器,導(dǎo)致它只能在web程序中使用。

而攔截器(Interceptor) 它是一個Spring組件,并由Spring容器管理,并不依賴Tomcat等容器,是可以單獨使用的。不僅能應(yīng)用在web程序中,也可以用于Application、Swing等程序中。

3、觸發(fā)時機不同

過濾器 和 攔截器的觸發(fā)時機也不同,我們看下邊這張圖。

過濾器Filter是在請求進入容器后,但在進入servlet之前進行預(yù)處理,請求結(jié)束是在servlet處理完以后。

攔截器 Interceptor 是在請求進入servlet后,在進入Controller之前進行預(yù)處理的,Controller 中渲染了對應(yīng)的視圖之后請求結(jié)束。

4、攔截的請求范圍不同

在上邊我們已經(jīng)同時配置了過濾器和攔截器,再建一個Controller接收請求測試一下。

 
 
 
 
  1. @Controller 
  2. @RequestMapping() 
  3. public class Test { 
  4.  
  5.     @RequestMapping("/test1") 
  6.     @ResponseBody 
  7.     public String test1(String a) { 
  8.         System.out.println("我是controller"); 
  9.         return null; 
  10.     } 

項目啟動過程中發(fā)現(xiàn),過濾器的init()方法,隨著容器的啟動進行了初始化。

此時瀏覽器發(fā)送請求,F(xiàn)12 看到居然有兩個請求,一個是我們自定義的 Controller請求,另一個是訪問靜態(tài)圖標(biāo)資源的請求。

看到控制臺的打印日志如下:

執(zhí)行順序 :Filter 處理中 -> Interceptor 前置 -> 我是controller -> Interceptor 處理中 -> Interceptor 處理后Filter 處理中Interceptor 前置Interceptor 處理中Interceptor 后置Filter 處理中

過濾器Filter執(zhí)行了兩次,攔截器Interceptor只執(zhí)行了一次。這是因為過濾器幾乎可以對所有進入容器的請求起作用,而攔截器只會對Controller中請求或訪問static目錄下的資源請求起作用。

5、注入Bean情況不同

在實際的業(yè)務(wù)場景中,應(yīng)用到過濾器或攔截器,為處理業(yè)務(wù)邏輯難免會引入一些service服務(wù)。

下邊我們分別在過濾器和攔截器中都注入service,看看有什么不同?

 
 
 
 
  1. @Component 
  2. public class TestServiceImpl implements TestService { 
  3.  
  4.     @Override 
  5.     public void a() { 
  6.         System.out.println("我是方法A"); 
  7.     } 

過濾器中注入service,發(fā)起請求測試一下 ,日志正常打印出“我是方法A”。

 
 
 
 
  1. @Autowired 
  2.     private TestService testService; 
  3.  
  4.     @Override 
  5.     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 
  6.  
  7.         System.out.println("Filter 處理中"); 
  8.         testService.a(); 
  9.         filterChain.doFilter(servletRequest, servletResponse); 
  10.     } 
 
 
 
 
  1. Filter 處理中 
  2. 我是方法A 
  3. Interceptor 前置 
  4. 我是controller 
  5. Interceptor 處理中 
  6. Interceptor 后置 

在攔截器中注入service,發(fā)起請求測試一下 ,竟然TM的報錯了,debug跟一下發(fā)現(xiàn)注入的service怎么是Null啊?

這是因為加載順序?qū)е碌膯栴},攔截器加載的時間點在springcontext之前,而Bean又是由spring進行管理。

攔截器:老子今天要進洞房;Spring:兄弟別鬧,你媳婦我還沒生出來呢!

解決方案也很簡單,我們在注冊攔截器之前,先將Interceptor 手動進行注入?!缸⒁狻梗涸趓egistry.addInterceptor()注冊的是getMyInterceptor() 實例。

 
 
 
 
  1. @Configuration 
  2. public class MyMvcConfig implements WebMvcConfigurer { 
  3.  
  4.     @Bean 
  5.     public MyInterceptor getMyInterceptor(){ 
  6.         System.out.println("注入了MyInterceptor"); 
  7.         return new MyInterceptor(); 
  8.     } 
  9.      
  10.     @Override 
  11.     public void addInterceptors(InterceptorRegistry registry) { 
  12.  
  13.         registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**"); 
  14.     } 

6、控制執(zhí)行順序不同

實際開發(fā)過程中,會出現(xiàn)多個過濾器或攔截器同時存在的情況,不過,有時我們希望某個過濾器或攔截器能優(yōu)先執(zhí)行,就涉及到它們的執(zhí)行順序。

過濾器用@Order注解控制執(zhí)行順序,通過@Order控制過濾器的級別,值越小級別越高越先執(zhí)行。

 
 
 
 
  1. @Order(Ordered.HIGHEST_PRECEDENCE) 
  2. @Component 
  3. public class MyFilter2 implements Filter { 

攔截器默認(rèn)的執(zhí)行順序,就是它的注冊順序,也可以通過Order手動設(shè)置控制,值越小越先執(zhí)行。

 
 
 
 
  1. @Override 
  2.    public void addInterceptors(InterceptorRegistry registry) { 
  3.        registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2); 
  4.        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1); 
  5.        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3); 
  6.    } 

看到輸出結(jié)果發(fā)現(xiàn),先聲明的攔截器 preHandle() 方法先執(zhí)行,而postHandle()方法反而會后執(zhí)行。

postHandle() 方法被調(diào)用的順序跟 preHandle() 居然是相反的!如果實際開發(fā)中嚴(yán)格要求執(zhí)行順序,那就需要特別注意這一點。

 
 
 
 
  1. Interceptor1 前置 
  2. Interceptor2 前置 
  3. Interceptor 前置 
  4. 我是controller 
  5. Interceptor 處理中 
  6. Interceptor2 處理中 
  7. Interceptor1 處理中 
  8. Interceptor 后置 
  9. Interceptor2 處理后 
  10. Interceptor1 處理后 

「那為什么會這樣呢?」 得到答案就只能看源碼了,我們要知道controller 中所有的請求都要經(jīng)過核心組件DispatcherServlet路由,都會執(zhí)行它的 doDispatch() 方法,而攔截器postHandle()、preHandle()方法便是在其中調(diào)用的。

 
 
 
 
  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { 
  2.      
  3.         try { 
  4.          ........... 
  5.             try { 
  6.             
  7.                 // 獲取可以執(zhí)行當(dāng)前Handler的適配器 
  8.                 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 
  9.  
  10.                 // Process last-modified header, if supported by the handler. 
  11.                 String method = request.getMethod(); 
  12.                 boolean isGet = "GET".equals(method); 
  13.                 if (isGet || "HEAD".equals(method)) { 
  14.                     long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); 
  15.                     if (logger.isDebugEnabled()) { 
  16.                         logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); 
  17.                     } 
  18.                     if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { 
  19.                         return; 
  20.                     } 
  21.                 } 
  22.                 // 注意: 執(zhí)行Interceptor中PreHandle()方法 
  23.                 if (!mappedHandler.applyPreHandle(processedRequest, response)) { 
  24.                     return; 
  25.                 } 
  26.  
  27.                 // 注意:執(zhí)行Handle【包括我們的業(yè)務(wù)邏輯,當(dāng)拋出異常時會被Try、catch到】 
  28.                 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 
  29.  
  30.                 if (asyncManager.isConcurrentHandlingStarted()) { 
  31.                     return; 
  32.                 } 
  33.                 applyDefaultViewName(processedRequest, mv); 
  34.  
  35.                 // 注意:執(zhí)行Interceptor中PostHandle 方法【拋出異常時無法執(zhí)行】 
  36.                 mappedHandler.applyPostHandle(processedRequest, response, mv); 
  37.             } 
  38.         } 
  39.         ........... 
  40.     } 

看看兩個方法applyPreHandle()、applyPostHandle()具體是如何被調(diào)用的,就明白為什么postHandle()、preHandle() 執(zhí)行順序是相反的了。

 
 
 
 
  1. boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception { 
  2.         HandlerInterceptor[] interceptors = this.getInterceptors(); 
  3.         if(!ObjectUtils.isEmpty(interceptors)) { 
  4.             for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) { 
  5.                 HandlerInterceptor interceptor = interceptors[i]; 
  6.                 if(!interceptor.preHandle(request, response, this.handler)) { 
  7.                     this.triggerAfterCompletion(request, response, (Exception)null); 
  8.                     return false; 
  9.                 } 
  10.             } 
  11.         } 
  12.  
  13.         return true; 
  14.     } 
 
 
 
 
  1. void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception { 
  2.         HandlerInterceptor[] interceptors = this.getInterceptors(); 
  3.         if(!ObjectUtils.isEmpty(interceptors)) { 
  4.             for(int i = interceptors.length - 1; i >= 0; --i) { 
  5.                 HandlerInterceptor interceptor = interceptors[i]; 
  6.                 interceptor.postHandle(request, response, this.handler, mv); 
  7.             } 
  8.         } 
  9.     } 

發(fā)現(xiàn)兩個方法中在調(diào)用攔截器數(shù)組 HandlerInterceptor[] 時,循環(huán)的順序竟然是相反的。。。,導(dǎo)致postHandle()、preHandle() 方法執(zhí)行的順序相反。

總結(jié)

我相信大部分人都能熟練使用濾器和攔截器,但兩者的差別還是需要多了解下,不然開發(fā)中使用不當(dāng),時不時就會出現(xiàn)奇奇怪怪的問題,以上內(nèi)容比較簡單,新手學(xué)習(xí)老鳥復(fù)習(xí),有遺漏的地方還望大家積極補充,如有理解錯誤之處,還望不吝賜教。


網(wǎng)頁標(biāo)題:一口氣說出過濾器和攔截器6個區(qū)別,別再傻傻分不清了
文章網(wǎng)址:http://www.dlmjj.cn/article/cddgeii.html