Tomcat組件梳理—Digester的使用
再吐槽一下,本來以為可以不用再開一個篇章來梳理Digester了,但是發現在研究Service的創建時,還是對Digester的很多接口或者機制不熟悉,簡直搞不懂。想想還是算了,再回頭一下,把這個也給梳理了。所以此文主要做兩件事情,
1.梳理Digester的設計思想和常用接口。
2.梳理Digester對server.xml文件的解析。這么看也算是理論和實際相結合吧。
1.XML文件解析的兩種方案。
Java解析XML文件有兩個主要的思想,分別是:
1.1.預加載DOM樹
該方法的思路是:將整個XML文件讀取到內存中,在內容中構造一個DOM樹,也叫對象模型集合,然后java代碼只需要操作這個樹就可以。
該方法的主要實現為DOM解析,在此基礎上有兩個擴展:JDOM解析,DOM4J解析。這兩種方法的思路都是一樣的,只不過一個是官方出的,一個是社區出的,好不好用的問題。Java很奇怪,都是社區出的更好用,即DOM4J。
該思想有優點和缺點:
- 優點:使用DOM時,XML文檔的結構在內存中很清晰,元素與元素之間的關系保留了下來。即能記錄文檔的所有信息。
- 缺點:如果XML文檔非常大,把整個XML文檔裝在進內存容易造成內容溢出,無法加載了。
1.2.事件機制的SAX
該方法的思路是:一行一行的讀取XML文件,沒遇到一個節點,就看看有沒有監聽該事件的監聽器,如果有就觸發。當XML文件讀取結束后,內存里不會保存任何數據,整個XML的解析就結束了。所以,這里面最重要的是對感興趣的節點進行監聽和處理。
該思想仍然有優點和缺點:
- 優點:使用SAX不會占用大量內存來保存XML文檔數據,效率高。
- 缺點:當解析一個元素時,上一個元素的信息已經丟棄,也就是說沒有保存元素與元素之間的結構關系。
2.Digester的設計思想
2.1.主要解決的問題
Digester是對SAX的封裝和抽象,解決在SAX中特別不好用的地方,那么Digester主要解決了哪些問題呢?
-
1.XML的內容是變化的,那么不同的內容如何采用不同的方法去處理呢?用多個if進行判斷?顯然不是。
Digester解決該問題的方法是,抽象出兩個東西,一個是XML里面的內容,即XML中的節點,該內容可以使用正則進行書寫。另一個是方法,找到該節點時需要調用的處理方法。處理方法統一繼承Rule接口。
這樣當在讀取XML文件時,發現新的節點,就可以去找對應的正則,如果匹配,就可以調用對應的處理方法了。
-
2.解析一個XML上的節點時,該如何解析?
Digester解決該方法時,是將XML節點解析的生命周期進行抽象,不同生成周期做不同的事情,不同的處理方法只需要重寫生命周期對應的方法就行。
-
3.如何確保XML上不同的節點有依賴關系呢?
Digester在內部自己維護了一個棧的結構,每遇到一個新的節點時,把這個對象把它壓入棧(push),節點解析結束時,可以再從棧中彈出(pop),棧里面最頂部的對象永遠都是現在正在解析的對象,第二個是它的父節點,提供API調用父節點的方法把引用當前對象,這樣就解決了依賴的問題。
2.2.源碼主要結構
先對架構進行解釋一下,
-
最上面的
DefaultHandler
來自SAX,Digester
集成該類,足以說明Digester
底層用的是SAX了。 -
Client
表示調用Digester的Java代碼。 -
Digester
是真個Digester組件的核心類和入口類,Client需要實例化該類,設置改類,並調用里面的參數。 -
Rules
是一個保存XML的節點和規則的映射關系的接口,默認實現類是RulesBase
-
RuesBase
是Rules
的默認實現類。當有一個XML節點開始解析時,會在這里面找是否有對應的節點,並根據節點查找對應的處理規則。 -
Rule
對節點的處理方法的接口,內置的或者自定義的規則都是集成該接口。 -
**Rule
是接口Rule
的很多實現,根據具體的實現不同而不同。
使用Digester解析XML文檔的流程:
- 1.首先,Client需要創建一個Digester對象。
- 2.然后,Client必須根據自己的XML格式來添加所有的Rule。
- 3.添加完Rule后,Client調用Digester的parse操作來解析XML文件。
- 4.Digester實現了SAX的接口,解析時遇到具體的XML對象時會調用startElement等接口函數。
- 5.在這些SAX接口函數中,會掃描規則鏈(RulesBase),找到匹配規則,規則匹配一般都是根據具體的元素名稱來進行匹配。
- 6.找到對應的rule后,依次執行rule,這里的startElement對應的是begin操作,endElement對應的是end操作,具體要做的事情都在每個rule的begin,body,end函數中。
- 7.文檔結束后,會執行所有rule的finish函數。
Digester的巧妙設計:
1.XML格式變化:Digester把這個變化拋給具體用戶去解決,用戶在使用Digester之前必須自己根據自己的XML文件格式來構造規則鏈,Digester只提供構造規則鏈的手段,體現了有所為有所不為的設計思想。
2.處理方式變化的問題,Digester將處理方式抽象為規則Rule,一個規則對應一個處理方式,Digester提供了通用的缺省的Rule,如果覺得提供的規則不滿足自己的要求,可以自己另外定制。
Digester在代碼中的使用流程
//3.用digester解析server.xml文件,把配置文件中的配置解析成java對象
//3.1.准備好用來解析server.xml文件需要用的digester。
Digester digester = createStartDigester();
//3.2.server.xml文件作為一個輸入流傳入
File file = configFile();
InputStream inputStream = new FileInputStream(file);
//3.3.使用inputStream構造一個sax的inputSource
InputSource inputSource = new InputSource(file.toURI().toURL().toString());
inputSource.setByteStream(inputStream);
//3.4.把當前類壓入到digester的棧頂,用來作為digester解析出來的對象的一種引用
digester.push(this);
//3.5.調用digester的parse()方法進行解析。
digester.parse(inputSource);
- 3.1.准備好用來解析server.xml文件需要用的digester。
- 3.2.讀取server.xml文件作為一個輸入流。
- 3.3.使用inputStream構造一個sax的inputSource,因為digester底層用的是sax去解析的。
- 3.4.把當前類壓入到digester的棧頂,用來作為digester解析出來的對象的一種引用,digester自帶一個棧的結構。
- 3.5.調用digester的parse()方法進行解析。前面幾步都是在准備環境,這里才是正真的去解析了。
2.3.內部維護的棧結構
Digester用棧來維護每個正在解析的對象,注意,這個棧里面存放的是正在解析的,即開始解析時就放入棧,解析玩就彈出棧。
這個棧是Digester自己寫的一個,用ArrayList
來實現的,但是方法都一樣,在Digester類中進行維護,代碼為:
//org.apache.tomcat.util.digester.Digester
/**
* The object stack being constructed.
*/
protected ArrayStack<Object> stack = new ArrayStack<>();
該棧的主要方法有:
clear()
:清楚棧內的所有元素。peek()
:返回棧頂對象的引用,而不進行刪除。pop()
:彈出棧頂的元素。push()
:壓入一個元素到棧頂。
3.Digester的常用接口
3.1.解析XML節點時的生命周期
在解析XML節點時,有一整個生命周期在里面,解析一個節點時,會有一下生命周期,1.遇到匹配的節點的開始部分時開始解析,2.解析文本內容,3.遇到匹配的節點的結束部分時結束解析,4.最后處理資源。
-
begin()
:當讀取到匹配節點的開始部分時調用,會將該節點的所有屬性作為從參數傳入。 -
body()
:當讀取到匹配節點的內容時調用,注意指的不是子節點,而是嵌入內容為普通文本。 -
end()
:當讀取到匹配節點的結束部分時調用,如果存在子節點,只有當子節點處理完畢后該方法才會被調用。 -
finish()
:當整個parse()方法完成時調用,多用於清楚臨時數據和緩存數據。
3.2.內置的規則接口
Digester內置了一些規則,可以調用接口直接使用,還有一些其他的API可調用:
ObjectCreateRule
: 當begin()方法調用時,該規則會將指定的Java類實例化,並將其放入對象棧。具體的Java類可由該規則在構造方法出啊如,也可以通過當前處理XML節點的某個屬性指定,屬性名稱通過構造方法傳入。當end()方法調用時,該規則創建的對象將從棧中取出。FactoryCreateRule
:ObJectCreateRule規則的一個變體,用於處理Java類無默認構造方法的情況,或者需要在Digester處理該對象之前執行某些操作的情況。SetPropertiesRule
:當begin()方法調用時,Digester使用標准的Java Bean屬性操作方法(setter)將當前XML節點的屬性值設置到對象棧頂部的對象中。SetPropertyRule
:當begin()方法調用時,Digester會設置棧頂部對象指定屬性的值,其中屬性名和屬性值分別通過XML節點的兩個屬性指定。SetNextRule
:當end()方法調用時,Digester會找到位於棧頂部對象的下一個對象,並調用其指定的方法,同時將棧頂部對象作為參數傳入,用於設置父對象的子對象,以便在棧對象之間建立父子關系,從而形成對象樹,方便引用。SetTopRule
:與setNextRule對象,當end()方法調用時,Digester會找到位於站頂部的對象,調用其指定方法,同時將位於頂部下一個對象作為參數傳入,用於設置當前對象的父對象。CallMethRule
:該規則用於在end()方法調用時執行棧頂對象的某個方法,參數值由CallParamRule獲取。CallParamRule
:該規則與CallMethodRule配合使用,作為其子節點的處理規則創建方法參數,參數值可取自某個特殊屬性,也可以取自節點的內容。NodeCreateRule
:用於將XML文檔樹的一部分轉換為DOM節點,並放入棧。
3.3.自定義規則
如果需要自定義規則,只需要創建一個類繼承Rule接口,並重寫里面的生命周期方法,可以選擇只重寫部分。
例如:
public class ConnectorCreateRule extends Rule {
@Override
public void begin(String namespace, String name, Attributes attributes){
//do something
}
@Override
public void begin(String namespace, String name, Attributes attributes){
//do something
}
}
最后在使用自定義的規則時,方法如下:
digester.addRule("Server/Service/Connector",new ConnectorCreateRule());
此時即可。
3.4.addRuleSet方法
很多時候針對同一個節點,我們會執行很多規則,比如:創建一個對象,設置屬性,調用方法。至少三個。每次都寫三個是比較麻煩的,那么有沒有比較方便的方法呢?
有。使用addRuleSet()
方法,傳入需要匹配的節點名稱,然后對該節點使用多個規則。此方法方便重用。
案例:
首先自定義一個規則:
public class MyRuleSet
extends RuleSetBase {
public MyRuleSet()
{
this("");
}
public MyRuleSet(String prefix)
{
super();
this.prefix = prefix;
this.namespaceURI = "http://www.mycompany.com/MyNamespace";
}
protected String prefix = null;
public void addRuleInstances(Digester digester)
{
digester.addObjectCreate( prefix + "foo/bar",
"com.mycompany.MyFoo" );
digester.addSetProperties( prefix + "foo/bar" );
}
}
然后可以直接調用:
Digester digester = new Digester();
... configure Digester properties ...
digester.addRuleSet( new MyRuleSet( "baz/" ) );
4.Digester解析Server.xml的案例分析
上面說了這么多,終於可以開始梳理Tomcat中Digester對server.xml文件的解析了。
4.1.總的步驟
使用Digester的方法在上面有說過,再展示一下:
//3.用digester解析server.xml文件,把配置文件中的配置解析成java對象
//3.1.准備好用來解析server.xml文件需要用的digester。
Digester digester = createStartDigester();
//3.2.server.xml文件作為一個輸入流傳入
File file = configFile();
InputStream inputStream = new FileInputStream(file);
//3.3.使用inputStream構造一個sax的inputSource
InputSource inputSource = new InputSource(file.toURI().toURL().toString());
inputSource.setByteStream(inputStream);
//3.4.把當前類壓入到digester的棧頂,用來作為digester解析出來的對象的一種引用
digester.push(this);
//3.5.調用digester的parse()方法進行解析。
digester.parse(inputSource);
解釋一下:
- 3.1.准備好用來解析server.xml文件需要用的digester。
- 3.2.讀取server.xml文件作為一個輸入流。
- 3.3.使用inputStream構造一個sax的inputSource,因為digester底層用的是sax去解析的。
- 3.4.把當前類壓入到digester的棧頂,用來作為digester解析出來的對象的一種引用,digester自帶一個棧的結構。
- 3.5.調用digester的parse()方法進行解析。前面幾步都是在准備環境,這里才是正真的去解析了。
在第1個步驟中,需要先構造一個套規則,存放在digester實例中。構造代碼比較多,我拆開說
4.2.節點綁定規則
以下均為createStartDigester()
方法的內容
4.2.1.設置giester的參數:
// 創建一個digester實例
Digester digester = new Digester();
//是否對xml文檔進行類似XSD等類型的校驗,默認為fasle。
digester.setValidating(false);
//是否進行節點設置規則校驗,如果xml中相應節點沒有設置解析規則會在控制台顯示提示信息。
digester.setRulesValidation(true);
//將XML節點中的className作為假屬性,不必調用默認的setter方法
//(一般的節點屬性在解析時會以屬性作為入參調用該節點相應對象的setter方法,而className屬性的作用
// 提示解析器使用該屬性的值作來實例化對象)
Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
List<String> objectAttrs = new ArrayList<>();
objectAttrs.add("className");
fakeAttributes.put(Object.class, objectAttrs);
// Ignore attribute added by Eclipse for its internal tracking
List<String> contextAttrs = new ArrayList<>();
contextAttrs.add("source");
fakeAttributes.put(StandardContext.class, contextAttrs);
digester.setFakeAttributes(fakeAttributes);
//使用當前線程的上下文類加載器,主要加載FactoryCreateRule和ObjectCreateRule
digester.setUseContextClassLoader(true);
4.2.2.Server的解析
1.創建Server實例
digester.addObjectCreate("Server","org.apache.catalina.core.StandardServer","className");
digester.addSetProperties("Server");
digester.addSetNext("Server","setServer","org.apache.catalina.Server");
創建對象,設置屬性,調用方法添加到Catalina類中。
2.為Server添加全局J2EE企業命名上下文
digester.addObjectCreate("Server/GlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
digester.addSetProperties("Server/GlobalNamingResources");
digester.addSetNext("Server/GlobalNamingResources",
"setGlobalNamingResources",
"org.apache.catalina.deploy.NamingResourcesImpl");
創建對象,設置屬性,添加到Server中。
3.為Server添加生命周期監聽器
digester.addObjectCreate("Server/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Listener");
digester.addSetNext("Server/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
給Server添加監聽器,Server一共添加了5個監聽器,xml文件中顯示如下:
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- Security listener. Documentation at /docs/config/listeners.html
<Listener className="org.apache.catalina.security.SecurityListener" />
-->
<!--APR library loader. Documentation at /docs/apr.html -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
這5個監聽器的作用分別是:
VersionLoggerListener
:在Server初始化之前打印操作系統,JVM以及服務器的版本信息。AprLifecycleListener
:在Server初始化之前加載APR庫,並於Server停止之后銷毀。JreMemoryLeakPreventionListener
:在Server初始化之前調用,以解決單例對象創建導致的JVM內存泄漏問題以及鎖文件問題。GlobalResourcesLifecycleListener
:在Server啟動時,將JNDI資源注冊為MBean進行管理。ThreadLocalLeakPreventionListener
:用於在Context停止時重建Exceutor池中的線程,避免導致內存泄漏。
4.給Server添加Service實例
digester.addObjectCreate("Server/Service",
"org.apache.catalina.core.StandardService",
"className");
digester.addSetProperties("Server/Service");
digester.addSetNext("Server/Service",
"addService",
"org.apache.catalina.Service");
創建Service對象,設置參數,添加到Server中。
5.給Service添加生命周期監聽器
digester.addObjectCreate("Server/Service/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Listener");
digester.addSetNext("Server/Service/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
具體的監聽器由className屬性指定,默認情況下,Catalina未指定Service監聽器。
6.為Service添加Executor
digester.addObjectCreate("Server/Service/Executor",
"org.apache.catalina.core.StandardThreadExecutor",
"className");
digester.addSetProperties("Server/Service/Executor");
digester.addSetNext("Server/Service/Executor",
"addExecutor",
"org.apache.catalina.Executor");
通過該配置我們可以知道,Catalina共享Excetor的級別為Service,Catalina默認情況下未配置Executor,即不共享。
7.為Service添加Connector
digester.addRule("Server/Service/Connector",
new ConnectorCreateRule());
digester.addRule("Server/Service/Connector",
new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"}));
digester.addSetNext("Server/Service/Connector",
"addConnector",
"org.apache.catalina.connector.Connector");
設置相關屬性時,將executor和sslImplementationName屬性排除,因為在Connector創建時,會判斷當前是否指定了executor屬性,如果是,則從Service中查找該名稱的executor並設置到Connector中。同樣,Connector創建時,也會判斷是否添加了sslIlplementationName屬性,如果是,則將屬性值設置到使用的協議中,為其指定一個SSL實現。
8.Connector添加虛擬主機SSL配置
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
"org.apache.tomcat.util.net.SSLHostConfig");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
digester.addSetNext("Server/Service/Connector/SSLHostConfig",
"addSslHostConfig",
"org.apache.tomcat.util.net.SSLHostConfig");
digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
new CertificateCreateRule());
digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
new SetAllPropertiesRule(new String[]{"type"}));
digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
"addCertificate",
"org.apache.tomcat.util.net.SSLHostConfigCertificate");
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
"org.apache.tomcat.util.net.openssl.OpenSSLConf");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");
digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
"setOpenSslConf",
"org.apache.tomcat.util.net.openssl.OpenSSLConf");
digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
"org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd");
digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
"addCmd",
"org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
8.5版本新加內容
9.為Connector添加生命周期監聽器
digester.addObjectCreate("Server/Service/Connector/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Connector/Listener");
digester.addSetNext("Server/Service/Connector/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
具體監聽器類由className屬性指定。默認情況下,Catalina未指定Connector監聽器。
10.為Connector添加升級協議
digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
null, // MUST be specified in the element
"className");
digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
"addUpgradeProtocol",
"org.apache.coyote.UpgradeProtocol");
用於支持HTTP/2,8.5新增。
11.添加子元素解析規則
digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));
// When the 'engine' is found, set the parentClassLoader.
digester.addRule("Server/Service/Engine",
new SetParentClassLoaderRule(parentClassLoader));
addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");
此部分指定了Servlet容器相關的各級嵌套子節點的解析規則,而且沒類嵌套子節點的解析規則封裝為一個RuleSet,包括GlobalNamingResources,Engine,Host,Context以及Cluster解析。
4.2.3.Engine的解析
Engine的解析是放在digester.addRuleSet(new EngineRuleSet("Server/Service/"));
方法里面的。
具體如下
1.創建Engine實例
digester.addObjectCreate(prefix + "Engine",
"org.apache.catalina.core.StandardEngine",
"className");
digester.addSetProperties(prefix + "Engine");
digester.addRule(prefix + "Engine",
new LifecycleListenerRule
("org.apache.catalina.startup.EngineConfig",
"engineConfigClass"));
digester.addSetNext(prefix + "Engine",
"setContainer",
"org.apache.catalina.Engine");
創建實例,並通過setContainer添加Service中。同時,還為Engine添加了一個生命周期的監聽器,代碼里寫死,不是通過server.xml配置的。用於打印Engine啟動和停止日志。
2.為Engine添加集群配置
digester.addObjectCreate(prefix + "Engine/Cluster",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Engine/Cluster");
digester.addSetNext(prefix + "Engine/Cluster",
"setCluster",
"org.apache.catalina.Cluster");
具體集群實現類由className屬性指定。
3.為Engine添加生命周期監聽器
digester.addObjectCreate(prefix + "Engine/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Engine/Listener");
digester.addSetNext(prefix + "Engine/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
由server.xml配置,默認情況下,未指定Engine監聽器。
4.為Engine添加安全配置
digester.addObjectCreate(prefix + "Engine/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Engine/Valve");
digester.addSetNext(prefix + "Engine/Valve",
"addValve",
"org.apache.catalina.Valve");
為Engine添加安全配置以及攔截器Valve,具體的攔截器由className屬性指定。
4.2.4.Host的解析
Host的解析位於HostRuleSet
類,digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
1.創建Host實例
digester.addObjectCreate(prefix + "Host",
"org.apache.catalina.core.StandardHost",
"className");
digester.addSetProperties(prefix + "Host");
digester.addRule(prefix + "Host",
new CopyParentClassLoaderRule());
digester.addRule(prefix + "Host",
new LifecycleListenerRule
("org.apache.catalina.startup.HostConfig",
"hostConfigClass"));
digester.addSetNext(prefix + "Host",
"addChild",
"org.apache.catalina.Container");
digester.addCallMethod(prefix + "Host/Alias",
"addAlias", 0);
創建Host實例,通過addChild()方法添加到Engine上。同時還為Host添加了一個生命周期監聽器HostConfig,同樣,該監聽器由Catalina默認添加,而不是server.xml配置。
2.為Host添加集群
digester.addObjectCreate(prefix + "Host/Cluster",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Cluster");
digester.addSetNext(prefix + "Host/Cluster",
"setCluster",
"org.apache.catalina.Cluster");
配置集群,集群的配置即可以在Engine級別,也可以在Host級別。
3.為Host添加生命周期管理
digester.addObjectCreate(prefix + "Host/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Listener");
digester.addSetNext(prefix + "Host/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
此部分監聽器由server.xml配置。默認情況下,Catalina未指定Host監聽器。
4.為Host添加安全配置
digester.addObjectCreate(prefix + "Host/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Host/Valve");
digester.addSetNext(prefix + "Host/Valve",
"addValve",
"org.apache.catalina.Valve");
為Host添加安全配置以及攔截器Valve,具體攔截器類由className屬性指定。Catalina為Host默認添加的攔截器為AccessLogValve,即用於記錄訪問日志。
4.2.5.Context的解析
Context的解析,位於ContextRuleSet
類,digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
。
多數情況下,我們並不需要在server.xml中配置Context,而是由HostConfig自動掃描部署目錄,以context.xml文件為基礎進行解析創建。
1.創建Context實例
if (create) {
digester.addObjectCreate(prefix + "Context",
"org.apache.catalina.core.StandardContext", "className");
digester.addSetProperties(prefix + "Context");
} else {
digester.addRule(prefix + "Context", new SetContextPropertiesRule());
}
if (create) {
digester.addRule(prefix + "Context",
new LifecycleListenerRule
("org.apache.catalina.startup.ContextConfig",
"configClass"));
digester.addSetNext(prefix + "Context",
"addChild",
"org.apache.catalina.Container");
}
Context的解析會根據create屬性的不同而有所區別,這主要是由於Context來源於多處。通過server.xml配置Context時,create是true,因此需要創建Context實例;而通過HostConfig自動創建Context時,create為false,此時僅需要解析節點即可。
2.為Context添加生命周期監聽器
digester.addObjectCreate(prefix + "Context/Listener",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Listener");
digester.addSetNext(prefix + "Context/Listener",
"addLifecycleListener",
"org.apache.catalina.LifecycleListener");
具體監聽器類由屬性className指定。
3.為Context指定類加載器
digester.addObjectCreate(prefix + "Context/Loader",
"org.apache.catalina.loader.WebappLoader",
"className");
digester.addSetProperties(prefix + "Context/Loader");
digester.addSetNext(prefix + "Context/Loader",
"setLoader",
"org.apache.catalina.Loader");
默認為org.apache.catalina.loader.WebappLoader,通過className屬性可以指定自己的實現類。
4.為Context添加會話管理器
digester.addObjectCreate(prefix + "Context/Manager",
"org.apache.catalina.session.StandardManager",
"className");
digester.addSetProperties(prefix + "Context/Manager");
digester.addSetNext(prefix + "Context/Manager",
"setManager",
"org.apache.catalina.Manager");
digester.addObjectCreate(prefix + "Context/Manager/Store",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Manager/Store");
digester.addSetNext(prefix + "Context/Manager/Store",
"setStore",
"org.apache.catalina.Store");
digester.addObjectCreate(prefix + "Context/Manager/SessionIdGenerator",
"org.apache.catalina.util.StandardSessionIdGenerator",
"className");
digester.addSetProperties(prefix + "Context/Manager/SessionIdGenerator");
digester.addSetNext(prefix + "Context/Manager/SessionIdGenerator",
"setSessionIdGenerator",
"org.apache.catalina.SessionIdGenerator");
默認實現為StandardManager,同時為管理器指定會話存儲方式和會話標識生成器。Context提供了多種會話管理方式。
5.為Context添加初始化參數
digester.addObjectCreate(prefix + "Context/Parameter",
"org.apache.tomcat.util.descriptor.web.ApplicationParameter");
digester.addSetProperties(prefix + "Context/Parameter");
digester.addSetNext(prefix + "Context/Parameter",
"addApplicationParameter",
"org.apache.tomcat.util.descriptor.web.ApplicationParameter");
通過該配置,為Context添加初始化參數。
6.為Context添加安全配置以及web資源配置
digester.addRuleSet(new RealmRuleSet(prefix + "Context/"));
digester.addObjectCreate(prefix + "Context/Resources",
"org.apache.catalina.webresources.StandardRoot",
"className");
digester.addSetProperties(prefix + "Context/Resources");
digester.addSetNext(prefix + "Context/Resources",
"setResources",
"org.apache.catalina.WebResourceRoot");
digester.addObjectCreate(prefix + "Context/Resources/PreResources",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Resources/PreResources");
digester.addSetNext(prefix + "Context/Resources/PreResources",
"addPreResources",
"org.apache.catalina.WebResourceSet");
digester.addObjectCreate(prefix + "Context/Resources/JarResources",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Resources/JarResources");
digester.addSetNext(prefix + "Context/Resources/JarResources",
"addJarResources",
"org.apache.catalina.WebResourceSet");
digester.addObjectCreate(prefix + "Context/Resources/PostResources",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Resources/PostResources");
digester.addSetNext(prefix + "Context/Resources/PostResources",
"addPostResources",
"org.apache.catalina.WebResourceSet");
7.為Context添加資源連接
digester.addObjectCreate(prefix + "Context/ResourceLink",
"org.apache.tomcat.util.descriptor.web.ContextResourceLink");
digester.addSetProperties(prefix + "Context/ResourceLink");
digester.addRule(prefix + "Context/ResourceLink",
new SetNextNamingRule("addResourceLink",
"org.apache.tomcat.util.descriptor.web.ContextResourceLink"));
為Context添加資源鏈接ContextResourceLink,用於J2EE命名服務。
8.為Context添加Valve
digester.addObjectCreate(prefix + "Context/Valve",
null, // MUST be specified in the element
"className");
digester.addSetProperties(prefix + "Context/Valve");
digester.addSetNext(prefix + "Context/Valve",
"addValve",
"org.apache.catalina.Valve");
為Context添加攔截器Valve。
9.為Context添加守護資源配置
digester.addCallMethod(prefix + "Context/WatchedResource",
"addWatchedResource", 0);
digester.addCallMethod(prefix + "Context/WrapperLifecycle",
"addWrapperLifecycle", 0);
digester.addCallMethod(prefix + "Context/WrapperListener",
"addWrapperListener", 0);
digester.addObjectCreate(prefix + "Context/JarScanner",
"org.apache.tomcat.util.scan.StandardJarScanner",
"className");
digester.addSetProperties(prefix + "Context/JarScanner");
digester.addSetNext(prefix + "Context/JarScanner",
"setJarScanner",
"org.apache.tomcat.JarScanner");
digester.addObjectCreate(prefix + "Context/JarScanner/JarScanFilter",
"org.apache.tomcat.util.scan.StandardJarScanFilter",
"className");
digester.addSetProperties(prefix + "Context/JarScanner/JarScanFilter");
digester.addSetNext(prefix + "Context/JarScanner/JarScanFilter",
"setJarScanFilter",
"org.apache.tomcat.JarScanFilter");
WatchedResource標簽用於為Context添加監視資源,當這些資源發生變更時,Web應用將會被重新加載,默認為WEB-INF/web.xml
WrapperLifecycle標簽用於為Context添加一個生命周期監聽類,此類的實例並非添加到Context上,而是添加到Context包含的Wrapper上。
WrapperListener標簽用於為Context添加一個容器監聽類,此類同樣添加到Wrapper上。
JarScanner標簽用於為Context添加一個Jar掃描器。JarScanner掃描Web應用和類加載層級的Jar包。
10.為Context添加Cookie處理器
digester.addObjectCreate(prefix + "Context/CookieProcessor",
"org.apache.tomcat.util.http.Rfc6265CookieProcessor",
"className");
digester.addSetProperties(prefix + "Context/CookieProcessor");
digester.addSetNext(prefix + "Context/CookieProcessor",
"setCookieProcessor",
"org.apache.tomcat.util.http.CookieProcessor");
5.總結
本以為Digester就是對server.xml解析,能有多大的問題,不想梳理的,迫於不懂,梳理之后發現它這個設計模式還是挺棒的,用戶只需要配置節點名,規則就行。通過棧獲取元素。其他的都寫好了,只需要用就行。很棒的設計模式,應該用的是策略模式的思想,非常棒。也是有不小收獲的。
參考:
- 《xml解析技術及JDOM和DOM4J的比較》https://blog.csdn.net/weixin_41547486/article/details/80906700
- 《Digester:一個通用xml引擎的設計剖析》https://blog.csdn.net/iteye_1315/article/details/81691465
- 《官方文檔》https://commons.apache.org/proper/commons-digester/guide/core.html
- 《Tomcat架構解析》—作者:劉光瑞