欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Tomcat7启动分析(三)Digester的使用

程序员文章站 2022-03-07 15:25:48
...

前一篇文章里最后看到Bootstrap的main方法最后会调用org.apache.catalina.startup.Catalina对象的load和start两个方法,那么就来看看这两个方法里面到底做了些什么。

 

load方法:

    /**
     * Start a new server instance.
     */
    public void load() {

        long t1 = System.nanoTime();

        initDirs();

        // Before digester - it may be needed

        initNaming();

        // Create and execute our Digester
        Digester digester = createStartDigester();

        InputSource inputSource = null;
        InputStream inputStream = null;
        File file = null;
        try {
            file = configFile();
            inputStream = new FileInputStream(file);
            inputSource = new InputSource(file.toURI().toURL().toString());
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("catalina.configFail", file), e);
            }
        }
        if (inputStream == null) {
            try {
                inputStream = getClass().getClassLoader()
                    .getResourceAsStream(getConfigFile());
                inputSource = new InputSource
                    (getClass().getClassLoader()
                     .getResource(getConfigFile()).toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail",
                            getConfigFile()), e);
                }
            }
        }

        // This should be included in catalina.jar
        // Alternative: don't bother with xml, just create it manually.
        if( inputStream==null ) {
            try {
                inputStream = getClass().getClassLoader()
                        .getResourceAsStream("server-embed.xml");
                inputSource = new InputSource
                (getClass().getClassLoader()
                        .getResource("server-embed.xml").toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail",
                            "server-embed.xml"), e);
                }
            }
        }


        if (inputStream == null || inputSource == null) {
            if  (file == null) {
                log.warn(sm.getString("catalina.configFail",
                        getConfigFile() + "] or [server-embed.xml]"));
            } else {
                log.warn(sm.getString("catalina.configFail",
                        file.getAbsolutePath()));
                if (file.exists() && !file.canRead()) {
                    log.warn("Permissions incorrect, read permission is not allowed on the file.");
                }
            }
            return;
        }

        try {
            inputSource.setByteStream(inputStream);
            digester.push(this);
            digester.parse(inputSource);
        } catch (SAXParseException spe) {
            log.warn("Catalina.start using " + getConfigFile() + ": " +
                    spe.getMessage());
            return;
        } catch (Exception e) {
            log.warn("Catalina.start using " + getConfigFile() + ": " , e);
            return;
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                // Ignore
            }
        }

        getServer().setCatalina(this);

        // Stream redirection
        initStreams();

        // Start the new server
        try {
            getServer().init();
        } catch (LifecycleException e) {
            if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
                throw new java.lang.Error(e);
            } else {
                log.error("Catalina.start", e);
            }

        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms");
        }

    }

这个110行的代码看起来东西挺多,把注释、异常抛出、记录日志、流关闭、非空判断这些放在一边就会发现实际上真正做事的就这么几行代码:

	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的解析方式,在其上提供了一层包装,对于使用者更简便友好罢了。最早是在struts1里面用的,后来独立出来成为apache的Commons下面的一个单独的子项目。Tomcat里又把它又封装了一层,为了描述方便,直接拿Tomcat里的Digester建一个单独的Digester的例子来介绍。

package org.study.digester;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import junit.framework.Assert;

import org.apache.tomcat.util.digester.Digester;
import org.xml.sax.InputSource;

public class MyDigester {

	private MyServer myServer;

	public MyServer getMyServer() {
		return myServer;
	}

	public void setMyServer(MyServer myServer) {
		this.myServer = myServer;
	}

	private Digester createStartDigester() {
		// 实例化一个Digester对象
		Digester digester = new Digester();

		// 设置为false表示解析xml时不需要进行DTD的规则校验
		digester.setValidating(false);

		// 是否进行节点设置规则校验,如果xml中相应节点没有设置解析规则会在控制台显示提示信息
		digester.setRulesValidation(true);

		// 将xml节点中的className作为假属性,不必调用默认的setter方法(一般的节点属性在解析时将会以属性值作为入参调用该节点相应对象的setter方法,而className属性的作用是提示解析器用该属性的值来实例化对象)
		HashMap<Class<?>, List<String>> fakeAttributes = new HashMap<Class<?>, List<String>>();
		ArrayList<String> attrs = new ArrayList<String>();
		attrs.add("className");
		fakeAttributes.put(Object.class, attrs);
		digester.setFakeAttributes(fakeAttributes);

		// addObjectCreate方法的意思是碰到xml文件中的Server节点则创建一个MyStandardServer对象
		digester.addObjectCreate("Server",
				"org.study.digester.MyStandardServer", "className");
		// 根据Server节点中的属性信息调用相应属性的setter方法,以上面的xml文件为例则会调用setPort、setShutdown方法,入参分别是8005、SHUTDOWN
		digester.addSetProperties("Server");
		// 将Server节点对应的对象作为入参调用栈顶对象的setMyServer方法,这里的栈顶对象即下面的digester.push方法所设置的当前类的对象this,就是说调用MyDigester类的setMyServer方法
		digester.addSetNext("Server", "setMyServer",
				"org.study.digester.MyServer");

		// 碰到xml的Server节点下的Listener节点时取className属性的值作为实例化类实例化一个对象
		digester.addObjectCreate("Server/Listener", null, "className");
		digester.addSetProperties("Server/Listener");
		digester.addSetNext("Server/Listener", "addLifecycleListener",
				"org.apache.catalina.LifecycleListener");

		digester.addObjectCreate("Server/Service",
				"org.study.digester.MyStandardService", "className");
		digester.addSetProperties("Server/Service");
		digester.addSetNext("Server/Service", "addMyService",
				"org.study.digester.MyService");

		digester.addObjectCreate("Server/Service/Listener", null, "className");
		digester.addSetProperties("Server/Service/Listener");
		digester.addSetNext("Server/Service/Listener", "addLifecycleListener",
				"org.apache.catalina.LifecycleListener");
		return digester;
	}

	public MyDigester() {
		Digester digester = createStartDigester();

		InputSource inputSource = null;
		InputStream inputStream = null;
		try {
			String configFile = "myServer.xml";
			inputStream = getClass().getClassLoader().getResourceAsStream(
					configFile);
			inputSource = new InputSource(getClass().getClassLoader()
					.getResource(configFile).toString());

			inputSource.setByteStream(inputStream);
			digester.push(this);
			digester.parse(inputSource);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				inputStream.close();
			} catch (IOException e) {
				// Ignore
			}
		}

		getMyServer().setMyDigester(this);
	}

	public static void main(String[] agrs) {
		MyDigester md = new MyDigester();
		Assert.assertNotNull(md.getMyServer());
	}
}

上面是我自己写的一个拿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步:1.实例化一个Digester对象,并在对象里设置相应的节点解析规则。2.设置要解析的文件作为输入源(InputSource),这里InputSource与SAX里的一样,用以表示一个xml实体。3.将当前对象压入栈,4.调用Digester的parse方法解析xml,生成相应的对象。第3步中将当前对象压入栈中的作用是可以保存一个到Digester生成的一系列对象直接的引用,方便后续使用而已,所以不必是当前对象,只要有一个地方存放这个引用即可。

 

这里有必要说明的是很多文章里按照代码顺序来描述Digester的所谓栈模型来讲述addSetNext方法时的调用对象,实际上我的理解addSetNext方法具体哪个调用对象与XML文件里的节点树形结构相关,当前节点的父节点是哪个对象该对象就是调用对象。可以试验一下把这里的代码顺序打乱仍然可以按规则解析出来,createStartDigester方法只是在定义解析规则,具体解析与addObjectCreate、addSetProperties、addSetNext这些方法的调用顺序无关。Digester自己内部在解析xml的节点元素时增加了一个rule的概念,addObjectCreate、addSetProperties、addSetNext这些方法内部实际上是在添加rule,在最后解析xml时将会根据读取到的节点匹配相应节点路径下的rule,调用内部的方法。关于Tomcat内的Digester的解析原理以后可以单独写篇文章分析一下。

该示例的代码在附件中,将其放入tomcat7的源代码环境中即可直接运行。

 

看懂了上面的例子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方法,代码如下:

    /**
     * Start a new server instance.
     */
    public void start() {

        if (getServer() == null) {
            load();
        }

        if (getServer() == null) {
            log.fatal("Cannot start server. Server instance is not configured.");
            return;
        }

        long t1 = System.nanoTime();

        // Start the new server
        try {
            getServer().start();
        } catch (LifecycleException e) {
            log.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
        }

        // Register shutdown hook
        if (useShutdownHook) {
            if (shutdownHook == null) {
                shutdownHook = new CatalinaShutdownHook();
            }
            Runtime.getRuntime().addShutdownHook(shutdownHook);

            // If JULI is being used, disable JULI's shutdown hook since
            // shutdown hooks run in parallel and log messages may be lost
            // if JULI's hook completes before the CatalinaShutdownHook()
            LogManager logManager = LogManager.getLogManager();
            if (logManager instanceof ClassLoaderLogManager) {
                ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                        false);
            }
        }

        if (await) {
            await();
            stop();
        }
    }

这里最主要的是调用StandardServer对象的start方法。

 

经过以上分析发现,在解析xml产生相应一系列对象后会顺序调用StandardServer对象的init、start方法,这里将会涉及到Tomcat的容器生命周期(Lifecycle),关于这点留到下一篇文章中分析。

相关标签: tomcat 源码分析