Digester是apache开源项目Commons中的一个子项目,是一款解析处理XML文档的工具。现在Java领域中流传了很多有关处理XML文档解析的工具,除官方(Sun)的标准的SAX(最新版本2.0),DOM(最新版本3.0,在Tiger版本中集成)外[JAXP只是Sun定义的一组规范接口],其他开源不泛多多,比如Jdom,Dom4j,Castor等等,包括这款Apache的digester。说到这里,你不得不佩服开源组织的强大智慧的结晶,digester处理XML文档基于XML节点树Path的规则,实在给人一种赏心悦目之感,这也是偶一直对其情有独钟的最大理由。废话免了,转入正题。
刚说到Digester处理是基本类似于XML文档树节点遍历的规则来进行处理,底层处理是采用了SAX,基于事件驱动的模式。举个例子:
<Company>
<Technology>
<name length="4">Corx</name>
<date>2005.06.27</date>
</Technology>
<Product>
<name length="4">Kxcp</name>
<date>2004.12.29</date>
</Product>
</Company>
在digester中,定义了一些规则(rule),对遍历的节点path预先对应好要处理的规则,即当解析器遍历到某个节点的时候,如果发现当前节点有对应的处理规则,调用相应的rule进行处理。举个例子:
Company/Technology -> ObjectCreatedRule //对象创建规则
Company/Technology/name -> BeanPropertySetterRule //属性存取规则
...
对以上的解释可能还不太明白,不要着急,下面详细解释一下digester的基本原理,喝杯咖啡,慢慢来~
首先看看org.apache.commons.digester.Digester这个类,查看source发现Digester本身继承了 DefaultHandler句柄,DefaultHandler句柄是SAX中基于时间驱动的缺省的句柄实现(包含ContentHandler, ErrorHandler, EntityResolver, DTDHandler),这个句柄不用多介绍了吧,相信用过SAX的哥们都明白。:)。刚刚不是说到了Rule了嘛,digester中定义了一个规则处理接口org.apache.commons.digester.Rule,此接口类似于ContentHandler接口中的方法,稍稍有点不同,主要有begin(), body(), end(), finish()方法。而digester缺省定义了许多有效的常用规则,每个规则都实现这个接口, 如果没有什么特殊需求,一般这些规则是够用了,罗列一下:BeanPropertySetterRule, CallMethodRule, CallParamRule, FactoryCreateRule, NodeCreateRule, ObjectCreateRule, ObjectParamRule,PathCallParamRule, SetNestedPropertiesRule, SetNextRule, SetPropertiesRule, SetPropertyRule, SetRootRule, SetTopRule,这些规则的意思稍后说。同时,对这些规则,digester还定义了一个规则的容器接口Rules(抽象类),这个抽象类接口容器容纳规则,并定义了规则匹配的模式,digester实现了一个基本的匹配模式RulesBase,简要看看这个实现中的两个最重要的方法:
.....
public void add(String pattern, Rule rule) {
// to help users who accidently add '/' to the end of their patterns
int patternLength = pattern.length();
if (patternLength>1 && pattern.endsWith("/" ) {
pattern = pattern.substring(0, patternLength-1);
}
List list = (List) cache.get(pattern);
if (list == null) {
list = new ArrayList();
cache.put(pattern, list);
}
list.add(rule);
rules.add(rule);
if (this.digester != null) {
rule.setDigester(this.digester);
}
if (this.namespaceURI != null) {
rule.setNamespaceURI(this.namespaceURI);
}
}
还有一个方法:
...
public List match(String namespaceURI, String pattern) {
// List rulesList = (List) this.cache.get(pattern);
List rulesList = lookup(namespaceURI, pattern);
if ((rulesList == null) || (rulesList.size() < 1)) {
// Find the longest key, ie more discriminant
String longKey = "";
Iterator keys = this.cache.keySet().iterator();
while (keys.hasNext()) {
String key = (String) keys.next();
if (key.startsWith("*/" ) {
if (pattern.equals(key.substring(2)) ||
pattern.endsWith(key.substring(1))) {
if (key.length() > longKey.length()) {
// rulesList = (List) this.cache.get(key);
rulesList = lookup(namespaceURI, key);
longKey = key;
}
}
}
}
}
if (rulesList == null) {
rulesList = new ArrayList();
}
return (rulesList);
}
...
以上基本实现只是digester默认匹配规则,如果你要更换自己的规则匹配模式,则只需要继承 org.apache.commons.digester.Rules接口,定义自己的匹配方式,digester同时还给我们提供了一个比较复杂,不过非常常用的匹配模式,那就是通配符匹配模式,引入了”!“、”*“、”?“三个符号进行通配的匹配模式,这个类就是 org.apache.commons.digester.ExtendsBaseRules,后续再说。
digester就是通过以上的几种接口组件,同时配合操作数栈,进行XML解析。具体说,就是在parse XML文档之前,预先向容器集合(默认就是RulesBase容器)对XML文档中的节点path注入匹配规则,然后在parse文档的时候,遭遇到节点时时,调用SAX句柄中相应的方法,配合操作数栈,根据定义好的匹配模式,调用相应规则中的方法,将XML序列化成Java Object。介绍有点抽象,沿用digester本身带的例子介绍一下:
...
//对如下的XML文档
<address-book>
<person id="1" category="acquaintance">
<name>Gonzo</name>
<email type="business">
[email protected]</email>
</person>
<person id="2" category="rolemodel">
<name>Kermit</name>
<email type="business">
[email protected]</email>
<email type="home">
[email protected]</email>
</person>
</address-book>
...
...
Digester digester = new Digester();
AddressBook book = new AddressBook();
d.push(book); //将AddressBook实例压入堆栈
digester.addObjectCreate("address-book/person", Person.class);//对person节点注入对象创建规则,即在SAX的事件遭遇到person节点的时候,创建Person类的实例,并压入堆栈,此时堆栈中从栈顶到栈底分别为AddressBook实例,Person类实例。
digester.addSetProperties("address-book/person" ;// 对person节点注入属性设置规则,即在SAX的事件遭遇到person节点中的Attributes时,根据属性列表中的属性值对,这儿就是 id="1", category="acquaintance",使用Java反射(reflection)机制,调用当前栈顶对象即Person实例类中id、 category属性的标准的JavaBean方法,setId, setCategory。
digester.addSetNext("address-book/person", "addPerson" ;// 对person节点注入父节点方法调用规则,即在SAX事件遭遇到person节点的时候,调用栈中Person实例的父实例中的addPerson方法。d.addCallMethod("address-book/person/name", "setName", 0);//对name节点注入方法调用规则,调用当前栈顶对象即Person实例中的setName方法,而此方法的参数即是当前name节点的字符内容。通常这个规则和addCallParam规则配合使用,这儿是一种特殊情况。
digester.addCallMethod("address-book/person/email", "addEmail", 2);//对email节点注入方法调用规则,调用当前栈顶对象即Person实例中的addEmail方法,此方法需要两个参数,一个是从属性值的 type属性获取,一个是从email本身的字符内容获取。
digester.addCallParam("address-book/person/email", 0, "type" ;//对email节点注入参数调用规则,将当前节点的type属性值压入方法操作数栈
digester.addCallParam("address-book/person/email", 1);//对email节点注入参数调用规则,将当前节点的字符属性值压入方法操作数栈。
System.out.println(book);//打印book中值。。
...
通过以上注释应该不难理解吧。
下面再对以上所说的几种常用规则作一个详细的介绍:
ObjectCreateRule:这个规则比较简单,此规则就是对指定的模式创建一个类的实例,并将当前实例压入堆栈,并且在遭遇元素结束
时,将当前的栈顶实例弹出栈。
对应Digester中有关这个规则的Javadoc方法说明:
addObjectCreate(java.lang.String pattern, java.lang.Class clazz) - 方法参数说明了一切
addObjectCreate(java.lang.String pattern, java.lang.String className) - 同上
addObjectCreate(java.lang.String pattern, java.lang.String attributeName, java.lang.Class clazz) - 这个稍微解释一下,
多了一个参数attributeName,这个参数的意思就是如果在当前匹配模式的节点中定义了属性,则默认就采用这个attributeName所
对应的值来加载实例。比如以上面的例子加入person元素还有一个属性, class="test.org.apache.commons.digester.Person1",
则此规则会加载Person1实例而不是Person实例。明白?!
addObjectCreate(java.lang.String pattern, java.lang.String className, java.lang.String attributeName) -同上
FactoryCreateRule:这个规则是基于工厂模式创建指定模式的一个类的实例。跟ObjectCreateRule类似,不同的是其参数Class继承了
ObjectCreationFactory接口。此接口中有个方法createObject(Attributes attrs),创建类的实例,在规则中将此类的实例压入堆栈。
对应Digester中有关这个规则的Javadoc方法说明:
addFactoryCreate(java.lang.String pattern, java.lang.Class clazz) - 一目了然,不用说了。只是clazz必须是实现了
ObjectCreationFactory接口的类。
addFactoryCreate(java.lang.String pattern, java.lang.Class clazz, boolean ignoreCreateExceptions) - 同上,多了一个参
数,ignoreCreateExceptions表明是否忽略在创建类的过程中忽略抛出的exception。
addFactoryCreate(java.lang.String pattern, java.lang.Class clazz, java.lang.String attributeName) - 稍微介绍一下这里
的attributeName参数,这个参数跟ObjectCreationRule规则中的attributeName雷同,不同的是这个属性的值必须是实现了接口
ObjectCreationFactory接口的类。
addFactoryCreate(java.lang.String pattern, java.lang.Class clazz, java.lang.String attributeName, boolean ignoreCreateExceptions)
- 同上
addFactoryCreate(java.lang.String pattern, ObjectCreationFactory creationFactory) - 同上
addFactoryCreate(java.lang.String pattern, ObjectCreationFactory creationFactory, boolean ignoreCreateExceptions) - 同上
addFactoryCreate(java.lang.String pattern, java.lang.String className) - 同上
addFactoryCreate(java.lang.String pattern, java.lang.String className, boolean ignoreCreateExceptions) - 同上
addFactoryCreate(java.lang.String pattern, java.lang.String className, java.lang.String attributeName) - 同上
addFactoryCreate(java.lang.String pattern, java.lang.String className, java.lang.String attributeName, boolean ignoreCreateExceptions)
BeanPropertySetterRule:Bean属性设置规则,对匹配当前指定模式的元素设置bean属性同名或者指定属性的值。
对应Digester中有关这个规则的Javadoc方法说明:
addBeanPropertySetter(java.lang.String pattern) - 对匹配当前指定模式的元素设置bean属性同名属性的值。
addBeanPropertySetter(java.lang.String pattern, java.lang.String propertyName) - 对匹配当前指定模式的元素设置bean属性指定属性的值。
CallMethodRule:方法调用规则,对匹配当前指定模式的元素,初始化指定的方法类型和方法参数,并压入堆栈。此规则需要和CallMethodRule
规则配合使用:
对应Digester中有关这个规则的Javadoc方法说明:
addCallMethod(java.lang.String pattern, java.lang.String methodName) - 初始化指定的方法,只是当前方法不需要任何参数。
addCallMethod(java.lang.String pattern, java.lang.String methodName, int paramCount) - 初始化指定的方法,参数个数为
paramCount,参数类型缺省为java.lang.String。注意有种特殊情况,就是paramCount为0的时候,默认使用当前元素的字符数据作为
参数值。
addCallMethod(java.lang.String pattern, java.lang.String methodName, int paramCount, java.lang.Class[] paramTypes) -
同上,只是有指定的参数类型.
addCallMethod(java.lang.String pattern, java.lang.String methodName, int paramCount, java.lang.String[] paramTypes) -
同上
CallParamRule:提供CallMethodRule规则所需要的参数,必须跟CallMethodRule配合使用。
对应Digester中有关这个规则的Javadoc方法说明:
addCallParam(java.lang.String pattern, int paramIndex) - 使用匹配当前指定模式元素的字符数据作为索引为paramIndex的参数值.
addCallParam(java.lang.String pattern, int paramIndex, boolean fromStack) - 从操作数栈中,默认取出栈顶对象作为索引
为paramIndex的参数值.
addCallParam(java.lang.String pattern, int paramIndex, int stackIndex) - 从操作数栈中,取出从栈顶数第stackIndex + 1个
对象作为索引为paramIndex的参数值.
addCallParam(java.lang.String pattern, int paramIndex, java.lang.String attributeName) - 使用属性attrbuteName的值作为
索引为paramIndex的参数值.
PathCallParamRule:提供当前匹配的模式路径作为方法调用所需要的参数,配合CallMethodRule使用。
对应Digester中有关这个规则的Javadoc方法说明:
addCallParamPath(java.lang.String pattern, int paramIndex) - 指定索引为paramIndex的值为当前匹配模式的路径.
ObjectParamRule;指定对象作为指定索引的值,配合CallMethodRule使用。
对应Digester中有关这个规则的Javadoc方法说明:
addObjectParam(java.lang.String pattern, int paramIndex, java.lang.Object paramObj) - 指定索引为paramIndex的值为给定
的对象的值.
SetNestedPropertiesRule:当前匹配模式的直接子元素和对应bean的属性之间的映射.
对应Digester中有关这个规则的Javadoc方法说明:
addSetNestedProperties(java.lang.String pattern) - 默认当前匹配模式的元素的直接子元素和bean中对应属性之间值的映射
addSetNestedProperties(java.lang.String pattern, java.lang.String[] elementNames, java.lang.String[] propertyNames) -
当前匹配模式的直接子元素集和bean中属性集之间的映射
addSetNestedProperties(java.lang.String pattern, java.lang.String elementName, java.lang.String propertyName) -
当前匹配模式的直接子元素和bean中属性之间的映射
SetNextRule:匹配当前模式时,将栈顶对象作为次栈顶对象中指定方法的参数。
对应Digester中有关这个规则的Javadoc方法说明:
addSetNext(java.lang.String pattern, java.lang.String methodName) - 指定次栈顶元素的方法名称,将栈顶对象作为指定方法的参数。
addSetNext(java.lang.String pattern, java.lang.String methodName, java.lang.String paramType) - 指定次栈顶元素的方法名称和参数类型,将栈顶对象作为指定方法的参数。
SetPropertiesRule:匹配当前模式的元素的属性与栈顶对象中同名或者指定对应关系的属性值。
对应Digester中有关这个规则的Javadoc方法说明:
addSetProperties(java.lang.String pattern) - 指定栈顶对象属性的值为当前匹配元素中同名元素属性的值。
addSetProperties(java.lang.String pattern, java.lang.String[] attributeNames, java.lang.String[] propertyNames) - 对应栈顶对象属性的值为指定的当前匹配元素中元素属性的值
addSetProperties(java.lang.String pattern, java.lang.String attributeName, java.lang.String propertyName) - 同上
SetPropertyRule:不常用的一个规则,主要用于key-value值对。一个元素属性为栈顶对象的属性,一个元素属性为栈顶对象的属性的值。
对应Digester中有关这个规则的Javadoc方法说明:
addSetProperty(java.lang.String pattern, java.lang.String name, java.lang.String value) - name和value都是匹配当前模式的元素属性,name的值是栈顶对象中同名的属性名称,而value的值是栈顶对象中属性名称为name的值。(好像有点绕口)
SetRootRule:将当前栈顶对象作为根对象中指定方法的参数。
对应Digester中有关这个规则的Javadoc方法说明:
addSetRoot(java.lang.String pattern, java.lang.String methodName) - 将当前栈顶对象作为根对象中指定为methodName方法的参数。
addSetRoot(java.lang.String pattern, java.lang.String methodName, java.lang.String paramType) - 同上,只是多了一个方法参数的类型
SetTopRule:与SetNextRule正好想法,是将次栈顶元素作为栈顶元素指定方法的参数。
对应Digester中有关这个规则的Javadoc方法说明:
addSetTop(java.lang.String pattern, java.lang.String methodName) - 将次栈顶元素作为栈顶元素指定为methodName方法的参数。
addSetTop(java.lang.String pattern, java.lang.String methodName, java.lang.String paramType) - 同上,只是多了一个方法参数的类型
前面介绍的基本就是digester的常用主要用法,正常来说足够了!不过为了提供一些额外更强大的扩展,digester提供了扩展的通配符匹配规则,更强大也更方便!那就是ExtendedBaseRules,这个类扩展了基本的匹配规则RulesBase,提供了更通用的通配符匹配规则,以下简要介绍一下:
首先说一下基本的匹配模式,有三种:
Parent Match(可以理解为匹配子元素的精确父匹配):a/b/c/? */a/b/c/?
Ancester Match(可以理解为匹配那种出身自一个精确序列元素的元素):a/b/* */a/b/*
Universal Wildcard Match(可以理解为通配符匹配,都以!开头):!*a/b !a/b/? !*a/b/? !a/b/* !*/a/b/*
Wild Match (可以理解为更通用更模糊的通配符匹配):* !*
?代表直接子元素
* 代表任意的父或子元素
! 代表以什么什么为开头
举个例子:
Digester digester = new Digester();
digester.setRules(new ExtendedBaseRules());
digester.setValidating(false);
digester.addObjectCreate("!*/b", BetaBean.class);
digester.addObjectCreate("!*/a", AlphaBean.class);
digester.addObjectCreate("root", ArrayList.class);
digester.addSetProperties("!*" ;
digester.addSetNext("!*/b/?", "setChild" ;
digester.addSetNext("!*/a/?", "setChild" ;
digester.addSetNext("!root/?", "add" ;
ArrayList root =
(ArrayList) digester.parse(getInputStream("Test4.xml" );
assertEquals("Wrong array size", 2, root.size());
AlphaBean one = (AlphaBean) root.get(0);
assertTrue(one.getChild() instanceof BetaBean);
BetaBean two = (BetaBean) one.getChild();
assertEquals("Wrong name (1)", two.getName() , "TWO" ;
assertTrue(two.getChild() instanceof AlphaBean);
AlphaBean three = (AlphaBean) two.getChild();
assertEquals("Wrong name (2)", three.getName() , "THREE" ;
BetaBean four = (BetaBean) root.get(1);
assertEquals("Wrong name (3)", four.getName() , "FOUR" ;
assertTrue(four.getChild() instanceof BetaBean);
BetaBean five = (BetaBean) four.getChild();
assertEquals("Wrong name (4)", five.getName() , "FIVE" ;
Test4.xml文件:
<root>
<a name="ONE">
<b name="TWO">
<a name="THREE"/>
</b>
</a>
<b name="FOUR">
<b name="FIVE"/>
</b>
</root>
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/jackoo/archive/2009/06/24/4294501.aspx