• Digester库


      在之前所学习关于启动简单的Tomcat部分实现的代码中,我们使用一个启动类Bootstrap类 来实例化连接器、servlet容器、wrapper实例、和其他组件,然后调用各个对象的set方法将他们关联起来,例如。要实例化一个连接器 和 一个 servlet容器,可以使用下列代码

    // 初始化一个连接器
            Connector connector = new HttpConnector();
    // 初始化一个Context容器
            Context context = new StandardContext();

    然后在使用下面的代码将它们关联起来

    connector.setContainer(context);

    当然了 如果使用了服务器和服务组件虽然 不需要上面这一步 但是 还是需要其他set资源 

    接着在调用各个对象的set方法为其设置各种属性,例如可以用Context实例的setPath 和 setDocBase方法设置其path 和 docBase属性,

    // 设置根路径 从 浏览器请求 该Context的地址 也就是 /app1
            context.setPath("/app1");
            // 设置根文件夹
            context.setDocBase("app1");

    然后,可以通过实例化各种组件,并调用Context对象调用响应的add方法将这些组件添加到Context对象中,例如下面的diamante展示如何为Context对象添加一个生命周期监听器 和一个载入器 和 添加子容器

            // 给StandardContext 创建一个 配置监听器
            LifecycleListener listener = new SimpleContextConfig();
    
            ((Lifecycle) context).addLifecycleListener(listener);
    context.addChild(wrapper1);
            context.addChild(wrapper2);
    Loader loader = new WebappLoader();
    		context.setLoader(loader);
    

      在完成关联  和添加组件的所有必要操作之后,就可以调用连接器的initialize方法和 start方法,以及servlet容器的start方法来启动整个应用程序,

        connector.initialize();
                ((Lifecycle) connector).start();
                ((Lifecycle) context).start();

      这种配置应用程序的方法有一个明显缺陷,即所有的配置都必须硬编码,调整组件配置或属性值都 必须要要重新编译Bootstrap类,幸运的是,Tomcat的设计者使用了一种更加优雅的配置方式,即使用一个名为server.xml的XML文档来对应用程序进行配置,server.xml文件中的每个元素都会转化为一个java对象,元素的属性会用于设置java对象的属性,这样,就可以通过简单的编辑server.xml文件来修改Tomcat的配置,例如 server.xml文件中的Context元素表示一个Context实例:

      

    <context/>

    要为Context实例设置path属性 和 docBase属性,可以使用这样的配置:

    <context docBase="myApp" path="/myApp"/>

    Tomcat使用了开源库Digester来将XML文档中的元素转化成java对象,

    由于一个Context实例表示一个Web应用程序,因此配置Web应用程序是通过对已经实例化的Context实例进行配置的,用来配置Web应用程序的XML文件的名称是web.xml,该文件位于Web应用程序的WEB-INF目录下。

    Digester库

      Digester是Apache软件基金会的Jakarta项目下的子 Commons项目下的一个开源项目,可以从http://jakarta.apache.org/commons/digester/下载Digester库,Digester API 包含3个包,三者都被打包到commons-digester.jar文件中。

    • org.apache.commons.digester : 该包提供了基于规则的、可吹了任意XML文档的类;
    • org.apache.commons.digester.rss : 该包 包含了一些可以用来解析与很多新闻源使用的RSS(Rich Site Summary,富站点摘要)格式兼容的XML文档的类;
    • org.apahce.commons.figester.xmlrules; 该包 为Digester库提供了一些规则继续XML的定义;

    Digester类

      org.apahce.commons.digester.Digester类时Digester库中的主类,该类用于解析XML文档。对于XML文档中的每个元素,Digester对象都会检查它是否要做事先预定义的事件。在调用DIgester对象的parse方法之前,程序员要定义好Digester对象要执行哪一些操作。

      那么如何定义在Digester对象遇到某个XML元素时它应该执行什么动作呢?很简单,程序员要先定义好模式,然后将每个模式与一条或者多条规则相关联,XML文档中根元素的模式与元素的名字相同。

    <?xml version="1.0" encoding="ISO-8859-1"?>
    
    <employee firstName="Brian" lastName="May">
    
        <office>
            <address streeName="Wellington Street"     streetNumber="110"/>
      </office>
    </employee>

      该XML文档中的根元素是employee。employee元素有一个模式,名为employee,office元素是employee元素的子元素,子元素的模式由该元素的父元素的模式在加上“/” 符号,以及该元素的名字拼接而成的,因此,office元素的模式是employee/office。依次类推,address元素的模式 是 由如下字符串拼接而成的。

      父元素的模式+“/”+该元素的名字

    其中 ,address的元素的父元素是office元素。office元素的模式是 employee/office ,因此 address的模式 为 employee/office/address。

      那么现在应该已经了解了如何从XML文档中推导出元素的模式,下面就讨论一下规则。

      一条规则指明了当Digester对象遇到某个特殊的模式时要执行的一个或多个动作,规则是org.apache.commons.digester.Rule类的实例,Digester类可以包含0个或多个Rule对象。在Digester实例中,这些规则 和 其相关联的模式 都储存在由 org.apahce.commons.digester.Rules接口表示的一类存储器中,每当把一条规则添加到Digester实例中时,Rule对象都会被添加到Rules对象中。

      另外。Rule类有 begin()和end()方法,在解析XML文档时,当Digester实例遇到 匹配某个模式 的元素的开始标签时,它会调用对应的Rule规则对象的begin方法,而当Digester实例遇到相应元素的结束标签时,它会调用对应其规则的Rule对象的end()方法。

    举一个例子:

      当解析上文中 包含<employee>元素的xml文档时吗,Digester对象会执行如下的操作:

    1. Digester对象首先会先遇到 employee元素的开始标签,然后 Digester对象 会检查  是否有规则和模式employee相关联,若有 Digester会执行相应Rule对象的begin方法,若有多个Rule对象与该模式匹配,则按照其添加到Digester对象的顺序逐个执行;
    2. 然后 它会 遇到office元素的开始标签,Digester对象会检查是否有规则与模式  employee/office相关联,若有,则执行Rule的begin()方法,
    3. 接下来Digester会遇到address标签的开始标签,它会检查是否有规则与模式employee/office/address相关联,若有则执行它的 begin()方法。
    4. 接着,Digester对象会遇到 address元素的结束标签,会执行相匹配的Rule的end()方法。
    5. 再下来,Digester对象会遇到office元素的的结束标签,会执行相匹配的Rule对象的end()方法
    6. 最后,Digester对象会遇到employee元素的结束标签,会执行相匹配的Rule对象的end()方法。

     那么有哪些现成的规则可以使用呢?Digester库已经预定义了一些规则,可以直接使用这些规则,而无需深入理解Rule类的实现。但是,如果这些规则仍然不够,你可以实现自己的规则,预定义的规则包括创建对象和设置属性值等的规则。

     1 创建对象

      若想要Digester对象在遇到某个特殊的模式时创建对象,则需要调用其addObjectCreate()方法,该方法有4个重载的版本。下面是比较常用的两个版本的方法:

        public void addObjectCreate(java.lang.String pattern, java.lang.String className) {
            this.addRule(pattern, new ObjectCreateRule(className));
        }
    public void addObjectCreate(java.lang.String pattern, java.lang.Class clazz) {
            this.addRule(pattern, new ObjectCreateRule(clazz));
        }

      需要传入一个模式和一个Class对象 或这 类名来调用该方法,例如,如果你想让Digester对象在遇到模式employee时,创建一个myex15.pyrmont.digestertest.Employee对象,则可以用下面的代码来调用addObjectCreate方法;

    digester.addObjectCreate("employee",myex15.pyroment.digestertest.Employee.class);

    或者

    digester.addObjectCreate("employee","myex15.pyroment.digestertest.Employee");

    addObjectCreate()方法的另外两个重载版本允许在XML文档中定义类的名字,而无需将其作为参数传入。这一点非常有用,因为这使得类名可以在运行决定,下面是这两个重载方法

    1     public void addObjectCreate(String pattern, String className, String attributeName) {
    2         this.addRule(pattern, new ObjectCreateRule(className, attributeName));
    3     }
    1 public void addObjectCreate(String pattern, String attributeName, Class clazz) {
    2         this.addRule(pattern, new ObjectCreateRule(attributeName, clazz));
    3 }

      在这两个重载方法中,参数 attributeName 参数 指明了XML元素的属性的名字,该属性包含了将要实例化的类的名字,例如你,可以使用下面的代码来创建对象的一条规则;

    digester.addObjectCreate("employee",null,"className");

    其中,XML元素的属性名为className。,然后可以使用这个属性传入XML元素的类名

    <emloyee firstName="Brian" lastName="May" className="myex.pyrmont.digestertest.Employee">

    或者,可以在addObjectCreate()方法中定义默认的类名 ,

    digester.addObjectCreate("employee","myex15.pyrmont.digestertest.Employee","className");

     如果employee元素包含clssName属性,那么该属性指定的值会用来作为待实例化的类的名字,如果没有className属性,则会使用默认的类名。

    addObjectCreate()方法创建的对象会被压入一个内部栈中,Digester库提供了一些其他的方法来对新建的对象执行查看、入栈、出栈等操作。

      2 设置属性

     另一个比较有用的方法是addSetProperties()方法,使用该方法可以使Digester对象为创建的对象设置属性。该方法的一个重载版本方法是:

    public void addSetProperties(String pattern) {
            this.addRule(pattern, new SetPropertiesRule());
        }

    使用时传入一个模式到该方法中,例如,考虑下面的代码

    digester.addObjectCreate("employee","myex15.pyrmont.digestertest.Employee");
    digester.addSetProperties("employee");

    上面的Digester实例相当于创建了两个Rule对象,分别用来创建对象 和 设置属性,它们都是通过"employee"模式触发的,Rule对象按照其添加到Digester实例中的顺序逐个执行Rule,对下面的XML文档的employee元素;

    <employee firstName="Brian" lastName="May">

      Digeseter实例首先会创建myex15.pyrmont.digestertest.Employee类的一个实例,因为创建对象的规则是第一个添加到Digester中的,然后Digester实例会应用与模式employee相关联的第二条规则,调用已经实例化的Employee对象的setFirstName方法  和 setLastName方法,并分别传入参数值 Brian  和 May来设置属性,employee元素中的属性对应于Employee对象中的属性,如果Employee类中没有定义这这样的属性会发生错误。

      3 调用方法

      Digester类允许通过添加一条规则,使Digester在遇到与规则相关联的模式时调用 内部栈中最顶端对象的某个方法(最顶端对象其实就刚刚遇到的模式对应创建的对象)这需要用到Digester类的addcallMethod()方法该方法的一个重载版本

    public void addCallMethod(String pattern, String methodName) {
            this.addRule(pattern, new CallMethodRule(methodName));
        }

    4 创建对象之间的关系

      Digester实例有一个内部栈,用于临时存储创建的对象,当使用addObjectCreate()方法实例化一个类时,会吧结果压入这个栈中,可以将栈想象成一口井,将对象压入栈中,就好像是将一个与井的直径相同的圆形物体丢入井中,从栈中弹出一个对象就像是从井中取出最上层的对象。

      当调用了两次addObjectCreate()方法时,第一个对象会被先丢入井中,然后是第二个对象。addSetNext方法hi调用第一个对象发的指定方法并将第二对象作为参数传入该方法来创建第一个对象和第二个对象之间的关系,下面是addSetNext的方法

    public void addSetNext(String pattern, String methodName) {
            this.addRule(pattern, new SetNextRule(methodName));
        }

      参数pattern是指明了触发该规则的具体模式,参数methodName是将要调用的第一个对象的方法的名称,模式应该具有如下面的格式:

      firstObject/secondObject

    例如 employee元素包含了一个office元素,为了创建employee对象和 其office对象之间的关系,先要调用两次addObjectCreate()方法创建对象:

    digester.addObjectCreate("employee","myex15.pyrmonet.digestertest.Employee");
    digestert.addObjectCreate("employee/office,"myex15.pyrmoent.digestertest.Office");

      第一个addObjectCreate()方法hi根据employee元素创建Employee实例,第二个addObjectCreate()方法在遇到employee元素下的office元素时,会创建Office实例。

      两次调用addObjectCreate()方法创建的对象都会被压入内部栈中,现在Employee对象在栈底,Office对象在栈顶,为了创建它们之间的关系,需要另外定义一条规则,使用addSetNext方法来建立关系

    digester.addSetNext("employee/office","addOffice");

      其中 addOffice()方法是定义于Employee类中,该方法接收一个Office对象作为参数,

    5 验证XML文档

      Digester对象解析的XML文档的有效性是通过某个模式进行验证的,Digester类的validating属性指明了是否要对XML文档进行有效性的验证,默认情况下,该属性值的值为false。

    setValidating()方法用来设置是否要对XML文档进行有效性验证,

    public void setValidating(boolean validating) {
            this.validating = validating;
        }

      如果你想要对格式良好的XML文档进行有效性验证,可以在调用该方法时传入true

    那么下面就来几个演示的实例吧 

    示例1

    展示了如何使用Digester库动态创建对象,并设置相应的属性值。 利用Employee类作为实例类,Digester库将会创建Employee类的实例;

    package myex15.pyrmont.digestertest;
    
    import java.util.ArrayList;
    
    /**
     * <p>
     * <b>Title:Employee.java</b>
     * </p>
     * <p>
     * Copyright:ChenDong 2018
     * </p>
     * <p>
     * Company:仅学习时使用
     * </p>
     * <p>
     * 类功能描述:作为Digester库动态创建的示例类 Employee
     * </p>
     * 
     * @author 陈东
     * @date 2018年12月22日 下午1:46:41
     * @version 1.0
     */
    public class Employee {
    
        private String firstName;
    
        private String lastName;
    
        private ArrayList offices = new ArrayList<>();
    
        public Employee() {
            System.out.println("创建Employee实例");
        }
    
        /**
         * @return the firstName
         */
        public String getFirstName() {
            return firstName;
        }
    
        /**
         * @param firstName
         *            the firstName to set
         */
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        /**
         * @return the lastName
         */
        public String getLastName() {
            return lastName;
        }
    
        /**
         * @param lastName
         *            the lastName to set
         */
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public void addOffice(Office office) {
            System.out.println("Adding Office to this employee");
            offices.add(office);
        }
    
        public ArrayList getOffices() {
            return offices;
        }
    
        public void printName() {
            System.out.println("My name is " + firstName + " " + lastName);
        }
    
    }

    Employee类有三个属性,分别是firstName、lastName和offices,属性firstName和lastName是字符串类型的,属性offices是myex15.pyrmont.digsetertest.Office。

      Employee类还有一个方法 printName,该方法仅仅是将属性firstName和lastName值输出到控制台上,

    现在要写一个测试类Test01,它使用Digester类,并为其添加创建Employee对象和设置其属性的规则,

     1 /**
     2  * <p>Title:Test01.java</p>
     3  * <p>Description:纯属学习</p> 
     4  * <p>Copyright:ChenDong 2018</p>
     5  * @author 陈东
     6  * @date 2018年12月22日
     7  * @version 1.0
     8  */
     9 package myex15.pyrmont.digestertest;
    10 
    11 import java.io.File;
    12 import java.io.IOException;
    13 
    14 import org.apache.commons.digester.Digester;
    15 import org.xml.sax.SAXException;
    16 
    17 /**
    18  * <p>
    19  * <b>Title:Test01.java</b>
    20  * </p>
    21  * <p>
    22  * Copyright:ChenDong 2018
    23  * </p>
    24  * <p>
    25  * Company:仅学习时使用
    26  * </p>
    27  * <p>
    28  * 类功能描述:
    29  * </p>
    30  * 
    31  * @author 陈东
    32  * @date 2018年12月22日 下午2:14:46
    33  * @version 1.0
    34  */
    35 public class Test01 {
    36 
    37     /**
    38      * 
    39      * <p>
    40      * Title: main
    41      * </p>
    42      * 
    43      * @date 2018年12月22日 下午2:14:46
    44      * 
    45      *       <p>
    46      *       功能描述:
    47      *       </p>
    48      * 
    49      * @param args
    50      * 
    51      */
    52     public static void main(String[] args) {
    53         // XML文件所在的文件夹
    54         String path = System.getProperty("user.dir") + File.separator + "etc";
    55         // 为XML文件实例化一个file对象
    56         File file = new File(path, "myEmployee1.xml");
    57 
    58         // 创建一个Digester对象
    59         Digester digester = new Digester();
    60         // ----------------------一定要注意顺序 先添加先执行 顺序搞错了 会报空指针的
    61         // 添加创建对象规则
    62         digester.addObjectCreate("employee", Employee.class);
    63 
    64         // 添加设置属性规则
    65         digester.addSetProperties("employee");
    66 
    67         // 设置调用方法规则
    68         digester.addCallMethod("employee", "printName");
    69 
    70         // 解析xml
    71         try {
    72             Employee employee = (Employee) digester.parse(file);
    73             System.out.println("FirstName = " + employee.getFirstName());
    74 
    75             System.out.println("LastName = " + employee.getLastName());
    76         } catch (IOException e) {
    77             
    78             e.printStackTrace();
    79         } catch (SAXException e) {
    80             
    81             e.printStackTrace();
    82         }
    83     }
    84 
    85 }

    下面是我的XMl文件

    <?xml version="1.0" encoding="UTF-8"?>
    <employee firstName="陈" lastName="东">
    </employee>

      上面的代码呢 首先定义了包含XML文件位置的路径,并将其传入到File类的构造函数中,然后,使用下面的代码创建了一个Digester对象,并为模式employee添加了三条规则;

     1 // 创建一个Digester对象
     2         Digester digester = new Digester();
     3         // ----------------------一定要注意顺序 先添加先执行 顺序搞错了 会报空指针的
     4         // 添加创建对象规则
     5         digester.addObjectCreate("employee", Employee.class);
     6 
     7         // 添加设置属性规则
     8         digester.addSetProperties("employee");
     9 
    10         // 设置调用方法规则
    11         digester.addCallMethod("employee", "printName");

      接下来,调用Digester对象的parse()方法,并传入引用XML文档的File对象作为参数,parse()方法的返回值是Digeseter对象的内部栈中的第一个对象;

    Employee employee = (Employee) digester.parse(file);

    这样就获得了Digester对象实例化的Employee对象。若要是查看是否设置了Employee对象的属性,可以调用Employee对象的getFirstName方法和 getLastName()方法:

    System.out.println("FirstName = " + employee.getFirstName());
    
                System.out.println("LastName = " + employee.getLastName());

    运行Test01类的输出结果如下所示:

    创建Employee实例
    Setting  firstName :陈
    Setting lastName:东
    My name is 陈 东
    FirstName = 陈
    LastName = 东

    这就是执行的操作。

      当调用Digester对象的parse方法时,它会打开指定的XML文档,开始解析它,首先Digester类会查看employee 元素的开始标签,这回触发与employee模式关联的3条规则,按照其添加到Digester对象中的顺序逐个执行。

      第一条规则用于创建Employee对象。因此Digester对象会实例化Employee类,调用Employee类的构造函数,Employee的构造函数会输出字符串 “创建Employee实例”。

      第二条规则设置Employee对象的属性,在employee元素中包含了两个属性,分别是firstName 和 lastName。该规则调用属性firstName 和 lastName的set方法,分别输出一下的字符串

      

    FirstName = 陈
    LastName = 东
      第三条规则会调用Employee类的printName方法,输出如下字符串
    My name is 陈 东

    最后两行是调用Employee对象的 getFirstName 和 getLastName方法的输出内容

    FirstName = 陈
    LastName = 东

      示例2

      主要说明Digester对象如何创建两个对象,并建立他们之间的关系,首先要定义要创建的关系的类型,例如,员工可以工作在一个或者多个办公室中,办公室由Office类的实例表示,可以创建一个Employee对象 和一个Office对象,并未它们创建关系,下面给出Office类的定义:

     1 package myex15.pyrmont.digestertest;
     2 
     3 /**
     4  * <p>
     5  * <b>Title:Office.java</b>
     6  * </p>
     7  * <p>
     8  * Copyright:ChenDong 2018
     9  * </p>
    10  * <p>
    11  * Company:仅学习时使用
    12  * </p>
    13  * <p>
    14  * 类功能描述:
    15  * </p>
    16  * 
    17  * @author 陈东
    18  * @date 2018年12月22日 下午1:56:04
    19  * @version 1.0
    20  */
    21 public class Office {
    22 
    23     private Address address;
    24 
    25     private String description;
    26 
    27     public Office() {
    28         System.out.println(".....Creating Office");
    29     }
    30 
    31     /**
    32      * @return the address
    33      */
    34     public Address getAddress() {
    35         return address;
    36     }
    37 
    38     /**
    39      * @param address
    40      *            the address to set
    41      */
    42     public void setAddress(Address address) {
    43         System.out.println("正在使用setAddress方法 :参数address为:" + address);
    44         this.address = address;
    45     }
    46 
    47     /**
    48      * @return the description
    49      */
    50     public String getDescription() {
    51         return description;
    52     }
    53 
    54     /**
    55      * @param description
    56      *            the description to set
    57      */
    58     public void setDescription(String description) {
    59         System.out.println("正在使用setDescription方法 :参数 description为:" + description);
    60         this.description = description;
    61     }
    62 
    63 }

    可以通过调用父对象的某个方法来创建关系,注意,当前的示例使用示例1中的Employee类,Employee类有一个addOffice的方法,将一个office对象添加到其集合类型的变量offices中。

    如果我们不使用Digester库来开发的话 就要写下面类似的代码

    Employee employee  =  new Employee();
    
    Office office = new Office();
    
    employee.addOffice(office);

      言归正传,那么我们使用的Office 对象 也就是办公室,也包含另外一个对象 ,地址对象,一个办公室有一个地址,地址是由Address类的实例来表示的,下面给出Address的代码实现

     1 package myex15.pyrmont.digestertest;
     2 
     3 /**
     4  * <p>
     5  * <b>Title:Address.java</b>
     6  * </p>
     7  * <p>
     8  * Copyright:ChenDong 2018
     9  * </p>
    10  * <p>
    11  * Company:仅学习时使用
    12  * </p>
    13  * <p>
    14  * 类功能描述:
    15  * </p>
    16  * 
    17  * @author 陈东
    18  * @date 2018年12月22日 下午2:15:36
    19  * @version 1.0
    20  */
    21 public class Address {
    22     /*
    23      * (non-Javadoc)
    24      * 
    25      * @see java.lang.Object#toString()
    26      */
    27     /**
    28      * 
    29      * <p>
    30      * Title: toString
    31      * </p>
    32      * 
    33      * @date 2018年12月22日 下午2:18:09
    34      * 
    35      *       <p>
    36      *       功能描述:
    37      *       </p>
    38      * 
    39      * @return
    40      * 
    41      */
    42     @Override
    43     public String toString() {
    44         return "Address [steetName=" + streetName + ", streetNumber=" + streetNumber + "]";
    45     }
    46 
    47     /**
    48      * @return the steetName
    49      */
    50     public String getStreetName() {
    51         return streetName;
    52     }
    53 
    54     /**
    55      * @param streetName
    56      *            the steetName to set
    57      */
    58     public void setStreetName(String streetName) {
    59         System.out.println("正在使用setStreet方法。参数streetname为:" + streetName);
    60         this.streetName = streetName;
    61     }
    62 
    63     /**
    64      * @return the streetNumber
    65      */
    66     public String getStreetNumber() {
    67         return streetNumber;
    68     }
    69 
    70     /**
    71      * @param streetNumber
    72      *            the streetNumber to set
    73      */
    74     public void setStreetNumber(String streetNumber) {
    75         System.out.println("正在使用setStreetNumber方法 参数 streetNumber为:" + streetNumber);
    76         this.streetNumber = streetNumber;
    77     }
    78 
    79     private String streetName;
    80 
    81     private String streetNumber;
    82 
    83     public Address() {
    84         System.out.println(".........Creating Address");
    85     }
    86 
    87 }

      若想要将Address对象关联到Office中,则可以调用Office的setAddress()方法,若没有Digester库的帮助,同样也是需要下面的硬编码

    Office office = new Office();
    
    Address address = new Address();
    
    office.setAddress(address);

     关于Digester库 使用的第二个示例,程序展示了如何使用DIgester库创建对象并且为其创建关联对象,这里将要使用到的类有 Employee类、Office类、Address类,下面就是我们Test02的示例代码,该类使用了一个Digester对象,并为其添加规则。

    package myex15.pyrmont.digestertest;
    
    import java.io.File;
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.Iterator;
    
    import org.apache.commons.digester.Digester;
    import org.xml.sax.SAXException;
    
    /**
     * <p>
     * <b>Title:Test02.java</b>
     * </p>
     * <p>
     * Copyright:ChenDong 2018
     * </p>
     * <p>
     * Company:仅学习时使用
     * </p>
     * <p>
     * 类功能描述:该示例重点在展示 如何使用Digester来帮助我们在创建对象时,帮助创建其关联对象的应用
     * </p>
     * 
     * @author 陈东
     * @date 2018年12月22日 下午8:11:21
     * @version 1.0
     */
    public class Test02 {
    
        public static void main(String[] args) {
            // 包含XMl文件的路径位置
            String path = System.getProperty("user.dir") + File.separator+ "etc";
    
            // 指定的XML的FIle
            File file = new File(path, "MyEmployee2.xml");
    
            // 创建一个Digester对象
            Digester digester = new Digester();
            // ------------创建规则
            // 第一步 创建匹配employee规则 创建Employee对象
            digester.addObjectCreate("employee", Employee.class);
            // 设置属性
            digester.addSetProperties("employee");
    
            // 添加创建office对象规则
            digester.addObjectCreate("employee/office", Office.class);
            // 添加 为office设置属性的规则
            digester.addSetProperties("employee/office");
    
            // 将 employee 和 office 关联
            digester.addSetNext("employee/office", "addOffice");
    
            // 接着 为创建 address添加规则
    
            digester.addObjectCreate("employee/office/address", Address.class);
    
            // 设置属性
    
            digester.addSetProperties("employee/office/address");
    
            // 将 address 与 office做关联
            digester.addSetNext("employee/office/address", "setAddress");
    
            try {
                Employee employee = (Employee) digester.parse(file);
                ArrayList offices = employee.getOffices();
                Iterator iterator = offices.iterator();
    
                System.out.println("--------------------------------------------------------");
    
                while (iterator.hasNext()) {
                    Office office = (Office) iterator.next();
                    Address address = office.getAddress();
                    System.out.println("Office description: "+office.getDescription());
                    System.out.println("Address: "+address.getStreetName()+ "" + address.getStreetNumber());
                    
                }
    
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SAXException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
        }
    
    }

    输出结果

    创建Employee实例
    Setting  firstName :陈
    Setting lastName:东
    .....Creating Office
    正在使用setDescription方法 :参数 description为:工作办公室
    .........Creating Address
    正在使用setStreet方法。参数streetname为:海淀区西二旗烽火科技大厦
    正在使用setStreetNumber方法 参数 streetNumber为:108号
    正在使用setAddress方法 :参数address为:Address [steetName=海淀区西二旗烽火科技大厦, streetNumber=108号]
    Adding Office to this employee
    .....Creating Office
    正在使用setDescription方法 :参数 description为:私人办公室
    .........Creating Address
    正在使用setStreet方法。参数streetname为:昌平区西二区
    正在使用setStreetNumber方法 参数 streetNumber为:108号
    正在使用setAddress方法 :参数address为:Address [steetName=昌平区西二区, streetNumber=108号]
    Adding Office to this employee
    --------------------------------------------------------
    Office description: 工作办公室
    Address: 海淀区西二旗
    Office description: 私人办公室
    Address: 昌平区

    Rule类

      Rule类包含一些方法,其中最重要的两个方法是 begin方法 和 end方法,当Digester示例遇到某个XML元素开始标签时,它会调用它所包含的匹配Rule对象的begin方法。Rule类的begin方法的标签如下:

    public void begin(org.xml.sax.Attributes attributes) throws Exception 
        

      当Digester实例遇到某个XML元素的结束标签时,它会调用它所包含的匹配所有Rule实例的 end方法,Rule类的end方法签名如下:

    public void end() throws Exception 
        

       Digester对象是如何完成这些工作的呢?当调用Digester对象的addObjectCreate方法、addCallMethod()方法、addSetNext()方法或其他方法时,都会间接的嗲用Digester类的addRule方法,该方法会将一个Rule对象和它所匹配的模式添加到Digester对象的Rules集合中,addRule方法的签名如下:

    public void addRule(java.lang.String pattern, org.apache.commons.digester.Rule rule) {

    Digester类的addRule的实现如下

    1 public void addRule(String pattern, Rule rule) {
    2         rule.setDigester(this);
    3         this.getRules().add(pattern, rule);
    4     }

    查看Digester类的addObjectCreate方法的重载实现:

       public void addObjectCreate(String pattern, String className) {
            this.addRule(pattern, new ObjectCreateRule(className));
        }
    
        public void addObjectCreate(String pattern, Class clazz) {
            this.addRule(pattern, new ObjectCreateRule(clazz));
        }
    
        public void addObjectCreate(String pattern, String className, String attributeName) {
            this.addRule(pattern, new ObjectCreateRule(className, attributeName));
        }
    
        public void addObjectCreate(String pattern, String attributeName, Class clazz) {
            this.addRule(pattern, new ObjectCreateRule(attributeName, clazz));
        }

      这四个重载方法 都调用了addRule方法,ObjectCreateRule类是Rule的子类,该类的实例可以作为addRule方法的第二个参数来使用,下面是ObjectCreateRule类中的begin方法 和 end方法的实现:

        public void begin(Attributes attributes) throws Exception {
            String realClassName = this.className;
            if (this.attributeName != null) {
                String value = attributes.getValue(this.attributeName);
                if (value != null) {
                    realClassName = value;
                }
            }
    
            if (this.digester.log.isDebugEnabled()) {
                this.digester.log.debug("[ObjectCreateRule]{" + this.digester.match + "}New " + realClassName);
            }
    
            Class clazz = this.digester.getClassLoader().loadClass(realClassName);
            Object instance = clazz.newInstance();
            this.digester.push(instance);
        }
    
        public void end() throws Exception {
            Object top = this.digester.pop();
            if (this.digester.log.isDebugEnabled()) {
                this.digester.log.debug("[ObjectCreateRule]{" + this.digester.match + "} Pop " + top.getClass().getName());
            }
    
        }

    begin方法的最后三行 会创建一个模式匹配类的 实例,并将其压入Digester对象的内部栈中,end方法会将内部栈的栈顶元素弹出 所以栈内元素不会一直存在很多的。

    Rule类的其他子类具有相似的功能,如果你想知道每条规则的内幕可以打开源码。看下其他Rule的实现。

    示例3

      该示例主要展示如何向Digester对象中添加Rule对象,除了上面所提供的一些方法外,还可以调用其addRuleSet方法,方法签名如下:

    public void addRuleSet(org.apache.commons.digester.RuleSet ruleSet) 

    Rule对象集合是org.apahce.commons.digester.RuleSet接口的实例,该接口定义了两个方法 分别是 addRuleInstances() 和 getNamespaceURI(),addRuleInstance方法的签名如下:

    void addRuleInstances(org.apache.commons.digester.Digester var1);

    addRuleInstances方法主要是将当前RuleSet中的Rule对象的集合 添加到Digester实例中,

    getNamespaceURI方法返回将要应用在RuleSet中所有的Rule对象的命名空间URI,该方法的签名如下:

    String getNamespaceURI();

      因此,在创建了Digester对象之后,可以创建一个RuleSet对象,并调用其addRuleInstances方法将其添加到Digester对象中。

      实现RuleSet接口有一个基类RuleSetBase,方便使用,RuleSetBase类是一个抽象类,提供了getNamespaceURl()的实现,所以 你只需要提供addRuleInstances方法的实现就可以了,

      作为一个例子,这里会修改之前例子中使用过的Test02类,引入EmployeRuleSet类,代码在下面做展示:

     1 package myex15.pyrmont.digestertest;
     2 
     3 import org.apache.commons.digester.Digester;
     4 import org.apache.commons.digester.RuleSetBase;
     5 
     6 /**
     7  * <p>
     8  * <b>Title:EmployeeRuleSet.java</b>
     9  * </p>
    10  * <p>
    11  * Copyright:ChenDong 2018
    12  * </p>
    13  * <p>
    14  * Company:仅学习时使用
    15  * </p>
    16  * <p>
    17  * 类功能描述:作为如何使用自定义的RuleSet 并且使用的示例
    18  * </p>
    19  * 
    20  * @author 陈东
    21  * @date 2018年12月22日 下午9:58:53
    22  * @version 1.0
    23  */
    24 public class EmployeeRuleSet extends RuleSetBase {
    25 
    26 
    27     /**
    28      * 
    29      * <p>
    30      * Title: addRuleInstances
    31      * </p>
    32      * 
    33      * @date 2018年12月22日 下午9:58:53
    34      * 
    35      *       <p>
    36      *       功能描述:其实就是包装一个小块的 规则 同一添加到Digester中
    37      *       </p>
    38      * 
    39      * @param var1
    40      * 
    41      */
    42     @Override
    43     public void addRuleInstances(Digester digester) {
    44         // 添加规则:
    45         digester.addObjectCreate("employee", Employee.class);
    46         digester.addSetProperties("employee");
    47         digester.addObjectCreate("employee/office", Office.class);
    48         digester.addSetProperties("employee/office");
    49         digester.addSetNext("employee/office", "addOffice");
    50         digester.addObjectCreate("employee/office/address", Address.class);
    51         digester.addSetProperties("employee/office/address");
    52         digester.addSetNext("employee/office/address", "setAddress");
    53         
    54     }
    55 
    56 }

      注意,EmployeeRuleSet类中的addRuleInstances方法的实现的功能就像Test02类中的一样,将相同的Rule对象添加到Digester中,Test03类中会创建EmployeeRuleSet类的一个实例,然后将其添加到之前创建的Digester对象中,

    package myex15.pyrmont.digestertest;
    
    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Iterator;
    
    import org.apache.commons.digester.Digester;
    import org.apache.commons.digester.RuleSet;
    import org.xml.sax.SAXException;
    
    /**
     * <p>
     * <b>Title:Test03.java</b>
     * </p>
     * <p>
     * Copyright:ChenDong 2018
     * </p>
     * <p>
     * Company:仅学习时使用
     * </p>
     * <p>
     * 类功能描述:如何使用自定义RuleSet的示例
     * </p>
     * 
     * @author 陈东
     * @date 2018年12月22日 下午10:05:05
     * @version 1.0
     */
    public class Test03 {
    
        /**
         * 
         * <p>
         * Title: main
         * </p>
         * 
         * @date 2018年12月22日 下午10:05:05
         * 
         *       <p>
         *       功能描述:
         *       </p>
         * 
         * @param args
         * 
         */
        public static void main(String[] args) {
            // 定义XML文件路径
            String path = System.getProperty("user.dir") + File.separator + "etc";
            // 包含xml文件引用File
            File file = new File(path, "MyEmployee2.xml");
    
            // 创建Digester对象
            Digester digester = new Digester();
    
            // 创建包装好 关于Employee、office、address的RuleSet
            RuleSet ruleSet = new EmployeeRuleSet();
            // 将ruleset中的Rule集合添加到 Digester对象中
            ruleSet.addRuleInstances(digester);
            
            try {
                Employee employee = (Employee) digester.parse(file);
                ArrayList offices = employee.getOffices();
                Iterator iterator = offices.iterator();
    
                System.out.println("--------------------------------------------------------");
    
                while (iterator.hasNext()) {
                    Office office = (Office) iterator.next();
                    Address address = office.getAddress();
                    System.out.println("Office description: "+office.getDescription());
                    System.out.println("Address: "+address.getStreetName()+ "" + address.getStreetNumber());
                    
                }
    
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SAXException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
    
        }
    
    }

    输出和Test02完全一样

    创建Employee实例
    Setting  firstName :陈
    Setting lastName:东
    .....Creating Office
    正在使用setDescription方法 :参数 description为:工作办公室
    .........Creating Address
    正在使用setStreet方法。参数streetname为:海淀区西二旗烽火科技大厦
    正在使用setStreetNumber方法 参数 streetNumber为:108号
    正在使用setAddress方法 :参数address为:Address [steetName=海淀区西二旗烽火科技大厦, streetNumber=108号]
    Adding Office to this employee
    .....Creating Office
    正在使用setDescription方法 :参数 description为:私人办公室
    .........Creating Address
    正在使用setStreet方法。参数streetname为:昌平区西二区
    正在使用setStreetNumber方法 参数 streetNumber为:108号
    正在使用setAddress方法 :参数address为:Address [steetName=昌平区西二区, streetNumber=108号]
    Adding Office to this employee
    --------------------------------------------------------
    Office description: 工作办公室
    Address: 海淀区西二旗烽火科技大厦108号
    Office description: 私人办公室
    Address: 昌平区西二区108号

      运行Test03类会产生和Test02类得到相同的输出结果。但是请注意。Test03的代码量少一些 简洁一些,因为添加Rule对象的代码隐藏在EmployeeRuleSet类中了。那么下面将会介绍到Tomcat使用了RuleSetBase类的子类实现,来初始化它的服务器组件和其他的组件,那么在下面也能看出来Digester库在Tomcat中扮演的重要角色。

    ContextConfig类

      Context容器 与其他类型的容器不同,StandardContext实例必须有一个监听器,这个监听器会负责配置StandardContext实例,设置成功后会将StandardContext实例的变量configured布尔变量设置为true,在前面的学习中,一直使用的SimpleContextConfig类的实例作为StandardContext类的监听器,这个类非常简单,其唯一的用途就是设置configured变量,这样StandardContext类的start方法才能继续执行,

      在Tomcat的实际部署中,StandardContext类的标准监听器是 org.apache.catalina.startup.ContextConfig类的一个实例,与我们自己实现的简陋的SimpleContextConfig类不同,ContextConfig类的实例会执行很多对StandardContext来说必不可少的任务,例如,与某个StandardContext实例关联的ContextConfig实例会安装一个验证器阀到StandardContext实例的管道对象中,此外,他还会添加一个许可阀(org.apache.catalina.values.CertificateValue类的实例)到管道对象中,

      但更重要的是,ContextConfig类的实例还要读取 和 解析 web.xml文件和应用程序自定义的web.xml文件,并将XML元素转换成java对象,默认的web.xml文件位于CATALINA_HOME目录下的conf目录中,其中定义并映射了很多默认的servlet,配置了很多MIME类型文件的映射,定义了默认的Session超时时间,以及定义了欢迎文件的列表。

      应用程序的web.xml文件时应用程序自定义的配置文件,位于应用程序目录下的WEB-INF目录中,这两个文件都不是必须的,即使这两个文件都没有找到,ContextConfig实例仍然会继续执行,

      ContextConfig实例会为每个servlet元素创建一个StandardWrapper类,因此正如你在下面的应用程序中看到的,配置变简单了,不在需要实例化一个Wrapper实例了,

      因此在Bootstrap类中,需要实例化一个ContextConfig类,并调用org.apache.catalina.Lifecycle接口的addLifecycleListener方法将ContextConfig实例作为一个监听器添加到StandardContext实例中,

      在启动StandardContext实例时,会触发以下事件:

    • BEFORE_START_EVENT
    • START_EVENT
    • AFTER_START_EVENT

      当程序停止时,StandardContext实例会触发以下事件

    • BEFORE_STOP_EVENT
    • STOP_EVENT
    • AFTER_STOP_EVENT

     ContextConfig会对这两种事件作出响应,分别是START_EVENT 和 STOP_EVENT,每次StandardContext实例触发这两种事件时,都会调用ContextConfig实例的LifecycleEvent方法,

     1 /**
     2      * 处理关联Context 的START 与 STOP事件。
     3      *
     4      * @param event
     5      *            已发生的生命周期事件
     6      */
     7     public void lifecycleEvent(LifecycleEvent event) {
     8 
     9         // 确定我们关联的上下文
    10         try {
    11             context = (Context) event.getLifecycle();
    12             if (context instanceof StandardContext) {
    13                 int contextDebug = ((StandardContext) context).getDebug();
    14                 if (contextDebug > this.debug)
    15                     this.debug = contextDebug;
    16             }
    17         } catch (ClassCastException e) {
    18             log(sm.getString("contextConfig.cce", event.getLifecycle()), e);
    19             return;
    20         }
    21 
    22         // 处理已发生的事件 只有当事件类型为下面两种类型是才会做出想用的处理
    23         if (event.getType().equals(Lifecycle.START_EVENT))
    24             start();
    25         else if (event.getType().equals(Lifecycle.STOP_EVENT))
    26             stop();
    27 
    28     }

      在上面方法的末尾,它会调用其start方法 或stop方法,

    start方法

     1     /**
     2      * 处理Context的“开始”事件。
     3      */
     4     private synchronized void start() {
     5 
     6         if (debug > 0)
     7             log(sm.getString("contextConfig.start"));
     8         // 先将Context 的 configured的值 设置为false
     9         context.setConfigured(false);
    10         // 状态标记值
    11         ok = true;
    12 
    13         // 基于DefaultContext设置属性
    14         Container container = context.getParent();
    15         if (!context.getOverride()) {
    16             if (container instanceof Host) {
    17                 ((Host) container).importDefaultContext(context);
    18                 container = container.getParent();
    19             }
    20             if (container instanceof Engine) {
    21                 ((Engine) container).importDefaultContext(context);
    22             }
    23         }
    24 
    25         // 处理默认和应用程序web.xml文件
    26         defaultConfig();
    27         applicationConfig();
    28         if (ok) {
    29             validateSecurityRoles();
    30         }
    31 
    32         // 扫描标记库描述符文件以获取其他监听器类
    33         if (ok) {
    34             try {
    35                 tldScan();
    36             } catch (Exception e) {
    37                 log(e.getMessage(), e);
    38                 ok = false;
    39             }
    40         }
    41 
    42         // 如果需要,配置证书暴露器阀
    43         if (ok)
    44             certificatesConfig();
    45 
    46         //如果需要,请配置一个身份验证器
    47         if (ok)
    48             authenticatorConfig();
    49 
    50         // 如果请求,则打印此管道的内容
    51         if ((debug >= 1) && (context instanceof ContainerBase)) {
    52             log("Pipline Configuration:");
    53             Pipeline pipeline = ((ContainerBase) context).getPipeline();
    54             Valve valves[] = null;
    55             if (pipeline != null)
    56                 valves = pipeline.getValves();
    57             if (valves != null) {
    58                 for (int i = 0; i < valves.length; i++) {
    59                     log("  " + valves[i].getInfo());
    60                 }
    61             }
    62             log("======================");
    63         }
    64 
    65         // 如果没有遇到任何问题,则使应用程序可用
    66         if (ok)
    67             context.setConfigured(true);
    68         else {
    69             log(sm.getString("contextConfig.unavailable"));
    70             context.setConfigured(false);
    71         }
    72 
    73     }

     defaultConfig方法

      defaultConfig方法负责读取并解析位于%CATALINA_HOME%/config目录下的默认web.xml文件

    /**
         * 如果默认配置文件存在,则对其进行处理。
         * 
         * 
         */
        private void defaultConfig() {
    
            // 如果默认web.xml文件存在,则打开该文件
            File file = new File(Constants.DefaultWebXml);
            if (!file.isAbsolute())
                file = new File(System.getProperty("catalina.base"), Constants.DefaultWebXml);
            FileInputStream stream = null;
            try {
                stream = new FileInputStream(file.getCanonicalPath());
                stream.close();
                stream = null;
            } catch (FileNotFoundException e) {
                log(sm.getString("contextConfig.defaultMissing"));
                return;
            } catch (IOException e) {
                log(sm.getString("contextConfig.defaultMissing"), e);
                return;
            }
    
            // 处理默认的web.xml文件 webDigester就是Digester的引用
            synchronized (webDigester) {
                try {
                    InputSource is = new InputSource("file://" + file.getAbsolutePath());
                    stream = new FileInputStream(file);
                    is.setByteStream(stream);
                    webDigester.setDebug(getDebug());
                    if (context instanceof StandardContext)
                        ((StandardContext) context).setReplaceWelcomeFiles(true);
                    webDigester.clear();
                    webDigester.push(context);
                    webDigester.parse(is);
                } catch (SAXParseException e) {
                    log(sm.getString("contextConfig.defaultParse"), e);
                    log(sm.getString("contextConfig.defaultPosition", "" + e.getLineNumber(), "" + e.getColumnNumber()));
                    ok = false;
                } catch (Exception e) {
                    log(sm.getString("contextConfig.defaultParse"), e);
                    ok = false;
                } finally {
                    try {
                        if (stream != null) {
                            stream.close();
                        }
                    } catch (IOException e) {
                        log(sm.getString("contextConfig.defaultClose"), e);
                    }
                }
            }
    
        }

      defaultConfig方法首先会创建一个File对象,该File对象引用默认的web.xml配置文件:

    // 如果默认web.xml文件存在,则打开该文件
            File file = new File(Constants.DefaultWebXml);
            if (!file.isAbsolute())
                file = new File(System.getProperty("catalina.base"), Constants.DefaultWebXml);

    常量DefaultWebXml的值定义在org.apache.catalina.startup.Constans类中

     public static final String DefaultWebXml = "conf/web.xml";

    然后开始处理web.xml文件,先锁定webDigester对象然后开始解析文件

    // 处理默认的web.xml文件 webDigester就是Digester的引用
            synchronized (webDigester) {
                try {
                    InputSource is = new InputSource("file://" + file.getAbsolutePath());
                    stream = new FileInputStream(file);
                    is.setByteStream(stream);
                    webDigester.setDebug(getDebug());
                    if (context instanceof StandardContext)
                        ((StandardContext) context).setReplaceWelcomeFiles(true);
                    webDigester.clear();
                    webDigester.push(context);
                    webDigester.parse(is);
                } catch (SAXParseException e) {
                    log(sm.getString("contextConfig.defaultParse"), e);
                    log(sm.getString("contextConfig.defaultPosition", "" + e.getLineNumber(), "" + e.getColumnNumber()));
                    ok = false;
                } catch (Exception e) {
                    log(sm.getString("contextConfig.defaultParse"), e);
                    ok = false;
                } finally {
                    try {
                        if (stream != null) {
                            stream.close();
                        }
                    } catch (IOException e) {
                        log(sm.getString("contextConfig.defaultClose"), e);
                    }
                }
            }

      对象变量wenDigester是一个指向Digester实例的引用,该Digester实例已经包含了处理web.xml所需要的规则,这个在后面在展示

      applicationConfig()方法

      applicationConfig方法与defaultConfig方法类似,只不过它处理的是应用程序自定义的部署描述符,该部署描述文件位于应用程序目录下的WEB-INF目录中

    /**
         * 如果应用程序配置文件存在,则对其进行处理。
         */
        private void applicationConfig() {
    
            // 如果应用程序web.xml文件存在,则打开它
            InputStream stream = null;
            ServletContext servletContext = context.getServletContext();
            if (servletContext != null)
                stream = servletContext.getResourceAsStream(Constants.ApplicationWebXml);
            if (stream == null) {
                log(sm.getString("contextConfig.applicationMissing"));
                return;
            }
    
            // 处理应用程序的配置文件
            synchronized (webDigester) {
                try {
                    URL url = servletContext.getResource(Constants.ApplicationWebXml);
    
                    InputSource is = new InputSource(url.toExternalForm());
                    is.setByteStream(stream);
                    webDigester.setDebug(getDebug());
                    if (context instanceof StandardContext) {
                        ((StandardContext) context).setReplaceWelcomeFiles(true);
                    }
                    webDigester.clear();
                    webDigester.push(context);
                    webDigester.parse(is);
                } catch (SAXParseException e) {
                    log(sm.getString("contextConfig.applicationParse"), e);
                    log(sm.getString("contextConfig.applicationPosition", "" + e.getLineNumber(),
                            "" + e.getColumnNumber()));
                    ok = false;
                } catch (Exception e) {
                    log(sm.getString("contextConfig.applicationParse"), e);
                    ok = false;
                } finally {
                    try {
                        if (stream != null) {
                            stream.close();
                        }
                    } catch (IOException e) {
                        log(sm.getString("contextConfig.applicationClose"), e);
                    }
                }
            }
    
        }

      重点来了 其实核心的动作 逻辑 都再Web Digester中

    创建Web Digester

      在ContextConfig类中,使用变量WebDigester来引用一个Digester类型的对象:

    /**
         * 我们将使用<code>Digester</code>来处理Web应用程序部署描述符文件
         */
        private static Digester webDigester = createWebDigester();

      这个Digester对象用于解析默认的web.xml文件和应用程序自定义的web.xml文件,在调用createWebDigester时会添加用来处理web.xml文件的规则,

    private static Digester createWebDigester() {
    
            URL url = null;
            Digester webDigester = new Digester();
            webDigester.setValidating(true);
            url = ContextConfig.class.getResource(Constants.WebDtdResourcePath_22);
            webDigester.register(Constants.WebDtdPublicId_22, url.toString());
            url = ContextConfig.class.getResource(Constants.WebDtdResourcePath_23);
            webDigester.register(Constants.WebDtdPublicId_23, url.toString());
            webDigester.addRuleSet(new WebRuleSet());
            return (webDigester);
    
        }

      注意:createWebDigester方法调用了 变量webDigester的addRuleSet方法,并传入一个org.apache.catalina.startup.WebRuleSet类型的对象作为参数,WebRuleSet类时org.apache.commons.digester.RuleSetBase类的子类,

    package org.apache.catalina.startup;
    
    import java.lang.reflect.Method;
    import org.apache.catalina.Context;
    import org.apache.catalina.Wrapper;
    import org.apache.catalina.deploy.SecurityConstraint;
    import org.apache.commons.digester.Digester;
    import org.apache.commons.digester.Rule;
    import org.apache.commons.digester.RuleSetBase;
    import org.xml.sax.Attributes;
    
    /**
     * 
     * <p>
     * <b>Title:WebRuleSet.java</b>
     * </p>
     * <p>
     * Copyright:ChenDong 2018
     * </p>
     * <p>
     * Company:仅学习时使用
     * </p>
     * <p>
     * 类功能描述:用于处理Web应用程序部署描述符(/WEB-INF/web.xml)资源的内容的规则集。
     * </p>
     * 
     * @author 
     * @date 2018年12月23日 下午9:12:02
     * @version 1.0
     */
    public class WebRuleSet extends RuleSetBase {
    
        // ----------------------------------------------------- Instance Variables
    
        /**
         * 用于识别元素的匹配模式前缀.
         */
        protected String prefix = null;
    
        // ------------------------------------------------------------ Constructor
    
        /**
         * 
         * 
         * <p>
         * Title:
         * </p>
         * 
         * <p>
         * Description:使用默认匹配模式前缀构造此RuleSet的实例。
         * </p>
         */
        public WebRuleSet() {
    
            this("");
    
        }
    
        /**
         *
         * 
         * <p>
         * Title:
         * </p>
         * 
         * <p>
         * Description: 使用指定的匹配模式前缀构造此RuleSet的实例。
         * </p>
         * 
         * @param prefix
         *            匹配模式规则的前缀(包括后面的斜杠字符)
         */
        public WebRuleSet(String prefix) {
    
            super();
            this.namespaceURI = null;
            this.prefix = prefix;
    
        }
    
        // --------------------------------------------------------- Public Methods
    
        
    
        /**
         * <p>
         * 将此RuleSet中定义的一组Rule实例添加到指定的Digester实例,将它们与我们的命名空间URI(如果有的话)关联。
         * 此方法应该只由Digester实例调用。
         * </p>
         * 
         * @param digester
         *            应该向其添加新Rule实例的<code>Digester</code>实例。
         */
        public void addRuleInstances(Digester digester) {
    
            digester.addRule(prefix + "web-app", new SetPublicIdRule(digester, "setPublicId"));
    
            digester.addCallMethod(prefix + "web-app/context-param", "addParameter", 2);
            digester.addCallParam(prefix + "web-app/context-param/param-name", 0);
            digester.addCallParam(prefix + "web-app/context-param/param-value", 1);
    
            digester.addCallMethod(prefix + "web-app/display-name", "setDisplayName", 0);
    
            digester.addRule(prefix + "web-app/distributable", new SetDistributableRule(digester));
    
            digester.addObjectCreate(prefix + "web-app/ejb-local-ref", "org.apache.catalina.deploy.ContextLocalEjb");
            digester.addSetNext(prefix + "web-app/ejb-local-ref", "addLocalEjb",
                    "org.apache.catalina.deploy.ContextLocalEjb");
    
            digester.addCallMethod(prefix + "web-app/ejb-local-ref/description", "setDescription", 0);
            digester.addCallMethod(prefix + "web-app/ejb-local-ref/ejb-link", "setLink", 0);
            digester.addCallMethod(prefix + "web-app/ejb-local-ref/ejb-ref-name", "setName", 0);
            digester.addCallMethod(prefix + "web-app/ejb-local-ref/ejb-ref-type", "setType", 0);
            digester.addCallMethod(prefix + "web-app/ejb-local-ref/local", "setLocal", 0);
            digester.addCallMethod(prefix + "web-app/ejb-local-ref/local-home", "setHome", 0);
    
            digester.addObjectCreate(prefix + "web-app/ejb-ref", "org.apache.catalina.deploy.ContextEjb");
            digester.addSetNext(prefix + "web-app/ejb-ref", "addEjb", "org.apache.catalina.deploy.ContextEjb");
    
            digester.addCallMethod(prefix + "web-app/ejb-ref/description", "setDescription", 0);
            digester.addCallMethod(prefix + "web-app/ejb-ref/ejb-link", "setLink", 0);
            digester.addCallMethod(prefix + "web-app/ejb-ref/ejb-ref-name", "setName", 0);
            digester.addCallMethod(prefix + "web-app/ejb-ref/ejb-ref-type", "setType", 0);
            digester.addCallMethod(prefix + "web-app/ejb-ref/home", "setHome", 0);
            digester.addCallMethod(prefix + "web-app/ejb-ref/remote", "setRemote", 0);
    
            digester.addObjectCreate(prefix + "web-app/env-entry", "org.apache.catalina.deploy.ContextEnvironment");
            digester.addSetNext(prefix + "web-app/env-entry", "addEnvironment",
                    "org.apache.catalina.deploy.ContextEnvironment");
    
            digester.addCallMethod(prefix + "web-app/env-entry/description", "setDescription", 0);
            digester.addCallMethod(prefix + "web-app/env-entry/env-entry-name", "setName", 0);
            digester.addCallMethod(prefix + "web-app/env-entry/env-entry-type", "setType", 0);
            digester.addCallMethod(prefix + "web-app/env-entry/env-entry-value", "setValue", 0);
    
            digester.addObjectCreate(prefix + "web-app/error-page", "org.apache.catalina.deploy.ErrorPage");
            digester.addSetNext(prefix + "web-app/error-page", "addErrorPage", "org.apache.catalina.deploy.ErrorPage");
    
            digester.addCallMethod(prefix + "web-app/error-page/error-code", "setErrorCode", 0);
            digester.addCallMethod(prefix + "web-app/error-page/exception-type", "setExceptionType", 0);
            digester.addCallMethod(prefix + "web-app/error-page/location", "setLocation", 0);
    
            digester.addObjectCreate(prefix + "web-app/filter", "org.apache.catalina.deploy.FilterDef");
            digester.addSetNext(prefix + "web-app/filter", "addFilterDef", "org.apache.catalina.deploy.FilterDef");
    
            digester.addCallMethod(prefix + "web-app/filter/description", "setDescription", 0);
            digester.addCallMethod(prefix + "web-app/filter/display-name", "setDisplayName", 0);
            digester.addCallMethod(prefix + "web-app/filter/filter-class", "setFilterClass", 0);
            digester.addCallMethod(prefix + "web-app/filter/filter-name", "setFilterName", 0);
            digester.addCallMethod(prefix + "web-app/filter/large-icon", "setLargeIcon", 0);
            digester.addCallMethod(prefix + "web-app/filter/small-icon", "setSmallIcon", 0);
    
            digester.addCallMethod(prefix + "web-app/filter/init-param", "addInitParameter", 2);
            digester.addCallParam(prefix + "web-app/filter/init-param/param-name", 0);
            digester.addCallParam(prefix + "web-app/filter/init-param/param-value", 1);
    
            digester.addObjectCreate(prefix + "web-app/filter-mapping", "org.apache.catalina.deploy.FilterMap");
            digester.addSetNext(prefix + "web-app/filter-mapping", "addFilterMap", "org.apache.catalina.deploy.FilterMap");
    
            digester.addCallMethod(prefix + "web-app/filter-mapping/filter-name", "setFilterName", 0);
            digester.addCallMethod(prefix + "web-app/filter-mapping/servlet-name", "setServletName", 0);
            digester.addCallMethod(prefix + "web-app/filter-mapping/url-pattern", "setURLPattern", 0);
    
            digester.addCallMethod(prefix + "web-app/listener/listener-class", "addApplicationListener", 0);
    
            digester.addObjectCreate(prefix + "web-app/login-config", "org.apache.catalina.deploy.LoginConfig");
            digester.addSetNext(prefix + "web-app/login-config", "setLoginConfig",
                    "org.apache.catalina.deploy.LoginConfig");
    
            digester.addCallMethod(prefix + "web-app/login-config/auth-method", "setAuthMethod", 0);
            digester.addCallMethod(prefix + "web-app/login-config/realm-name", "setRealmName", 0);
            digester.addCallMethod(prefix + "web-app/login-config/form-login-config/form-error-page", "setErrorPage", 0);
            digester.addCallMethod(prefix + "web-app/login-config/form-login-config/form-login-page", "setLoginPage", 0);
    
            digester.addCallMethod(prefix + "web-app/mime-mapping", "addMimeMapping", 2);
            digester.addCallParam(prefix + "web-app/mime-mapping/extension", 0);
            digester.addCallParam(prefix + "web-app/mime-mapping/mime-type", 1);
    
            digester.addCallMethod(prefix + "web-app/resource-env-ref", "addResourceEnvRef", 2);
            digester.addCallParam(prefix + "web-app/resource-env-ref/resource-env-ref-name", 0);
            digester.addCallParam(prefix + "web-app/resource-env-ref/resource-env-ref-type", 1);
    
            digester.addObjectCreate(prefix + "web-app/resource-ref", "org.apache.catalina.deploy.ContextResource");
            digester.addSetNext(prefix + "web-app/resource-ref", "addResource",
                    "org.apache.catalina.deploy.ContextResource");
    
            digester.addCallMethod(prefix + "web-app/resource-ref/description", "setDescription", 0);
            digester.addCallMethod(prefix + "web-app/resource-ref/res-auth", "setAuth", 0);
            digester.addCallMethod(prefix + "web-app/resource-ref/res-ref-name", "setName", 0);
            digester.addCallMethod(prefix + "web-app/resource-ref/res-sharing-scope", "setScope", 0);
            digester.addCallMethod(prefix + "web-app/resource-ref/res-type", "setType", 0);
    
            digester.addObjectCreate(prefix + "web-app/security-constraint",
                    "org.apache.catalina.deploy.SecurityConstraint");
            digester.addSetNext(prefix + "web-app/security-constraint", "addConstraint",
                    "org.apache.catalina.deploy.SecurityConstraint");
    
            digester.addRule(prefix + "web-app/security-constraint/auth-constraint", new SetAuthConstraintRule(digester));
            digester.addCallMethod(prefix + "web-app/security-constraint/auth-constraint/role-name", "addAuthRole", 0);
            digester.addCallMethod(prefix + "web-app/security-constraint/display-name", "setDisplayName", 0);
            digester.addCallMethod(prefix + "web-app/security-constraint/user-data-constraint/transport-guarantee",
                    "setUserConstraint", 0);
    
            digester.addObjectCreate(prefix + "web-app/security-constraint/web-resource-collection",
                    "org.apache.catalina.deploy.SecurityCollection");
            digester.addSetNext(prefix + "web-app/security-constraint/web-resource-collection", "addCollection",
                    "org.apache.catalina.deploy.SecurityCollection");
            digester.addCallMethod(prefix + "web-app/security-constraint/web-resource-collection/http-method", "addMethod",
                    0);
            digester.addCallMethod(prefix + "web-app/security-constraint/web-resource-collection/url-pattern", "addPattern",
                    0);
            digester.addCallMethod(prefix + "web-app/security-constraint/web-resource-collection/web-resource-name",
                    "setName", 0);
    
            digester.addCallMethod(prefix + "web-app/security-role/role-name", "addSecurityRole", 0);
    
            digester.addRule(prefix + "web-app/servlet", new WrapperCreateRule(digester));
            digester.addSetNext(prefix + "web-app/servlet", "addChild", "org.apache.catalina.Container");
    
            digester.addCallMethod(prefix + "web-app/servlet/init-param", "addInitParameter", 2);
            digester.addCallParam(prefix + "web-app/servlet/init-param/param-name", 0);
            digester.addCallParam(prefix + "web-app/servlet/init-param/param-value", 1);
    
            digester.addCallMethod(prefix + "web-app/servlet/jsp-file", "setJspFile", 0);
            digester.addCallMethod(prefix + "web-app/servlet/load-on-startup", "setLoadOnStartupString", 0);
            digester.addCallMethod(prefix + "web-app/servlet/run-as/role-name", "setRunAs", 0);
    
            digester.addCallMethod(prefix + "web-app/servlet/security-role-ref", "addSecurityReference", 2);
            digester.addCallParam(prefix + "web-app/servlet/security-role-ref/role-link", 1);
            digester.addCallParam(prefix + "web-app/servlet/security-role-ref/role-name", 0);
    
            digester.addCallMethod(prefix + "web-app/servlet/servlet-class", "setServletClass", 0);
            digester.addCallMethod(prefix + "web-app/servlet/servlet-name", "setName", 0);
    
            digester.addCallMethod(prefix + "web-app/servlet-mapping", "addServletMapping", 2);
            digester.addCallParam(prefix + "web-app/servlet-mapping/servlet-name", 1);
            digester.addCallParam(prefix + "web-app/servlet-mapping/url-pattern", 0);
    
            digester.addCallMethod(prefix + "web-app/session-config/session-timeout", "setSessionTimeout", 1,
                    new Class[] { Integer.TYPE });
            digester.addCallParam(prefix + "web-app/session-config/session-timeout", 0);
    
            digester.addCallMethod(prefix + "web-app/taglib", "addTaglib", 2);
            digester.addCallParam(prefix + "web-app/taglib/taglib-location", 1);
            digester.addCallParam(prefix + "web-app/taglib/taglib-uri", 0);
    
            digester.addCallMethod(prefix + "web-app/welcome-file-list/welcome-file", "addWelcomeFile", 0);
    
        }
    
    }
    
    // ----------------------------------------------------------- Private Classes
    
    /**
     * A Rule that calls the <code>setAuthConstraint(true)</code> method of the top
     * item on the stack, which must be of type
     * <code>org.apache.catalina.deploy.SecurityConstraint</code>.
     */
    
    final class SetAuthConstraintRule extends Rule {
    
        public SetAuthConstraintRule(Digester digester) {
            super(digester);
        }
    
        public void begin(Attributes attributes) throws Exception {
            SecurityConstraint securityConstraint = (SecurityConstraint) digester.peek();
            securityConstraint.setAuthConstraint(true);
            if (digester.getDebug() > 0)
                digester.log("Calling SecurityConstraint.setAuthConstraint(true)");
        }
    
    }
    
    /**
     * Class that calls <code>setDistributable(true)</code> for the top object on
     * the stack, which must be a <code>org.apache.catalina.Context</code>.
     */
    
    final class SetDistributableRule extends Rule {
    
        public SetDistributableRule(Digester digester) {
            super(digester);
        }
    
        public void begin(Attributes attributes) throws Exception {
            Context context = (Context) digester.peek();
            context.setDistributable(true);
            if (digester.getDebug() > 0)
                digester.log(context.getClass().getName() + ".setDistributable( true)");
        }
    
    }
    
    /**
     * Class that calls a property setter for the top object on the stack, passing
     * the public ID of the entity we are currently processing.
     */
    
    final class SetPublicIdRule extends Rule {
    
        public SetPublicIdRule(Digester digester, String method) {
            super(digester);
            this.method = method;
        }
    
        private String method = null;
    
        public void begin(Attributes attributes) throws Exception {
    
            Context context = (Context) digester.peek(digester.getCount() - 1);
            Object top = digester.peek();
            Class paramClasses[] = new Class[1];
            paramClasses[0] = "String".getClass();
            String paramValues[] = new String[1];
            paramValues[0] = digester.getPublicId();
    
            Method m = null;
            try {
                m = top.getClass().getMethod(method, paramClasses);
            } catch (NoSuchMethodException e) {
                digester.log("Can't find method " + method + " in " + top + " CLASS " + top.getClass());
                return;
            }
    
            m.invoke(top, paramValues);
            if (digester.getDebug() >= 1)
                digester.log("" + top.getClass().getName() + "." + method + "(" + paramValues[0] + ")");
    
        }
    
    }
    
    /**
     * A Rule that calls the factory method on the specified Context to create the
     * object that is to be added to the stack.
     */
    
    final class WrapperCreateRule extends Rule {
    
        public WrapperCreateRule(Digester digester) {
            super(digester);
        }
    
        public void begin(Attributes attributes) throws Exception {
            Context context = (Context) digester.peek(digester.getCount() - 1);
            Wrapper wrapper = context.createWrapper();
            digester.push(wrapper);
            if (digester.getDebug() > 0)
                digester.log("new " + wrapper.getClass().getName());
        }
    
        public void end() throws Exception {
            Wrapper wrapper = (Wrapper) digester.pop();
            if (digester.getDebug() > 0)
                digester.log("pop " + wrapper.getClass().getName());
        }
    
    }

    这个结合一下web.xml吧 我找了一个tomcat 7的 应用程序的web.xml 文件结合起来看下

    <?xml version="1.0" encoding="ISO-8859-1"?>
    <!--
     Licensed to the Apache Software Foundation (ASF) under one or more
      contributor license agreements.  See the NOTICE file distributed with
      this work for additional information regarding copyright ownership.
      The ASF licenses this file to You under the Apache License, Version 2.0
      (the "License"); you may not use this file except in compliance with
      the License.  You may obtain a copy of the License at
    
          http://www.apache.org/licenses/LICENSE-2.0
    
      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      See the License for the specific language governing permissions and
      limitations under the License.
    -->
    
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
      version="3.0"
      metadata-complete="true">
    
      <display-name>Tomcat Manager Application</display-name>
      <description>
        A scriptable management web application for the Tomcat Web Server;
        Manager lets you view, load/unload/etc particular web applications.
      </description>
    
      <servlet>
        <servlet-name>Manager</servlet-name>
        <servlet-class>org.apache.catalina.manager.ManagerServlet</servlet-class>
        <init-param>
          <param-name>debug</param-name>
          <param-value>2</param-value>
        </init-param>
      </servlet>
      <servlet>
        <servlet-name>HTMLManager</servlet-name>
        <servlet-class>org.apache.catalina.manager.HTMLManagerServlet</servlet-class>
        <init-param>
          <param-name>debug</param-name>
          <param-value>2</param-value>
        </init-param>
        <!-- Uncomment this to show proxy sessions from the Backup manager or a
             StoreManager in the sessions list for an application
        <init-param>
          <param-name>showProxySessions</param-name>
          <param-value>true</param-value>
        </init-param>
        -->
        <multipart-config>
          <!-- 50MB max -->
          <max-file-size>52428800</max-file-size>
          <max-request-size>52428800</max-request-size>
          <file-size-threshold>0</file-size-threshold>
        </multipart-config>
      </servlet>
      <servlet>
        <servlet-name>Status</servlet-name>
        <servlet-class>org.apache.catalina.manager.StatusManagerServlet</servlet-class>
        <init-param>
          <param-name>debug</param-name>
          <param-value>0</param-value>
        </init-param>
      </servlet>
    
      <servlet>
        <servlet-name>JMXProxy</servlet-name>
        <servlet-class>org.apache.catalina.manager.JMXProxyServlet</servlet-class>
      </servlet>
    
      <!-- Define the Manager Servlet Mapping -->
      <servlet-mapping>
        <servlet-name>Manager</servlet-name>
          <url-pattern>/text/*</url-pattern>
      </servlet-mapping>
      <servlet-mapping>
        <servlet-name>Status</servlet-name>
        <url-pattern>/status/*</url-pattern>
      </servlet-mapping>
      <servlet-mapping>
        <servlet-name>JMXProxy</servlet-name>
          <url-pattern>/jmxproxy/*</url-pattern>
      </servlet-mapping>
      <servlet-mapping>
        <servlet-name>HTMLManager</servlet-name>
        <url-pattern>/html/*</url-pattern>
      </servlet-mapping>
    
      <filter>
        <filter-name>SetCharacterEncoding</filter-name>
        <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>
        <init-param>
          <param-name>encoding</param-name>
          <param-value>UTF-8</param-value>
        </init-param>
      </filter>
    
      <filter-mapping>
        <filter-name>SetCharacterEncoding</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>
    
      <filter>
        <filter-name>CSRF</filter-name>
        <filter-class>org.apache.catalina.filters.CsrfPreventionFilter</filter-class>
        <init-param>
          <param-name>entryPoints</param-name>
          <param-value>/html,/html/,/html/list,/index.jsp</param-value>
        </init-param>
      </filter>
    
      <filter-mapping>
        <filter-name>CSRF</filter-name>
        <servlet-name>HTMLManager</servlet-name>
      </filter-mapping>
    
      <!-- Define a Security Constraint on this Application -->
      <!-- NOTE:  None of these roles are present in the default users file -->
      <security-constraint>
        <web-resource-collection>
          <web-resource-name>HTML Manager interface (for humans)</web-resource-name>
          <url-pattern>/html/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
           <role-name>manager-gui</role-name>
        </auth-constraint>
      </security-constraint>
      <security-constraint>
        <web-resource-collection>
          <web-resource-name>Text Manager interface (for scripts)</web-resource-name>
          <url-pattern>/text/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
           <role-name>manager-script</role-name>
        </auth-constraint>
      </security-constraint>
      <security-constraint>
        <web-resource-collection>
          <web-resource-name>JMX Proxy interface</web-resource-name>
          <url-pattern>/jmxproxy/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
           <role-name>manager-jmx</role-name>
        </auth-constraint>
      </security-constraint>
      <security-constraint>
        <web-resource-collection>
          <web-resource-name>Status interface</web-resource-name>
          <url-pattern>/status/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
           <role-name>manager-gui</role-name>
           <role-name>manager-script</role-name>
           <role-name>manager-jmx</role-name>
           <role-name>manager-status</role-name>
        </auth-constraint>
      </security-constraint>
    
      <!-- Define the Login Configuration for this Application -->
      <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>Tomcat Manager Application</realm-name>
      </login-config>
    
      <!-- Security roles referenced by this web application -->
      <security-role>
        <description>
          The role that is required to access the HTML Manager pages
        </description>
        <role-name>manager-gui</role-name>
      </security-role>
      <security-role>
        <description>
          The role that is required to access the text Manager pages
        </description>
        <role-name>manager-script</role-name>
      </security-role>
      <security-role>
        <description>
          The role that is required to access the HTML JMX Proxy
        </description>
        <role-name>manager-jmx</role-name>
      </security-role>
      <security-role>
        <description>
          The role that is required to access to the Manager Status pages
        </description>
        <role-name>manager-status</role-name>
      </security-role>
    
      <error-page>
        <error-code>401</error-code>
        <location>/WEB-INF/jsp/401.jsp</location>
      </error-page>
      <error-page>
        <error-code>403</error-code>
        <location>/WEB-INF/jsp/403.jsp</location>
      </error-page>
      <error-page>
        <error-code>404</error-code>
        <location>/WEB-INF/jsp/404.jsp</location>
      </error-page>
    
    </web-app>

    这样一来就能很方便的通过修改xml 文件 和 修改WebRuleSet的一些规则就可以很灵活的配置。

      Tomcat 用于不同的配置环境下,通过使用Digester对象将XML元素转换为java对象使得用户可以通过编写server.xml文件来方便的配置Tomcat,此外,web.xml文档用于配置 servler/jsp应用程序,Tomcat必须要解析web.xml,并基于XML文档的元素配置context对象,Digester库很简单、方便的的解决了这个问题。

  • 相关阅读:
    快直播-基于WebRTC升级的低延时直播
    在HTML5上开发音视频应用的五种思路
    H.265/HEVC Web端直播播放器内核开发解密
    FFmpeg 命令行和API方式转换rtsp或264成Fragmented MP4
    rtsp流转为fmp4并由WebSocket网关转发,及对应js播放器
    基于FFMPEG封装aac及h264为FargmentMP4
    HTML5 直播协议之 WebSocket 和 MSE fmp4
    wasm + ffmpeg实现前端截取视频帧功能
    es~ElasticsearchTemplate的查询和聚合
    springboot~通过面向接口编程对控制反转IOC的理解
  • 原文地址:https://www.cnblogs.com/ChenD/p/Digester_Tomcat.html
Copyright © 2020-2023  润新知