Tomcat架構(gòu)中各個(gè)組件及組件間關(guān)系(二)

前言
由于換工作的原因,需要融入新的開發(fā)團(tuán)隊(duì),開展新的業(yè)務(wù)征途,因此,距離上一次更新博客已有一段時(shí)間,現(xiàn)在稍微穩(wěn)定下來可以繼續(xù)Tomcat源碼的分析。在回顧思路時(shí)發(fā)現(xiàn),之前對(duì)于Tomcat組件和生命周期的文章主要從宏觀角度分析了兩者執(zhí)行的大體流程,對(duì)于細(xì)節(jié)點(diǎn)的分析有所欠缺,而這些細(xì)節(jié)可能又是后期理解 “Tomcat處理請(qǐng)求響應(yīng)” 這種重量級(jí)流程的前提。為了更好的溫故而知新,未來將對(duì)Tomcat架構(gòu)中各個(gè)組件及組件間關(guān)系Tomcat的生命周期兩篇文章進(jìn)行更深程度的剖析,本文是其中的第一部分,主要強(qiáng)化的內(nèi)容如下:

  • Digester解析xml文件模式的詳細(xì)分析,結(jié)合之前的文章,讀者會(huì)了解到Tomcat中涉及到的所有關(guān)鍵解析規(guī)則和原理
  • 其他Connector、Container相關(guān)組件解析的詳細(xì)過程。在之前的文章中,僅以<Server>頂層標(biāo)簽舉例,并不涉及<Engine>、<Host><Context>等“子標(biāo)簽”,而Tomcat標(biāo)簽的解析越往“子標(biāo)簽” 越復(fù)雜,越接近Tomcat處理請(qǐng)求響應(yīng)的核心,因此為了進(jìn)一步的深入也需要將“子標(biāo)簽”的解析吃透

Tomcat架構(gòu)中各個(gè)組件及組件間關(guān)系中,我們已對(duì)Digester類工作的大體思路進(jìn)行了分析,并且以<Server>標(biāo)簽的解析進(jìn)行了舉例,<Service><Server>的子標(biāo)簽,對(duì)應(yīng)的Digester解析規(guī)則如下

圖1. <Service>標(biāo)簽的解析規(guī)則

addObjectCreate(String pattern, String className, String attributeName)底層使用的規(guī)則為ObjectCreateRule,方法的第一個(gè)參數(shù)是pattern,表明了解析到什么標(biāo)簽才會(huì)使用配置的規(guī)則對(duì)標(biāo)簽的內(nèi)容進(jìn)行解析,和正則表達(dá)式匹配的作用類似。比如上圖中的pattern為Server/Service表示解析到<Server>下的<Service>標(biāo)簽時(shí)運(yùn)用規(guī)則進(jìn)行解析,這里用/表示一種父子關(guān)系。 第二個(gè)參數(shù)className很明顯表示標(biāo)簽對(duì)應(yīng)的java實(shí)體類,從上圖中來說<Service>標(biāo)簽對(duì)應(yīng)的實(shí)體實(shí)際上就是StandardService,其實(shí)該參數(shù)是一個(gè)可選參數(shù),可以傳null,用第三個(gè)參數(shù)attributeName在運(yùn)行時(shí)指定該標(biāo)簽對(duì)應(yīng)的類,以圖中舉例就是說<Service>標(biāo)簽可以存在一個(gè)屬性,屬性名為className,當(dāng)?shù)诙€(gè)參數(shù)沒有指定時(shí),Digester會(huì)自動(dòng)解析該屬性,并通過反射生成該類的實(shí)例再壓入Digester內(nèi)部的棧頂。
addSetProperties(String pattern)底層使用的規(guī)則為SetPropertiesRule,方法唯一的參數(shù)也是pattern,同樣表示遇到何種標(biāo)簽才進(jìn)行解析,SetPropertiesRule規(guī)則用于解析標(biāo)簽對(duì)應(yīng)的屬性。以上圖舉例,<Service>標(biāo)簽如下所示
圖2. server.xml中Service標(biāo)簽

其屬性只有name一個(gè),那我們猜想在StandardService中可能存在一個(gè)該屬性對(duì)應(yīng)的set方法,看下StandardService的代碼發(fā)現(xiàn)確實(shí)如此
圖3. StandardService中setName方法

這里有一個(gè)小坑需要說明一下,實(shí)際上標(biāo)簽對(duì)應(yīng)的實(shí)體類并不一定存在標(biāo)簽屬性對(duì)應(yīng)的set方法,并且也不是存在對(duì)應(yīng)屬性的set方法就會(huì)調(diào)用,理解這個(gè)細(xì)節(jié)我們需要進(jìn)入到SetPropertiesRule類的begin()方法中
圖4. SetPropertiesRule類的begin方法

紅框處存在三個(gè)判斷,第一個(gè)digester.isFakeAtrribute(top, name),其中top是當(dāng)前Digester內(nèi)部棧中棧頂元素,對(duì)于<Service>而言棧頂元素就是StandardService,name是每一個(gè)屬性的名稱,isFakeAtrribute具體的邏輯如下
圖5. isFakeAtrribute方法

該方法實(shí)際上就是在一個(gè)Map<Class,List<String>>的集合中判斷某個(gè)類是否存在某個(gè)名稱的屬性,如果存在就返回true,進(jìn)而不去調(diào)用該屬性的set方法,那么有哪些屬性被放在了這個(gè)“假屬性”集合中呢?我們回頭看Catalina中定義server.xml解析規(guī)則的方法
圖6. Catalina的createStartDigester方法

Tomcat在創(chuàng)建Digester類之前默認(rèn)添加了key為Object.class的entry,其value為包含className 的集合,結(jié)合圖5代碼的邏輯可知,如果標(biāo)簽沒有設(shè)置特定的fake attributes,那么總會(huì)返回默認(rèn)的,包含名稱為className的集合。而正常情況下所有的標(biāo)簽都是沒有設(shè)置特定的fake attributes的,也就是說Digester在解析所有標(biāo)簽時(shí)都會(huì)排除名稱為className的屬性
我們?cè)賮砜磮D4判斷中的第二個(gè)部分IntrospectionUtils.setProperty(top, name, value),最終調(diào)用的邏輯如 代碼清單1

    public static boolean setProperty(Object o, String name, String value,
            boolean invokeSetProperty) {
        if (log.isDebugEnabled())
            log.debug("IntrospectionUtils: setProperty(" +
                    o.getClass() + " " + name + "=" + value + ")");

        String setter = "set" + capitalize(name);

        try {
            Method methods[] = findMethods(o.getClass());
            Method setPropertyMethodVoid = null;
            Method setPropertyMethodBool = null;

            // First, the ideal case - a setFoo( String ) method
            for (int i = 0; i < methods.length; i++) {
                Class<?> paramT[] = methods[i].getParameterTypes();
                if (setter.equals(methods[i].getName()) && paramT.length == 1
                        && "java.lang.String".equals(paramT[0].getName())) {

                    methods[i].invoke(o, new Object[] { value });
                    return true;
                }
            }

            // Try a setFoo ( int ) or ( boolean )
            for (int i = 0; i < methods.length; i++) {
                boolean ok = true;
                if (setter.equals(methods[i].getName())
                        && methods[i].getParameterTypes().length == 1) {

                    // match - find the type and invoke it
                    Class<?> paramType = methods[i].getParameterTypes()[0];
                    Object params[] = new Object[1];

                    // Try a setFoo ( int )
                    if ("java.lang.Integer".equals(paramType.getName())
                            || "int".equals(paramType.getName())) {
                        try {
                            params[0] = Integer.valueOf(value);
                        } catch (NumberFormatException ex) {
                            ok = false;
                        }
                    // Try a setFoo ( long )
                    }else if ("java.lang.Long".equals(paramType.getName())
                                || "long".equals(paramType.getName())) {
                            try {
                                params[0] = Long.valueOf(value);
                            } catch (NumberFormatException ex) {
                                ok = false;
                            }

                        // Try a setFoo ( boolean )
                    } else if ("java.lang.Boolean".equals(paramType.getName())
                            || "boolean".equals(paramType.getName())) {
                        params[0] = Boolean.valueOf(value);

                        // Try a setFoo ( InetAddress )
                    } else if ("java.net.InetAddress".equals(paramType
                            .getName())) {
                        try {
                            params[0] = InetAddress.getByName(value);
                        } catch (UnknownHostException exc) {
                            if (log.isDebugEnabled())
                                log.debug("IntrospectionUtils: Unable to resolve host name:" + value);
                            ok = false;
                        }

                        // Unknown type
                    } else {
                        if (log.isDebugEnabled())
                            log.debug("IntrospectionUtils: Unknown type " +
                                    paramType.getName());
                    }

                    if (ok) {
                        methods[i].invoke(o, params);
                        return true;
                    }
                }

                // save "setProperty" for later
                if ("setProperty".equals(methods[i].getName())) {
                    if (methods[i].getReturnType()==Boolean.TYPE){
                        setPropertyMethodBool = methods[i];
                    }else {
                        setPropertyMethodVoid = methods[i];
                    }

                }
            }

            // Ok, no setXXX found, try a setProperty("name", "value")
            if (invokeSetProperty && (setPropertyMethodBool != null ||
                    setPropertyMethodVoid != null)) {
                Object params[] = new Object[2];
                params[0] = name;
                params[1] = value;
                if (setPropertyMethodBool != null) {
                    try {
                        return ((Boolean) setPropertyMethodBool.invoke(o,
                                params)).booleanValue();
                    }catch (IllegalArgumentException biae) {
                        //the boolean method had the wrong
                        //parameter types. lets try the other
                        if (setPropertyMethodVoid!=null) {
                            setPropertyMethodVoid.invoke(o, params);
                            return true;
                        }else {
                            throw biae;
                        }
                    }
                } else {
                    setPropertyMethodVoid.invoke(o, params);
                    return true;
                }
            }

        } catch (IllegalArgumentException ex2) {
            log.warn("IAE " + o + " " + name + " " + value, ex2);
        } catch (SecurityException ex1) {
            log.warn("IntrospectionUtils: SecurityException for " +
                    o.getClass() + " " + name + "=" + value + ")", ex1);
        } catch (IllegalAccessException iae) {
            log.warn("IntrospectionUtils: IllegalAccessException for " +
                    o.getClass() + " " + name + "=" + value + ")", iae);
        } catch (InvocationTargetException ie) {
            ExceptionUtils.handleThrowable(ie.getCause());
            log.warn("IntrospectionUtils: InvocationTargetException for " +
                    o.getClass() + " " + name + "=" + value + ")", ie);
        }
        return false;
    }

方法中將原始的方法名通過capitalize進(jìn)行首字母大寫處理,最終加上set前綴賦給setter變量。之后會(huì)根據(jù)類的實(shí)例得到對(duì)象所有的方法,并與setter進(jìn)行匹配,匹配成功則直接invoke調(diào)用并返回true,沒有找到對(duì)應(yīng)屬性的set方法則返回false。正是這種處理解釋了上面我們提及的:有些標(biāo)簽即便配置了相關(guān)的屬性也不會(huì)調(diào)用對(duì)應(yīng)類的set方法
接下來我們?cè)倏吹谌齻€(gè)解析規(guī)則addSetNext(String pattern, String methodName, String paramType),底層使用的規(guī)則為SetNextRule,方法的第一個(gè)參數(shù)指明了觸發(fā)該規(guī)則的具體模式,第二個(gè)參數(shù)表明調(diào)用父標(biāo)簽對(duì)應(yīng)實(shí)體的方法名稱,第三個(gè)參數(shù)就是方法參數(shù)的類型。在這里因?yàn)楫?dāng)前棧頂元素為StandardServiceaddSetNext會(huì)調(diào)用StandardServeraddService(Service service)方法,將當(dāng)前StandardService與其父元素StandardServer建立關(guān)聯(lián),涉及相關(guān)代碼如下

圖7. StandardServer中addService方法

我們可以稍微總結(jié)一下Digester內(nèi)置的三大解析規(guī)則類對(duì)應(yīng)的用途

Rule 對(duì)應(yīng)方法 用途
ObjectCreateRule addObjectCreate 根據(jù)匹配解析模式創(chuàng)建對(duì)應(yīng)標(biāo)簽的實(shí)體類
SetPropertiesRule addSetProperties 根據(jù)匹配解析模式為對(duì)應(yīng)標(biāo)簽實(shí)體類設(shè)置相關(guān)屬性
SetNextRule addSetNext 建立標(biāo)簽對(duì)應(yīng)實(shí)體之間子父類關(guān)系

至此<Service>標(biāo)簽的規(guī)則配置及解析流程分析完畢,我們接著看<Connector>標(biāo)簽

圖8. Connector標(biāo)簽的解析規(guī)則

創(chuàng)建<Connector>對(duì)象的規(guī)則和<Service>規(guī)則不太一樣,并沒有使用Digester內(nèi)建的ObjectCreateRule,而是自己繼承Rule創(chuàng)建了ConnectorCreateRule。前文中分析過,當(dāng)解析到對(duì)應(yīng)標(biāo)簽的開始處會(huì)調(diào)用規(guī)則類的begin(),我們來看看它做了什么
圖9. ObjectCreateRule的執(zhí)行流程

方法中首先取出此時(shí)棧頂元素StandardService(Connector尚未創(chuàng)建),再?gòu)陌?code><Connector>標(biāo)簽屬性的attributes中查找是否存在exector屬性,存在最終會(huì)調(diào)用_setExecutor(Connector, Executor)方法,該方法的主要作用是設(shè)置處理端到端連接的線程池,默認(rèn)情況下server.xml中并不會(huì)事先設(shè)置該線程池,但即便不設(shè)置,之后在Tomcat啟動(dòng)時(shí)也會(huì)默認(rèn)創(chuàng)建一個(gè),在后面分析啟動(dòng)流程時(shí)會(huì)詳細(xì)分析,這里暫先按默認(rèn)未設(shè)置線程池流程走。之后會(huì)根據(jù)protocol屬性創(chuàng)建Connector對(duì)象,基于Tomcat架構(gòu)中各個(gè)組件及組件間關(guān)系中給出的server.xml可知,<Connector>標(biāo)簽共有兩種協(xié)議,一種是HTTP/1.1,另一種是AJP/1.3。前者大家很清楚是HTTP協(xié)議的1.1版本,后者一般用于web容器之間通信,比HTTP協(xié)議在web容器間擁有更高的吞吐量。因?yàn)榇嬖趦煞N協(xié)議,那就會(huì)存在兩個(gè)Connector實(shí)體,為了突出重點(diǎn),我們只分析最常用的HTTP協(xié)議對(duì)應(yīng)的Connector初始化流程
圖10. Connector構(gòu)造器

Connector構(gòu)造器會(huì)繼續(xù)調(diào)用setProtocol(String protocol)方法,并將協(xié)議對(duì)應(yīng)的字符串傳入
圖11. 確認(rèn)協(xié)議對(duì)應(yīng)處理器類型

第一個(gè)判斷涉及到一種apr請(qǐng)求處理方法,可以將其理解為一種高效的IO模式,底層基于JNI技術(shù)調(diào)用操作系統(tǒng)級(jí)別的IO接口,在默認(rèn)情況下也是關(guān)閉的。因此上述代碼最終會(huì)調(diào)用setProtocolHandlerClassName("org.apache.coyote.http11.Http11Protocol"),將Connector中的成員變量ProtocolHandler置為Http11Protocol,該類在處理請(qǐng)求響應(yīng)的流程中起到了重要作用,后續(xù)文章會(huì)詳細(xì)分析,這里記住即可
<Connector>在處理屬性是也添加了名為SetAllPropertiesRule的規(guī)則,該規(guī)則接收了一個(gè)排除屬性的數(shù)組,其中僅包含executor屬性
圖12. SetAllPropertiesRule

begin()方法中不僅按上面提到的流程對(duì)屬性進(jìn)行了篩選,而且根據(jù)該規(guī)則中設(shè)置的排除屬性數(shù)組再一次進(jìn)行了過濾。同樣的,在addSetNext方法中會(huì)調(diào)用父標(biāo)簽StandardServiceaddConnector(Connector),從而建立父子關(guān)聯(lián)關(guān)系
圖13. StandardService中addConnector方法

在方法中使用了synchronized代碼塊解決了并發(fā)訪問下新增Connector被覆蓋的問題,在Tomcat的生命周期中說到,每一個(gè)容器都一個(gè)生命周期狀態(tài)的概念,這里getState()就獲得了此時(shí)Connector的狀態(tài),在剛創(chuàng)建時(shí)容器的state為NEW,available屬性值為false,并不會(huì)立即啟動(dòng)Connector容器,至此<Connector>的解析過程也分析完畢
Tomcat從整體架構(gòu)上可以分為兩大部分:監(jiān)聽請(qǐng)求并生成對(duì)應(yīng)Request和Response的Connector連接器,以及處理請(qǐng)求和控制tomcat容器運(yùn)轉(zhuǎn)的Container<Engine>標(biāo)簽就是Container的頂層組件,每一個(gè)Engine相當(dāng)于一個(gè)Servlet引擎,其下可以存在多個(gè)HostContext子容器
圖14. Engine相關(guān)解析規(guī)則

乍一看貌似<Engine>相關(guān)的rule特別多,但仔細(xì)一看其實(shí)都是套路,按照上面的分析方式都能一一拿下,這里只說一些重點(diǎn)和不同的部分。規(guī)則中為<Engine>添加了一個(gè)名為EngineConfig的Listener,用于對(duì)StandardEngine組件的生命周期監(jiān)控
圖15. EngineConfig的所有邏輯

從圖中可以看到該類在事件為start和stop時(shí)會(huì)進(jìn)行日志的打印,此外并沒有進(jìn)行其他的操作,在StandardEngine初始化時(shí)存在一個(gè)管道Pipeline和閥門Valve的概念
圖16. StandardEngine構(gòu)造器

Tomcat中為了更高效的處理請(qǐng)求,內(nèi)部設(shè)計(jì)了PipelineValve的概念,相當(dāng)于Servlet中的Filter和FilterChain,管道中可以通過addValve(Valve)添加或通過removeValve(Valve)移除多個(gè)閥門,而有一種閥門被稱為基礎(chǔ)閥門,該閥門總是最后一個(gè)執(zhí)行的,比如這里的StandardEngineValve,關(guān)于兩者的詳細(xì)分析會(huì)在后續(xù)文章開展,這里不做累述。參數(shù)backgroundProcessorDelayContainerBase中的內(nèi)部類ContainerBackgroundProcessor有關(guān),該類實(shí)現(xiàn)了Runnable接口,用于檢測(cè)war包中的類文件是否改動(dòng),是否需要重新加載,而參數(shù)乘以默認(rèn)的基數(shù)就是執(zhí)行的間隔時(shí)間,具體的處理流程后續(xù)文章同樣會(huì)講到。setJvmRoute()是給該機(jī)器設(shè)置一個(gè)唯一的標(biāo)識(shí),當(dāng)有多臺(tái)機(jī)器組成cluster時(shí),每臺(tái)機(jī)器都會(huì)用這唯一的標(biāo)識(shí)代表自身在集群中的位置
回到EngineRuleSet的規(guī)則定義上,我們發(fā)現(xiàn)Tomcat還為每一個(gè)StandardEngine添加了RealmRuleSet,該規(guī)則對(duì)應(yīng)<Engine>的子標(biāo)簽<Realm>,此標(biāo)簽引出了一個(gè)“域”的概念,我們可以將多個(gè)web應(yīng)用劃分成多個(gè)域,給每個(gè)域設(shè)定不同的訪問權(quán)限,只有擁有對(duì)應(yīng)域訪問權(quán)限的角色才能訪問對(duì)應(yīng)的web應(yīng)用,因此該規(guī)則的設(shè)定主要為了安全訪問和權(quán)限管理
一個(gè)<Host>表示一個(gè)虛擬主機(jī),其下可以存在多個(gè)<Context>標(biāo)簽,<Host>標(biāo)簽對(duì)應(yīng)的規(guī)則定義如下
圖17. Host相關(guān)解析規(guī)則

<Host>對(duì)應(yīng)的實(shí)體類為StandardHost,在初始化時(shí)也給Host容器中的管道添加了一個(gè)基礎(chǔ)閥門StandardHostValve。同StandardEngine一樣,Tomcat也為StandardHost添加了一個(gè)監(jiān)聽器HostConfig,但其功能遠(yuǎn)比EngineConfig復(fù)雜很多

圖18. HostConfig中l(wèi)ifecycleEvent

它根據(jù)不同的事件類型對(duì)web應(yīng)用的進(jìn)行相應(yīng)的檢查發(fā)布,停止以及和上面提到的ContainerBackgroundProcessor線程結(jié)合起來監(jiān)控應(yīng)用是否需要reload等功能,這部分內(nèi)容和容器的生命周期關(guān)系更加緊密,且可講的內(nèi)容較多,將放在生命周期強(qiáng)化的第二部分講解
一個(gè)<Context>可以認(rèn)為對(duì)應(yīng)一個(gè)webapps下的目錄,或者一個(gè)war包。代表虛擬主機(jī)的<Host>下可以存在多個(gè)<Context>標(biāo)簽,<Context>對(duì)應(yīng)的解析規(guī)則也是繼承RuleSetBase創(chuàng)建了自己的規(guī)則集合ContextRuleSet
圖19. ContextRuleSet

標(biāo)簽對(duì)應(yīng)的實(shí)體是StandardContext,也存在基礎(chǔ)閥門StandardContextValve,添加了對(duì)應(yīng)的監(jiān)聽器ContextConfig,在對(duì)該監(jiān)聽器進(jìn)行說明之前不知道大家想過沒有,到目前為止,我們一直在討論server.xml文件的解析,那其他的xml文件,比如context.xml、web.xml是什么時(shí)候解析的呢?為了回答這一問題,我們來看一下ContextConfig是如何處理監(jiān)聽事件發(fā)生的
圖20. ContextConfig處理監(jiān)聽事件邏輯

Tomcat的生命周期中曾給出Tomcat生命周期流轉(zhuǎn)圖和全部生命周期狀態(tài)字段,結(jié)合上圖兩處紅框中的Lifecycle類型可知,Lifecycle.AFTER_INIT_EVENT發(fā)生在Lifecycle.CONFIGURE_START_EVENT之前,而前者的init()中就定義了解析web.xml文件的所有規(guī)則
圖21. ContextConfig中init方法

繼續(xù)深入
圖22. createWebXmlDigester(boolean namespaceAware, boolean validation)

WebRuleSet同樣繼承了RuleSetweb.xml存在兩種形式,一種是我們“通常”意義上,放在每一個(gè)war包內(nèi)的webapps/WEB-INF/web.xml,該配置文件是以<web-app>作為根元素的;另一種是為了支持Servlet3.0新特性將web.xml分成多個(gè)小部分,運(yùn)行時(shí)再將各個(gè)部分聚集起來解析的配置文件web-fragment.xml,該文件是以<web-fragment>作為根元素。代碼清單2WebRuleSet設(shè)置的所有標(biāo)簽的解析規(guī)則

@Override
    public void addRuleInstances(Digester digester) {
        digester.addRule(fullPrefix,
                         new SetPublicIdRule("setPublicId"));
        digester.addRule(fullPrefix,
                         new IgnoreAnnotationsRule());
        digester.addRule(fullPrefix,
                new VersionRule());

        // Required for both fragments and non-fragments
        digester.addRule(fullPrefix + "/absolute-ordering", absoluteOrdering);
        digester.addRule(fullPrefix + "/ordering", relativeOrdering);

        if (fragment) {
            // web-fragment.xml
            digester.addRule(fullPrefix + "/name", name);
            digester.addCallMethod(fullPrefix + "/ordering/after/name",
                                   "addAfterOrdering", 0);
            digester.addCallMethod(fullPrefix + "/ordering/after/others",
                                   "addAfterOrderingOthers");
            digester.addCallMethod(fullPrefix + "/ordering/before/name",
                                   "addBeforeOrdering", 0);
            digester.addCallMethod(fullPrefix + "/ordering/before/others",
                                   "addBeforeOrderingOthers");
        } else {
            // web.xml
            digester.addCallMethod(fullPrefix + "/absolute-ordering/name",
                                   "addAbsoluteOrdering", 0);
            digester.addCallMethod(fullPrefix + "/absolute-ordering/others",
                                   "addAbsoluteOrderingOthers");
        }

        digester.addCallMethod(fullPrefix + "/context-param",
                               "addContextParam", 2);
        digester.addCallParam(fullPrefix + "/context-param/param-name", 0);
        digester.addCallParam(fullPrefix + "/context-param/param-value", 1);

        digester.addCallMethod(fullPrefix + "/display-name",
                               "setDisplayName", 0);

        digester.addRule(fullPrefix + "/distributable",
                         new SetDistributableRule());

        configureNamingRules(digester);

        digester.addObjectCreate(fullPrefix + "/error-page",
                                 "org.apache.catalina.deploy.ErrorPage");
        digester.addSetNext(fullPrefix + "/error-page",
                            "addErrorPage",
                            "org.apache.catalina.deploy.ErrorPage");

        digester.addCallMethod(fullPrefix + "/error-page/error-code",
                               "setErrorCode", 0);
        digester.addCallMethod(fullPrefix + "/error-page/exception-type",
                               "setExceptionType", 0);
        digester.addCallMethod(fullPrefix + "/error-page/location",
                               "setLocation", 0);

        digester.addObjectCreate(fullPrefix + "/filter",
                                 "org.apache.catalina.deploy.FilterDef");
        digester.addSetNext(fullPrefix + "/filter",
                            "addFilter",
                            "org.apache.catalina.deploy.FilterDef");

        digester.addCallMethod(fullPrefix + "/filter/description",
                               "setDescription", 0);
        digester.addCallMethod(fullPrefix + "/filter/display-name",
                               "setDisplayName", 0);
        digester.addCallMethod(fullPrefix + "/filter/filter-class",
                               "setFilterClass", 0);
        digester.addCallMethod(fullPrefix + "/filter/filter-name",
                               "setFilterName", 0);
        digester.addCallMethod(fullPrefix + "/filter/icon/large-icon",
                               "setLargeIcon", 0);
        digester.addCallMethod(fullPrefix + "/filter/icon/small-icon",
                               "setSmallIcon", 0);
        digester.addCallMethod(fullPrefix + "/filter/async-supported",
                "setAsyncSupported", 0);

        digester.addCallMethod(fullPrefix + "/filter/init-param",
                               "addInitParameter", 2);
        digester.addCallParam(fullPrefix + "/filter/init-param/param-name",
                              0);
        digester.addCallParam(fullPrefix + "/filter/init-param/param-value",
                              1);

        digester.addObjectCreate(fullPrefix + "/filter-mapping",
                                 "org.apache.catalina.deploy.FilterMap");
        digester.addSetNext(fullPrefix + "/filter-mapping",
                                 "addFilterMapping",
                                 "org.apache.catalina.deploy.FilterMap");

        digester.addCallMethod(fullPrefix + "/filter-mapping/filter-name",
                               "setFilterName", 0);
        digester.addCallMethod(fullPrefix + "/filter-mapping/servlet-name",
                               "addServletName", 0);
        digester.addCallMethod(fullPrefix + "/filter-mapping/url-pattern",
                               "addURLPattern", 0);

        digester.addCallMethod(fullPrefix + "/filter-mapping/dispatcher",
                               "setDispatcher", 0);

         digester.addCallMethod(fullPrefix + "/listener/listener-class",
                                "addListener", 0);
         
        digester.addRule(fullPrefix + "/jsp-config",
                         jspConfig);

        digester.addObjectCreate(fullPrefix + "/jsp-config/jsp-property-group",
                                 "org.apache.catalina.deploy.JspPropertyGroup");
        digester.addSetNext(fullPrefix + "/jsp-config/jsp-property-group",
                            "addJspPropertyGroup",
                            "org.apache.catalina.deploy.JspPropertyGroup");
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/deferred-syntax-allowed-as-literal",
                               "setDeferredSyntax", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/el-ignored",
                               "setElIgnored", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/include-coda",
                               "addIncludeCoda", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/include-prelude",
                               "addIncludePrelude", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/is-xml",
                               "setIsXml", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/page-encoding",
                               "setPageEncoding", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/scripting-invalid",
                               "setScriptingInvalid", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/trim-directive-whitespaces",
                               "setTrimWhitespace", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/url-pattern",
                               "addUrlPattern", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/default-content-type",
                               "setDefaultContentType", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/buffer",
                               "setBuffer", 0);
        digester.addCallMethod(fullPrefix + "/jsp-config/jsp-property-group/error-on-undeclared-namespace",
                               "setErrorOnUndeclaredNamespace", 0);

        digester.addRule(fullPrefix + "/login-config",
                         loginConfig);

        digester.addObjectCreate(fullPrefix + "/login-config",
                                 "org.apache.catalina.deploy.LoginConfig");
        digester.addSetNext(fullPrefix + "/login-config",
                            "setLoginConfig",
                            "org.apache.catalina.deploy.LoginConfig");

        digester.addCallMethod(fullPrefix + "/login-config/auth-method",
                               "setAuthMethod", 0);
        digester.addCallMethod(fullPrefix + "/login-config/realm-name",
                               "setRealmName", 0);
        digester.addCallMethod(fullPrefix + "/login-config/form-login-config/form-error-page",
                               "setErrorPage", 0);
        digester.addCallMethod(fullPrefix + "/login-config/form-login-config/form-login-page",
                               "setLoginPage", 0);

        digester.addCallMethod(fullPrefix + "/mime-mapping",
                               "addMimeMapping", 2);
        digester.addCallParam(fullPrefix + "/mime-mapping/extension", 0);
        digester.addCallParam(fullPrefix + "/mime-mapping/mime-type", 1);


        digester.addObjectCreate(fullPrefix + "/security-constraint",
                                 "org.apache.catalina.deploy.SecurityConstraint");
        digester.addSetNext(fullPrefix + "/security-constraint",
                            "addSecurityConstraint",
                            "org.apache.catalina.deploy.SecurityConstraint");

        digester.addRule(fullPrefix + "/security-constraint/auth-constraint",
                         new SetAuthConstraintRule());
        digester.addCallMethod(fullPrefix + "/security-constraint/auth-constraint/role-name",
                               "addAuthRole", 0);
        digester.addCallMethod(fullPrefix + "/security-constraint/display-name",
                               "setDisplayName", 0);
        digester.addCallMethod(fullPrefix + "/security-constraint/user-data-constraint/transport-guarantee",
                               "setUserConstraint", 0);

        digester.addObjectCreate(fullPrefix + "/security-constraint/web-resource-collection",
                                 "org.apache.catalina.deploy.SecurityCollection");
        digester.addSetNext(fullPrefix + "/security-constraint/web-resource-collection",
                            "addCollection",
                            "org.apache.catalina.deploy.SecurityCollection");
        digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/http-method",
                               "addMethod", 0);
        digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/http-method-omission",
                               "addOmittedMethod", 0);
        digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/url-pattern",
                               "addPattern", 0);
        digester.addCallMethod(fullPrefix + "/security-constraint/web-resource-collection/web-resource-name",
                               "setName", 0);

        digester.addCallMethod(fullPrefix + "/security-role/role-name",
                               "addSecurityRole", 0);

        digester.addRule(fullPrefix + "/servlet",
                         new ServletDefCreateRule());
        digester.addSetNext(fullPrefix + "/servlet",
                            "addServlet",
                            "org.apache.catalina.deploy.ServletDef");

        digester.addCallMethod(fullPrefix + "/servlet/init-param",
                               "addInitParameter", 2);
        digester.addCallParam(fullPrefix + "/servlet/init-param/param-name",
                              0);
        digester.addCallParam(fullPrefix + "/servlet/init-param/param-value",
                              1);

        digester.addCallMethod(fullPrefix + "/servlet/jsp-file",
                               "setJspFile", 0);
        digester.addCallMethod(fullPrefix + "/servlet/load-on-startup",
                               "setLoadOnStartup", 0);
        digester.addCallMethod(fullPrefix + "/servlet/run-as/role-name",
                               "setRunAs", 0);

        digester.addObjectCreate(fullPrefix + "/servlet/security-role-ref",
                                 "org.apache.catalina.deploy.SecurityRoleRef");
        digester.addSetNext(fullPrefix + "/servlet/security-role-ref",
                            "addSecurityRoleRef",
                            "org.apache.catalina.deploy.SecurityRoleRef");
        digester.addCallMethod(fullPrefix + "/servlet/security-role-ref/role-link",
                               "setLink", 0);
        digester.addCallMethod(fullPrefix + "/servlet/security-role-ref/role-name",
                               "setName", 0);

        digester.addCallMethod(fullPrefix + "/servlet/servlet-class",
                              "setServletClass", 0);
        digester.addCallMethod(fullPrefix + "/servlet/servlet-name",
                              "setServletName", 0);
        
        digester.addObjectCreate(fullPrefix + "/servlet/multipart-config",
                                 "org.apache.catalina.deploy.MultipartDef");
        digester.addSetNext(fullPrefix + "/servlet/multipart-config",
                            "setMultipartDef",
                            "org.apache.catalina.deploy.MultipartDef");
        digester.addCallMethod(fullPrefix + "/servlet/multipart-config/location",
                               "setLocation", 0);
        digester.addCallMethod(fullPrefix + "/servlet/multipart-config/max-file-size",
                               "setMaxFileSize", 0);
        digester.addCallMethod(fullPrefix + "/servlet/multipart-config/max-request-size",
                               "setMaxRequestSize", 0);
        digester.addCallMethod(fullPrefix + "/servlet/multipart-config/file-size-threshold",
                               "setFileSizeThreshold", 0);

        digester.addCallMethod(fullPrefix + "/servlet/async-supported",
                               "setAsyncSupported", 0);
        digester.addCallMethod(fullPrefix + "/servlet/enabled",
                               "setEnabled", 0);

        
        digester.addRule(fullPrefix + "/servlet-mapping",
                               new CallMethodMultiRule("addServletMapping", 2, 0));
        digester.addCallParam(fullPrefix + "/servlet-mapping/servlet-name", 1);
        digester.addRule(fullPrefix + "/servlet-mapping/url-pattern", new CallParamMultiRule(0));

        digester.addRule(fullPrefix + "/session-config", sessionConfig);
        digester.addObjectCreate(fullPrefix + "/session-config",
                                 "org.apache.catalina.deploy.SessionConfig");
        digester.addSetNext(fullPrefix + "/session-config", "setSessionConfig",
                            "org.apache.catalina.deploy.SessionConfig");
        digester.addCallMethod(fullPrefix + "/session-config/session-timeout",
                               "setSessionTimeout", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/name",
                               "setCookieName", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/domain",
                               "setCookieDomain", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/path",
                               "setCookiePath", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/comment",
                               "setCookieComment", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/http-only",
                               "setCookieHttpOnly", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/secure",
                               "setCookieSecure", 0);
        digester.addCallMethod(fullPrefix + "/session-config/cookie-config/max-age",
                               "setCookieMaxAge", 0);
        digester.addCallMethod(fullPrefix + "/session-config/tracking-mode",
                               "addSessionTrackingMode", 0);

        // Taglibs pre Servlet 2.4
        digester.addRule(fullPrefix + "/taglib", new TaglibLocationRule(false));
        digester.addCallMethod(fullPrefix + "/taglib",
                               "addTaglib", 2);
        digester.addCallParam(fullPrefix + "/taglib/taglib-location", 1);
        digester.addCallParam(fullPrefix + "/taglib/taglib-uri", 0);

        // Taglibs Servlet 2.4 onwards
        digester.addRule(fullPrefix + "/jsp-config/taglib", new TaglibLocationRule(true));
        digester.addCallMethod(fullPrefix + "/jsp-config/taglib",
                "addTaglib", 2);
        digester.addCallParam(fullPrefix + "/jsp-config/taglib/taglib-location", 1);
        digester.addCallParam(fullPrefix + "/jsp-config/taglib/taglib-uri", 0);

        digester.addCallMethod(fullPrefix + "/welcome-file-list/welcome-file",
                               "addWelcomeFile", 0);

        digester.addCallMethod(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping",
                              "addLocaleEncodingMapping", 2);
        digester.addCallParam(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping/locale", 0);
        digester.addCallParam(fullPrefix + "/locale-encoding-mapping-list/locale-encoding-mapping/encoding", 1);

        digester.addRule(fullPrefix + "/post-construct",
                new LifecycleCallbackRule("addPostConstructMethods", 2, true));
        digester.addCallParam(fullPrefix + "/post-construct/lifecycle-callback-class", 0);
        digester.addCallParam(fullPrefix + "/post-construct/lifecycle-callback-method", 1);

        digester.addRule(fullPrefix + "/pre-destroy",
                new LifecycleCallbackRule("addPreDestroyMethods", 2, false));
        digester.addCallParam(fullPrefix + "/pre-destroy/lifecycle-callback-class", 0);
        digester.addCallParam(fullPrefix + "/pre-destroy/lifecycle-callback-method", 1);
    }

其中的fullPrefix對(duì)應(yīng)的就是上面兩種xml文件的根元素。這里需要引入Digester中另外兩個(gè)內(nèi)置解析規(guī)則類CallMethodRuleCallParamRule,分別來源于digester.addCallMethod(String pattern, String methodName, int paramCount)digester.addCallParam(String pattern, int paramIndex),前者如果pattern匹配成功,會(huì)調(diào)用當(dāng)前棧頂元素的名為methodName,屬性數(shù)量為paramCount的方法;后者需要與前者配合使用,其含義為找到與pattern匹配的標(biāo)簽對(duì)應(yīng)的值,該值作為前者調(diào)用方法的第paramIndex參數(shù)的值傳入,舉個(gè)例子來說

圖23. 舉例說明CallMethodRule和CallParamRule兩種規(guī)則

相信寫過web程序的讀者都知道<context-param>的含義,當(dāng)解析到第一句時(shí)會(huì)調(diào)用此時(shí)digester內(nèi)部棧棧頂元素的addContextParam(String param, String value)方法,該方法有兩個(gè)參數(shù),當(dāng)解析到子標(biāo)簽<param-name>時(shí),將該標(biāo)簽的值對(duì)應(yīng)方法中的第一個(gè)參數(shù)param,當(dāng)解析到子標(biāo)簽<param-value>時(shí),將該標(biāo)簽的值對(duì)應(yīng)方法中的第二個(gè)參數(shù)value
從代碼清單2中我們還可以知道<filter>對(duì)應(yīng)的實(shí)體為FilterDef<filter-mapping>對(duì)應(yīng)的實(shí)體為FilterMap,<servlet>對(duì)應(yīng)的實(shí)體又在ServletDefCreateRule中給出定義,為ServletDef。需要說明的一點(diǎn)是,這里只是定義了web.xml的解析規(guī)則,Tomcat將這些解析規(guī)則封裝到了圖22中的兩個(gè)成員變量webRuleSetwebFragmentRuleSet中,真正的解析是在圖20中,當(dāng)ContextConfig監(jiān)聽到事件Lifecycle.CONFIGURE_START_EVENT,進(jìn)而調(diào)用configureStart()時(shí)才發(fā)生的,具體的流程將在生命周期的補(bǔ)充文章中講解

后記
未來計(jì)劃用兩到三篇文章對(duì)Tomcat容器生命周期相關(guān)知識(shí)點(diǎn)進(jìn)行補(bǔ)充分析,主要包括兩部分內(nèi)容:

  1. 容器的初始化和啟動(dòng)流程
  2. 各個(gè)容器相關(guān)監(jiān)聽器在Tomcat運(yùn)行時(shí)的作用
    完成這些前期準(zhǔn)備工作后再用兩到三篇文章對(duì)Tomcat如何接收處理請(qǐng)求,又如何正確的將請(qǐng)求分發(fā)到對(duì)應(yīng)的war包,對(duì)應(yīng)的Servlet中的
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,534評(píng)論 19 139
  • 前言借著上次對(duì)Tomcat類加載機(jī)制的分析,就想著看都看了,何不再看看Tomcat內(nèi)部的實(shí)現(xiàn)原理和架構(gòu)設(shè)計(jì),向優(yōu)秀...
    寶之家閱讀 3,862評(píng)論 1 5
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚_t_閱讀 34,638評(píng)論 18 399
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,787評(píng)論 11 349
  • +今天是今日有所思第94天。 昨天清晨重讀了《財(cái)富自由之路》24日的概念后,鬼使神差地選了個(gè)題目《如何選書》,不料...
    荒原蒼狼閱讀 282評(píng)論 0 0

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