Filter和Interceptor的比較

Filter

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

  2. FilterFilter Chain是密不可分的, Filter可以實(shí)現(xiàn)依次調(diào)用正是因?yàn)橛辛?code>Filter Chain

    Filter調(diào)用鏈

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

    下面看下FilterFilter Chain的源碼

       // Filter
        public interface Filter {
    
            // 容器創(chuàng)建的時(shí)候調(diào)用, 即啟動(dòng)tomcat的時(shí)候調(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;
        
            // 容器銷毀的時(shí)候調(diào)用, 即關(guān)閉tomcat的時(shí)候調(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;
        }
    
    • 正是因?yàn)?code>Filter Chain在調(diào)用每一個(gè)Filter.doFilter()時(shí)將自身引用傳遞進(jìn)去, 才實(shí)現(xiàn)了Filter的依次調(diào)用, 在Filter全部調(diào)用完之后再調(diào)用真正處理請(qǐng)求的servlet, 并且再次逆序回調(diào)Filter. 可能這么看還是不太明白是怎么實(shí)現(xiàn)Filter的順序調(diào)用, 調(diào)用真正的servlet, 逆序調(diào)用Filter的, 一起看下Tomcat的源碼就一目了然了.
  3. 在tomcat中Filter Chain的默認(rèn)實(shí)現(xiàn)是ApplicationFilterChain, 在ApplicationFilterChain中最關(guān)鍵的方法就是internalDoFilter, 整個(gè)Filter流程的實(shí)現(xiàn)就是由這個(gè)方法完成.

       // internalDoFilter(只保留關(guān)鍵代碼)
        private void internalDoFilter(ServletRequest request, ServletResponse response)
            throws IOException, ServletException {
    
            // Call the next filter if there is one
            // pos: 當(dāng)前的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ǎn), 調(diào)用Filter的doFilter方法并把Filter Chain本身傳進(jìn)去(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í)行的代碼
                ...
            }
        }
    
    • 看了上面我添加的注釋之后應(yīng)該可以知道Filter的正序調(diào)用的過程和調(diào)用真正的servlet的過程了, 但是Filter的逆序調(diào)用在哪里體現(xiàn)了呢?
  4. 假設(shè)下面的Filter就是調(diào)用鏈中的最后一個(gè)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方法就結(jié)束(出棧)了, 緊接著就是調(diào)用Log.info("after")了, 然后LogFilter的doFilter就結(jié)束了(也出棧了), 緊接著就是internalDoFilterfilter.doFilter(request, response, this)的結(jié)束然后return, 然后就是調(diào)用上一個(gè)filter的chain.doFilter()之后的代碼, 以此類推.

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

Interceptor

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

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

// Interceptor的源碼
public interface HandlerInterceptor {

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

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

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

}


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

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)用真正的處理請(qǐng)求的方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

其實(shí)看了doDispatch的關(guān)鍵代碼, Spring Mvc對(duì)整個(gè)請(qǐng)求的處理流程已經(jīng)很清楚了:

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

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

進(jìn)入到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)用當(dāng)前攔截器的triggerAfterCompletion方法, 然后返回, 并且不再調(diào)用后續(xù)的攔截器
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
    }

進(jìn)入到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方法始終會(huì)被調(diào)用

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

        HandlerInterceptor[] interceptors = getInterceptors();
        // 攔截器數(shù)組不為空
        if (!ObjectUtils.isEmpty(interceptors)) {
           // 從成功執(zhí)行的最后一個(gè)攔截器開始逆序調(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);
                }
            }
        }
    }

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

總結(jié)

  1. 從以上分析可以看到過濾器和攔截器實(shí)現(xiàn)的方式的不同. Filter是利用了方法的調(diào)用(入棧出棧)完成整個(gè)流程, 而Interceptor是利用了for循環(huán)完成了整個(gè)流程.
  2. Filter的實(shí)現(xiàn)比較占用棧空間, 在Filter多的情況下可能會(huì)有棧溢出的風(fēng)險(xiǎn)存在.
  3. Interceptor的實(shí)現(xiàn)邏輯更加的清晰簡(jiǎn)單
  4. Filter組件更加的通用, 只要支持java servlet的容器都可以使用, 而Interceptor必須依賴于Spring
  5. Filter的優(yōu)先級(jí)是高于Interceptor, 即請(qǐng)求是先到Filter再到Interceptor的, 因?yàn)?code>Interceptor的實(shí)現(xiàn)主體還是一個(gè)servlet
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末堆生,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖竭恬,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扒磁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡境析,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門派诬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劳淆,“玉大人,你說我怎么就攤上這事默赂∨嫱遥” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵缆八,是天一觀的道長(zhǎng)曲掰。 經(jīng)常有香客問我疾捍,道長(zhǎng),這世上最難降的妖魔是什么蜈缤? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任拾氓,我火速辦了婚禮,結(jié)果婚禮上底哥,老公的妹妹穿的比我還像新娘咙鞍。我一直安慰自己,他們只是感情好趾徽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布续滋。 她就那樣靜靜地躺著,像睡著了一般孵奶。 火紅的嫁衣襯著肌膚如雪疲酌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天了袁,我揣著相機(jī)與錄音朗恳,去河邊找鬼。 笑死载绿,一個(gè)胖子當(dāng)著我的面吹牛粥诫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播崭庸,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼怀浆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了怕享?” 一聲冷哼從身側(cè)響起执赡,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎函筋,沒想到半個(gè)月后沙合,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驻呐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年灌诅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片含末。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖即舌,靈堂內(nèi)的尸體忽然破棺而出佣盒,到底是詐尸還是另有隱情,我是刑警寧澤顽聂,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布肥惭,位于F島的核電站盯仪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蜜葱。R本人自食惡果不足惜全景,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望牵囤。 院中可真熱鬧爸黄,春花似錦、人聲如沸揭鳞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽野崇。三九已至称开,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乓梨,已是汗流浹背鳖轰。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扶镀,地道東北人蕴侣。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像狈惫,于是被迫代替她去往敵國(guó)和親睛蛛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容