coding……
但行好事 莫问前程

Tomcat源码解读『server.xml解析』

上篇文章我们介绍到,在Catalina的load方法中,完成了server.xml的解析,将server.xml配置的Server、Service、Connector、Engine、Host各组件实例化,并维护父子级关系。本篇文章我们来看一下server.xml是如何解析的。

不难看到,parseServerXml方法执行后,各Tomcat组件就已经生成了。

1. parseServerXml

核心的解析逻辑就在上图38~49行:

Digester digester = start ? createStartDigester() : createStopDigester();
InputStream inputStream = resource.getInputStream();
InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);

创建一个 Digester对象,将当前对象压入Digester 里的对象栈顶,根据 inputSource里对应的文件xml路径及所创建的Digester对象所包含的解析规则生成相应对象,并调用相应方法将对象之间关联起来。这里我们引入一个新的概念Digester,Digester就是用来解析xml的。

2. XML文件解析方法

Java解析XML文件主要存在两种方式:预加载DOM树及事件机制的SAX。

2.1 预加载DOM树

将整个XML文件读取到内存中,在内容中构造一个DOM树,然后java代码只需要操作这个树就可以。该方法的主要实现为DOM解析,在此基础上有两个扩展:JDOM解析,DOM4J解析,DOM4J更常用一些。

  • 优点:使用DOM时,XML文档的结构在内存中很清晰,元素与元素之间的关系保留了下来,即能记录文档的所有信息
  • 缺点:如果XML文档非常大,把整个XML文档装在进内存容易造成内容溢出,无法加载了

2.2 事件机制的SAX

一行一行的读取XML文件,每遇到一个节点,就看看有没有监听该事件的监听器,如果有就触发。当XML文件读取结束后,内存里不会保存任何数据,整个XML的解析就结束了。所以,这里面最重要的是对感兴趣的节点进行监听和处理。

  • 优点:使用SAX不会占用大量内存来保存XML文档数据,效率高
  • 缺点:不像DOM一样将文档树长期留驻在内存,数据不是长久的。事件过后,若没保存数据,那么数据就会丢失

3. Digester

3.1 Digester基本架构

  • DefaultHandler2:来自SAX,Digester继承该类,说明Digester底层用的是SAX
  • Digester:Digester解析的核心类和入口类,客户端(需要解析xml的调用方)需要实例化该类,设置该类,并调用该类的方法
  • Rules:保存XML的节点和规则的映射关系,默认实现类是RulesBase
  • RuesBase:是Rules的默认实现类。当有一个XML节点开始解析时,会在这里面找是否有对应的节点,并根据节点查找对应的处理规则
  • Rule:节点的处理方法,内置的或者自定义的规则都是继承自该接口

3.2 Digester解析XML文档的流程

  • Client需要创建一个Digester对象
  • Client必须根据自己的XML格式来添加所有的Rule
  • Client调用Digester的parse操作来解析XML文件。
  • Digester实现了SAX的接口,解析时遇到具体的XML对象时会调用startElement(继承自DefaultHandler2)等方法
  • 在这些SAX接口函数中,会扫描规则链(RulesBase),找到匹配规则,规则匹配一般都是根据具体的元素名称来进行匹配
  • 找到对应的Rule后,依次执行Rule
  • 文档结束后,会执行所有Rule的finish函数

3.3 使用流程

Digester digester = createStartDigester();
File file = configFile();
InputStream inputStream = new FileInputStream(file);
InputSource inputSource = new InputSource(file.toURI().toURL().toString());
inputSource.setByteStream(inputStream);
digester.push(this);
digester.parse(inputSource);
  • 准备用来解析server.xml文件需要用的digester
  • 读取server.xml文件作为一个输入流
  • 使用inputStream构造一个sax的inputSource,因为digester底层用的是sax去解析的
  • 把当前类压入到digester的栈顶,用来作为digester解析出来的对象的一种引用,digester自带一个栈的结构
  • 调用digester的parse()方法进行解析。前面几步都是在准备环境,这里才是正真的去解析了

4 Digester解析xml示例

下面我们使用digester解析一段我们自定义的xml。首先我们需要引入依赖包:

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-digester3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-digester3</artifactId>
    <version>3.2</version>
</dependency>

4.1 自定义xml

我们通过xml表达学校、年级、班级这三层概念:

  1. 一个学校有名字属性,下面有多个年级
  2. 每个年级有名字属性,下面有多个班
  3. 每个班有名字和学生人数两个属性

我们自定义一个school.xml文件,如下:

<?xml version='1.0' encoding='utf-8'?>
<School name="Shiyan">
    <Grade name="1">
        <Class name="1" number="31"/>
        <Class name="2" number="32"/>
    </Grade>
    <Grade name="2">
        <Class name="1" number="41"/>
        <Class name="2" number="42"/>
        <Class name="3" number="37"/>
    </Grade>
</School>

4.2 自定义班级Class对象

public class Class {
    private String name;
    private int number;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}

跟上面xml对应,有名称和学生人数两个属性。

4.3 自定义年级Grade对象

public class Grade {
    private String name;
    private Class[] classes = new Class[0];
    private final Object servicesLock = new Object();

    public void addClass(Class c) {
        synchronized (servicesLock) {
            Class[] results = new Class[classes.length + 1];
            System.arraycopy(classes, 0, results, 0, classes.length);
            results[classes.length] = c;
            classes = results;
        }
    }

    public Class[] getClasses() {
        return classes;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

每个年级有名称属性,并且包含多个班级。

4.4 自定义学校School属性

public class School {
    private String name;
    private Grade[] grades = new Grade[0];
    private final Object servicesLock = new Object();

    public void addGrade(Grade g) {
        synchronized (servicesLock) {
            Grade[] results = new Grade[grades.length + 1];
            System.arraycopy(grades, 0, results, 0, grades.length);
            results[grades.length] = g;
            grades = results;
        }
    }

    public Grade[] getGrades() {
        return grades;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

学校有名称属性,并且包含多个年级。

4.5 使用Degister解析xml

public class DigesterTest {
    // 属性和get/set方法,假设我们解析出来的School对象放在这儿
    private School school;

    public School getSchool() {
        return school;
    }

    public void setSchool(School s) {
        this.school = s;
    }

    private void digester() throws IOException, SAXException {
        // 读取根据文件的路径,创建InputSource对象,digester解析的时候需要用到
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("school.xml");
        InputSource inputSource = new InputSource();
        inputSource.setByteStream(inputStream);

        // 创建Digester对象
        Digester digester = new Digester();
        // 是否需要用DTD验证XML文档的合法性
        //digester.setValidating(true);
        // 将当前对象放到对象堆的最顶层,这也是这个类为什么要有school属性的原因!
        digester.push(this);

        /*
         * 下面开始为Digester创建匹配规则
         * Digester中的School、School/Grade、School/Grade/Class,分别对应School.xml的School、Grade、Class节点
         */

        // 为School创建规则

        /*
         * Digester.addObjectCreate(String pattern, String className, String attributeName)
         * pattern, 匹配的节点
         * className, 该节点对应的默认实体类
         * attributeName, 如果该节点有attributeName属性, 用attributeName属性对应的值替换默认实体类
         *
         * Digester匹配到School节点
         *
         * 1. 如果School节点没有className属性,将创建com.zhuoli.service.digester.School对象;
         * 2. 如果School节点有className属性,将创建指定的(className属性的值)对象
         */
        digester.addObjectCreate("School", School.class.getName(), "className");
        // 将指定节点的属性映射到对象,即将School节点的name的属性映射到School.java
        digester.addSetProperties("School");

        /*
         * Digester.addSetNext(String pattern, String methodName, String paramType)
         * pattern, 匹配的节点
         * methodName, 调用父节点的方法
         * paramType, 父节点的方法接收的参数类型
         * Digester匹配到School节点,将调用DigesterTest(School的父节点)的setSchool方法,参数为School对象
         */
        digester.addSetNext("School", "setSchool", School.class.getName());

        // 为School/Grade创建规则
        digester.addObjectCreate("School/Grade", Grade.class.getName(), "className");
        digester.addSetProperties("School/Grade");

        // Grade的父节点为School
        digester.addSetNext("School/Grade", "addGrade", Grade.class.getName());

        // 为School/Grade/Class创建规则
        digester.addObjectCreate("School/Grade/Class", Class.class.getName(), "className");
        digester.addSetProperties("School/Grade/Class");
        digester.addSetNext("School/Grade/Class", "addClass", Class.class.getName());
        // 解析输入源
        digester.parse(inputSource);
    }

    // 只是将School对象进行控制台输出
    private void print(School s) {
        if (s != null) {
            System.out.println(s.getName() + "有" + s.getGrades().length + "个年级");
            for (int i = 0; i < s.getGrades().length; i++) {
                if (s.getGrades()[i] != null) {
                    Grade g = s.getGrades()[i];
                    System.out.println(g.getName() + "年级 有 " + g.getClasses().length + "个班:");
                    for (int j = 0; j < g.getClasses().length; j++) {
                        if (g.getClasses()[j] != null) {
                            Class c = g.getClasses()[j];
                            System.out.println(c.getName() + "班有" + c.getNumber() + "人");
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws IOException, SAXException {
        DigesterTest digesterTest = new DigesterTest();
        digesterTest.digester();
        digesterTest.print(digesterTest.school);
    }
}

运行结果:

4.6 Degister使用总结

Degister总体上,我们可以将其方法分为两类:操作类和规则类。

  • 操作类
    • void setValidating(boolean validating):是否根据DTD校验XML
    • void push(Object object):将对象压入栈
    • Object peek():获取栈顶对象
    • Object pop():弹出栈顶对象
    • Object parse(InputSource input):解析输入源
  • 规则类
    • void addObjectCreate(String pattern, String className, String attributeName):增加对象创建规则,当匹配到pattern模式时,如果指定了attributeName,则根据attributeName创建类对象;否则根据className创建类对象
    • void addSetProperties(String pattern):增加属性设置规则,当匹配到pattern模式时,就填充其属性
    • void addSetNext(String pattern, String methodName, String paramType):增加设置下一个规则,当匹配到pattern模式时,调用父节点的methodName方法,paramType为方法传入参数的类型
    • void addRule(String pattern, Rule rule):当匹配到pattern模式时,增加一个自定义规则
    • void addRuleSet(RuleSet ruleSet):增加规则集,一个规则集指的是对一个节点及下面的所有子节点(子节点、子节点的子节点…)的解析

5. Tomcat server.xml解析

5.1 server.xml

5.2 Degister创建

protected Digester createStartDigester() {
   // Initialize the digester
   Digester digester = new Digester();
   digester.setValidating(false);
   digester.setRulesValidation(true);
   Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
   // Ignore className on all elements
   List<String> objectAttrs = new ArrayList<>();
   objectAttrs.add("className");
   fakeAttributes.put(Object.class, objectAttrs);
   // Ignore attribute added by Eclipse for its internal tracking
   List<String> contextAttrs = new ArrayList<>();
   contextAttrs.add("source");
   fakeAttributes.put(StandardContext.class, contextAttrs);
   // Ignore Connector attribute used internally but set on Server
   List<String> connectorAttrs = new ArrayList<>();
   connectorAttrs.add("portOffset");
   fakeAttributes.put(Connector.class, connectorAttrs);
   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.addRule("Server/Listener",
           new ListenerCreateRule(null, "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");

   digester.addObjectCreate("Server/Service/Listener",
                            null, // MUST be specified in the element
                            "className");
   digester.addSetProperties("Server/Service/Listener");
   digester.addSetNext("Server/Service/Listener",
                       "addLifecycleListener",
                       "org.apache.catalina.LifecycleListener");

   //Executor
   digester.addObjectCreate("Server/Service/Executor",
                    "org.apache.catalina.core.StandardThreadExecutor",
                    "className");
   digester.addSetProperties("Server/Service/Executor");

   digester.addSetNext("Server/Service/Executor",
                       "addExecutor",
                       "org.apache.catalina.Executor");

   digester.addRule("Server/Service/Connector",
                    new ConnectorCreateRule());
   digester.addSetProperties("Server/Service/Connector",
           new String[]{"executor", "sslImplementationName", "protocol"});
   digester.addSetNext("Server/Service/Connector",
                       "addConnector",
                       "org.apache.catalina.connector.Connector");

   digester.addRule("Server/Service/Connector", new AddPortOffsetRule());

   digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
                            "org.apache.tomcat.util.net.SSLHostConfig");
   digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
   digester.addSetNext("Server/Service/Connector/SSLHostConfig",
           "addSslHostConfig",
           "org.apache.tomcat.util.net.SSLHostConfig");

   digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
                    new CertificateCreateRule());
   digester.addSetProperties("Server/Service/Connector/SSLHostConfig/Certificate", new String[]{"type"});
   digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
                       "addCertificate",
                       "org.apache.tomcat.util.net.SSLHostConfigCertificate");

   digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
                            "org.apache.tomcat.util.net.openssl.OpenSSLConf");
   digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");
   digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
                       "setOpenSslConf",
                       "org.apache.tomcat.util.net.openssl.OpenSSLConf");

   digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
                            "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
   digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd");
   digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
                       "addCmd",
                       "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");

   digester.addObjectCreate("Server/Service/Connector/Listener",
                            null, // MUST be specified in the element
                            "className");
   digester.addSetProperties("Server/Service/Connector/Listener");
   digester.addSetNext("Server/Service/Connector/Listener",
                       "addLifecycleListener",
                       "org.apache.catalina.LifecycleListener");

   digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
                             null, // MUST be specified in the element
                             "className");
   digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
   digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
                       "addUpgradeProtocol",
                       "org.apache.coyote.UpgradeProtocol");

   // Add RuleSets for nested elements
   digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
   digester.addRuleSet(new EngineRuleSet("Server/Service/"));
   digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
   digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
   addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
   digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));

   // When the 'engine' is found, set the parentClassLoader.
   digester.addRule("Server/Service/Engine",
                    new SetParentClassLoaderRule(parentClassLoader));
   addClusterRuleSet(digester, "Server/Service/Engine/Cluster/");

   return digester;

5.3 Server解析

5.3.1 创建Server实例

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

创建StandardServer对象,设置其对象的属性,调用父节点Catalina的setServer方法将Server添加到Catalina中。

5.3.2 为Server添加全局J2EE企业命名上下文

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

创建对象,设置属性,添加到父节点Server中。

5.3.4 为Server添加生命周期监听器

digester.addRule("Server/Listener",
        new ListenerCreateRule(null, "className"));
digester.addSetProperties("Server/Listener");
digester.addSetNext("Server/Listener",
                    "addLifecycleListener",
                    "org.apache.catalina.LifecycleListener");

当匹配到”Server/Listener”节点,使用ListenerCreateRule解析。注意ListenerCreateRule第一个参数是className,即默认类名,为null。所以Listener不会有默认值类型。因为Listener本来就是自定义的。

比如在server.xml中,为Server添加如下监听器:

<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- 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" />
<!-- 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" />
  • VersionLoggerListener:在Server初始化之前打印操作系统,JVM以及服务器的版本信息
  • AprLifecycleListener:在Server初始化之前加载APR库,并于Server停止之后销毁
  • JreMemoryLeakPreventionListener:在Server初始化之前调用,以解决单例对象创建导致的JVM内存泄漏问题以及锁文件问题
  • GlobalResourcesLifecycleListener:在Server启动时,将JNDI资源注册为MBean进行管理
  • ThreadLocalLeakPreventionListener:用于在Context停止时重建Exceutor池中的线程,避免导致内存泄漏

5.4 Servive解析

5.4.1 创建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实例,调用StandardService实例的set方法设置属性,最后调用父节点Server的addService方法,将service添加到Server。

5.4.2 为Service添加生命周期监听器

digester.addObjectCreate("Server/Service/Listener",
                         null, // MUST be specified in the element
                         "className");
digester.addSetProperties("Server/Service/Listener");
digester.addSetNext("Server/Service/Listener",
                    "addLifecycleListener",
                    "org.apache.catalina.LifecycleListener");

这里创建生命周期监听器,className上送值为null,所以不存在默认类型,比如有”Server/Service/Listener”节点的className指定类型。

随后调用父节点Service的addLifecycleListener方法,将监听器添加到Service。

5.5 Executor解析

//Executor
digester.addObjectCreate("Server/Service/Executor",
                 "org.apache.catalina.core.StandardThreadExecutor",
                 "className");
digester.addSetProperties("Server/Service/Executor");

digester.addSetNext("Server/Service/Executor",
                    "addExecutor",
                    "org.apache.catalina.Executor");

创建StandardThreadExecutor实例,并调用父节点Service的addExecutor方法添加到Service。通过该配置我们可以知道,Tomcat共享Excetor的级别为Service,Catalina默认情况下未配置Executor,即不共享。

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

创建Connector实例,并调用父组建Service的addConnector方法添加到Service。设置相关属性时,将executor和sslImplementationName属性排除,因为在Connector创建时,会判断当前是否指定了executor属性,如果是,则从Service中查找该名称的executor并设置到Connector中。同样,Connector创建时,也会判断是否添加了sslIlplementationName属性,如果是,则将属性值设置到使用的协议中,为其指定一个SSL实现。

5.6.1 为Connector添加虚拟主机SSL配置

digester.addObjectCreate("Server/Service/Connector/SSLHostConfig",
                         "org.apache.tomcat.util.net.SSLHostConfig");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig");
digester.addSetNext("Server/Service/Connector/SSLHostConfig",
        "addSslHostConfig",
        "org.apache.tomcat.util.net.SSLHostConfig");

digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
                 new CertificateCreateRule());
digester.addRule("Server/Service/Connector/SSLHostConfig/Certificate",
                 new SetAllPropertiesRule(new String[]{"type"}));
digester.addSetNext("Server/Service/Connector/SSLHostConfig/Certificate",
                    "addCertificate",
                    "org.apache.tomcat.util.net.SSLHostConfigCertificate");

digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
                         "org.apache.tomcat.util.net.openssl.OpenSSLConf");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf");
digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf",
                    "setOpenSslConf",
                    "org.apache.tomcat.util.net.openssl.OpenSSLConf");

digester.addObjectCreate("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
                         "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");
digester.addSetProperties("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd");
digester.addSetNext("Server/Service/Connector/SSLHostConfig/OpenSSLConf/OpenSSLConfCmd",
                    "addCmd",
                    "org.apache.tomcat.util.net.openssl.OpenSSLConfCmd");

5.6.2 为Connector添加生命周期监听器

digester.addObjectCreate("Server/Service/Connector/Listener",
                         null, // MUST be specified in the element
                         "className");
digester.addSetProperties("Server/Service/Connector/Listener");
digester.addSetNext("Server/Service/Connector/Listener",
                    "addLifecycleListener",
                    "org.apache.catalina.LifecycleListener");

5.6.3 为Connector添加升级协议

digester.addObjectCreate("Server/Service/Connector/UpgradeProtocol",
                          null, // MUST be specified in the element
                          "className");
digester.addSetProperties("Server/Service/Connector/UpgradeProtocol");
digester.addSetNext("Server/Service/Connector/UpgradeProtocol",
                    "addUpgradeProtocol",
                    "org.apache.coyote.UpgradeProtocol");

用于支持HTTP/2。

5.7 容器组件解析

// Add RuleSets for nested elements
digester.addRuleSet(new NamingRuleSet("Server/GlobalNamingResources/"));
digester.addRuleSet(new EngineRuleSet("Server/Service/"));
digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));
digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));
addClusterRuleSet(digester, "Server/Service/Engine/Host/Cluster/");
digester.addRuleSet(new NamingRuleSet("Server/Service/Engine/Host/Context/"));

此部分指定了Servlet容器相关的各级嵌套子节点的解析规则,而且没类嵌套子节点的解析规则封装为一个RuleSet,包括GlobalNamingResources,Engine,Host,Context以及Cluster解析。

5.7.1 Engine解析

Engine解析是在digester.addRuleSet(new EngineRuleSet("Server/Service/"));方法中完成的。最终EngineRuleSet的addRuleInstances会生效,将规则添加到Digester,如下所示:

public void addRuleInstances(Digester digester) {

    digester.addObjectCreate(prefix + "Engine",
                             "org.apache.catalina.core.StandardEngine",
                             "className");
    digester.addSetProperties(prefix + "Engine");
    digester.addRule(prefix + "Engine",
                     new LifecycleListenerRule
                     ("org.apache.catalina.startup.EngineConfig",
                      "engineConfigClass"));
    digester.addSetNext(prefix + "Engine",
                        "setContainer",
                        "org.apache.catalina.Engine");

    //Cluster configuration start
    digester.addObjectCreate(prefix + "Engine/Cluster",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Engine/Cluster");
    digester.addSetNext(prefix + "Engine/Cluster",
                        "setCluster",
                        "org.apache.catalina.Cluster");
    //Cluster configuration end

    digester.addObjectCreate(prefix + "Engine/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Engine/Listener");
    digester.addSetNext(prefix + "Engine/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");


    digester.addRuleSet(new RealmRuleSet(prefix + "Engine/"));

    digester.addObjectCreate(prefix + "Engine/Valve",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Engine/Valve");
    digester.addSetNext(prefix + "Engine/Valve",
                        "addValve",
                        "org.apache.catalina.Valve");
}

可以看到,该方法中完成了如下几项工作:

  • 创建实例,并通过setContainer添加Service中
  • 为Engine添加了一个生命周期的监听器EngineConfig,这个监听器是代码里写死,不是通过server.xml配置的
  • 为Engine添加集群配置
  • 为Engine添加生命周期监听器,这里的监听器是server.xml文件中配置的
  • 为Engine添加Valve,具体的拦截器由className属性指定,不指定默认值

5.7.2 Host的解析

Host的解析是在digester.addRuleSet(new HostRuleSet("Server/Service/Engine/"));方法中完成的。最终HostRuleSet的addRuleInstances会生效,将规则添加到Digester,如下所示:

public void addRuleInstances(Digester digester) {
    
    digester.addObjectCreate(prefix + "Host",
                             "org.apache.catalina.core.StandardHost",
                             "className");
    digester.addSetProperties(prefix + "Host");
    digester.addRule(prefix + "Host",
                     new CopyParentClassLoaderRule());
    digester.addRule(prefix + "Host",
                     new LifecycleListenerRule
                     ("org.apache.catalina.startup.HostConfig",
                      "hostConfigClass"));
    digester.addSetNext(prefix + "Host",
                        "addChild",
                        "org.apache.catalina.Container");

    digester.addCallMethod(prefix + "Host/Alias",
                           "addAlias", 0);

    //Cluster configuration start
    digester.addObjectCreate(prefix + "Host/Cluster",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Cluster");
    digester.addSetNext(prefix + "Host/Cluster",
                        "setCluster",
                        "org.apache.catalina.Cluster");
    //Cluster configuration end

    digester.addObjectCreate(prefix + "Host/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Listener");
    digester.addSetNext(prefix + "Host/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    digester.addRuleSet(new RealmRuleSet(prefix + "Host/"));

    digester.addObjectCreate(prefix + "Host/Valve",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Host/Valve");
    digester.addSetNext(prefix + "Host/Valve",
                        "addValve",
                        "org.apache.catalina.Valve");
}

该方法中完成了如下几项工作:

  • 创建Host实例,通过addChild()方法添加到Engine上
  • 为Host添加了一个生命周期监听器HostConfig,这个监听器是代码里写死,不是通过server.xml配置的。这里的HostConfig在后面的文章介绍Context构建时,会重点提到
  • 为Host配置集群,所以集群的配置即可以在Engine级别,也可以在Host级别
  • 为Host添加生命周期监听器,这里的监听器是server.xml文件中配置的
  • 为Host添加Valve,具体的拦截器由className属性指定,不指定默认值

5.7.3 Context解析

多数情况下,我们并不需要在server.xml中配置Context,而是由HostConfig自动扫描部署目录,以context.xml文件为基础进行解析创建

Context的解析,是在digester.addRuleSet(new ContextRuleSet("Server/Service/Engine/Host/"));方法中完成的,最终ContextRuleSet的addRuleInstances会生效,将规则添加到Digester,如下所示:

public void addRuleInstances(Digester digester) {

    // 1. 创建Context实例,通过server.xml配置Context时,create是true,需要创建Context实例;通过HostConfig创建Context时,create为false,此时仅需要解析节点即可
    if (create) {
        digester.addObjectCreate(prefix + "Context",
                "org.apache.catalina.core.StandardContext", "className");
        digester.addSetProperties(prefix + "Context");
    } else {
        digester.addSetProperties(prefix + "Context", new String[]{"path", "docBase"});
    }

    if (create) {
        digester.addRule(prefix + "Context",
                         new LifecycleListenerRule
                             ("org.apache.catalina.startup.ContextConfig",
                              "configClass"));
        digester.addSetNext(prefix + "Context",
                            "addChild",
                            "org.apache.catalina.Container");
    }

    // 2. 为Context添加生命周期监听器
    digester.addObjectCreate(prefix + "Context/Listener",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Listener");
    digester.addSetNext(prefix + "Context/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");

    // 3. 为Context指定类加载器,默认为org.apache.catalina.loader.WebappLoader
    digester.addObjectCreate(prefix + "Context/Loader",
                        "org.apache.catalina.loader.WebappLoader",
                        "className");
    digester.addSetProperties(prefix + "Context/Loader");
    digester.addSetNext(prefix + "Context/Loader",
                        "setLoader",
                        "org.apache.catalina.Loader");

    // 4. 为Context添加会话管理器,默认实现为StandardManager
    digester.addObjectCreate(prefix + "Context/Manager",
                             "org.apache.catalina.session.StandardManager",
                             "className");
    digester.addSetProperties(prefix + "Context/Manager");
    digester.addSetNext(prefix + "Context/Manager",
                        "setManager",
                        "org.apache.catalina.Manager");

    digester.addObjectCreate(prefix + "Context/Manager/Store",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Manager/Store");
    digester.addSetNext(prefix + "Context/Manager/Store",
                        "setStore",
                        "org.apache.catalina.Store");

    digester.addObjectCreate(prefix + "Context/Manager/SessionIdGenerator",
                             "org.apache.catalina.util.StandardSessionIdGenerator",
                             "className");
    digester.addSetProperties(prefix + "Context/Manager/SessionIdGenerator");
    digester.addSetNext(prefix + "Context/Manager/SessionIdGenerator",
                        "setSessionIdGenerator",
                        "org.apache.catalina.SessionIdGenerator");

    //5. 为Context添加初始化参数,通过该配置,为Context添加初始化参数
    digester.addObjectCreate(prefix + "Context/Parameter",
                             "org.apache.tomcat.util.descriptor.web.ApplicationParameter");
    digester.addSetProperties(prefix + "Context/Parameter");
    digester.addSetNext(prefix + "Context/Parameter",
                        "addApplicationParameter",
                        "org.apache.tomcat.util.descriptor.web.ApplicationParameter");

    // 6. 为Context添加安全配置以及web资源配置
    digester.addRuleSet(new RealmRuleSet(prefix + "Context/"));

    digester.addObjectCreate(prefix + "Context/Resources",
                             "org.apache.catalina.webresources.StandardRoot",
                             "className");
    digester.addSetProperties(prefix + "Context/Resources");
    digester.addSetNext(prefix + "Context/Resources",
                        "setResources",
                        "org.apache.catalina.WebResourceRoot");

    digester.addObjectCreate(prefix + "Context/Resources/PreResources",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Resources/PreResources");
    digester.addSetNext(prefix + "Context/Resources/PreResources",
                        "addPreResources",
                        "org.apache.catalina.WebResourceSet");

    digester.addObjectCreate(prefix + "Context/Resources/JarResources",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Resources/JarResources");
    digester.addSetNext(prefix + "Context/Resources/JarResources",
                        "addJarResources",
                        "org.apache.catalina.WebResourceSet");

    digester.addObjectCreate(prefix + "Context/Resources/PostResources",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Resources/PostResources");
    digester.addSetNext(prefix + "Context/Resources/PostResources",
                        "addPostResources",
                        "org.apache.catalina.WebResourceSet");

    // 7. 为Context添加资源连接,默认为ContextResourceLink,用于J2EE命名服务
    digester.addObjectCreate(prefix + "Context/ResourceLink",
            "org.apache.tomcat.util.descriptor.web.ContextResourceLink");
    digester.addSetProperties(prefix + "Context/ResourceLink");
    digester.addRule(prefix + "Context/ResourceLink",
            new SetNextNamingRule("addResourceLink",
                    "org.apache.tomcat.util.descriptor.web.ContextResourceLink"));

    // 8. 为Context添加Valve
    digester.addObjectCreate(prefix + "Context/Valve",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Valve");
    digester.addSetNext(prefix + "Context/Valve",
                        "addValve",
                        "org.apache.catalina.Valve");

    // 9. 为Context添加守护资源配置
    digester.addCallMethod(prefix + "Context/WatchedResource",
                           "addWatchedResource", 0);

    digester.addCallMethod(prefix + "Context/WrapperLifecycle",
                           "addWrapperLifecycle", 0);

    digester.addCallMethod(prefix + "Context/WrapperListener",
                           "addWrapperListener", 0);

    digester.addObjectCreate(prefix + "Context/JarScanner",
                             "org.apache.tomcat.util.scan.StandardJarScanner",
                             "className");
    digester.addSetProperties(prefix + "Context/JarScanner");
    digester.addSetNext(prefix + "Context/JarScanner",
                        "setJarScanner",
                        "org.apache.tomcat.JarScanner");

    digester.addObjectCreate(prefix + "Context/JarScanner/JarScanFilter",
                             "org.apache.tomcat.util.scan.StandardJarScanFilter",
                             "className");
    digester.addSetProperties(prefix + "Context/JarScanner/JarScanFilter");
    digester.addSetNext(prefix + "Context/JarScanner/JarScanFilter",
                        "setJarScanFilter",
                        "org.apache.tomcat.JarScanFilter");

    // 10. 为Context添加Cookie处理器
    digester.addObjectCreate(prefix + "Context/CookieProcessor",
                             "org.apache.tomcat.util.http.Rfc6265CookieProcessor",
                             "className");
    digester.addSetProperties(prefix + "Context/CookieProcessor");
    digester.addSetNext(prefix + "Context/CookieProcessor",
                        "setCookieProcessor",
                        "org.apache.tomcat.util.http.CookieProcessor");
}

通过如上Degister机制,我们实现了将server.xml解析的过程,输出结果就是相关的Tomcat的组件被实例化出来,并且维护好了组件的上下级关系。后续我们调用init方法和start方法,也是针对这些已经实例化后的组件展开的。

参考链接:

1. Digester组件

2. Tomcat组件梳理—Digester的使用

3. Engine、Host及Context的解析过程

4. Tomcat源码

赞(0) 打赏
Zhuoli's Blog » Tomcat源码解读『server.xml解析』
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

zhuoli's blog

联系我关于我

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏