Filter和Interceptor的比較

Filter

  1. Filterservlet規(guī)范中定義的java web組件, 在所有支持java web的容器中都可以使用

  2. FilterFilter Chain是密不可分的, Filter可以實現(xiàn)依次調(diào)用正是因為有了Filter Chain

    Filter調(diào)用鏈

    上圖是Filter對請求進行攔截的原理圖, 那么java web容器(以tomcat為例子)是如何實現(xiàn)這個功能的呢?

    下面看下FilterFilter Chain的源碼

       // Filter
        public interface Filter {
    
            // 容器創(chuàng)建的時候調(diào)用, 即啟動tomcat的時候調(diào)用
            public void init(FilterConfig filterConfig) throws ServletException;
        
            // 由FilterChain調(diào)用, 并且傳入Filter Chain本身
            public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException;
        
            // 容器銷毀的時候調(diào)用, 即關閉tomcat的時候調(diào)用
            public void destroy();
        }
        
        // FilterChain
        public interface FilterChain {
        
            // 由Filter.doFilter()中的chain.doFilter調(diào)用
            public void doFilter(ServletRequest request, ServletResponse response)
                throws IOException, ServletException;
        }
    
    • 正是因為Filter Chain在調(diào)用每一個Filter.doFilter()時將自身引用傳遞進去, 才實現(xiàn)了Filter的依次調(diào)用, 在Filter全部調(diào)用完之后再調(diào)用真正處理請求的servlet, 并且再次逆序回調(diào)Filter. 可能這么看還是不太明白是怎么實現(xiàn)Filter的順序調(diào)用, 調(diào)用真正的servlet, 逆序調(diào)用Filter的, 一起看下Tomcat的源碼就一目了然了.
  3. 在tomcat中Filter Chain的默認實現(xiàn)是ApplicationFilterChain, 在ApplicationFilterChain中最關鍵的方法就是internalDoFilter, 整個Filter流程的實現(xiàn)就是由這個方法完成.

       // internalDoFilter(只保留關鍵代碼)
        private void internalDoFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {
    
            // Call the next filter if there is one
            // pos: 當前的filter的索引, n: 調(diào)用鏈中所有的Filter的數(shù)量
            // 如果調(diào)用鏈中還有沒有調(diào)用的Filter就繼續(xù)調(diào)用, 否則跳過if語句
            if (pos < n) {
                ApplicationFilterConfig filterConfig = filters[pos++];
                try {
                    // 獲取Filter
                    Filter filter = filterConfig.getFilter();
                    if( Globals.IS_SECURITY_ENABLED ) {
                        ...
                        其他代碼
                        ...    
                    } else {
                        // 這句話是重點, 調(diào)用Filter的doFilter方法并把Filter Chain本身傳進去(this)
                        filter.doFilter(request, response, this);
                    }
                } catch (IOException | ServletException | RuntimeException e) {
                    ...
                    異常處理代碼
                    ...    
                }
                return;
            }
    
            // We fell off the end of the chain -- call the servlet instance
            try {
                ...
                其他代碼
                ...
                // Use potentially wrapped request from this point
                if ((request instanceof HttpServletRequest) &&
                        (response instanceof HttpServletResponse) &&
                        Globals.IS_SECURITY_ENABLED ) {
                    ...
                    其他代碼
                    ...
                } else {
                        // 調(diào)用真正的Filter
                    servlet.service(request, response);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                ...
                異常處理代碼
                ...
            } finally {
                ...
                始終要執(zhí)行的代碼
                ...
            }
        }
    
    • 看了上面我添加的注釋之后應該可以知道Filter的正序調(diào)用的過程和調(diào)用真正的servlet的過程了, 但是Filter的逆序調(diào)用在哪里體現(xiàn)了呢?
  4. 假設下面的Filter就是調(diào)用鏈中的最后一個Filter

public class LogFilter implements Filter {
        public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        Log.info("before");
            chain.doFilter(request, response);       
            Log.info("after");
     }
}
  • 那么在調(diào)用chain.doFilter之后就跳過了if語句從而調(diào)用了真正的servlet, 然后internalDoFilter方法就結束(出棧)了, 緊接著就是調(diào)用Log.info("after")了, 然后LogFilter的doFilter就結束了(也出棧了), 緊接著就是internalDoFilterfilter.doFilter(request, response, this)的結束然后return, 然后就是調(diào)用上一個filter的chain.doFilter()之后的代碼, 以此類推.

  • 因此Filter調(diào)用鏈的實現(xiàn)其實就是一個方法調(diào)用鏈的過程. 剛開始, Filter Chain每調(diào)用一個Filter.doFilter()方法就是向方法調(diào)用棧中進行壓棧操作(代碼上的體現(xiàn)就是執(zhí)行Filter.doFilter之前的代碼), 當Filter全部調(diào)用完成之后就調(diào)用真正處理請求的servlet, 然后由方法調(diào)用鏈自動進行出棧操作(代碼上的體現(xiàn)就是執(zhí)行Filter.doFilter之后的代碼), 從而完成整個Filter的調(diào)用鏈. 因為Filter功能實現(xiàn)實際上就是利用了方法的壓棧出棧, 所以可以在調(diào)用chain.doFilter之前將方法返回, 讓容器不在調(diào)用servlet方法, 從而實現(xiàn)權限的控制, 關鍵詞的過濾等功能.

Interceptor

  1. Interceptor不是servlet規(guī)范中的java web組件, 而是Spring提供的組件, 功能上和Filter差不多. 但是實現(xiàn)上和Filter不一樣.

Interceptor功能的實現(xiàn)主要是在Spring Mvc的DispatcherServelt.doDispatch方法中, 讓我們來看看源碼

// Interceptor的源碼
public interface HandlerInterceptor {

    // 在調(diào)用真正的處理請求類之前調(diào)用
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception;

    // 在調(diào)用真正的處理請求類之后調(diào)用
    void postHandle(
            HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception;

 // 在完成渲染或者出錯之后調(diào)用
    void afterCompletion(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception;

}


// doDispatch源碼(只保留關鍵代碼)

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                ....
                其它的處理代碼
                ....
                
                // 調(diào)用攔截器的前置處理方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler.
                // 調(diào)用真正的處理請求的方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                // 找到渲染模版
                applyDefaultViewName(processedRequest, mv);
                
                // 調(diào)用攔截器的后置處理方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                ....
                異常處理代碼
                ....
        }
        finally {
            ....
            始終要執(zhí)行的代碼
            ....
    }

其實看了doDispatch的關鍵代碼, Spring Mvc對整個請求的處理流程已經(jīng)很清楚了:

調(diào)用攔截器的前置方法 -> 調(diào)用處理請求的方法 -> 渲染模版 -> 調(diào)用攔截器的后置處理方法 -> 調(diào)用攔截器的完成方法

接下來看一看Spring Mvc是如何實現(xiàn)依此調(diào)用這么多攔截器的前置方法, 后置方法, 完成方法的

進入到mapperHandler.applyPreHandle()方法中(調(diào)用攔截器的前置方法)

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        // 如果攔截器數(shù)組不為空
        if (!ObjectUtils.isEmpty(interceptors)) {
           // 按順序調(diào)用攔截器數(shù)組中的preHandle方法
            for (int i = 0; i < interceptors.length; i++) {
                HandlerInterceptor interceptor = interceptors[i];
                // 如果攔截器的preHandle方法返回false, 則調(diào)用當前攔截器的triggerAfterCompletion方法, 然后返回, 并且不再調(diào)用后續(xù)的攔截器
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
    }

進入到mappedHandler.applyPostHandle()方法中(調(diào)用攔截器的后置方法)

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        // 如果攔截器數(shù)組不為空
        if (!ObjectUtils.isEmpty(interceptors)) {
            // 倒序調(diào)用攔截器數(shù)組中攔截器的postHandle方法
            for (int i = interceptors.length - 1; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }

不管是否出異常triggerAfterCompletion方法始終會被調(diào)用

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
            throws Exception {

        HandlerInterceptor[] interceptors = getInterceptors();
        // 攔截器數(shù)組不為空
        if (!ObjectUtils.isEmpty(interceptors)) {
           // 從成功執(zhí)行的最后一個攔截器開始逆序調(diào)用afterCompletion方法
            for (int i = this.interceptorIndex; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                try {
                    interceptor.afterCompletion(request, response, this.handler, ex);
                }
                catch (Throwable ex2) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
                }
            }
        }
    }

看過以上三個方法之后, Spring Mvc如何處理攔截器的前置, 后置, 完成方法就一目了然了. 其實Spring Mvc就是將攔截器統(tǒng)一放到了攔截器數(shù)組中, 然后在調(diào)用真正的處理請求方法之前和之后正序或者倒序遍歷攔截器, 同時調(diào)用攔截器的相應的方法. 最后不管是否正常結束這個流程還是出異常都會從成功的最后一個攔截器開始逆序調(diào)用afterCompletion方法

總結

  1. 從以上分析可以看到過濾器和攔截器實現(xiàn)的方式的不同. Filter是利用了方法的調(diào)用(入棧出棧)完成整個流程, 而Interceptor是利用了for循環(huán)完成了整個流程.
  2. Filter的實現(xiàn)比較占用??臻g, 在Filter多的情況下可能會有棧溢出的風險存在.
  3. Interceptor的實現(xiàn)邏輯更加的清晰簡單
  4. Filter組件更加的通用, 只要支持java servlet的容器都可以使用, 而Interceptor必須依賴于Spring
  5. Filter的優(yōu)先級是高于Interceptor, 即請求是先到Filter再到Interceptor的, 因為Interceptor的實現(xiàn)主體還是一個servlet
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容