Tomcat 7 啟動分析(三)Digester 的使用
前一篇文章裡最後看到 Bootstrap 的 main 方法最後會呼叫 org.apache.catalina.startup.Catalina 物件的 load 和 start 兩個方法,那麼就來看看這兩個方法裡面到底做了些什麼。
load 方法:
1 /**
2 * Start a new server instance.
3 */
4 public void load() {
5
6 long t1 = System.nanoTime();
7
8 initDirs();
9
10 // Before digester - it may be needed
11
12 initNaming();
13
14 // Create and execute our Digester
15 Digester digester = createStartDigester();
16
17 InputSource inputSource = null;
18 InputStream inputStream = null;
19 File file = null;
20 try {
21 file = configFile();
22 inputStream = new FileInputStream(file);
23 inputSource = new InputSource(file.toURI().toURL().toString());
24 } catch (Exception e) {
25 if (log.isDebugEnabled()) {
26 log.debug(sm.getString("catalina.configFail", file), e);
27 }
28 }
29 if (inputStream == null) {
30 try {
31 inputStream = getClass().getClassLoader()
32 .getResourceAsStream(getConfigFile());
33 inputSource = new InputSource
34 (getClass().getClassLoader()
35 .getResource(getConfigFile()).toString());
36 } catch (Exception e) {
37 if (log.isDebugEnabled()) {
38 log.debug(sm.getString("catalina.configFail",
39 getConfigFile()), e);
40 }
41 }
42 }
43
44 // This should be included in catalina.jar
45 // Alternative: don't bother with xml, just create it manually.
46 if( inputStream==null ) {
47 try {
48 inputStream = getClass().getClassLoader()
49 .getResourceAsStream("server-embed.xml");
50 inputSource = new InputSource
51 (getClass().getClassLoader()
52 .getResource("server-embed.xml").toString());
53 } catch (Exception e) {
54 if (log.isDebugEnabled()) {
55 log.debug(sm.getString("catalina.configFail",
56 "server-embed.xml"), e);
57 }
58 }
59 }
60
61
62 if (inputStream == null || inputSource == null) {
63 if (file == null) {
64 log.warn(sm.getString("catalina.configFail",
65 getConfigFile() + "] or [server-embed.xml]"));
66 } else {
67 log.warn(sm.getString("catalina.configFail",
68 file.getAbsolutePath()));
69 if (file.exists() && !file.canRead()) {
70 log.warn("Permissions incorrect, read permission is not allowed on the file.");
71 }
72 }
73 return;
74 }
75
76 try {
77 inputSource.setByteStream(inputStream);
78 digester.push(this);
79 digester.parse(inputSource);
80 } catch (SAXParseException spe) {
81 log.warn("Catalina.start using " + getConfigFile() + ": " +
82 spe.getMessage());
83 return;
84 } catch (Exception e) {
85 log.warn("Catalina.start using " + getConfigFile() + ": " , e);
86 return;
87 } finally {
88 try {
89 inputStream.close();
90 } catch (IOException e) {
91 // Ignore
92 }
93 }
94
95 getServer().setCatalina(this);
96
97 // Stream redirection
98 initStreams();
99
100 // Start the new server
101 try {
102 getServer().init();
103 } catch (LifecycleException e) {
104 if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
105 throw new java.lang.Error(e);
106 } else {
107 log.error("Catalina.start", e);
108 }
109
110 }
111
112 long t2 = System.nanoTime();
113 if(log.isInfoEnabled()) {
114 log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
115 }
116
117 }
這個 117 行的程式碼看起來東西挺多,把註釋、異常丟擲、記錄日誌、流關閉、非空判斷這些放在一邊就會發現實際上真正做事的就這麼幾行程式碼:
Digester digester = createStartDigester();
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
getServer().setCatalina(this);
getServer().init();
做的事情就兩個,一是建立一個 Digester 物件,將當前物件壓入 Digester 裡的物件棧頂,根據 inputSource 裡設定的檔案 xml 路徑及所建立的 Digester 物件所包含的解析規則生成相應物件,並呼叫相應方法將物件之間關聯起來。二是呼叫 Server 介面物件的 init 方法。
這裡碰到了 Digester,有必要介紹一下 Digester 的一些基礎知識。一般來說 Java 裡解析 xml 檔案有兩種方式:一種是 Dom4J 之類將檔案全部讀取到記憶體中,在記憶體裡構造一棵 Dom 樹的方式來解析。一種是 SAX 的讀取檔案流,在流中碰到相應的xml節點觸發相應的節點事件回撥相應方法,基於事件的解析方式,優點是不需要先將檔案全部讀取到記憶體中。
Digester 本身是採用 SAX 的解析方式,在其上提供了一層包裝,對於使用者更簡便友好罷了。最早是在 struts 1 裡面用的,後來獨立出來成為 apache 的 Commons 下面的一個單獨的子專案。Tomcat 裡又把它又封裝了一層,為了描述方便,直接拿 Tomcat 裡的 Digester 建一個單獨的 Digester 的例子來介紹。
1 package org.study.digester;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.util.ArrayList;
6 import java.util.HashMap;
7 import java.util.List;
8
9 import junit.framework.Assert;
10
11 import org.apache.tomcat.util.digester.Digester;
12 import org.xml.sax.InputSource;
13
14 public class MyDigester {
15
16 private MyServer myServer;
17
18 public MyServer getMyServer() {
19 return myServer;
20 }
21
22 public void setMyServer(MyServer myServer) {
23 this.myServer = myServer;
24 }
25
26 private Digester createStartDigester() {
27 // 例項化一個Digester物件
28 Digester digester = new Digester();
29
30 // 設定為false表示解析xml時不需要進行DTD的規則校驗
31 digester.setValidating(false);
32
33 // 是否進行節點設定規則校驗,如果xml中相應節點沒有設定解析規則會在控制檯顯示提示資訊
34 digester.setRulesValidation(true);
35
36 // 將xml節點中的className作為假屬性,不必呼叫預設的setter方法(一般的節點屬性在解析時將會以屬性值作為入參呼叫該節點相應物件的setter方法,而className屬性的作用是提示解析器用該屬性的值來例項化物件)
37 HashMap, List> fakeAttributes = new HashMap, List>();
38 ArrayList attrs = new ArrayList();
39 attrs.add("className");
40 fakeAttributes.put(Object.class, attrs);
41 digester.setFakeAttributes(fakeAttributes);
42
43 // addObjectCreate方法的意思是碰到xml檔案中的Server節點則建立一個MyStandardServer物件
44 digester.addObjectCreate("Server",
45 "org.study.digester.MyStandardServer", "className");
46 // 根據Server節點中的屬性資訊呼叫相應屬性的setter方法,以上面的xml檔案為例則會呼叫setPort、setShutdown方法,入參分別是8005、SHUTDOWN
47 digester.addSetProperties("Server");
48 // 將Server節點對應的物件作為入參呼叫棧頂物件的setMyServer方法,這裡的棧頂物件即下面的digester.push方法所設定的當前類的物件this,就是說呼叫MyDigester類的setMyServer方法
49 digester.addSetNext("Server", "setMyServer",
50 "org.study.digester.MyServer");
51
52 // 碰到xml的Server節點下的Listener節點時取className屬性的值作為例項化類例項化一個物件
53 digester.addObjectCreate("Server/Listener", null, "className");
54 digester.addSetProperties("Server/Listener");
55 digester.addSetNext("Server/Listener", "addLifecycleListener",
56 "org.apache.catalina.LifecycleListener");
57
58 digester.addObjectCreate("Server/Service",
59 "org.study.digester.MyStandardService", "className");
60 digester.addSetProperties("Server/Service");
61 digester.addSetNext("Server/Service", "addMyService",
62 "org.study.digester.MyService");
63
64 digester.addObjectCreate("Server/Service/Listener", null, "className");
65 digester.addSetProperties("Server/Service/Listener");
66 digester.addSetNext("Server/Service/Listener", "addLifecycleListener",
67 "org.apache.catalina.LifecycleListener");
68 return digester;
69 }
70
71 public MyDigester() {
72 Digester digester = createStartDigester();
73
74 InputSource inputSource = null;
75 InputStream inputStream = null;
76 try {
77 String configFile = "myServer.xml";
78 inputStream = getClass().getClassLoader().getResourceAsStream(
79 configFile);
80 inputSource = new InputSource(getClass().getClassLoader()
81 .getResource(configFile).toString());
82
83 inputSource.setByteStream(inputStream);
84 digester.push(this);
85 digester.parse(inputSource);
86 } catch (Exception e) {
87 e.printStackTrace();
88 } finally {
89 try {
90 inputStream.close();
91 } catch (IOException e) {
92 // Ignore
93 }
94 }
95
96 getMyServer().setMyDigester(this);
97 }
98
99 public static void main(String[] agrs) {
100 MyDigester md = new MyDigester();
101 Assert.assertNotNull(md.getMyServer());
102 }
103 }
上面是我自己寫的一個拿 Tomcat 裡的 Digester 的工具類解析 xml 檔案的例子,關鍵方法的呼叫含義已經在註釋中寫明,解析的是專案原始檔釋出的根目錄下的 myServer.xml 檔案。
<?xml version='1.0' encoding='utf-8'?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener
className="org.apache.catalina.core.JasperListener" />
<Listener
className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<Service name="Catalina">
<Listener
className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
</Service>
</Server>
Digester 的使用一般來說有4步:
- 例項化一個 Digester 物件,並在物件裡設定相應的節點解析規則。
- 設定要解析的檔案作為輸入源( InputSource ),這裡 InputSource 與 SAX 裡的一樣,用以表示一個 xml 實體。
- 將當前物件壓入棧。
- 呼叫 Digester 的 parse 方法解析 xml,生成相應的物件。
第 3 步中將當前物件壓入棧中的作用是可以儲存一個到 Digester 生成的一系列物件直接的引用,方便後續使用而已,所以不必是當前物件,只要有一個地方存放這個引用即可。
這裡有必要說明的是很多文章裡按照程式碼順序來描述 Digester 的所謂棧模型來講述 addSetNext 方法時的呼叫物件,實際上我的理解 addSetNext 方法具體哪個呼叫物件與XML檔案裡的節點樹形結構相關,當前節點的父節點是哪個物件該物件就是呼叫物件。可以試驗一下把這裡的程式碼順序打亂仍然可以按規則解析出來,createStartDigester 方法只是在定義解析規則,具體解析與 addObjectCreate、addSetProperties、addSetNext 這些方法的呼叫順序無關。Digester 自己內部在解析 xml 的節點元素時增加了一個 rule 的概念,addObjectCreate、addSetProperties、addSetNext 這些方法內部實際上是在新增 rule,在最後解析 xml 時將會根據讀取到的節點匹配相應節點路徑下的 rule,呼叫內部的方法。關於 Tomcat 內的 Digester 的解析原理以後可以單獨寫篇文章分析一下。
該示例的程式碼在附件中,將其放入 tomcat 7 的原始碼環境中即可直接執行。
看懂了上面的例子 Catalina 的 createStartDigester 方法應該就可以看懂了,它只是比示例要處理的節點型別更多,並且增加幾個自定義的解析規則,如 384 行在碰到 Server/GlobalNamingResources/ 節點時將會呼叫 org.apache.catalina.startup.NamingRuleSet 類中的 addRuleInstances 方法新增解析規則。
要解析的 XML 檔案預設會先找 conf/server.xml,如果當前專案找不到則通過其他路徑找 xml 檔案來解析,這裡以預設情況為例將會解析 server.xml
<?xml version='1.0' encoding='utf-8'?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Note: A "Server" is not itself a "Container", so you may not
define subcomponents such as "Valves" at this level.
Documentation at /docs/config/server.html
-->
<Server port="8005" shutdown="SHUTDOWN">
<!-- 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" />
<!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->
<Listener className="org.apache.catalina.core.JasperListener" />
<!-- 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" />
<!-- Global JNDI resources
Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<!-- A "Service" is a collection of one or more "Connectors" that share
a single "Container" Note: A "Service" is not itself a "Container",
so you may not define subcomponents such as "Valves" at this level.
Documentation at /docs/config/service.html
-->
<Service name="Catalina">
<!--The connectors can use a shared executor, you can define one or more named thread pools-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
<!-- A "Connector" represents an endpoint by which requests are received
and responses are returned. Documentation at :
Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
Java AJP Connector: /docs/config/ajp.html
APR (HTTP/AJP) Connector: /docs/apr.html
Define a non-SSL HTTP/1.1 Connector on port 8080
-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- A "Connector" using the shared thread pool-->
<!--
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->
<!-- Define a SSL HTTP/1.1 Connector on port 8443
This connector uses the JSSE configuration, when using APR, the
connector should be using the OpenSSL style configuration
described in the APR documentation -->
<!--
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
maxThreads="150" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" />
-->
<!-- Define an AJP 1.3 Connector on port 8009 -->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<!-- An Engine represents the entry point (within Catalina) that processes
every request. The Engine implementation for Tomcat stand alone
analyzes the HTTP headers included with the request, and passes them
on to the appropriate Host (virtual host).
Documentation at /docs/config/engine.html -->
<!-- You should set jvmRoute to support load-balancing via AJP ie :
<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
-->
<Engine name="Catalina" defaultHost="localhost">
<!--For clustering, please take a look at documentation at:
/docs/cluster-howto.html (simple how to)
/docs/config/cluster.html (reference documentation) -->
<!--
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
-->
<!-- Use the LockOutRealm to prevent attempts to guess user passwords
via a brute-force attack -->
<Realm className="org.apache.catalina.realm.LockOutRealm">
<!-- This Realm uses the UserDatabase configured in the global JNDI
resources under the key "UserDatabase". Any edits
that are performed against this UserDatabase are immediately
available for use by the Realm. -->
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
這樣經過對 xml 檔案的解析將會產生 org.apache.catalina.core.StandardServer、org.apache.catalina.core.StandardService、org.apache.catalina.connector.Connector、org.apache.catalina.core.StandardEngine、org.apache.catalina.core.StandardHost、org.apache.catalina.core.StandardContext 等等一系列物件,這些物件從前到後前一個包含後一個物件的引用(一對一或一對多的關係)。
解析完 xml 之後關閉檔案流,接著設定 StandardServer 物件(該物件在上面解析 xml 時)的 catalina 的引用為當前物件,這種物件間的雙向引用在 Tomcat 的很多地方都會碰到。
接下來將呼叫 StandardServer 物件的 init 方法。
上面分析的是 Catalina 的 load 方法,上一篇文章裡看到 Bootstrap 類啟動時還會呼叫 Catalina 物件的 start 方法,程式碼如下:
1 /**
2 * Start a new server instance.
3 */
4 public void start() {
5
6 if (getServer() == null) {
7 load();
8 }
9
10 if (getServer() == null) {
11 log.fatal("Cannot start server. Server instance is not configured.");
12 return;
13 }
14
15 long t1 = System.nanoTime();
16
17 // Start the new server
18 try {
19 getServer().start();
20 } catch (LifecycleException e) {
21 log.fatal(sm.getString("catalina.serverStartFail"), e);
22 try {
23 getServer().destroy();
24 } catch (LifecycleException e1) {
25 log.debug("destroy() failed for failed Server ", e1);
26 }
27 return;
28 }
29
30 long t2 = System.nanoTime();
31 if(log.isInfoEnabled()) {
32 log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
33 }
34
35 // Register shutdown hook
36 if (useShutdownHook) {
37 if (shutdownHook == null) {
38 shutdownHook = new CatalinaShutdownHook();
39 }
40 Runtime.getRuntime().addShutdownHook(shutdownHook);
41
42 // If JULI is being used, disable JULI's shutdown hook since
43 // shutdown hooks run in parallel and log messages may be lost
44 // if JULI's hook completes before the CatalinaShutdownHook()
45 LogManager logManager = LogManager.getLogManager();
46 if (logManager instanceof ClassLoaderLogManager) {
47 ((ClassLoaderLogManager) logManager).setUseShutdownHook(
48 false);
49 }
50 }
51
52 if (await) {
53 await();
54 stop();
55 }
56 }
這裡最主要的是呼叫 StandardServer 物件的 start 方法。
經過以上分析發現,在解析 xml 產生相應一系列物件後會順序呼叫 StandardServer 物件的 init、start 方法,這裡將會涉及到 Tomcat 的容器生命週期( Lifecycle ),關於這點留到下一篇文章中分析。
相關文章
- Tomcat 7 啟動分析(一)啟動指令碼Tomcat指令碼
- Tomcat 7 啟動分析(二)Bootstrap 類中的 main 方法TomcatbootAI
- Tomcat 7 啟動分析(四)各元件 init、start 方法呼叫Tomcat元件
- Tomcat 7 啟動分析(五)Lifecycle 機制和實現原理Tomcat
- Tomcat原始碼分析--啟動流程Tomcat原始碼
- TOMCAT原始碼分析(啟動框架)Tomcat原始碼框架
- 曹工說Tomcat3:深入理解 Tomcat DigesterTomcat
- 詳解Tomcat系列(一)-從原始碼分析Tomcat的啟動Tomcat原始碼
- centos7 設定tomcat自啟動CentOSTomcat
- 【shell】實現tomcat的啟動使用者判定Tomcat
- CentOS 7 下Tomcat啟動超慢的原因及解決方案CentOSTomcat
- Tomcat啟動過程(三):從SocketProcessor到ContainerTomcatAI
- Flutter系列三:Flutter啟動流程分析Flutter
- tomcat原始碼分析(第二篇 tomcat啟動過程詳解)Tomcat原始碼
- Tomcat 7 的一次請求分析(四)Tomcat 7 閥機制原理Tomcat
- 使用Digester解析XML文件示例 (轉)XML
- win7 64位的tomcat7無法作為服務啟動的問題Win7Tomcat
- maven外掛 tomcat7啟動報錯,異常如下MavenTomcat
- Tomcat 7 中 NIO 處理分析Tomcat
- Tomcat啟動指令碼Tomcat指令碼
- tomcat 啟動失敗Tomcat
- SpringBoot 使用外部Tomcat方法及啟動原理Spring BootTomcat
- SpringBootApplication是如何啟動Tomcat的? | 破解SpringBoot Tomcat啟動之謎 !Spring BootAPPTomcat
- Tomcat啟動流程簡析Tomcat
- Tomcat直接啟動正常,通過myeclipse啟動tomcat記憶體溢位TomcatEclipse記憶體溢位
- Tomcat 7 的一次請求分析(三)請求與容器中具體元件的匹配Tomcat元件
- 程式在Tomcat中的啟動順序Tomcat
- tomcat無法啟動的解決方法Tomcat
- 深入淺出Tomcat/2 - Tomcat啟動和停止Tomcat
- tomcat的啟動和網站的訪問Tomcat網站
- Tomcat 7中JDBC DataSources配置使用TomcatJDBC
- Netty原始碼分析(三):客戶端啟動Netty原始碼客戶端
- OpenRTMFP/Cumulus Primer(7)CumulusServer啟動流程分析(續3)Server
- OpenRTMFP/Cumulus Primer(7)CumulusServer 啟動流程分析(續3)Server
- centos7伺服器tomcat啟動正常,但是訪問頁面404CentOS伺服器Tomcat
- Linux下Tomcat重新啟動LinuxTomcat
- tomcat在linux下自啟動TomcatLinux
- tomcat開機啟動指令碼Tomcat指令碼