美文网首页Tomcat
Tomcat的配置解析工具Apache Commons Dige

Tomcat的配置解析工具Apache Commons Dige

作者: 晴天哥_王志 | 来源:发表于2018-10-10 09:59 被阅读62次

    开篇

     最近开始看Tomcat的源码,在catalina. createStartDigester方法中,Tomcat开发人员采用了Digester来读取conf/server.xml文件,以前读取xml文件一般采用Dom4j和SAX,因此对Digester比较陌生,顺便就抽时间研究了一下Digester是如何解析xml文件的。

    先简单阐述下Dom4j和SAX解析XML的区别:

    • Dom4j是把一个xml文件全部读取到内存中,构建成一个DOM树来解析,所以Dom4j适合读取比较小的xml文件。
    • SAX是基于文件流来解析xml文件的,在读取xml文件流时,SAX会通过节点来触发相应的操作,也可以说SAX是基于文件流的事情触发机制来解析xml文件的。
    • Digeter是apache的common项目,作用是将XML转化成对象,使用者直接从对象中获取xml的节点信息。Digester是对SAX的包装,它也是基于文件流来解析xml文件,只不过这些解析操作对用户是透明的。
    • Tomcat的配置文件conf/server.xml就是用Digester来读取的。

    Digester的来源

    • Digester本来仅仅是Jakarta Struts中的一个工具,用于处理struts-config.xml配置文件。显然,将XML文件转换成相应的Java对象是一项很通用的功能,这个工具理应具有更广泛的用途,所以很快它就在Jakarta Commons项目(用于提供可重用的Java组件库)中有了一席之地。

    • 如今Digester随着Struts的发展以及其的公用性而被提到commons中独自立项,是apache的一个组件 apache commons-digester.jar,通过它可以很方便的从xml文件生成java对象。

    Digester工作原理:

    • Digester由"事件"驱动,通过调用预定义的规则操作对象栈,将XML文件转换为Java对象。

    • Digester底层采用SAX解析XML文件,所以很自然的,对象转换由"事件"驱动,即在识别出特定XML元素时(实际被细分为begin、body、end、finish四个时点),将执行特定的动作,比如创建特定的Java对象,或调用特定对象的方法等。此处的XML元素根据匹配模式(matching pattern)识别,而相关操作由规则(rule)定义。

    • 在转换过程中,Digester维持了一个对象栈,可以看作对象转换的工作台,用来存放转换中生成的、或是为转换临时创建的Java对象。对输入XML文件作了一趟完整的扫描后,对象栈的栈顶元素即为目标对象。由于Digester屏蔽了SAX解析的细节,使用者仅需关注转换操作本身,大大简化了转换操作。

    以下的内容都是分享自文末参考文章的内容,对于一些API的使用非常建议直接下载jar包看源码注释

            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-digester3</artifactId>
                <version>3.2</version>
            </dependency>
    

    使用Digester的基本步骤

    • 1、创建一个 org.apache.commons.digester3.Digester 类的实例对象。这里补充说明下,只要我们已经完成XML解析操作,并且不在多个线程中使用同一个Digester对象,那么就可以安全的重复使用我们预先创建的这个Digester实例;不过重用Digester实例并不是非常推荐,最好每个XML解析对应一个单独的Digester实例;

    • 2、为Digester实例配置属性值,通过配置属性值,我们可以改变Digester 的解析行为,具体有哪些属性值可以配置,待会会介绍;

    • 3、可选的, 可以将我们的一些初始对象push到Digester栈里;

    • 4、在输入的XML文档中,给所有需要触发规则(rule)处理的元素匹配模式(pattern)注册规则;针对任何一个模式,你可以注册任意数量的规则;补充说明下,如果一个模式对应多个规则,则begin和body事件方法会按照它们注册的顺序依次执行,而end事件方法是倒序执行的;

    • 5、最后,调用digester.parse()方法,该方法需要传入XML文件的引用作为参数,该参数支持多种格式的文件流;另外需要注意的是,该方法会抛出IOException or SAXException异常,以及各种可能的在规则解析处理时遇到的异常,如NoSuchMethodException、IllegalAccessException…

     了解基本步骤后,来看一个简单的示例,如下所示,是我们即将要解析的xml文件:

    <foo name="The Parent">
        <bar id="123" title="The First Child" />
        <bar id="456" title="The Second Child" />
        <bar id="789" title="The Second Child" />
    </foo>
    

     首先,创建两个java bean对应xml中的元素信息:

    Foo类

    package apache.commons.digester3.example.pojo;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    /**
     * @author http://www.cnblogs.com/chenpi/
     * @version 2017年6月3日
     */
    public class Foo
    {
        private String name;
        private List<Bar> barList = new ArrayList<Bar>();
    
        public void addBar(Bar bar)
        {
            barList.add(bar);
        }
    
        public Bar findBar(int id)
        {
            for (Bar bar : barList)
            {
                if (bar.getId() == id)
                {
                    return bar;
                }
            }
            return null;
        }
    
        public Iterator<Bar> getBars()
        {
            return barList.iterator();
        }
    
        /**
         * @return the name
         */
        public String getName()
        {
            return name;
        }
    
        /**
         * @param name the name to set
         */
        public void setName(String name)
        {
            this.name = name;
        }
    
        /**
         * @return the barList
         */
        public List<Bar> getBarList()
        {
            return barList;
        }
    
        /**
         * @param barList the barList to set
         */
        public void setBarList(List<Bar> barList)
        {
            this.barList = barList;
        }
    }
    

    Bar类

    package apache.commons.digester3.example.pojo;
    
    /**
     * @author    http://www.cnblogs.com/chenpi/
     * @version   2017年6月3日
     */
    public class Bar
    {
    
        private int id;
        private String title;
    
        /**
         * @return the id
         */
        public int getId()
        {
            return id;
        }
    
        /**
         * @param id the id to set
         */
        public void setId(int id)
        {
            this.id = id;
        }
    
        /**
         * @return the title
         */
        public String getTitle()
        {
            return title;
        }
    
        /**
         * @param title the title to set
         */
        public void setTitle(String title)
        {
            this.title = title;
        }
    
    }
    

     使用Digester解析xml:

    package apache.commons.digester3.example.simpletest;
    
    import java.io.IOException;
    
    import org.apache.commons.digester3.Digester;
    import org.xml.sax.SAXException;
    
    import apache.commons.digester3.example.pojo.Bar;
    import apache.commons.digester3.example.pojo.Foo;
    
    /**
     * 
     * @author http://www.cnblogs.com/chenpi/
     * @version 2017年6月3日
     */
    
    public class Main
    {
    
        public static void main(String[] args)
        {
    
            try
            {
                //1、创建Digester对象实例
                Digester digester = new Digester();
    
                //2、配置属性值
                digester.setValidating(false);
    
                //3、push对象到对象栈
                //digester.push(new Foo());
                
                //4、设置匹配模式、规则
                digester.addObjectCreate("foo", "apache.commons.digester3.example.pojo.Foo");
                digester.addSetProperties("foo");
                digester.addObjectCreate("foo/bar", "apache.commons.digester3.example.pojo.Bar");
                digester.addSetProperties("foo/bar");
                digester.addSetNext("foo/bar", "addBar", "apache.commons.digester3.example.pojo.Bar");
    
                //5、开始解析
                Foo foo = digester.parse(Main.class.getClassLoader().getResourceAsStream("example.xml"));
    
                //6、打印解析结果
                System.out.println(foo.getName());
                for (Bar bar : foo.getBarList())
                {
                    System.out.println(bar.getId() + "," + bar.getTitle());
                }
    
            }
            catch (IOException e)
            {
    
                e.printStackTrace();
            }
            catch (SAXException e)
            {
    
                e.printStackTrace();
            }
        }
    }
    

     结果打印:

    The Parent
    123,The First Child
    456,The Second Child
    789,The Second Child
    

     注意以上代码涉及类型的自动转换,如id属性,由字符串类型转为整型,这里所有的类型转换都是由commons-beanutils包的ConvertUtils来完成的。

    Digester属性配置

     org.apache.commons.digester3.Digester实例对象包含若干成员属性,这些属性值是可以设置的,以便我们自定义解析操作;

     为了让这些配置在XML解析前生效,这些属性值的更改一定要在parse方法调用之前设置;下面是一些可以配置的属性:

     另外,我们可以通过Digester的register方法,让Digester在遇到DOCTYPE声明时,使用本地dtd,而不是从网上获取,如下所示:

    URL url = new URL("/org/apache/struts/resources/struts-config_1_0.dtd");
    digester.register("-//Apache Software Foundation//DTD Struts Configuration 1.0//EN", url.toString());
    

    Digester对象栈

     Digester使用的一个核心技术就是动态构建一颗java对象树,在构建的过程中,一个重要的辅助数据结构即对象栈;

     以如下xml为例:

    <foo name="The Parent">
        <bar id="123" title="The First Child" />
        <bar id="456" title="The Second Child" />
        <bar id="789" title="The Second Child" />
    </foo>
    

     在解析的时候,首先会创建一个foo对象,并压入对象栈,然后设置foo属性值name,紧接着,创建bar对象并压入栈,然后设置bar的属性值,然后将该bar对象添加的到foo对象的barlist属性集合中,然后bar对象弹出对象栈;

     以此类推,遇到起始标记的元素创建对象入栈,遇到结尾标记的元素做出栈操作,出栈前,需要将出栈对象并关联到上一个栈顶对象;

     最终,解析完xml后,留在栈顶的就关联了所有在xml解析中创建的动态对象了;

     Digester暴露出的与对象栈操作API如下所示:

    • clear() - 清除对象栈.
    • peek() - 返回栈顶对象引用,但是不弹出.
    • pop() - 返回栈顶对象,并弹出.
    • push() - 入栈操作.

    Digester元素匹配模式

     Digester的一个关键特性是可以自动识别xml的层次结构,程序员只需要关心遇到匹配到某个元素后需要做哪些操作即可;

     如下是一个示例,其中a, a/b, a/b/c为匹配模式,对应xml中特定位置的元素:

    <a>         -- Matches pattern "a"
        <b>       -- Matches pattern "a/b"
          <c/>    -- Matches pattern "a/b/c"
          <c/>    -- Matches pattern "a/b/c"
        </b>
        <b>       -- Matches pattern "a/b"
          <c/>    -- Matches pattern "a/b/c"
          <c/>    -- Matches pattern "a/b/c"
          <c/>    -- Matches pattern "a/b/c"
        </b>
      </a>
    

    Digester规则处理

     当匹配到模式时,会触发规则处理,具体的规则处理机制是由这个org.apache.commons.digester3.Rule接口封装的,该接口定义了以下几个方法:

    • begin() - 匹配到xml元素开始标记时,调用该方法;
    • body() - 匹配到xml元素body时,调用该方法;
    • end() - 匹配到xml元素结束标记时,调用该方法;
    • finish() - 当所有解析方法解析完毕后,调用该方法,用于清楚临时数据等;

     默认情况下,Digester提供了以下Rule接口的实现类,我们在编码的时候可以直接使用,详见API文档:

     如下是一个SetNextRule规则实现类的示例(两种写法):

    Rule rule = new SetNextRule("addBar",Bar.class);
    digester.addRule("foo/bar", rule );
    
    //digester.addSetNext("foo/bar", "addBar", Bar.class.getName());
    

    Digester日志

     日志是调试、排查错误非常关键的一个环节,Digester记录了非常详细的日志,我们可以按如下方式来开启日志打印功能,这里的日志实现选择log4j。

    • 1、首先,在pom.xml加上如下依赖:
            <!-- https://mvnrepository.com/artifact/log4j/log4j -->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
    
    • 2、然后,编写一个配置文件log4j.properties放到resources路径下:
    ### set log levels ###
    log4j.rootLogger = debug, stdout
    
    ### \u8F93\u51FA\u5230\u63A7\u5236\u53F0 ###
    log4j.appender.stdout = org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target = System.out
    log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
    
    • 3、运行程序,发现已经可以看到DEBUG调试日志了日志:
    2017-06-04 18:26:33  [ main:51 ] - [ DEBUG ]    Fire body() for SetPropertiesRule[aliases={}, ignoreMissingProperty=true]
    2017-06-04 18:26:33  [ main:51 ] - [ DEBUG ]    Popping body text ''
    2017-06-04 18:26:33  [ main:51 ] - [ DEBUG ]    Fire end() for SetPropertiesRule[aliases={}, ignoreMissingProperty=true]
    2017-06-04 18:26:33  [ main:52 ] - [ DEBUG ]    Fire end() for ObjectCreateRule[className=apache.commons.digester3.example.pojo.Foo, attributeName=null]
    2017-06-04 18:26:33  [ main:52 ] - [ DEBUG ]  [ObjectCreateRule]{foo} Pop 'apache.commons.digester3.example.pojo.Foo'
    2017-06-04 18:26:33  [ main:52 ] - [ DEBUG ]  endDocument()
    The Parent
    123,The First Child
    456,The Second Child
    789,The Second Child
    

    Digester例子

     前面我们已经举了一个Digester的简单使用例子,这里将继续展示几个示例,解析xml元素body值。
     如下XML文档就是我们要解析内容:

    <web-app>
        <servlet>
            <servlet-name>action</servlet-name>
            <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
                <init-param>
                    <param-name>application</param-name>
                    <param-value>org.apache.struts.example.ApplicationResources</param-value>
                </init-param>
                <init-param>
                    <param-name>config</param-name>
                    <param-value>/WEB-INF/struts-config.xml</param-value>
                </init-param>
        </servlet>
    </web-app>
    
    • 1、首先,定义一个ServletBean,存储以上xml信息,如下所示:
    /*
     * File Name: ServletBean.java
     * Description: 
     * Author: http://www.cnblogs.com/chenpi/
     * Create Date: 2017年6月4日
     */
    package apache.commons.digester3.example.pojo;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 
     * @author    http://www.cnblogs.com/chenpi/
     * @version   2017年6月4日
     */
    
    public class ServletBean
    {
    
        private String servletName;
        private String servletClass;
        private Map<String, String> initParams = new HashMap<String, String>();
        
        
        public void addInitParam(String paramName, String paramValue){
            initParams.put(paramName, paramValue);
        }
        /**
         * @return the servletName
         */
        public String getServletName()
        {
            return servletName;
        }
        /**
         * @param servletName the servletName to set
         */
        public void setServletName(String servletName)
        {
            this.servletName = servletName;
        }
        /**
         * @return the servletClass
         */
        public String getServletClass()
        {
            return servletClass;
        }
        /**
         * @param servletClass the servletClass to set
         */
        public void setServletClass(String servletClass)
        {
            this.servletClass = servletClass;
        }
        /**
         * @return the initParams
         */
        public Map<String, String> getInitParams()
        {
            return initParams;
        }
        /**
         * @param initParams the initParams to set
         */
        public void setInitParams(Map<String, String> initParams)
        {
            this.initParams = initParams;
        }
    }
    
    • 2、编写规则解析xml,如下所示:
    /*
     * File Name: Main2.java
     * Description: 
     * Author: http://www.cnblogs.com/chenpi/
     * Create Date: 2017年6月4日
     */
    package apache.commons.digester3.example.simpletest;
    
    import java.io.IOException;
    
    import org.apache.commons.digester3.Digester;
    import org.apache.commons.digester3.Rule;
    import org.apache.commons.digester3.SetNextRule;
    import org.xml.sax.SAXException;
    
    import apache.commons.digester3.example.pojo.Bar;
    import apache.commons.digester3.example.pojo.Foo;
    import apache.commons.digester3.example.pojo.ServletBean;
    
    /**
     * 
     * @author http://www.cnblogs.com/chenpi/
     * @version 2017年6月4日
     */
    
    public class WebMain
    {
    
        public static void main(String[] args)
        {
            try
            {
                // 1、创建Digester对象实例
                Digester digester = new Digester();
    
                // 2、配置属性值
                digester.setValidating(false);
    
                // 3、push对象到对象栈
    
                // 4、设置匹配模式、规则
                digester.addObjectCreate("web-app/servlet", "apache.commons.digester3.example.pojo.ServletBean");
                digester.addCallMethod("web-app/servlet/servlet-name", "setServletName", 0);
                digester.addCallMethod("web-app/servlet/servlet-class", "setServletClass", 0);
                digester.addCallMethod("web-app/servlet/init-param", "addInitParam", 2);
                digester.addCallParam("web-app/servlet/init-param/param-name", 0);
                digester.addCallParam("web-app/servlet/init-param/param-value", 1);
    
                // 5、开始解析
                ServletBean servletBean = digester
                    .parse(ExampleMain.class.getClassLoader().getResourceAsStream("web.xml"));
    
                // 6、打印解析结果
                System.out.println(servletBean.getServletName());
                System.out.println(servletBean.getServletClass());
                for(String key : servletBean.getInitParams().keySet()){
                    System.out.println(key + ": " + servletBean.getInitParams().get(key));
                }
    
            }
            catch (IOException e)
            {
    
                e.printStackTrace();
            }
            catch (SAXException e)
            {
    
                e.printStackTrace();
            }
    
        }
    }
    
    • 3、结果打印:
    action
    org.apache.struts.action.ActionServlet
    application: org.apache.struts.example.ApplicationResources
    config: /WEB-INF/struts-config.xml
    

    参考资料

    http://commons.apache.org/proper/commons-digester/guide/core.html

    示例代码

    https://github.com/peterchenhdu/apache-commons-digester-example

    参考文章

    Apache Commons Digester 一 (基础内容、核心API)
    Apache Commons Digester 二(规则模块绑定-RulesModule、异步解析-asyncParse、xml变量Substitutor、带参构造方法)
    Apache Commons Digester 三(规则注解)
    tomcat源码解析(三)——Digester类源码解析及Rule分析
    tomcat源码解读一 Digester的解析方式

    相关文章

      网友评论

        本文标题:Tomcat的配置解析工具Apache Commons Dige

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