转自 http://www.blogjava.net/sy1214520/archive/2009/03/06/258271.html
一个简单的例子
假定有两个JavaBean如下,分别为Foo和Bar
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);
}
public class Foo {
}
public mypackage;
public class Bar {
}
用下面的xml文件进行配置
用下面几行代码即可完成配置文件解析工作:
Digest解析代码 | 注释 |
Digester digester = new Digester(); | |
digester.setValidating(false); | 不进行XML与相应的DTD的合法性验证 |
digester.addObjectCreate("foo", "mypackage.Foo"); | 当遇到时创建一个mypackage.Foo对象,并将其放在栈顶 |
digester.addSetProperties("foo"); | 根据元素的属性(attribute),对刚创建的Foo对象的属性(property)进行设置 |
digester.addObjectCreate("foo/bar", "mypackage.Bar"); | 当遇到的子元素时创建一个mypackage.Bar对象,并将其放在栈顶。 |
digester.addSetProperties("foo/bar"); | 根据元素的属性(attribute),对刚创建的Bar对象的属性(property)进行设置 |
digester.addSetNext("foo/bar", "addBar", "mypackage.Bar"); | 当再次遇到的子元素时创建一个mypackage.Bar对象,并将其放在栈顶,同时调用第二栈顶元素(Foo对象)的addBar方法。 |
Foo foo = (Foo) digester.parse(); | 分析结束后,返回根元素。 |
基本情况
熟悉用SAX来处理XML文档的程序员,会发现Digester隐藏了遍历XML元素这些细节,而是提供了更高一层的、更友好的SAX事件接口,从而让程序员的精力放在对数据的处理过程中。使用Digester,须按照以下步骤:
- 创建一个org.apache.commons.digester.Digester实例。一个解析请求完成后,这个Digester可以被后面复用。但也不要试图在不同的线程中从共享一个Digester实例。
- 根据需要设置一些配置属性(configuration
properties),以控制下一步的解析操作。
- 将一个或几个初始对象(initial
object)压入Digester对象栈,本步骤不是必须的。
- 注册所有的元素匹配模板(elemet matching
pattern)。当一个模板被从输入文档中识别出来以后,与其相联系的处理规则(processing
rules)被激活。对一个特定的模板,可以定义任意多的规则,当识别出该模板后,这些规则依序依次执行。
- 调用digester.parse()方法,一个XML文档的引用(用多种方式供选择)要传给这个方法。注意,需要捕捉并处理IOException或SAXEception或处理过程中抛出的异常。
元素匹配模板
Digester能自动遍历目标XML文档的元素形成的层次结构,这个过程无需程序员参与。程序员的任务是决定,在解析的过程中,当由嵌套的元素形成的一个特定序列被识别出时,如何处理它。用以描述这种序列的机制,就叫“元素匹配模板”。具体说来,元素和其子元素间,用”/”相隔,如果一些元素前没有”/”则其必为根元素。如例:
字符”*”表示任意级别,如”*/a”表示任意级别的都可匹配(不包括根元素级的).熟悉XLST的朋友,对这种思路一定不陌生。
从上面的描述,可知某个元素同时满足多个匹配模板是非常可能的,在这种情况下,与各个模板相关联的处理规则(processing rule)的执行顺序如下:对begin或body方法,按照各个rule的注册顺序的先后,对end方法则是注册顺序的反序。
处理规则(processing rule)
元素匹配模板用以识别什么时候采取行动,处理规则则用以定义行动的内容。从形式上讲,一个处理规则是一个java类,它扩展了org.apache.commons.digester.Rule类。每个处理规则,实现下列的 一个或几个事件处理方法(event method),当相应的模板匹配成功以后,在已定义的某个时刻,这些事件方法会被触发。
- begin(),在一个匹配元素被识别出后的“开始”时刻被调用,这个元素的所有属性放在一个数据结构中被传递给begin()
- body(),当元素的嵌套内容(如子元素)被识别出时被调用。在解析的过程中,前后的空白被去掉了
- end(),匹配元素的“结束”时刻被调用。如果子元素也匹配相关的规则,则这些规则的方法需都执行毕,才能达到该元素的“结束”时刻。
- finish(),解析结束时被调用,以提供给各个规则以清理临时数据的机会。
在设置digester时,通过调用addRule()方法,来注册一个特定的元素匹配模板以及相应的一个Rule类的实例。如上所述,Rule类中的事件处理方法,会在适当的时间被调用。这个机制,允许动态地生成Rule的实现。
另外,digester也提供了一些处理常见情况的处理规则类。
- ObjectCreateRule,
当begin()方法被调用时,这个规则类实例化一个指定的java类,并将其压入栈顶。这个被实例化的类的名字,默认是这个规则类构造函数得到的参数,
也可以通过指定正在处理的xml元素的属性来传递一个新的类的名字。当end()方法被调用 时,栈顶的对象被弹出,Digester中对它的任何引用将
被忽略。
- FactoryCreateRule,一个非常有用的ObjectCreateRule的变体。
- SetPropertiesRule,
当begin()方法被调用时,digester使用标准的Java Relection
API来识别JavaBean的属性设置方法(setter
method),这些方法名称中包含属性(property)的名字,这些属性与XML元素的属性(attribute)匹配,于是这些方法被调用并将相
应的属性值(attribute
value)传给它们。这些自然的映射可以被重写。建议不要过度使用这项功能,在大多数情况下,使用标准的BeanInfo机制会更好。
- SetPropertyRule,
当begin()方法被调用时,digester调用栈顶对象的一个特定的属性设置方法(property
setter)并传给它特定的值(property和值分别由两个attribute命名)。这对XML需要遵循一个指定的DTD时比较有用,你可以设置
一个特别的属性(property),虽然在指定DTD没有attribute与其相对应。
- SetNextRule,
当end()方法被调用时,digester分析第二栈顶元素,寻找一个特定属性(property)的设置方法(setter
method),并接着调用这个方法,以栈顶的元素作参数。这个规则通常用来在两个对象间建立1对多的关系,所用的方法也常被叫做addChild什么
的。
- SetTopRule,当end()方法被调用时,digester分析栈
顶元素,寻找一个特定属性(property)的设置方法(setter
method),并接着调用这个方法,以第二栈顶的元素作参数。这个规则通常用来在两个对象间建立1对多的关系,所用的方法也常被叫做setParent
什么的。
- CallMethodRule,这个规则设置当end()被调用时执行的栈顶对象的自定义方法,通过对这个规则的设置,来指定方法的名字、参数的数量以及定义的参数类型的Java类的名字。实际的参数值,来自激活这个方法的元素的子元素。
- CallParamRule,这个规则用来指定CallMethodRule的参数的值的来源,它可以来自一个特定的属性,或子元素的body的内容.
- NodeCreateRule,一个特殊的规则,将对象树的一部分转换成一个DOM结点(Node),并压入栈顶。
对这些标准的规则类,可以创建它们的实例,并调用digester.addRule来注册它们。由于经常使用它们,所以digester定义了一些简便的方法来注册它们。如:
Rule rule = new SetNextRule(digester,
"addChild","com.mycompany.mypackage.MyChildClass");
digester.addRule("a/b/c", rule);
可以用下列代码替换
digester.addRule("a/b/c", rule);
digester.addSetNext("a/b/c", "addChild",
"com.mycompany.mypackage.MyChildClass");
对象栈
对digester技术最普通的应用,是用来动态创建一个由Java对象构成的树结构,各对象的属性以及对象间的关系,基于XML文档的内容来设置 (XML文档就是一棵树)。为实现这种应用,Digester提供了一个对象栈,以供在相关的模板识别后被激活的处理规则操作。此栈的基本操作包括:- clear(),清空栈的内容
- peek(),返回对栈顶对象的引用
- pop(),将栈顶对象弹出并返回
- push(),将一个新的对象压入栈顶
用栈的原因,就是当识别出一个XML元素的“开始”时,将相关对象生成并压入栈顶,这个对象在处理该元素的子元素的过程中一直在栈中,当所有子元素都处理完后,解析器遇到这个元素的“结束”时,则弹出此对象,并进行相关的处理。
如何描述对象间的关系呢?将栈顶的对象做为一个参数,传递给第二栈顶(即先于栈顶对象入栈的那个对象,在栈顶对象的下面)的一个方法,就可以简单地建立 起一种“父子关系”,从而可以简单地建立起1:1的关系(第二栈顶对象与栈顶对象之间)和1:N的关系(第二栈顶对象不动,N次压栈顶弹栈顶对象).
如果取得生成的第一个对象呢?可以让parse()方法返回,或者在调用parse()方法前,先行压入一个对象,在parse()方法结束后弹出这个对象,则其子对象即为我们想要的第一个对象。
日志(logging)
日志是一个调试Digester规则集的非常重要的工具,它可以记录非常丰富的信息,因它在使用Digester之前有必要了解日志是如何工作的。
Digester使用Jakarta Commons Logging,这个模块并不是具体的日志实现,而只是一个可设置的接口。可以设置它将各种日志信息传递它自身带的基本记录器,或者传递给其它的更复杂的 日志工具。具体请参考commons logging的文档,或Jakarta Commons Logging学习笔记
Digester主要使用两个记录器:
- SAX相关的信息,被送往org.apache.commons.digester.Digester.sax记录器,记录了Digester收到的SAX的事件的信息。
- 其它的所有信息,都被送往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
Digester包中的例子
***********Example.xml**********
Gonzo
gonzo@muppets.com
male
Kermit
kermit@muppets.com
kermie@acme.com
**********Person.java************
importjava.util.HashMap;
importjava.util.Iterator;
publicclass Person {
private int id;
private String category;
private String name;
private HashMap emails = new HashMap();
//
下面的两个方法的名字中set以后的部分,与的属性名字对映。当从xml文件中识别出的属性
时,如果有要求(即调用过addSetProperties方法),Digester会依据这种对映关系自动调用相应的方法。
public void setId(int id) {
this.id = id;
}
public void setCategory(String category) {
this.category = category;
}
//对name而言,因为其值来自标签的内容而非属性值,需要用addCallMethod指定识别后的要调用此方法(想自动调用也要可以,需要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***********
importjava.util.LinkedList;
importjava.util.Iterator;
publicclass AddressBook {
LinkedList people = new LinkedList();
**********Person.java************
import
import
public
}
**********AddressBook.java***********
import
import
public