Tomcat的Catalina篇1-Catalina介绍和Digester解析Server对象

1,083 阅读19分钟

欢迎大家关注 github.com/hsfxuebao ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈

1. Catalina介绍

Catalina包含了前面讲到的所有容器组件,以及后续章节将会涉及的安全、会话、集群、部署、管理等Servlet容器架构的各个方面。它通过松耦合的方式集成Coyote,以完成按照请求协议进行数据读写。同时,它还包括我们的启动入口、Shell程序等。如果以一个简单的模块依赖图来描述Catalina在整个Tomcat中的位置,如下所示:

image.png

Tomcat本质上是一款Servlet容器,因此Catalina是Tomcat的核心,其他模块均为Catalina提供支撑。通过Coyote模块提供链接通信,Jasper模块提供JSP引擎,Naming提供JNDI服务,Juli提供日志服务。

2. Digester解析

  • Digester是一款用于将xml转换为Java对象的事件驱动型工具,是对SAX的高层次的封装。Digester相对于SAX提供了更加友好的接口,隐藏了xml节点具体层次的细节,让开发者能加载专注于处理过程。
  • Digester最早是Web框架Apache Struts的一部分,后来由于其通用性移植到了Apache Common项目中。

2.1 Digester解决了SAX哪些问题

SAX基于事件解析Xml,最重要的就是要编写一个解析器类。

public class SaxHandler extends DefaultHandler {

     /**
     * 解析一个xml标签字符时触发回调
       (该触发回调发生在startElement之后endElement之前)
     * @param ch  xml文档完整字符数组
     * @param start   当前标签中字符在ch开始位置
     * @param length  当前标签中字符的长度
     * @throws SAXException
     */
    @Override
    public void characters(char[] ch, int start, int length){
        ..省略实现
    }
    
    
     /**
     * 解析一个xml标签结束时触发回调
     * @param uri
     * @param localName
     * @param name        当前标签的名称
     * @throws SAXException
     */
    @Override
    public void endElement(String uri, String localName, String name){
        ..省略实现
    }
    
    
    /**
     * 解析一个xml标签开始时触发回调
     * @param uri
     * @param localName
     * @param name        当前标签的名称
     * @param attributes  标签中的属性对象
     * @throws SAXException
     */
    @Override
    public void startElement(String uri, String localName, String name,
                             Attributes attributes) throws SAXException {
        ..省略实现
    }
                             
    /**
     * 解析一个xml文件开始时回调
     */
    @Override
    public void startDocument() throws SAXException{
        ..省略实现
    }

    /**
     * 解析一个xml文件结束时回调
     */
    @Override
    public void endDocument() throws SAXException{
        ..省略实现
    }

2.1.1 SAX缺陷

  • 如果解析XML上不同标签节点存在依赖关系时,需要开发者自行维护依赖关系,说具体就是如果需要子标签对象手动设置到父标签属性中,子标签需要程序开发者手动保存下来。
  • 不同xml的标签属性,字符都不同,而SAX只提供了所有标签通用回调事件,不能针对不同标签定制。

2.2 Digester设计

image.png

  • Digester继承DefaultHandler表明Digester是对SAX的扩展实现

  • Digester内部存类型为Rules的属性,Rules为了一个其实现类为RulesBase,RulesBase内部维护HashMap,其中key对应匹配xml规则的字符串,value表示表示针对此xml规则解析规则列表。每个解析规则使用Rule类来表示。

public class Digester extends DefaultHandler2 {
       ...省略代码
       protected Rules rules = null;
       ...省略代码
}

public class RulesBase implements Rules {
    ...省略代码
    //String     表示匹配xml匹配规则(可以用正则表达式)
    //List<Rule> 表示针对对应xml规则解析规则列表
    protected HashMap<String,List<Rule>> cache = new HashMap<>();
    ...省略代码
  • 同时Digester内部维护了一个栈数据结构,用来处理当前解析的xml标签节点,解析前会将xml标签对象入栈(push),解析后会将xml标签对象出栈(pop),栈最顶部的对象永远都是现在正在解析的对象。这样就可以将有父子关系的节点对象在栈中保存下来

2.3 对象栈

Digester的对象栈(Digester同名类)主要在匹配模式满足时,由处理规则进行操作。它提供了常见的栈操作。

  • clear:清空对象栈。
  • peek:该操作有数个重载方法,可以实现得到位于栈顶部的对象或者从顶部数第n个对象,但是不会将对象从栈中移除。
  • pop:将位于栈顶部的对象移除并返回。
  • push:将对象放到栈顶部。

Digester的设计模式是指,在文件读取过程中,如果遇到一个XML节点的开始部分,则会触发处理规则事件创建Java对象,并将其放入栈。当处理该节点的子节点时,该对象都维护在栈中。当遇到该节点的结束部分时,该对象将会从栈中取出并清除。当然,这种设计模式需要解决几个问题,这些问题及Digester的解决方案如下。

  • 如何在创建的对象之间建立关联?最终得到的结果应该是一个分层次的Java对象树。Digester提供了一个处理规则实现(SetNextRule),该规则会调用位于栈顶部对象之后对象(即父对象)的某个方法,同时将顶部对象(子对象)作为参数传入。通过此种方式可以很容易在XML各Java对象之间建立父子关系,无论是一对一还是一对多的关系。

  • 如何持有创建的首个对象,即XML的转换结果?从上面的对象创建过程可知,当XML转换结束时,由于遇到了XML节点的结束部分,对象将从栈中移除。Digester对于曾经放入栈中的第一个对象将会持有一个引用,同时作为parse()方法的返回值。还有一种方式,可以在调用parset()方法之前,传入一个已创建的对象引用,Digester会动态地为这个对象和首个创建的对象建立父子关系。通过这种方式,传入的对象将会维护首个创建对象的引用以及所有子节点,当然传入对象也会在调用parse()方法时返回。Tomcat创建Servlet容器时采用的是后者。

2.4 xml匹配模式

Digester主要特征是自动遍历XML文档,而使开发者不必关注解析过程。与之对应,需要确定当读取到某个约定的XML节点时需要执行何种操作。Digester通过匹配模式指定相关约定。Digester的匹配模式非常简单,具体如下所示。

匹配模式XML节点描述
a<a>匹配所有名字为“a”的根节点,注意嵌套的同名子节点无法匹配
a/b<a><b></b>/a>匹配所有父节点为根节点“a”的名称为“b”的节点

例子:

  <a>            
    <b>          
      <c></c>    
    </b>
    <b>
      <c></c>
      <c></c>
      <c></c>
    </b>
  </a>  
  
a匹配<a>标签 
a/b匹配<a><b>
a/b/c匹配<a><b><c>
*/b 匹配<a><b>

当然,匹配模式还支持模糊匹配,如果我们希望所有节点都采用同一个处理规则,那么直接指定匹配规则为“*”即可,我们还可以指定“*b”来处理所有的名称为“b”的节点,而不限制其层次或者上级节点的名称。

当同一个匹配模式指定多个处理规则,或者多个匹配规则匹配同一个节点时,均会出现一个节点执行多个处理规则的情况。此时,Digester的处理方式是,开始读取节点时按照注册顺序执行处理规则,而完成读取时按照反向顺序执行,即先进后出的规则。

2.5 处理规则

匹配模式确定了何时触发处理操作,而处理规则则定义了模式匹配时的具体操作。处理规则需要实现接口org.apache,commons.digester..Rule,该接口定义了模式匹配时触发的事件方法。

  • begin():当读取到匹配节点的开始部分时调用,会将该节点的所有属性作为从参数传入。

  • body():当读取到匹配节点的内容时调用,注意指的不是子节点,而是嵌入内容为普通文本。

  • end():当读取到匹配节点的结束部分时调用,如果存在子节点,只有当子节点处理完毕后该方法才会被调用。

  • finish():当整个parse()方法完成时调用,多用于清楚临时数据和缓存数据

2.5.1 给指定标签设置规则

  1. 创建一个定义规则类,该类需要继承Rule

  2. 调用digester.addRule API 函数

public class ConnectorCreateRule extends Rule {
 
  @Override
    public void begin(String namespace, String name, Attributes attributes){
      //do something
    }
  
  @Override
    public void begin(String namespace, String name, Attributes attributes){
      //do something
    }
}

//表示匹配到<Server><Service><Connector>结构的标签时,使用创建的自定义接口
digester.addRule("Server/Service/Connector",new ConnectorCreateRule());

2.5.2 为单个标签添加规则组(多个标签规则)

当需要某个xml规则添加多个规则时可以使用RuleSet

  1. 创建一个RuleSet实现类,该类需要继承RuleSetBase

  2. 构造方法中需要定义xml规则

  3. 实现addRuleInstances方法对传入的规则添加规则

public class MyRuleSet
  extends RuleSetBase {

  public MyRuleSet()
  {
    this("");
  }

  public MyRuleSet(String prefix)
  {
    super();
    this.prefix = prefix;
    this.namespaceURI = "http://www.mycompany.com/MyNamespace";
  }

  protected String prefix = null;

  public void addRuleInstances(Digester digester)
  {
    digester.addObjectCreate( prefix + "foo/bar",
      "com.mycompany.MyFoo" );
    digester.addSetProperties( prefix + "foo/bar" );
  }

}

digester.addRuleSet( new MyRuleSet( "baz/" ) );

2.5.3 内置的规则接口

Digester内置了一些规则,可以调用使用DigesterAPI 直接调用,给指定xml添加内置规则。

我们可以通过Digester类的addRule()方法为某个匹配模式指定一个处理规则,同时可以根据需要实现自己的规则。针对大多数常见的场景,Digester为我们提供了默认的处理规则实现类,如表所示(注意Tomcat并未包含表中列出的所有的规则类)。

规则类描述
ObjectCreateRule当begin()方法调用时,该规则会将指定的Java类实例化,并将其放入对象栈。具体的Java类可由该规则的构造方法传入,也可以通过当前处理XML节点的某个属性指定,属性名称通过构造方法传人。当end()方法调用时,该规则创建的对象将从栈中取出
FactoryCreateRuleObjectCreateRule规则的一个变体,用于处理Java类无默认构造方法的情况,或者需要在Digester处理该对象之前执行某些操作的情况
SetPropertiesRule当begin()方法调用时,Digester使用标准的Java Bean属性操作方式(setter)将当前XML节点的属性值设置到栈顶部的对象中(Java Bean)属性名与XML节点属性名匹配)
SetPropertyRule当begin()方法调用时,Digester会设置栈顶部对象指定属性的值,其中属性名和属性值分别通过XML节点的两个属性指定
SetNextRule当end()方法调用时,Digester会找到位于栈顶部对象之后的对象调用指定的方法,同时将栈顶部对象作为参数传入,用于设置父对象的子对象,以在栈对象之间建立父子关系,从而形成对象树
SetTopRule与SetNextRule对应,当end()方法调用时,Digester会找到位于栈顶部的对象,调用其指定方法,同时将位于顶部对象之后的对象作为参数传入,用于设置当前对象的父对象
CallMethodRule该规则用于在end()方法调用时执行栈顶部对象的某个方法,参数值由CallParamRule获取
CallParamRule该规则与CallMethodRule配合使用,作为其子节点的处理规则创建方法参数,参数值可取自某个特殊属性,也可以取自节点的内容
NodeCreateRule用于将XML文档树的一部分转换为DOM节点,并放入栈

2.6 Digester常用API

public void setValidating(boolean validating) // 是否根据DTD校验XML

public void push(Object object) // 将对象压入栈

public Object peek() // 获取栈顶对象

public Object pop() // 弹出栈顶对象

public Object parse(InputSource input) // 解析输入源

public void addRule(String pattern, Rule rule) //针对指定xml标签设置规则解析器

2.7 示例程序

<?xml version="1.0" encoding="utf-8" ?>
<department name="deptname001" code="deptcode001">
    <user name="username001" code="usercode001"></user>
    <user name="username002" code="usercode002"></user>
    <extension>
        <property-name>director</property-name>
        <property-value>joke</property-value>
    </extension>
</department>
public class Department {

    private String name;

    private String code;

    private Map<String, String> extension = new HashMap<String, String>();

    private List<User> users = new ArrayList<User>();

    public void addUser(User user){
        this.users.add(user);
    }

    public void putExtension(String name,String value){
        this.extension.put(name,value);
    }

    public String getName() {
        return name;
    }

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

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public Map<String, String> getExtension() {
        return extension;
    }

    public void setExtension(Map<String, String> extension) {
        this.extension = extension;
    }

    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }
}



public class User {
    private String name;
    private String code;

    public String getName() {
        return name;
    }

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

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}
public class DigesterRule {

    public Department execute(String filePath) throws Exception {
        Digester digester = new Digester();
        digester.setValidating(false);
        digester.setRulesValidation(true);

        // addObjectCreate方法的意思是碰到xml文件中的department节点则创建一个Department对象
        digester.addObjectCreate("department", "com.wuhao.web.tomcat.digester.Department");
        // addSetProperties方法的意思是根据department节点中的属性信息调用相应属性的setter方法
        digester.addSetProperties("department");
        // addObjectCreate方法的意思是碰到xml文件中的department节点则创建一个Department对象
        digester.addObjectCreate("department/user", "com.wuhao.web.tomcat.digester.User");
        digester.addSetProperties("department/user");

        digester.addSetNext("department/user", "addUser", "com.wuhao.web.tomcat.digester.User");
        digester.addCallMethod("department/extension", "putExtension", 2);
        digester.addCallParam("department/extension/property-name", 0);
        digester.addCallParam("department/extension/property-value", 1);
        URL url = this.getClass().getClassLoader().getResource(filePath);
        return (Department) digester.parse(new File(url.getFile()));
    }
}
public class Test {

    @org.junit.Test
    public void testJavaRule() throws Exception {
        Department department = new DigesterRule().execute("tomcat/department.xml");
        System.out.println(department);
    }
}

3. 采用Digester解析Server对象

路径:Bootstrap.main -> Bootstrap.load(args) -> Catalina.load()反射调用

// catalina的加载信息
public void load() {

    if (loaded) {
        return;
    }
    loaded = true;

    long t1 = System.nanoTime();

    initDirs();

    // Before digester - it may be needed
    initNaming();

    // Parse main server.xml
    // 解析服务器的server.xml文件 使用Digester 技术进行xml文档对象解析
    parseServerXml(true);
    Server s = getServer();
    if (s == null) {
        return;
    }
    ...
}

接下来看,createStartDigester()方法:

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
    // 1. 创建Server实例
    digester.addObjectCreate("Server",
                             "org.apache.catalina.core.StandardServer",
                             "className");
    digester.addSetProperties("Server");
    digester.addSetNext("Server",
                        "setServer",
                        "org.apache.catalina.Server");
    // 2.创建全局J2EE企业命名上下文
    digester.addObjectCreate("Server/GlobalNamingResources",
                             "org.apache.catalina.deploy.NamingResourcesImpl");
    digester.addSetProperties("Server/GlobalNamingResources");
    digester.addSetNext("Server/GlobalNamingResources",
                        "setGlobalNamingResources",
                        "org.apache.catalina.deploy.NamingResourcesImpl");
    // 3.为Server添加生命周期监听器
    digester.addRule("Server/Listener",
            new ListenerCreateRule(null, "className"));
    digester.addSetProperties("Server/Listener");
    digester.addSetNext("Server/Listener",
                        "addLifecycleListener",
                        "org.apache.catalina.LifecycleListener");
    // 4.构建Service实例
    digester.addObjectCreate("Server/Service",
                             "org.apache.catalina.core.StandardService",
                             "className");
    digester.addSetProperties("Server/Service");
    digester.addSetNext("Server/Service",
                        "addService",
                        "org.apache.catalina.Service");
    // 5.为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
    // 6.为Service添加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");
    // 7.为Service添加Connector
    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());
    // 8.为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.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");

    // 9.为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");
    // 10.为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");

    // Add RuleSets for nested elements
    // 11.添加子元素解析规则
    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;

}

通过上面解析就可以将conf/Server.xml文件全部解析成对应类和对象,包含父子关系。 核心步骤如下:

  1. 创建Server实例

    • Catalina中Server的默认实现类为org.apache.catalina.core.StandardServer,但是我们可以通过属性className:指定自己的实现类。Digester创建Server实例后,设置Server的相关属性,并将其设置到Catalina对象中(调用setServer)
  2. 创建全局J2EE企业命名上下文

    • Catalina根据GlobalNamingResources配置创建全局的J2EE企业命名上下文(JNDI),设置属性并将其设置到Server实例当中(setGlobalNamingResources)
  3. 为Server添加生命周期监听器

    • Server元素支持配置Listener节点,用于为当前的Server实例添加LifecycleListener监听器,具体的监听器类型由className属性指定。Catalina默认配置了5个监听器, |类|描述| |--|--| |AprLifecycleListener|在Server初始化之前加载APR库,并于Server停止之后销段 |VersionLoggerListener|在Server初始化之前打印操作系统、JVM以及服务器的版本信息| |JreMemoryLeakPreventionListener|在Server初始化之前调用,以解决单例对象创建导致的VM内存泄露问题以及锁文件问题| |GlobalResourcesLifecycleListener|在Server)启动时,将NDI资源注册为MBean进行管理| |ThreadLocalLeakPreventionListener|用于在Context停止时重建Executor池中的线程,避免导致内存泄露|
  4. 构建Service实例

    • 为Server添加Service实例。Catalina默认的Service实现为org.apache.catalina.core,StandardService,同时,我们也可以通过className属性指定自己的实现类。创建完成后,通过addService()方法添加到Server实例中。
  5. 为Service添加生命周期监听器

    • 具体监听器类由className属性指定。默认情况下,Catalina未指定Service监听器。
  6. 为Service添加Executor

    • 默认实现为org,apache.catalina,core,StandardThreadExecutor,同样也可以通过className属性指定自己的实现类。通过该配置我们可以知道,Catalina共享Exector的级别为Service.Catalina默认情况下未配置Executor,即不共享。
  7. 为Service添加Connector

    • 同时设置相关属性。注意设置属性时,将executor和sslImplementationName,属性排除。因为在Connector创建时(即ConnectorCreateRule类中),会判断当前是否指定了executor.属性,如果是,则从Service中查找该名称的executor并设置到Connector中。同样,Connector创建时,也会判断是否添加了sslImplementationName属性,如果是,则将属性值设置到使用的协议中,为其指定一个SSL实现。
  8. 为Connector添加虚拟主机SSL配置

  9. 为Connector添加生命周期监听器

    • 具体监听器类由className,属性指定。默认情况下,Catalina未指定Connector!监听器。
  10. 为Connector添加升级协议

    • 用于支持HTTP/2,这是8.5.6和9.0版本新增的配置
  11. 添加子元素解析规则

    • 此部分指定了Servlet容器相关的各级嵌套子节点的解析规则,而且每类嵌套子节点的解析封装为一个RuleSet,包括GlobalNamingResources、Engine、Host、Contextl以及Cluster的解析。接下来,我们将重点解析Engine、Host和Context。

3.1 Engine 解析

代码位于EngineRuleSet.addRuleInstances() 方法,源码如下:

public void addRuleInstances(Digester digester) {

    // 1.创建Engine实例
    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
    // 2.为Engine添加集群配置
    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

    // 4.为Engine添加生命周期
    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");

    // 5. 为Engine添加安全配置
    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");
}

核心步骤如下:

  1. 创建Engine实例
    • 创建Engine实例,并将其通过setContainer()方法添加到Service实例,Catalina默认实现为org.apache.catalina..core.StandardEngine。同时,还为Engine添加了一个生命周期监听器EngineConfig。注意,此类是在创建时默认添加的,并非由server.xml配置实现。该监听器用于打印Engine,启动和停止日志。
  2. 为Engine添加集群配置
    • 具体集群实现类由className属性指定。
  3. 为Engine添加生命周期
    • 与EngineConfig不同,此部分监听器由server.xml配置。默认情况下,Catalina未指定Engine监听器。
  4. 为Engine添加安全配置
    • 添加安全配置,以及拦截器Value,具体拦截器类由classname属性指定。

3.2 Host解析

HostRuleSet.addRuleInstances() 方法,源码如下:

public void addRuleInstances(Digester digester) {

    // 1.创建Host实例
    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
    // 2.为Host添加集群
    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

    // 3.为Host 添加生命周期管理
    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");

    // 4.为Host添加安全配置
    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");
}

核心步骤如下:

  1. 创建Host实例
    • 创建Host实例,并将其通过addChild()方法添加到Engine上,Catalina默认实现为org.apache,catalina.core,StandardHost。同时,还为Host添加了一个生命周期监听器HostConfig,同样,该监听器由Catalina默认添加,而不是由server.xml配置。该监听器在Web应用部署过程中做了大量工作,后续我们会进一步讲解。此外,通过Alis,Host还支持配置别名。
  2. 为Host添加集群
    • 由此可知,集群配置既可以在Engine级别,也可以在Host级别。
  3. 为Host 添加生命周期管理
    • 与HostConfig不同,此部分监听器由server.xml配置。默认情况下,Catalina未指定Host监听器。
  4. 为Host添加安全配置
    • 为Host添加安全配置(具体见RealmRuleSet,.详情请参见第9章)以及拦截器Valve,具体的拦截器类由className属性指定。Catalina为Host默认添加的拦截器为AccessLog Valve,即用于记录访问日志。

3.3 Context解析

Catalinal的Context配置并非来源一处,此处仅指server.xml中的配置。在多数情况下,我们并 不需要在server.xml中配置Context,而是由HostConfig自动扫描部署目录,以context.xml文件为基 础进行解析创建,具体过程我们随后会详细讲解。当然,如果我们通过IDE(如Eclipse)启动Tomcat 并部署Web应用,其Context配置将会被动态更新到server.xml中。

ContextRuleSet..addRuleInstances() 方法,源码如下:

public void addRuleInstances(Digester digester) {

    // 1.Context实例化
    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 指定类加载器
    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添加会话管理器
    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添加初始化参数
    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/CacheStrategy",
                             null, // MUST be specified in the element
                             "className");
    digester.addSetProperties(prefix + "Context/Resources/CacheStrategy");
    digester.addSetNext(prefix + "Context/Resources/CacheStrategy",
                        "setCacheStrategy",
                        "org.apache.catalina.WebResourceRoot$CacheStrategy");

    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添加资源链接
    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 添加Value
    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");
}

核心步骤如下:

  1. Context实例化
    • Context的解析会根据create属性的不同而有所区别,这主要是由于Context来源于多处。通过server.xml配置Contexth时,create为true,因此需要创建Context实例;而通过HlostConfig自动创建Contexth时,create为false,此时仅需要解析子节点即可。Catalina提供的Context实现类为org.apache.catalina.core,StandardContext。Catalina在创建Context实例的同时,还添加了一个生命周期监听器ContextConfig,用于详细配置Context,如解析web.xml等,相关的内容我们随后会详细讲解。
  2. 为Context添加生命周期监听器
  3. 为Context 指定类加载器
    • 默认为org.apache.catalina.loader.WebappLoader,可以通过className)属性指定自己的实现类。
  4. 为Context添加会话管理器
    • 默认实现为org,apache,catalina.session.StandardManager,同时为管理器指定会话存储方式和会话标识生成器。Context提供了多种会话管理方式。
  5. 为Context添加初始化参数
    • 通过该配置,为Context添加初始化参数。我们可以在context.xml文件中添加初始化参数,以实现在所有Web应用中的复用,而不必每个Web应用重复配置。当然,只有在Web应用确实允许与Tomcat紧耦合的情况下,我们才推荐使用该方式进行配置,否则会导致Web应用适应性非常差。
  6. 为Context添加安全配置以及Web资源配置
    • Tomcat8新增加了PreResources、JarResources、PostResources这3种资源的配置。这3类资源的用处在讲解Web应用加载时会详细说明。
  7. 为Context添加资源链接
    • 为Context添加资源链接ContextResourceLink,用于J2EE命名服务。
  8. 为Context 添加Value
    • 为Context添加拦截器Valve,具体的拦截器类由className属性指定。
  9. 为Context 添加守护资源配置
    • WatchedResource标签用于为Context添加监视资源,当这些资源发生变更时,Web应用将会被重新加载,默认为WEB-INF/web.xml(具体见conf/context.xml)。
    • WrapperLifecycle标签用于为Context添加一个生命周期监听器类,此类的实例并非添加到Context上,而是添加到Context包含的Wrapper上。
    • WrapperListener标签用于为Context添加一个容器监听器类(ContainerListener),此类的实例同样添加到Wrapper上。
    • JarScanner标签用于为Context添加一个Jar扫描器,Catalina的默认实现为org,apache.tomcat.util.scan.Standard].arScanner。JarScanner扫描Web应用和类加载器层级的Jar包,主要用于TLD扫描和web-fragment.xml扫描。通过JarScanFilter标签,我们还可以为JarScanner指定一个过滤器,只有符合条件的.Jar包才会被处理,默认为org.apache.tomcat.util.scan.Standard们arScanFilter.
  10. 为Context添加Cookie处理器
    • 8.5.6之前的版本默认实现为LegacyCookieProcessor,之后改为Rfc6265 CookieProcessor。

至此,我们已经完成了Servert创建过程的分析。

4. Tomcat解析流程图

image.png 各个阶段的代码注释详见tomcat-9.0.60-src源码解析 ,这里不再说明,请大家自行debug观察server.xml的解析过程。

参考文章

tomcat-9.0.60-src源码解析 
Tomcat架构解析
Digester(二)Digester使用和原理