前言
由于換工作的原因,需要融入新的開發(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ī)則如下

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)簽如下所示
其屬性只有name一個(gè),那我們猜想在
StandardService中可能存在一個(gè)該屬性對(duì)應(yīng)的set方法,看下StandardService的代碼發(fā)現(xiàn)確實(shí)如此
這里有一個(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()方法中
紅框處存在三個(gè)判斷,第一個(gè)
digester.isFakeAtrribute(top, name),其中top是當(dāng)前Digester內(nèi)部棧中棧頂元素,對(duì)于<Service>而言棧頂元素就是StandardService,name是每一個(gè)屬性的名稱,isFakeAtrribute具體的邏輯如下
該方法實(shí)際上就是在一個(gè)
Map<Class,List<String>>的集合中判斷某個(gè)類是否存在某個(gè)名稱的屬性,如果存在就返回true,進(jìn)而不去調(diào)用該屬性的set方法,那么有哪些屬性被放在了這個(gè)“假屬性”集合中呢?我們回頭看Catalina中定義server.xml解析規(guī)則的方法
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)前棧頂元素為StandardService,addSetNext會(huì)調(diào)用StandardServer的addService(Service service)方法,將當(dāng)前StandardService與其父元素StandardServer建立關(guān)聯(lián),涉及相關(guān)代碼如下

我們可以稍微總結(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)簽

創(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(),我們來看看它做了什么
方法中首先取出此時(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初始化流程
Connector構(gòu)造器會(huì)繼續(xù)調(diào)用setProtocol(String protocol)方法,并將協(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屬性
begin()方法中不僅按上面提到的流程對(duì)屬性進(jìn)行了篩選,而且根據(jù)該規(guī)則中設(shè)置的排除屬性數(shù)組再一次進(jìn)行了過濾。同樣的,在addSetNext方法中會(huì)調(diào)用父標(biāo)簽StandardService的addConnector(Connector),從而建立父子關(guān)聯(lián)關(guān)系
在方法中使用了
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è)Host和Context子容器
乍一看貌似
<Engine>相關(guān)的rule特別多,但仔細(xì)一看其實(shí)都是套路,按照上面的分析方式都能一一拿下,這里只說一些重點(diǎn)和不同的部分。規(guī)則中為<Engine>添加了一個(gè)名為EngineConfig的Listener,用于對(duì)StandardEngine組件的生命周期監(jiān)控
從圖中可以看到該類在事件為start和stop時(shí)會(huì)進(jìn)行日志的打印,此外并沒有進(jìn)行其他的操作,在
StandardEngine初始化時(shí)存在一個(gè)管道Pipeline和閥門Valve的概念
Tomcat中為了更高效的處理請(qǐng)求,內(nèi)部設(shè)計(jì)了
Pipeline和Valve的概念,相當(dāng)于Servlet中的Filter和FilterChain,管道中可以通過addValve(Valve)添加或通過removeValve(Valve)移除多個(gè)閥門,而有一種閥門被稱為基礎(chǔ)閥門,該閥門總是最后一個(gè)執(zhí)行的,比如這里的StandardEngineValve,關(guān)于兩者的詳細(xì)分析會(huì)在后續(xù)文章開展,這里不做累述。參數(shù)backgroundProcessorDelay和ContainerBase中的內(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ī)則定義如下
<Host>對(duì)應(yīng)的實(shí)體類為StandardHost,在初始化時(shí)也給Host容器中的管道添加了一個(gè)基礎(chǔ)閥門StandardHostValve。同StandardEngine一樣,Tomcat也為StandardHost添加了一個(gè)監(jiān)聽器HostConfig,但其功能遠(yuǎn)比EngineConfig復(fù)雜很多

它根據(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
標(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ā)生的
在Tomcat的生命周期中曾給出Tomcat生命周期流轉(zhuǎn)圖和全部生命周期狀態(tài)字段,結(jié)合上圖兩處紅框中的Lifecycle類型可知,
Lifecycle.AFTER_INIT_EVENT發(fā)生在Lifecycle.CONFIGURE_START_EVENT之前,而前者的init()中就定義了解析web.xml文件的所有規(guī)則
繼續(xù)深入

WebRuleSet同樣繼承了RuleSet,web.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>作為根元素。代碼清單2為WebRuleSet設(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ī)則類CallMethodRule和CallParamRule,分別來源于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è)例子來說

相信寫過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è)成員變量webRuleSet和webFragmentRuleSet中,真正的解析是在圖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)容:
- 容器的初始化和啟動(dòng)流程
- 各個(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中的