深入理解Tomcat(六)Digester组件

前言

Tomcat中的xml解析,是使用的apache开源组件digester。在tomcat源码中,Digester类所在位置为org.apache.tomcat.util.digester.Digester,它把开源组件digester的源代码拷贝了过来。

本文,我们主要是想了解一下digester的用法。以便在阅读tomcat源码的时候,看到xml解析相关代码的时候不会一脸茫然和懵逼。

digester有两种使用方式:

  1. 一种为tomat内嵌的org.apache.tomcat.util.digester.Digester
  2. 另一种为digester maven依赖

本文采用第二种--maven依赖的方式。

digester实现原理

digester最初是作为struct的一个工具模块,来完成xml解析的功能。但是很快地,有人发现并觉得digester不应该仅仅局限在struct,而应该变得更通用。于是经过apache的孵化,最终加入到了apache commons类库家族中,并形成了一个xml另类解析的工具类库。

digester底层是基于SAX+事件驱动+的方式来搭建实现的。那么在digester中,这三种元素分别起到什么作用呢?

  1. SAX,用于解析xml
  2. 事件驱动,在SAX解析的过程中加入事件来支持我们的对象映射
  3. 栈,当解析xml元素的开始和结束的时候,需要通过xml元素映射的类对象的入栈和出栈来完成事件的调用

通过一些实实在在的场景和例子,我们发现一个元素的作用无非是在其解析前后加入一些扩展逻辑!例如:

  1. 开始解析某个节点的时候,是否需要创建一个类
  2. 开始解析某个节点的时候,是否需要入栈操作
  3. 结束解析某个节点的时候,是否需要执行某个方法
  4. 结束解析某个节点的时候,是否需要出栈操作

如何引入依赖包

以maven为例,使用下面的dependency。

<dependency>
    <groupId>commons-digester</groupId>
    <artifactId>commons-digester</artifactId>
    <version>2.1</version>
</dependency>

如何使用

假如我们需要解析的xml为下面的格式。

<?xml version='1.0' encoding='utf-8'?>
<School name="Jen">
    <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>

同时,我们假设下面的约定成立:

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

根据上面的规则,我们需要创建关联的3个类,SchoolGradeClass
School有一个方法addGrade用于往学校对象中添加年级。

package com.juconcurrent.learn.apache.digester;

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;
    }
}

同样的,年级有一个addClass方法,用于往Grade对象添加Class班对象。

package com.juconcurrent.learn.apache.digester;

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;
    }
}

Class就比较简单了,只是一个简单的POJO对象。

package com.juconcurrent.learn.apache.digester;

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;
    }
}

好了,我们已经定义好了我们所需要创建对象的类。那么如何使用digester来创建我们所需的数据呢?我们先给出例子,然后再来详细分析其中的关键方法。

package com.juconcurrent.learn.apache.digester;

import org.apache.commons.digester.Digester;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

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解析的时候需要用到
        File file = new File("/Users/pro/ws/learn/learn-javaagent/src/main/resources/School.xml");
        InputStream inputStream = new FileInputStream(file);
        InputSource inputSource = new InputSource(file.toURI().toURL().toString());
        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, 如果该节点有className属性, 用className的值替换默认实体类
         *
         * Digester匹配到School节点
         *
         * 1. 如果School节点没有className属性,将创建com.juconcurrent.learn.apache.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() + "人");
                        }
                    }
                }
            }
        }
    }

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

这儿我们需要着重说明一下digester里面的几个方法,大体上我们可以将其方法分为两类:操作类和规则类。

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

Tomcat中的规则解析例子

上面我们写了一个非常简单的例子,相信通过这样的例子我们可以很快地入门了。那么tomcat里面又是怎样写的呢?我们看看org.apache.catalina.startup.Catalina.createStartDigester这个方法,这个方法用于定义对server.xml的解析。该方法比较长,但是我并不打算对这个方法进行阉割和压缩,而是原封不动地拷贝到这儿,以便大家对此有一个比较完整的认识。

protected Digester createStartDigester() {
    long t1=System.currentTimeMillis();
    // Initialize the digester
    Digester digester = new Digester();
    digester.setValidating(false);
    digester.setRulesValidation(true);

    // 这儿设置无效的属性,fake是赝品的意思,也就是在检查到这些属性直接认为是无效的
    Map<Class<?>, List<String>> fakeAttributes = new HashMap<>();
    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);
    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");

    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.addRule("Server/Service/Connector",
                     new SetAllPropertiesRule(new String[]{"executor", "sslImplementationName"}));
    digester.addSetNext("Server/Service/Connector",
                        "addConnector",
                        "org.apache.catalina.connector.Connector");

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

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

    // 根据t1和t2,算出整个server.xml的Digester创建花费的时间
    long t2=System.currentTimeMillis();
    if (log.isDebugEnabled()) {
        log.debug("Digester for server.xml created " + ( t2-t1 ));
    }
    return (digester);
}

这儿我们看到,在tomcat中明显地用到了前面例子中说明的几个规则,我们再简单罗列一下:

  1. addObjectCreate,对象创建规则
  2. addSetProperties,属性设置规则
  3. addSetNext,设置下一个规则
  4. addRule,自定义规则
  5. digester.addRuleSet,自定义规则集

总结

本文我们对digester做了一个使用说明。

我们首先简单地说明了一下digester是什么,内部基于什么原理来实现的。然后通过一个School、Grade和Class这样的生活中的例子来说明digester的用法。最后通过查看tomcat中关于digester的例子代码,加深了我们对于digester的理解。

相信通过这篇文章,让我们在阅读tomcat源码的过程中不再对xml解析产生疑惑!

参考链接

  1. https://blog.csdn.net/qq_24451605/article/details/51289519
  2. https://blog.csdn.net/flyliuweisky547/article/details/23872231
  3. https://tomcat.apache.org/tomcat-8.5-doc/api/org/apache/tomcat/util/digester/package-summary.html
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,182评论 11 349
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • 序 佛说,三千世界,如是我闻。 我说,芸芸众生,转眼消散。 “跟神打赌,啧啧啧鹤球你最近闲的可以哟!“名为慕念卿的...
    陰樂阅读 1,900评论 1 8
  • 根据您提供的资料,我为您量身设计了这份健康管理计划 它包括了生命保障、重大疾病保障、住院保障、意外和豁免5方面的保...
    李大女儿阅读 321评论 0 0