基于Tomcat的Servlet過濾器(1)實(shí)例及加載執(zhí)行源碼簡(jiǎn)析

實(shí)例

實(shí)現(xiàn)一個(gè)簡(jiǎn)單的過濾器只需要兩步
1巨朦,實(shí)現(xiàn)Filter接口寫一個(gè)過濾器實(shí)現(xiàn)類

public class DemoFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("[DemoFilter-before]doFilter");
        chain.doFilter(request, response);
        System.out.println("[DemoFilter-after]doFilter");
    }
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("[DemoFilter-before]init");
    }

    @Override
    public void destroy() {
        System.out.println("[DemoFilter-before]destroy");
    }

}

2盏触,web.xml文件中新增相關(guān)filter配置

<filter>  
        <filter-name>DemoFilter</filter-name>  
        <filter-class>com.ryan.springtest.filter.DemoFilter</filter-class>  
</filter>
  
<filter-mapping>  
        <filter-name>DemoFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
</filter-mapping>

輸出
此時(shí)啟動(dòng)tomcat訪問任一url玩徊,即可看到相應(yīng)的輸出信息

[DemoFilter-before]doFilter
[DemoFilter-after]doFilter

注:filterChain為過濾器鏈谍失,表示執(zhí)行完這個(gè)過濾器之后接著執(zhí)行下一個(gè)過濾器

原理

過濾器的具體實(shí)現(xiàn)依賴于容器晰筛,本文的源碼分析是基于Tomcat的實(shí)現(xiàn)嫡丙。
要完成過濾器的實(shí)現(xiàn),Tomcat首先需要加載我們定義的過濾器传惠,接著針對(duì)每一次請(qǐng)求找到對(duì)應(yīng)的過濾器迄沫,最后是執(zhí)行過濾器中的doFilter,觸發(fā)過濾器鏈的執(zhí)行卦方,下面將按照這個(gè)邏輯對(duì)源碼進(jìn)行簡(jiǎn)單的分析羊瘩。

過濾器加載

過濾器的加載是在Tomcat啟動(dòng)的時(shí)候完成的,Tomcat啟動(dòng)的時(shí)候盼砍,會(huì)加載web.xml中的配置信息尘吗,filter的加載具體是在ContextConfig類的configureContext方法中,關(guān)鍵代碼如下

for (FilterDef filter : webxml.getFilters().values()) {
    if (filter.getAsyncSupported() == null) {
        filter.setAsyncSupported("false");
    }
    context.addFilterDef(filter);
}
for (FilterMap filterMap : webxml.getFilterMappings()) {
    context.addFilterMap(filterMap);
}

此時(shí)分別加載filter和filterMap相關(guān)信息浇坐,并保存在上下文環(huán)境中

加載完相關(guān)配置信息后睬捶,還需對(duì)具體的filter進(jìn)行初始化,這一步在StandardContext類的startInternal方法中完成近刘,關(guān)鍵代碼如下

if (ok) {
    if (!filterStart()) {
        log.error(sm.getString("standardContext.filterFail"));
        ok = false;
    }
}
public boolean filterStart() {

        if (getLogger().isDebugEnabled()) {
            getLogger().debug("Starting filters");
        }
        // Instantiate and record a FilterConfig for each defined filter
        boolean ok = true;
        synchronized (filterConfigs) {
            filterConfigs.clear();
            for (Entry<String,FilterDef> entry : filterDefs.entrySet()) {
                String name = entry.getKey();
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug(" Starting filter '" + name + "'");
                }
                try {
                    ApplicationFilterConfig filterConfig =
                            new ApplicationFilterConfig(this, entry.getValue());
                    filterConfigs.put(name, filterConfig);
                } catch (Throwable t) {
                    t = ExceptionUtils.unwrapInvocationTargetException(t);
                    ExceptionUtils.handleThrowable(t);
                    getLogger().error(sm.getString(
                            "standardContext.filterStart", name), t);
                    ok = false;
                }
            }
        }

        return ok;
    }

遍歷剛剛從web.xml解析出來的filter配置信息擒贸,并調(diào)用ApplicationFilterConfig構(gòu)造方法進(jìn)行初始化,保存在filterConfigs中并存到上下文環(huán)境中觉渴。

過濾器鏈生成

當(dāng)請(qǐng)求進(jìn)入tomcat的時(shí)候介劫,會(huì)被匹配的過濾器過濾,多個(gè)匹配的過濾器組成一個(gè)過濾器鏈案淋,并按照我們?cè)趙eb.xml中定義的filter-mapping的順序執(zhí)行座韵。
被tomcat處理的請(qǐng)求,最終會(huì)被StandardWrapperValve類的invoke方法處理,對(duì)應(yīng)的過濾器鏈也是在此時(shí)生成的誉碴,關(guān)鍵代碼如下

ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
@SuppressWarnings("deprecation")
public static ApplicationFilterChain createFilterChain (ServletRequest request, Wrapper wrapper, Servlet servlet) {
        ApplicationFilterChain filterChain = null;
        if (request instanceof Request) {
            //略
        } else {
            // Request dispatcher in use
            filterChain = new ApplicationFilterChain();
        }

        filterChain.setServlet(servlet);
        filterChain.setSupport(((StandardWrapper)wrapper).getInstanceSupport());

        // Acquire the filter mappings for this Context
        StandardContext context = (StandardContext) wrapper.getParent();
        FilterMap filterMaps[] = context.findFilterMaps();

        // If there are no filter mappings, we are done
        if ((filterMaps == null) || (filterMaps.length == 0))
            return (filterChain);

        // Acquire the information we will need to match filter mappings
        String servletName = wrapper.getName();

        // Add the relevant path-mapped filters to this filter chain
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                continue;
            }
            if (!matchFiltersURL(filterMaps[i], requestPath))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            //略
            } else {
                filterChain.addFilter(filterConfig);
            }
        }

        // Add filters that match on servlet name second
        for (int i = 0; i < filterMaps.length; i++) {
            if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
                continue;
            }
            if (!matchFiltersServlet(filterMaps[i], servletName))
                continue;
            ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
            if (filterConfig == null) {
                // FIXME - log configuration problem
                continue;
            }
            //略
            } else {
                filterChain.addFilter(filterConfig);
            }
        }

        // Return the completed filter chain
        return (filterChain);
}

上述的代碼比較長(zhǎng)宦棺,主要的邏輯有

  1. 每個(gè)請(qǐng)求需要生成對(duì)應(yīng)的ApplicationFilterChain,invoke方法中調(diào)用ApplicationFilterFactory類的createFilterChain方法創(chuàng)建一個(gè)ApplicationFilterChain黔帕,其中包含了目標(biāo)servlet以及對(duì)應(yīng)的過濾器鏈代咸。
  2. createFilterChain方法中,首先設(shè)置了目標(biāo)servlet成黄,filterChain.setServlet(servlet);侣背。
  3. 接著從上下文環(huán)境中取出之前解析的filterMaps信息,FilterMap filterMaps[] = context.findFilterMaps();慨默。
  4. 遍歷filterMaps,判斷當(dāng)前的請(qǐng)求是否符合攔截條件弧腥,若符合則將filterConfig放進(jìn)filterChain中厦取,從這里可以看出,實(shí)際決定過濾器執(zhí)行順序的是filter-mapping在web.xml中的配置順序管搪。

至此一個(gè)ApplicationFilterChain便構(gòu)建好了虾攻,包含一個(gè)目標(biāo)servlet和我們想要的過濾器鏈。

過濾器鏈執(zhí)行

獲取到過濾器鏈之后更鲁,接下來就是過濾器鏈的具體執(zhí)行霎箍,回到上一步分析開始的StandardWrapperValve類的invoke方法中,現(xiàn)在我們拿到的ApplicationFilterChain澡为,便可以繼續(xù)向下分析了漂坏。

try {
    if ((servlet != null) && (filterChain != null)) {
        // Swallow output if needed
    if (context.getSwallowOutput()) {
            try {
                SystemLogHandler.startCapture();
                if (request.isAsyncDispatching()) {
                    request.getAsyncContextInternal().doInternalDispatch();
                } else if (comet) {
                    filterChain.doFilterEvent(request.getEvent());
                } else {
                    filterChain.doFilter(request.getRequest(), response.getResponse());
                }
            } finally {
                String log = SystemLogHandler.stopCapture();
                if (log != null && log.length() > 0) {
                    context.getLogger().info(log);
                }
            }
        } else {
            if (request.isAsyncDispatching()) {
                request.getAsyncContextInternal().doInternalDispatch();
            } else if (comet) {
                filterChain.doFilterEvent(request.getEvent());
            } else {
                filterChain.doFilter(request.getRequest(), response.getResponse());
            }
        }
    }
//略

上述代碼中,我們關(guān)注的是filterChain.doFilter方法媒至,在這里將會(huì)觸發(fā)過濾器鏈的執(zhí)行顶别,繼續(xù)跟蹤源碼

public void doFilter(ServletRequest request, ServletResponse response)  throws IOException, ServletException {  
    if( Globals.IS_SECURITY_ENABLED ) {  
        //略
    } else {  
        internalDoFilter(request,response);  
    }  
}  

最終實(shí)際的處理方法是internalDoFilter

private void internalDoFilter(ServletRequest request, ServletResponse response)  throws IOException, ServletException {
    if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();
                support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT, filter, request, response);

                //略
                if( Globals.IS_SECURITY_ENABLED ) {
                    //略
                } else {
                    filter.doFilter(request, response, this);
                }
                support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, filter, request, response);
            } catch (IOException | ServletException | RuntimeException e) {
                //略
            } catch (Throwable e) {
                //略
            }
            return;
        }

        // We fell off the end of the chain -- call the servlet instance
        try {
            //略
            if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
                if( Globals.IS_SECURITY_ENABLED ) {
                    //略
                } else {
                    servlet.service(request, response);
                }
            } else {
                servlet.service(request, response);
            }
            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
                                      servlet, request, response);
        } catch (IOException e) {
            //略
        } finally {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(null);
                lastServicedResponse.set(null);
            }
        }
}

上面只列出我們關(guān)注的關(guān)鍵代碼

  1. ApplicationFilterConfig filterConfig = filters[pos++];此處取出當(dāng)前要執(zhí)行的filter,并把pos加1拒啰。
  2. 執(zhí)行filter.doFilter方法驯绎,并將當(dāng)前的filterChain傳入過濾器中。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("[DemoFilter-before]doFilter");
        chain.doFilter(request, response);
        System.out.println("[DemoFilter-after]doFilter");
    }
  1. 上面是我們定義的filter谋旦,當(dāng)我們調(diào)用chain.doFilter的時(shí)候剩失,最終又回到上面的internalDoFilter方法中,取出過濾器鏈中的下一個(gè)過濾器進(jìn)行執(zhí)行册着。
  2. 當(dāng)過濾器鏈執(zhí)行完成后拴孤,便會(huì)執(zhí)行servlet.service方法。
  3. 最后internalDoFilter執(zhí)行完成后指蚜,便會(huì)回到上一個(gè)過濾器的doFilter中乞巧,繼續(xù)執(zhí)行chain.doFilter之后的代碼,直到執(zhí)行完所有匹配的過濾器摊鸡。

至此绽媒,過濾器鏈的執(zhí)行便完成了蚕冬。

過濾器關(guān)鍵類與接口

  1. Filter:實(shí)現(xiàn)一個(gè)過濾器可以實(shí)現(xiàn)該接口
  2. ContextConfig:加載web.xml中的配置信息,并保存到上下文環(huán)境中
  3. StandardContext:對(duì)具體的filter進(jìn)行初始化是辕,并保存到上下文環(huán)境中
  4. StandardWrapperValve:將請(qǐng)求映射到ApplicationFilterChain囤热,并負(fù)責(zé)過濾器的執(zhí)行。
  5. ApplicationFilterChain:負(fù)責(zé)過濾器鏈的遞歸調(diào)用

過濾器應(yīng)用示例

編碼設(shè)置:設(shè)置請(qǐng)求及相應(yīng)的編碼
日志記錄:記錄請(qǐng)求信息的日志获三,以便進(jìn)行信息監(jiān)控旁蔼、信息統(tǒng)計(jì)、計(jì)算PV(Page View)等疙教。
權(quán)限檢查:如登錄檢測(cè)棺聊,進(jìn)入處理器檢測(cè)檢測(cè)是否登錄,如果沒有直接返回到登錄頁面贞谓。
通用行為:讀取cookie得到用戶信息并將用戶對(duì)象放入請(qǐng)求限佩,從而方便后續(xù)流程使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末裸弦,一起剝皮案震驚了整個(gè)濱河市祟同,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌理疙,老刑警劉巖晕城,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異窖贤,居然都是意外死亡砖顷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門主之,熙熙樓的掌柜王于貴愁眉苦臉地迎上來择吊,“玉大人,你說我怎么就攤上這事槽奕〖妇Γ” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵粤攒,是天一觀的道長(zhǎng)所森。 經(jīng)常有香客問我,道長(zhǎng)夯接,這世上最難降的妖魔是什么焕济? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮盔几,結(jié)果婚禮上晴弃,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好上鞠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布际邻。 她就那樣靜靜地躺著,像睡著了一般芍阎。 火紅的嫁衣襯著肌膚如雪世曾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天谴咸,我揣著相機(jī)與錄音轮听,去河邊找鬼。 笑死岭佳,一個(gè)胖子當(dāng)著我的面吹牛血巍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播珊随,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼藻茂,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了玫恳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤优俘,失蹤者是張志新(化名)和其女友劉穎京办,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帆焕,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惭婿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年豹绪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了揽思。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幕庐。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赢赊,死狀恐怖却音,靈堂內(nèi)的尸體忽然破棺而出士败,到底是詐尸還是另有隱情述召,我是刑警寧澤心铃,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布满着,位于F島的核電站谦炒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏风喇。R本人自食惡果不足惜宁改,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望魂莫。 院中可真熱鬧还蹲,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锅论,卻和暖如春讼溺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背最易。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工怒坯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藻懒。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓剔猿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親嬉荆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子归敬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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

  • 僅作為自己學(xué)習(xí)記錄使用,文章來自: 1鄙早、http://blog.csdn.net/csh624366188/art...
    BakerZhang閱讀 1,013評(píng)論 1 5
  • 本文包括:1汪茧、Filter簡(jiǎn)介2、Filter是如何實(shí)現(xiàn)攔截的限番?3舱污、Filter開發(fā)入門4、Filter的生命周期...
    廖少少閱讀 7,273評(píng)論 3 56
  • 監(jiān)聽器(listener) 監(jiān)聽器簡(jiǎn)介 :監(jiān)聽器就是一個(gè)實(shí)現(xiàn)特定接口的普通java程序弥虐,這個(gè)程序?qū)iT用于監(jiān)聽另一個(gè)...
    奮斗的老王閱讀 2,511評(píng)論 0 53
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理扩灯,服務(wù)發(fā)現(xiàn),斷路器霜瘪,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 這周本來想請(qǐng)假珠插,因?yàn)橐厣虾⒓优嘤?xùn)考試。但是想著颖对,就算是流水賬捻撑,也要把寫作這個(gè)良好的習(xí)慣堅(jiān)持下去,我不是因...
    Linda_f34d閱讀 227評(píng)論 0 1