Tomcat

Tomcat启动分析(二) - Catalina类

2018-09-13  本文已影响3人  buzzerrookie

前文提到Bootstrap类利用反射实例化Catalina类后会调用Catalina类的load方法,本文先从load函数开始分析Catalina类,然后再分析start函数。load函数代码如下:

public void load() {
    if (loaded) {
        return;
    }
    loaded = true;
    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 {
        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);
            }
        }
        // 省略一些代码
        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 {
        // 省略一些代码
    }
    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
    // 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");
    }
}

简单地说,load函数主要做了以下几件事:

initDirs函数

initDirs函数很简单,就是看一下java.io.tmpdir属性表示的是否是一个存在的目录:

protected void initDirs() {
    String temp = System.getProperty("java.io.tmpdir");
    if (temp == null || (!(new File(temp)).isDirectory())) {
        log.error(sm.getString("embedded.notmp", temp));
    }
}

initNaming函数

暂时略过,不太懂JNDI。

解析server.xml文件

因为以前做过和XML解析有关的工作,所以在load函数中看到InputSource后就意识到该部分代码与XML有关。解析工作由Digester类完成,该类的实例由createStartDigester函数创建,解析入口在digester.parse(inputSource)这一行。Digester类一边解析XML,一边利用反射实例化各个组件并建立组件之间的关系。createStartDigester函数相关代码如下:

protected Digester createStartDigester() {
    long t1=System.currentTimeMillis();
    // Initialize the digester
    Digester digester = new Digester();
    digester.setValidating(false);
    digester.setRulesValidation(true);
    HashMap<Class<?>, List<String>> fakeAttributes = new HashMap<>();
    ArrayList<String> attrs = new ArrayList<>();
    attrs.add("className");
    fakeAttributes.put(Object.class, attrs);
    digester.setFakeAttributes(fakeAttributes);
    digester.setUseContextClassLoader(true);

    // Configure the actions we will be using
    digester.addObjectCreate("Server",
                                "org.apache.catalina.core.StandardServer",
                                "className");
    digester.addSetProperties("Server");
    digester.addSetNext("Server",
                        "setServer",
                        "org.apache.catalina.Server");

    digester.addObjectCreate("Server/GlobalNamingResources",
                                "org.apache.catalina.deploy.NamingResourcesImpl");
    digester.addSetProperties("Server/GlobalNamingResources");
    digester.addSetNext("Server/GlobalNamingResources",
                        "setGlobalNamingResources",
                        "org.apache.catalina.deploy.NamingResourcesImpl");

    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");

    digester.addObjectCreate("Server/Service",
                                "org.apache.catalina.core.StandardService",
                                "className");
    digester.addSetProperties("Server/Service");
    digester.addSetNext("Server/Service",
                        "addService",
                        "org.apache.catalina.Service");

    // 省略一些代码
    long t2=System.currentTimeMillis();
    if (log.isDebugEnabled()) {
        log.debug("Digester for server.xml created " + ( t2-t1 ));
    }
    return (digester);
}

Server、Server/Listener和Server/Service等看着很眼熟,很像server.xml中的元素。从Digester类看看server.xml是如何被解析的。

1. Digester类

Digester类继承了SAX包的DefaultHandler2,重写了如startElement,endElement等解析事件处理函数。其在内部维护了一个栈,用来存解析过程中创建的实例。在Catalina的load方法中,digester.push(this);使Catalina实例自身在开始解析前先入栈。
createStartDigester函数调用了很多次addObjectCreate、addSetNext、addSetProperties和addRule函数,这些函数代码如下,它们都与Rule的概念有关。

public void addObjectCreate(String pattern, String className, String attributeName) {
    addRule(pattern, new ObjectCreateRule(className, attributeName));
}

public void addSetNext(String pattern, String methodName, String paramType) {
    addRule(pattern, new SetNextRule(methodName, paramType));
}

public void addSetProperties(String pattern) {
    addRule(pattern, new SetPropertiesRule());
}

2. Rule

简单地说,Rule就是解析XML时遇到符合pattern的元素时执行的动作。抽象类Rule的代码如下所示,最重要的方法分别是begin、body、end,分别在遇到匹配元素的起始处、元素体(body)和结尾处触发。

public abstract class Rule {
    // 省略一些代码
    public void begin(String namespace, String name, Attributes attributes) throws Exception {
        // NO-OP by default.
    }

    public void body(String namespace, String name, String text) throws Exception {
        // NO-OP by default.
    }

    public void end(String namespace, String name) throws Exception {
        // NO-OP by default.
    }
}

从Digester的方法也可以看到Rule的各个方法的执行时机,startElement和endElement分别重写了DefaultHandler2的方法,表示在元素起始和结束时触发。从下述代码中可以看到startElement方法会执行匹配Rule的begin方法,而endElement方法会执行匹配Rule的body和end方法。

@Override
public void startElement(String namespaceURI, String localName, String qName, Attributes list)
        throws SAXException {
    // 省略一些代码
    // Fire "begin" events for all relevant rules
    List<Rule> rules = getRules().match(namespaceURI, match);
    matches.push(rules);
    if ((rules != null) && (rules.size() > 0)) {
        for (int i = 0; i < rules.size(); i++) {
            try {
                Rule rule = rules.get(i);
                if (debug) {
                    log.debug("  Fire begin() for " + rule);
                }
                rule.begin(namespaceURI, name, list);
            } catch (Exception e) {
                // 省略一些代码
            }
        }
    }
}

@Override
public void endElement(String namespaceURI, String localName, String qName)
        throws SAXException {
    // 省略一些代码
    // Fire "body" events for all relevant rules
    List<Rule> rules = matches.pop();
    if ((rules != null) && (rules.size() > 0)) {
        String bodyText = this.bodyText.toString();
        for (int i = 0; i < rules.size(); i++) {
            try {
                Rule rule = rules.get(i);
                if (debug) {
                    log.debug("  Fire body() for " + rule);
                }
                rule.body(namespaceURI, name, bodyText);
            } catch (Exception e) {
                // 省略一些代码
            }
        }
    }
    // Recover the body text from the surrounding element
    bodyText = bodyTexts.pop();
    // Fire "end" events for all relevant rules in reverse order
    if (rules != null) {
        for (int i = 0; i < rules.size(); i++) {
            int j = (rules.size() - i) - 1;
            try {
                Rule rule = rules.get(j);
                if (debug) {
                    log.debug("  Fire end() for " + rule);
                }
                rule.end(namespaceURI, name);
            } catch (Exception e) {
                // 省略一些代码
            }
        }
    }
    // 省略一些代码
}

3. 实例

下面以Tomcat自带的server.xml为例说明这些Rule的作用,server.xml的代码如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
  <GlobalNamingResources>
    <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>
  <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <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>

以Server模式为例,createStartDigester函数为其添加了三个Rule:

digester.addObjectCreate("Server",  "org.apache.catalina.core.StandardServer", "className");
digester.addSetProperties("Server");
digester.addSetNext("Server", "setServer", "org.apache.catalina.Server");

当XML解析器遇到server.xml里面的<Server>元素时:

4. 解析成果

对照createStartDigester函数和Tomcat自带的server.xml,XML解析后生成了如下组件:

为Server实例设置目录属性

getServer函数返回XML解析过程中生成的Server实例,默认实现类是StandardServer,以下代码为该实例设置目录属性。

getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

initStreams函数

initStreams函数很简单,就是调用了System类的方法:

protected void initStreams() {
    // Replace System.out and System.err with a custom PrintStream
    System.setOut(new SystemLogHandler(System.out));
    System.setErr(new SystemLogHandler(System.err));
}

Server实例初始化

Server实例初始化和后面Catalina类的start函数主要是调用StandardServer实例的init和start方法,它们都是org.apache.catalina.Lifecycle接口的方法,放在下篇文章分析。

参考文献

Apache Tomcat 8 Architecture
Apache Tomcat 8 Startup

上一篇下一篇

猜你喜欢

热点阅读