Tomcat 7 啓動分析(三)Digester 的使用

前一篇文章裏最後看到 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

  1. 實例化一個 Digester 對象,並在對象裏設置相應的節點解析規則。
  2. 設置要解析的文件做爲輸入源( InputSource ),這裏 InputSource 與 SAX 裏的同樣,用以表示一個 xml 實體。
  3. 將當前對象壓入棧。
  4. 調用 Digester 的 parse 方法解析 xml,生成相應的對象。

第 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 &quot;%r&quot; %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 ),關於這點留到下一篇文章中分析。

相關文章
相關標籤/搜索