您好,登錄后才能下訂單哦!
本篇內容介紹了“javascript過濾器和攔截器的區別是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
周末有個小伙伴加我微信,向我請教了一個問題:老哥,過濾器 (Filter
) 和 攔截器 (Interceptor
) 有啥區別啊? 聽到題目我的第一感覺就是:簡單!
畢竟這兩種工具開發中用到的頻率都相當高,應用起來也是比較簡單的,可當我準備回復他的時候,竟然不知道從哪說起,支支吾吾了半天,場面炒雞尷尬有木有,工作這么久一個基礎問題答成這樣,丟了大人了。 平時覺得簡單的知識點,但通常都不會太關注細節,一旦被別人問起來,反倒說不出個所以然來。
歸根結底,還是對這些知識了解的不夠,一直停留在會用的階段,以至于現在一看就會一說就廢!這是典型基礎不扎實的表現,哎·~,其實我也就是個虛胖!
知恥而后勇,下邊結合實踐,更直觀的來感受一下兩者到底有什么不同?
我們在項目中同時配置 攔截器
和 過濾器
。
過濾器的配置比較簡單,直接實現Filter
接口即可,也可以通過@WebFilter
注解實現對特定URL
攔截,看到Filter
接口中定義了三個方法。
init()
:該方法在容器啟動初始化過濾器時被調用,它在 Filter
的整個生命周期只會被調用一次。注意:這個方法必須執行成功,否則過濾器會不起作用。
doFilter()
:容器中的每一次請求都會調用該方法, FilterChain
用來調用下一個過濾器 Filter
。
destroy()
: 當容器銷毀 過濾器實例時調用該方法,一般在方法中銷毀或關閉資源,在過濾器 Filter
的整個生命周期也只會被調用一次
@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 后置"); } }
攔截器它是鏈式調用,一個應用中可以同時存在多個攔截器Interceptor
, 一個請求也可以觸發多個攔截器 ,而每個攔截器的調用會依據它的聲明順序依次執行。
首先編寫一個簡單的攔截器處理類,請求的攔截是通過HandlerInterceptor
來實現,看到HandlerInterceptor
接口中也定義了三個方法。
preHandle()
:這個方法將在請求處理之前進行調用。注意:如果該方法的返回值為false
,將視為當前請求結束,不僅自身的攔截器會失效,還會導致其他的攔截器也不再執行。
postHandle()
:只有在 preHandle()
方法返回值為true
時才會執行。會在Controller 中的方法調用之后,DispatcherServlet 返回渲染視圖之前被調用。 有意思的是:postHandle()
方法被調用的順序跟 preHandle()
是相反的,先聲明的攔截器 preHandle()
方法先執行,而postHandle()
方法反而會后執行。
afterCompletion()
:只有在 preHandle()
方法返回值為true
時才會執行。在整個請求結束之后, DispatcherServlet 渲染了對應的視圖之后執行。
@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
。
@Configuration public class MyMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**"); } }
過濾器 和 攔截器 均體現了AOP
的編程思想,都可以實現諸如日志記錄、登錄鑒權等功能,但二者的不同點也是比較多的,接下來一一說明。
過濾器和攔截器 底層實現方式大不相同,過濾器
是基于函數回調的,攔截器
則是基于Java的反射機制(動態代理)實現的。
這里重點說下過濾器!
在我們自定義的過濾器中都會實現一個 doFilter()
方法,這個方法有一個FilterChain
參數,而實際上它是一個回調接口。ApplicationFilterChain
是它的實現類, 這個實現類內部也有一個 doFilter()
方法就是回調方法。
public interface FilterChain { void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException; }
ApplicationFilterChain
里面能拿到我們自定義的xxxFilter
類,在其內部回調方法doFilter()
里調用各個自定義xxxFilter
過濾器,并執行 doFilter()
方法。
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()
方法,以此循環執行實現函數回調。
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { filterChain.doFilter(servletRequest, servletResponse); }
我們看到過濾器 實現的是 javax.servlet.Filter
接口,而這個接口是在Servlet
規范中定義的,也就是說過濾器Filter
的使用要依賴于Tomcat
等容器,導致它只能在web
程序中使用。 而攔截器(Interceptor
) 它是一個Spring
組件,并由Spring
容器管理,并不依賴Tomcat
等容器,是可以單獨使用的。不僅能應用在web
程序中,也可以用于Application
、Swing
等程序中。
過濾器
和 攔截器
的觸發時機也不同,我們看下邊這張圖。
過濾器Filter
是在請求進入容器后,但在進入servlet
之前進行預處理,請求結束是在servlet
處理完以后。
攔截器 Interceptor
是在請求進入servlet
后,在進入Controller
之前進行預處理的,Controller
中渲染了對應的視圖之后請求結束。
在上邊我們已經同時配置了過濾器和攔截器,再建一個Controller
接收請求測試一下。
@Controller @RequestMapping() public class Test { @RequestMapping("/test1") @ResponseBody public String test1(String a) { System.out.println("我是controller"); return null; } }
項目啟動過程中發現,過濾器的init()
方法,隨著容器的啟動進行了初始化。 此時瀏覽器發送請求,F12 看到居然有兩個請求,一個是我們自定義的 Controller
請求,另一個是訪問靜態圖標資源的請求。 看到控制臺的打印日志如下:
執行順序 :Filter 處理中
-> Interceptor 前置
-> 我是controller
-> Interceptor 處理中
-> Interceptor 處理后
Filter 處理中 Interceptor 前置 Interceptor 處理中 Interceptor 后置 Filter 處理中
過濾器Filter
執行了兩次,攔截器Interceptor
只執行了一次。這是因為過濾器幾乎可以對所有進入容器的請求起作用,而攔截器只會對Controller
中請求或訪問static
目錄下的資源請求起作用。
在實際的業務場景中,應用到過濾器或攔截器,為處理業務邏輯難免會引入一些service
服務。
下邊我們分別在過濾器和攔截器中都注入service
,看看有什么不同?
@Component public class TestServiceImpl implements TestService { @Override public void a() { System.out.println("我是方法A"); } }
過濾器中注入service
,發起請求測試一下 ,日志正常打印出“我是方法A”
。
@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); }
Filter 處理中 我是方法A Interceptor 前置 我是controller Interceptor 處理中 Interceptor 后置
在攔截器中注入service
,發起請求測試一下 ,竟然TM的報錯了,debug
跟一下發現注入的service
怎么是Null
啊? 這是因為加載順序導致的問題,攔截器
加載的時間點在springcontext
之前,而Bean
又是由spring
進行管理。
攔截器:老子今天要進洞房; Spring:兄弟別鬧,你媳婦我還沒生出來呢!
解決方案也很簡單,我們在注冊攔截器之前,先將Interceptor
手動進行注入。注意:在registry.addInterceptor()
注冊的是getMyInterceptor()
實例。
@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("/**"); } }
實際開發過程中,會出現多個過濾器或攔截器同時存在的情況,不過,有時我們希望某個過濾器或攔截器能優先執行,就涉及到它們的執行順序。
過濾器用@Order
注解控制執行順序,通過@Order
控制過濾器的級別,值越小級別越高越先執行。
@Order(Ordered.HIGHEST_PRECEDENCE) @Component public class MyFilter2 implements Filter {
攔截器默認的執行順序,就是它的注冊順序,也可以通過Order
手動設置控制,值越小越先執行。
@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()
居然是相反的!如果實際開發中嚴格要求執行順序,那就需要特別注意這一點。
Interceptor1 前置 Interceptor2 前置 Interceptor 前置 我是controller Interceptor 處理中 Interceptor2 處理中 Interceptor1 處理中 Interceptor 后置 Interceptor2 處理后 Interceptor1 處理后
那為什么會這樣呢? 得到答案就只能看源碼了,我們要知道controller
中所有的請求都要經過核心組件DispatcherServlet
路由,都會執行它的 doDispatch()
方法,而攔截器postHandle()
、preHandle()
方法便是在其中調用的。
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()
執行順序是相反的了。
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; }
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()
方法執行的順序相反。
“javascript過濾器和攔截器的區別是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。