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">gonzo@muppets.com</email>
</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>
...
...
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)
- 同上