Digester学习笔记

来源:互联网 发布:知乎方糖 编辑:程序博客网 时间:2024/04/28 23:10

Digester学习笔记

 在windows下开发程序,用MS提供的接口处理.ini文件或管理注册表的键值是非常方便的。在java平台上开发程序,则习惯于以xml格式的文件来存放系统的配置信息,对这种文件的解析和处理,可以用saxdom。有没有更简便的方法呢?有,就是用digester模块。
  DigesterJakarta 子项目Commons下的一个模块,支持基于规则的对任意XML文档的处理。它最初是Structs项目的一部分,后因其通用性而划归Commons.

下载及编译

cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic login
password: anoncvs
cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic checkout jakarta-commons/digester
cd jakarta-commons/digester
ant dist


  Digester的运行依赖下列包:

  1. 一个遵循Jaxp(1.1版本及以后)XML解析器

  2. Jakarta commons beanutils(1.5版本及以后)

  3. Jakarta commons collections(2.1版本及以后)

  4. Jakarta commons logging(1.0.2版本及以后)

 

一个简单的例子

  假定有两个JavaBean如下,分别为FooBar

package mypackage;
public class Foo {
  public void addBar(Bar bar);
  public Bar findBar(int id);
  public Iterator getBars();
  public String getName();
  public void setName(String name);
}

 

public mypackage;
public class Bar {
  public int getId();
  public void setId(int id);
  public String getTitle();
  public void setTitle(String title);
}


用下面的xml文件进行配置

 

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


  用下面几行代码即可完成配置文件解析工作:

Digester digester = new Digester();

//不进行XML与相应的DTD的合法性验证

digester.setValidating(false);

//当遇到<foo>时创建一个mypackage.Foo对象,并将其放在栈顶

digester.addObjectCreate("foo", "mypackage.Foo");

//根据<foo>元素的属性(attribute),对刚创建的Foo对象的属性(property)进行设置

digester.addSetProperties("foo");

//当遇到<foo>的子元素<bar>时创建一个mypackage.Bar对象,并将其放在栈顶。

digester.addObjectCreate("foo/bar", "mypackage.Bar");

//根据<bar>元素的属性(attribute),对刚创建的Bar对象的属性(property)进行设置

digester.addSetProperties("foo/bar");

//当再次遇到<foo>的子元素<bar>时创建一个mypackage.Bar对象,

//并将其放在栈顶,同时调用第二栈顶元素(Foo对象)addBar方法。

digester.addSetNext("foo/bar", "addBar", "mypackage.Bar");

//分析结束后,返回根元素。

Foo foo = (Foo) digester.parse();

 

基本情况

  熟悉用SAX来处理XML文档的程序员,会发现Digester隐藏了遍历XML元素这些细节,而是提供了更高一层的、更友好的SAX事件接口,从而让程序员的精力放在对数据的处理过程中。
  使用Digester,须按照以下步骤:

  1. 创建一个org.apache.commons.digester.Digester实例。一个解析请求完成后,这个Digester可以被后面复用。但也不要试图在不同的线程中从共享一个Digester实例。

  2. 根据需要设置一些配置属性(configuration properties),以控制下一步的解析操作。

  3. 将一个或几个初始对象(initial object)压入Digester对象栈,本步骤不是必须的。

  4. 注册所有的元素匹配模板(elemet matching pattern)。当一个模板被从输入文档中识别出来以后,与其相联系的处理规则(processing rules)被激活。对一个特定的模板,可以定义任意多的规则,当识别出该模板后,这些规则依序依次执行。

  5. 调用digester.parse()方法,一个XML文档的引用(用多种方式供选择)要传给这个方法。注意,需要捕捉并处理IOExceptionSAXEception或处理过程中抛出的异常。

 

元素匹配模板

  Digester能自动遍历目标XML文档的元素形成的层次结构,这个过程无需程序员参与。程序员的任务是决定,在解析的过程中,当由嵌套的元素形成的一个特定序列被识别出时,如何处理它。用以描述这种序列的机制,就叫元素匹配模板
  具体说来,元素和其子元素间,用”/”相隔,如果一些元素前没有”/”则其必为根元素。如例:

<a>         -- 匹配模板 "a"
  <b>       -- 
匹配模板
 "a/b"
    <c/>    -- 
匹配模板
 "a/b/c"
    <c/>    -- 
匹配模板
 "a/b/c"
  </b>
  <b>       -- 
匹配模板
 "a/b"
    <c/>    -- 
匹配模板
 "a/b/c"
    <c/>    -- 
匹配模板
 "a/b/c"
    <c/>    -- 
匹配模板
 "a/b/c"
  </b>
</a>


  字符”*”表示任意级别,如”*/a”表示任意级别的<a>都可匹配(不包括根元素级的).熟悉XLST的朋友,对这种思路一定不陌生。
  从上面的描述,可知某个元素同时满足多个匹配模板是非常可能的,在这种情况下,与各个模板相关联的处理规则(processing rule)的执行顺序如下:对beginbody方法,按照各个rule的注册顺序的先后,对end方法则是注册顺序的反序。

处理规则(processing rule)

  元素匹配模板用以识别什么时候采取行动,处理规则则用以定义行动的内容。
  从形式上讲,一个处理规则是一个java类,它扩展了org.apache.commons.digester.Rule类。每个处理规则,实现下列的一个或几个事件处理方法(event method),当相应的模板匹配成功以后,在已定义的某个时刻,这些事件方法会被触发。

  1. begin(),在一个匹配元素被识别出后的开始时刻被调用,这个元素的所有属性放在一个数据结构中被传递给begin()

  2. body(),当元素的嵌套内容(如子元素)被识别出时被调用。在解析的过程中,前后的空白被去掉了

  3. end(),匹配元素的结束时刻被调用。如果子元素也匹配相关的规则,则这些规则的方法需都执行毕,才能达到该元素的结束时刻。

  4. finish(),解析结束时被调用,以提供给各个规则以清理临时数据的机会。


  在设置digester时,通过调用addRule()方法,来注册一个特定的元素匹配模板以及相应的一个Rule类的实例。如上所述,Rule类中的事件处理方法,会在适当的时间被调用。这个机制,允许动态地生成Rule的实现。
  另外,digester也提供了一些处理常见情况的处理规则类。

  1. ObjectCreateRule,begin()方法被调用时,这个规则类实例化一个指定的java类,并将其压入栈顶。这个被实例化的类的名字,默认是这个规则类构造函数得到的参数,也可以通过指定正在处理的xml元素的属性来传递一个新的类的名字。当end()方法被调用 时,栈顶的对象被弹出,Digester中对它的任何引用将被忽略。

  2. FactoryCreateRule,一个非常有用的ObjectCreateRule的变体。

  3. SetPropertiesRule,begin()方法被调用时,digester使用标准的Java Relection API来识别JavaBean的属性设置方法(setter method),这些方法名称中包含属性(property)的名字,这些属性与XML元素的属性(attribute)匹配,于是这些方法被调用并将相应的属性值(attribute value)传给它们。这些自然的映射可以被重写。建议不要过度使用这项功能,在大多数情况下,使用标准的BeanInfo机制会更好。

  4. SetPropertyRule,begin()方法被调用时,digester调用栈顶对象的一个特定的属性设置方法(property setter)并传给它特定的值(property和值分别由两个attribute命名)。这对XML需要遵循一个指定的DTD时比较有用,你可以设置一个特别的属性(property),虽然在指定DTD没有attribute与其相对应。

  5. SetNextRule,end()方法被调用时,digester分析第二栈顶元素,寻找一个特定属性(property)的设置方法(setter method),并接着调用这个方法,以栈顶的元素作参数。这个规则通常用来在两个对象间建立1对多的关系,所用的方法也常被叫做addChild什么的。

  6. SetTopRule,end()方法被调用时,digester分析栈顶元素,寻找一个特定属性(property)的设置方法(setter method),并接着调用这个方法,以第二栈顶的元素作参数。这个规则通常用来在两个对象间建立1对多的关系,所用的方法也常被叫做setParent什么的。

  7. CallMethodRule,这个规则设置当end()被调用时执行的栈顶对象的自定义方法,通过对这个规则的设置,来指定方法的名字、参数的数量以及定义的参数类型的Java类的名字。实际的参数值,来自激活这个方法的元素的子元素。

  8. CallParamRule,这个规则用来指定CallMethodRule的参数的值的来源,它可以来自一个特定的属性,或子元素的body的内容.

  9. NodeCreateRule,一个特殊的规则,将对象树的一部分转换成一个DOM结点(Node),并压入栈顶。


  对这些标准的规则类,可以创建它们的实例,并调用digester.addRule来注册它们。由于经常使用它们,所以digester定义了一些简便的方法来注册它们。如:

Rule rule = new SetNextRule(digester, "addChild","com.mycompany.mypackage.MyChildClass");
digester.addRule("a/b/c", rule);

可以用下列代码替换

digester.addSetNext("a/b/c", "addChild", "com.mycompany.mypackage.MyChildClass");

 

对象栈

  对digester技术最普通的应用,是用来动态创建一个由Java对象构成的树结构,各对象的属性以及对象间的关系,基于XML文档的内容来设置(XML文档就是一棵树)。为实现这种应用,Digester提供了一个对象栈,以供在相关的模板识别后被激活的处理规则操作。此栈的基本操作包括:

  1. clear(),清空栈的内容

  2. peek(),返回对栈顶对象的引用

  3. pop(),将栈顶对象弹出并返回

  4. push(),将一个新的对象压入栈顶


  用栈的原因,就是当识别出一个XML元素的开始时,将相关对象生成并压入栈顶,这个对象在处理该元素的子元素的过程中一直在栈中,当所有子元素都处理完后,解析器遇到这个元素的结束时,则弹出此对象,并进行相关的处理。
  如何描述对象间的关系呢?将栈顶的对象做为一个参数,传递给第二栈顶(即先于栈顶对象入栈的那个对象,在栈顶对象的下面)的一个方法,就可以简单地建立起一种父子关系,从而可以简单地建立起11的关系(第二栈顶对象与栈顶对象之间)1N的关系(第二栈顶对象不动,N次压栈顶弹栈顶对象).
  如果取得生成的第一个对象呢?可以让parse()方法返回,或者在调用parse()方法前,先行压入一个对象,在parse()方法结束后弹出这个对象,则其子对象即为我们想要的第一个对象。

日志(logging)


  日志是一个调试Digester规则集的非常重要的工具,它可以记录非常丰富的信息,因它在使用Digester之前有必要了解日志是如何工作的。
  Digester使用Jakarta Commons Logging,这个模块并不是具体的日志实现,而只是一个可设置的接口。可以设置它将各种日志信息传递它自身带的基本记录器,或者传递给其它的更复杂的日志工具。具体请参考commons logging的文档,或Jakarta Commons Logging习笔记
  Digester主要使用两个记录器:

  1. SAX相关的信息,被送往org.apache.commons.digester.Digester.sax记录器,记录了Digester收到的SAX的事件的信息。

  2. 其它的所有信息,都被送往org.apache.commons.digester.Digester记录器,这个记录器在调试Digester时打开而在产品中常将其关闭


  假定用commons logging自带的基本日志工具,并以DEBUG级别记录Digester调试信息以及INFO级别记录SAX事件信息,则对logging的配置文件设置如下:

org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog org.apache.commons.logging.simplelog.log.org.apache.commons.digester.Digester=debug org.apache.commons.logging.simplelog.log.org.apache.commons.digester.Digester.sax=info

 

***********Example.xml**********

<address-book>

  <person id="1" category="acquaintance" try="would be ignored">

    <name>Gonzo</name>

    <email type="business">gonzo@muppets.com</email>

    <gender result="the whole tag would be ignored">male</gender>

  </person>

  <person id="2" category="rolemodel">

    <name>Kermit</name>

    <email type="business">kermit@muppets.com</email>

    <email type="home">kermie@acme.com</email>

  </person>

</address-book>

 

 

 

***********Person.java**********

import java.util.HashMap;

import java.util.Iterator;

public class Person {

  private int id;

  private String category;

  private String name;

  private HashMap emails = new HashMap();

  //下面的两个方法的名字中set以后的部分,与<person>的属性名字对映。

  //当从xml文件中识别出<person>的属性时,如果有要求

 //(即调用过addSetProperties方法),

 //Digester会依据这种对映关系自动调用相应的方法。

  public void setId(int id) {

      this.id = id;

  }

  public void setCategory(String category) {

      this.category = category;

  }

  //name而言,因为其值来自<name>标签的内容而非属性值,

  //需要用addCallMethod指定识别<name>后的要调用此方法

  //(想自动调用也要可以,需要addBeanPropertySetter,参见第下一个例子)

  public void setName(String name) {

      this.name = name;

  }

  //name,此时还要一一指定addEmail的参数值的来源。

  public void addEmail(String type, String address) {

      emails.put(type, address);

  }

  public void print() {

      System.out.println("Person #" + id);

      System.out.println("  category=" + category);

      System.out.println("  name=" + name);

      for(Iterator i = emails.keySet().iterator(); i.hasNext(); ) {

          String type = (String) i.next();

          String address = (String) emails.get(type);

          System.out.println("  email (type " + type + ") : " + address);

      }

  }

}

 

**********AddressBook.java***********

import java.util.LinkedList;

import java.util.Iterator;

public class AddressBook {

    LinkedList people = new LinkedList();

    public void addPerson(Person p) {

        people.addLast(p);

    }

    public void print() {

        System.out.println("Address book has " + people.size() + " entries");

 

        for(Iterator i = people.iterator(); i.hasNext(); ) {

            Person p = (Person) i.next();

            p.print();

        }

    }

}

 

 

 

************AddressBookDigester*********

import org.apache.commons.digester.Digester;

/**

 * Usage: java Example1 example.xml

 */

public class AddressBookDigester {

    public static void main(String[] args) {

        if (args.length != 1) {

            usage();

            System.exit(-1);

        }

        String filename = args[0];

        // 创建一个Digester实例

        Digester d = new Digester();

        // 创建AddressBook实例,并将其压入栈顶。

        AddressBook book = new AddressBook();

        d.push(book);

        // 增加规则

        addRules(d);

        // 处理输入的xml文件

        try {

            java.io.File srcfile = new java.io.File(filename);

            d.parse(srcfile);

        }

        catch(java.io.IOException ioe) {

            System.out.println("Error reading input file:" + ioe.getMessage());

            System.exit(-1);

        }

        catch(org.xml.sax.SAXException se) {

            System.out.println("Error parsing input file:" + se.getMessage());

            System.exit(-1);

        }

       

       

        // 将解析出的地址数据打印出来

        book.print();

    }

   

    private static void addRules(Digester d) {

        // 当遇到<person>时,创建类Person的一个实例,并将其压入栈顶

        d.addObjectCreate("address-book/person", Person.class);

       

        // <person>标签的属性(attribute)与栈顶Person类对象

              //的属性(property)设置方法根据各自的名字进行映射,

              //(例如,将标签属性id与属性设置方法setId进行映射,

              //将标签属性category与属性设置方法setCategory进行映射)

              //然后将属性的值作参数传递给执行相应的方法。

              //如果某标签属性没法通过名字找到相应的属性设置方法,

              //则此标签属性被忽略(example.xml中第一个<person>try属性)

        d.addSetProperties("address-book/person");

 

        // 调用第二栈顶对象(AddressBook实例)addPerson方法,

              //以栈对象(Person实例)的对象为参数

        d.addSetNext("address-book/person", "addPerson");       

       

        // 当遇到<person>的子元素<name>时,

              //调用栈顶对象(Person实例)setName方法。

        // 此处addCallMethod方法的第一参数是规则,

              //第二个参数是方法的名字,第三个是参数的数量(0时,

              //表示只有一个参数,且参数的值是元素的内容)

        d.addCallMethod("address-book/person/name", "setName", 0);

       

        // 当遇到<person>的子元素<email>时,

              //调用栈顶对象(Person实例)addEmail方法,addEmail方法有两个参数,

              //取值分别来自<email>的属性type的值和<email>本身的内容。

              //此处addCallParam方法的第一参数是规则,

              //第二个参数是指明被调用方法(addEmail)参数的序号,

              //第三个是参数为字符串时指属性的名字)

        d.addCallMethod("address-book/person/email", "addEmail", 2);

        d.addCallParam("address-book/person/email", 0, "type");

        d.addCallParam("address-book/person/email", 1);

    }

 

    private static void usage() {

        System.out.println("Usage: java Example1 example.xml");

    }

}

 

 

 

运行结果如下(运行时可能需要xml-crimson,一个源sunXML解析器,可到

http://xml.apache.org/crimson/下载)

Address book has 2 entries
Person #1
  category=acquaintance
  name=Gonzo
  email (type business) : gonzo@muppets.com
Person #2
  category=rolemodel
  name=Kermit
  email (type business) : kermit@muppets.com
  email (type home) : 
kermie@acme.com

 

 

 总觉得,Digester不仅仅能作配置文件解析,而且可以作得更多。

配置属性

  Digester用来解析应用系统的配置文件,其本身也有很可配置的属性。

属性

描述

classLoader

指定类装载器(class loader)ObjectCreateRule FactoryCreateRule两个规则中,需要动态加载一些类(如那些盛放XML解析出来的数据的javaBean等),装载器可以在次指定。如果不指定,对这此类的加载将会利用线程上下文中的加载器(当useContextClassLoader值为真时)或利用加载Digester的那个加载器。

errorHandler

指定 SAX ErrorHandler,以在出现此类错误时调用。默认情况下,任何解析错误都会被记入日志,Digest会继续进行解析。

namespaceAware

一个布尔值,为真时对XML文件的解析时会考虑元素的域名空间(如不同的域名空间的同名元素会视为不同的元素)

ruleNamespaceURI

指定后续加入的规则所属的命名空间,如果此值为null,则加入的规则不与任何命名空间相联系。

rules

设定规则模板与XML元素的匹配处理程序。由于这个匹配程序是插件式的,所以匹配工作的完成可以用用户定义的匹配程序未完成。默认情况下,使用Digester提供的匹配器。

useContextClassLoader

一个布尔值,为真时FactoryCreateRule ObjectCreateRule 两个规则中对类的装载将会采用当前线程上下文中指定的加载器。默认情况下,对类的动态加载会利用加载Digester的那个装载器。

validating

一个布尔值,为真时解析器会根据DTD内容对XML文档进行合法性检查,默认值是假,解析器只是检查XML是否格式良好(well formed).


  除了上述属性外,还可以注册一个本地DTD,以供DOCTYPE声明引用。这样的注册告诉XML解析器,当遇到DOCTYPE声明时,应使用刚注册的DTD的内容,而不是DOCTYPE声明中的标识符(identifier)
  例如,Struect框架控制器中,使用下述的注册,告诉Structs使用一个本地的DTD中的相关内容来处理Structs配置文件,这样可以适用于那些没有连接到互联网的应用环境,而在连到互联网的环境中可以加快运行速度(因为它避免了通过网络去取相关的资源)

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

 

规则集打包


  通常情况下,一个规则被创建后,接着便注册,然后等在event时被调用,这些规则集很难为其它应用程序直接复用。一个解决方法是将所有规则都放在一个类中,此由这些规则可以很简单地被装载然后被注册使用。RuleSet接口就是为些而设计,一般是通过扩展RuleSetBase类来开发规则集类。如例:

public class MyRuleSet extends RuleSetBase {
  public MyRuleSet() {
    this("");
  }
  public MyRuleSet(String prefix) {
    super();
    this.prefix = prefix;
    this.namespaceURI = "http://www.mycompany.com/MyNamespace";
  }
  protected String prefix = null;
  public void addRuleInstances(Digester digester) {
    digester.addObjectCreate(prefix + "foo/bar",
      "com.mycompany.MyFoo");
    digester.addSetProperties(prefix + "foo/bar");
  }
}

可以这样使用这个规则集

Digester digester = new Digester();
...
一些配置
Digester ...
digester.addRuleSet(new MyRuleSet("baz/"));

 

带命名空间的XML解析


  这种情况下,使用Digester的步骤为:

  1. Digester初始化部分,指明要考虑命名空间。

digester.setNamespaceAware(true);

 

  1. 指明一些规则的命名空间,

digester.setRuleNamespaceURI("http://www.mycompany.com/MyNamespace");

 

  1. 接下来定义一些与此命名空间有关的规则,此时可以省却前缀,如

digester.addObjectCreate("foo/bar", "com.mycompany.MyFoo");
digester.addSetProperties("foo/bar");

 

  1. 对其它命名空间,重复前面的2


  另外,在指明要digester考虑命名空间之后,在定义匹配模板时,可以将命名空间别名加作为元素名称的一部分使用。这与无命名空间时是一致的。

开发定制的匹配处理过程


  通过实现 org.apache.commons.digester.Rules接口或扩展org.apache.commons.digester.RulesBase类来达到定制匹配过程的目的。
  Digester提供ExtendedBaseRules来扩展了匹配模板的定义,引入了特殊通配字符?和*以及!,提供RegexRules来支持以正则式的语法定义匹配模板,提供WithDefaultsRulesWrapper来支持默认规则(即其它规则都不匹配时的处理规则)。

一些认识


  通过看说明材料,尤其在学习Digester包中的Catalog例子以后,有一些认识:
  1、由于xml对属性名字的定义要求,与Java中对方法名字的定义要求不一致,导致出现不能自动映射的情况,如year-made标签属性,就不可能有方法setYear-made;
  2、对于根元素,与其子元素建立联系,有几种办法:一种是先生成根元素实例,压入栈,然后解析,将调用方法规则建立联系;另一种是解析的过程中第一个创建它,然后用getRoot的方法得到。

  3、如果某对象类构造都要参数,则此时需要扩展AbstractObjectCreationFactory类为这种对象建立一个Factory,在这个Factory中取得初始化参数值然后再创建一个对象实例。
  4、设有某个标签,要想自动用该标签子元素的内容填充该标签对应的对象的属性,则需要用digester.setRules(new ExtendedBaseRules()),然后addRules(),然后再调用addBeanPropertySetter("bala/lala/?");进行规则定义,注意此模板中有通配符。
  5、如果对象的属性是整型,则Digester自动将xml文件中字符串值转换为整型。
  6、在指明要digester考虑命名空间之后,如果不会引起歧义,完全可以忽略命名空间的存在,除非你要针对特定的命名空间进行特定的处理。

作者:http://blog.csdn.net/asktalk