美文网首页Tomcat
Tomcat启动分析(二) - Catalina类

Tomcat启动分析(二) - Catalina类

作者: buzzerrookie | 来源:发表于2018-09-13 19:26 被阅读3次

前文提到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函数初始化目录,检查java.io.tmdir是否存在:
  • initNaming函数初始化命名,这个不清楚做什么的,好像是与JNDI有关的;
  • createStartDigester函数创建了一个Digester用来解析conf/server.xml文件,digester.parse(inputSource)开始真正的解析过程;
  • 为Server实例设置Tomcat主目录属性;
  • initStreams函数设置了新的标准输出和标准错误输出;
  • Server实例初始化。

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) {
                // 省略一些代码
            }
        }
    }
    // 省略一些代码
}
  • 2.1 ObjectCreateRule类
    ObjectCreateRule类的代码如下,正如其类名暗示的那样,其在begin方法中会根据类名利用反射实例化对象,并将该对象压进Digester的对象栈,而end方法会使Digester对象栈做出栈操作。
    public class ObjectCreateRule extends Rule {
        // 省略一些代码
        @Override
        public void begin(String namespace, String name, Attributes attributes)
                throws Exception {
    
            // Identify the name of the class to instantiate
            String realClassName = className;
            if (attributeName != null) {
                String value = attributes.getValue(attributeName);
                if (value != null) {
                    realClassName = value;
                }
            }
            if (digester.log.isDebugEnabled()) {
                digester.log.debug("[ObjectCreateRule]{" + digester.match +
                        "}New " + realClassName);
            }
    
            if (realClassName == null) {
                throw new NullPointerException("No class name specified for " +
                        namespace + " " + name);
            }
    
            // Instantiate the new object and push it on the context stack
            Class<?> clazz = digester.getClassLoader().loadClass(realClassName);
            Object instance = clazz.getConstructor().newInstance();
            digester.push(instance);
        }
    }
    
    @Override
    public void end(String namespace, String name) throws Exception {
    
        Object top = digester.pop();
        if (digester.log.isDebugEnabled()) {
            digester.log.debug("[ObjectCreateRule]{" + digester.match +
                    "} Pop " + top.getClass().getName());
        }
    }
    
  • 2.2 SetPropertiesRule类
    SetPropertiesRule类的代码如下,正如其类名暗示的那样,其在begin方法中使用IntrospectionUtils类的setProperty方法调用ObjectCreateRule创建实例的setter方法设置属性:
    public class SetPropertiesRule extends Rule {
        // 省略一些代码
        @Override
        public void begin(String namespace, String theName, Attributes attributes)
                throws Exception {
    
            // Populate the corresponding properties of the top object
            Object top = digester.peek();
            if (digester.log.isDebugEnabled()) {
                if (top != null) {
                    digester.log.debug("[SetPropertiesRule]{" + digester.match +
                                    "} Set " + top.getClass().getName() +
                                    " properties");
                } else {
                    digester.log.debug("[SetPropertiesRule]{" + digester.match +
                                    "} Set NULL properties");
                }
            }
    
            for (int i = 0; i < attributes.getLength(); i++) {
                String name = attributes.getLocalName(i);
                if ("".equals(name)) {
                    name = attributes.getQName(i);
                }
                String value = attributes.getValue(i);
    
                if (digester.log.isDebugEnabled()) {
                    digester.log.debug("[SetPropertiesRule]{" + digester.match +
                            "} Setting property '" + name + "' to '" +
                            value + "'");
                }
                if (!digester.isFakeAttribute(top, name)
                        && !IntrospectionUtils.setProperty(top, name, value)
                        && digester.getRulesValidation()) {
                    digester.log.warn("[SetPropertiesRule]{" + digester.match +
                            "} Setting property '" + name + "' to '" +
                            value + "' did not find a matching property.");
                }
            }
        }
    }
    
  • 2.3 SetNextRule类
    SetNextRule类的代码如下,其在end方法中使用IntrospectionUtils类的callMethod1方法在Digester对象栈栈顶元素和次栈顶元素间建立关联关系,即在次栈顶的对象上调用方法,参数是栈顶的对象。
    public class SetNextRule extends Rule {
        // 省略一些代码
        @Override
        public void end(String namespace, String name) throws Exception {
            // Identify the objects to be used
            Object child = digester.peek(0);
            Object parent = digester.peek(1);
            if (digester.log.isDebugEnabled()) {
                if (parent == null) {
                    digester.log.debug("[SetNextRule]{" + digester.match +
                            "} Call [NULL PARENT]." +
                            methodName + "(" + child + ")");
                } else {
                    digester.log.debug("[SetNextRule]{" + digester.match +
                            "} Call " + parent.getClass().getName() + "." +
                            methodName + "(" + child + ")");
                }
            }
    
            // Call the specified method
            IntrospectionUtils.callMethod1(parent, methodName,
                    child, paramType, digester.getClassLoader());
        }
    }
    
    以createStartDigester函数中的两行代码为例:
    • Server/Listener表示在处理<Server>元素的<Listener>子元素后需要执行SetNextRule规则,此时栈顶应该是Listener对象,次栈顶是Server对象。SetNextRule会调用Server的addLifecycleListener方法,参数即是Listener对象,参数类型是org.apache.catalina.LifecycleListener;
    • Server/Service/Listener表示在处理<Service>元素的<Listener>子元素后需要执行SetNextRule规则,此时栈顶应该是Listener对象,次栈顶是Service对象,再次栈顶才是Server对象。SetNextRule会调用Service的addLifecycleListener方法,参数即是Listener对象,参数类型是org.apache.catalina.LifecycleListener。
    digester.addSetNext("Server/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener");
    digester.addSetNext("Server/Service/Listener", "addLifecycleListener", "org.apache.catalina.LifecycleListener");
    

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>元素时:

  • 遇到<Server port="8005" shutdown="SHUTDOWN">时,ObjectCreateRule首先创建一个org.apache.catalina.core.StandardServer类型的实例;
  • 遇到<Server port="8005" shutdown="SHUTDOWN">时,SetPropertiesRule然后将<Server>元素的属性和值设置到生成的实例上,即在StandardServer实例上调用setPort将port设置为8005、调用setShutdown将shutdown设置为SHUTDOWN;
  • 遇到<Server/>时,Digester对象栈栈顶是生成的StandardServer实例,次栈顶对象是Catalina实例(上文提到开始解析前Catalina实例自身先入栈),SetNextRule在Catalina实例上调用setServer方法,参数是栈顶的StandardServer实例,方法参数类型是org.apache.catalina.Server。

4. 解析成果

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

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

    创建了一个StandardServer实例,关闭端口是8005,关闭命令是SHUTDOWN。

  • 4.2 Listener元素
    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");
    

    分别创建了VersionLoggerListener、AprLifecycleListener、JreMemoryLeakPreventionListener、GlobalResourcesLifecycleListener和ThreadLocalLeakPreventionListener实例,这些Listener都被添加到了StandardServer上。

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

    创建StandardService实例,这个实例被添加到了SnadardServer上。

  • 4.4 Connector元素
    digester.addRule("Server/Service/Connector", new ConnectorCreateRule());
    digester.addRule("Server/Service/Connector", new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"}));
    digester.addSetNext("Server/Service/Connector", "addConnector",  "org.apache.catalina.connector.Connector");
    

    ConnectorCreateRule的部分源码如下:

    @Override
    public void begin(String namespace, String name, Attributes attributes)
            throws Exception {
        Service svc = (Service)digester.peek();
        Executor ex = null;
        if ( attributes.getValue("executor")!=null ) {
            ex = svc.getExecutor(attributes.getValue("executor"));
        }
        Connector con = new Connector(attributes.getValue("protocol"));
        if (ex != null) {
            setExecutor(con, ex);
        }
        String sslImplementationName = attributes.getValue("sslImplementationName");
        if (sslImplementationName != null) {
            setSSLImplementationName(con, sslImplementationName);
        }
        digester.push(con);
    }
    

    ConnectorCreateRule的begin方法中调用了Connector类的构造函数,参数是xml属性protocol的值,相关代码如下:

    public Connector(String protocol) {
        setProtocol(protocol);
        // Instantiate protocol handler
        ProtocolHandler p = null;
        try {
            Class<?> clazz = Class.forName(protocolHandlerClassName);
            p = (ProtocolHandler) clazz.getConstructor().newInstance();
        } catch (Exception e) {
            log.error(sm.getString(
                    "coyoteConnector.protocolHandlerInstantiationFailed"), e);
        } finally {
            this.protocolHandler = p;
        }
    
        if (Globals.STRICT_SERVLET_COMPLIANCE) {
            uriCharset = StandardCharsets.ISO_8859_1;
        } else {
            uriCharset = StandardCharsets.UTF_8;
        }
    }
    
    public void setProtocol(String protocol) {
        boolean aprConnector = AprLifecycleListener.isAprAvailable() &&
                AprLifecycleListener.getUseAprConnector();
        if ("HTTP/1.1".equals(protocol) || protocol == null) {
            if (aprConnector) {
                setProtocolHandlerClassName("org.apache.coyote.http11.Http11AprProtocol");
            } else {
                setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");
            }
        } else if ("AJP/1.3".equals(protocol)) {
            if (aprConnector) {
                setProtocolHandlerClassName("org.apache.coyote.ajp.AjpAprProtocol");
            } else {
                setProtocolHandlerClassName("org.apache.coyote.ajp.AjpNioProtocol");
            }
        } else {
            setProtocolHandlerClassName(protocol);
        }
    }
    
    public void setProtocolHandlerClassName(String protocolHandlerClassName) {
        this.protocolHandlerClassName = protocolHandlerClassName;
    }
    
    • setProtocol函数根据xml属性protocol的值设置成员变量protocolHandlerClassName的值;aprConnector变量的含义是是否使用Tomcat的APR库替代Java NIO,默认是false,如何启用请参阅Connector文档的protocol属性一栏;
    • Connector构造函数接着根据protocolHandlerClassName利用反射实例化ProtocolHandler;

    server.xml中的HTTP/1.1和AJP/1.3分别创建了用Http11NioProtocol处理的Connector和用AjpNioProtocol处理的Connector两个实例。

  • 4.5 其他

    暂时略,有兴趣的读者可自行阅读createStartDigester函数。

为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

相关文章

网友评论

    本文标题:Tomcat启动分析(二) - Catalina类

    本文链接:https://www.haomeiwen.com/subject/tglhgftx.html