前一篇文章裏最後看到 Bootstrap 的 main 方法最後會調用 org.apache.catalina.startup.Catalina 對象的 load 和 start 兩個方法,那麼就來看看這兩個方法裏面到底作了些什麼。html
load 方法:java
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 行的代碼看起來東西挺多,把註釋、異常拋出、記錄日誌、流關閉、非空判斷這些放在一邊就會發現實際上真正作事的就這麼幾行代碼:web
Digester digester = createStartDigester();
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
getServer().setCatalina(this);
getServer().init();
複製代碼
作的事情就兩個,一是建立一個 Digester 對象,將當前對象壓入 Digester 裏的對象棧頂,根據 inputSource 裏設置的文件 xml 路徑及所建立的 Digester 對象所包含的解析規則生成相應對象,並調用相應方法將對象之間關聯起來。二是調用 Server 接口對象的 init 方法。express
這裏碰到了 Digester,有必要介紹一下 Digester 的一些基礎知識。通常來講 Java 裏解析 xml 文件有兩種方式:一種是 Dom4J 之類將文件所有讀取到內存中,在內存裏構造一棵 Dom 樹的方式來解析。一種是 SAX 的讀取文件流,在流中碰到相應的xml節點觸發相應的節點事件回調相應方法,基於事件的解析方式,優勢是不須要先將文件所有讀取到內存中。apache
Digester 自己是採用 SAX 的解析方式,在其上提供了一層包裝,對於使用者更簡便友好罷了。最先是在 struts 1 裏面用的,後來獨立出來成爲 apache 的 Commons 下面的一個單獨的子項目。Tomcat 裏又把它又封裝了一層,爲了描述方便,直接拿 Tomcat 裏的 Digester 建一個單獨的 Digester 的例子來介紹。tomcat
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方法,入參分別是800五、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 文件。bash
<?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步:app
第 3 步中將當前對象壓入棧中的做用是能夠保存一個到 Digester 生成的一系列對象直接的引用,方便後續使用而已,因此沒必要是當前對象,只要有一個地方存放這個引用便可。less
這裏有必要說明的是不少文章裏按照代碼順序來描述 Digester 的所謂棧模型來說述 addSetNext 方法時的調用對象,實際上個人理解 addSetNext 方法具體哪一個調用對象與XML文件裏的節點樹形結構相關,當前節點的父節點是哪一個對象該對象就是調用對象。能夠試驗一下把這裏的代碼順序打亂仍然能夠按規則解析出來,createStartDigester 方法只是在定義解析規則,具體解析與 addObjectCreate、addSetProperties、addSetNext 這些方法的調用順序無關。Digester 本身內部在解析 xml 的節點元素時增長了一個 rule 的概念,addObjectCreate、addSetProperties、addSetNext 這些方法內部其實是在添加 rule,在最後解析 xml 時將會根據讀取到的節點匹配相應節點路徑下的 rule,調用內部的方法。關於 Tomcat 內的 Digester 的解析原理之後能夠單獨寫篇文章分析一下。webapp
該示例的代碼在附件中,將其放入 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 ),關於這點留到下一篇文章中分析。