Digester是一款用于将XML转换为Java对象的事件驱动型工具,是SAX(另一种事件驱动型XML处理工具)的高层次封装。Digester针对SAX事件提供了更加友好的接口,隐藏了XML节点具体的层次细节,更加专注于处理过程。它最早作为Web框架Apache Steuts的一部分,后来考虑到通用性,移到了Apache Commons项目。
注意:尽管Tomcat使用了Digester API,但是并不依赖Apache Commons包,而是将其源代码直接包含到了Tomcat项目中,包路径于Apache Commons不同。
Digester(或SAX)的事件驱动就是通过流读取XML文件,当识别出特定XML节点后便会执行特定的动作,或者创建Java对象,或折执行对象的某个方法。因此Digester的核心是匹配模式和处理规则。此外,Digester提供了一套对象栈机制用于构造Java对象,因为XML是分层结构,所以创建的Java对象也应该是分层级的树状结构,且还要根据XML内容组织各层级Java对象的内部结构以及设置相关属性。
注意:Digester是非线程安全的
下面介绍一下Digester对象栈、匹配模式和处理规则。
1. 对象栈
Digester的对象栈主要在匹配模式满足时,由处理规则进行操作,它提供了常见的栈操作。
clear:清空对象栈。
peek:该操作有数个重载方法们可以实现得到位于栈顶部的对象或者从顶部数第n个对象,但是不会讲对象从栈中移除。
pop:将位于栈顶部的对象移除并返回。
push:讲对象放置栈顶部。
Digester的设计模式指,在文件读取过程中,如果遇到一个XML节点的开始部分,则会触发规则事件创建Java对象并将其放入栈。当处理该节点的子节点时,该对象都将维护在栈中;当遇到该节点的结束部分时,该对象将会从栈中取出并删除。
那么如何在创建的对象之间建立联系呢?Digester提供了一个处理规则(SetNextRule),该规则会调用位于栈顶部对象之后对象(父对象)的某个方法,同时将顶部对象(子对象)作为参数传入。此方式可以在XML各Java对象之间建立父子关系。
那么又如何持有创建的首个对象,即XML的转换结果?Digester对于曾经放入栈中的第一个对象将会持有一个引用,同时作为parse()方法的返回值。在调用parse()方法之前,传入一个已创建的对象引用,Digester会动态地为这个对象和首个创建的对象建立父子关系。通过这种方式,传入的对象将会维护首个创建对象的引用及所有子节点,传入对象也会在调用parse()方法时返回。
2. 匹配模式
Digester的主要特征是自动遍历XML文档,而使开发人员不必关注解析过程,Digester通过匹配模式指定相关约定,具体如下图。
以"a"、“a/b”、"a/b/c"为例,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>
匹配模式还支持模糊匹配,如果希望所有节点都采用同一个处理规则,那么直接指定匹配规则为"*"。
当同一个匹配模式指定多个处理规则,或多个匹配规则匹配同一个节点时,均会出现一个节点执行多个处理规则的情况。此时,Digester的处理方式是,开始读取节点时按照注册顺序执行处理规则,而完成读取时按照反向顺序执行,即先进后出。
3. 处理规则
匹配模式确定了合适触发处理规则,而处理规则则定义了匹配模式时的具体操作。处理规则需要实现接口org.apache.commons.digester.Rule,该接口定义了模式匹配时触发的方法。
begin():当读取到匹配节点的开始部分时调用,会将该节点的所有属性作为参数传入。
body():当读取匹配节点的内容时调用,这里指的不是子节点,而是嵌入内容为普通文本。
end():当读取到匹配节点的结束部分时调用,如果存在子节点,只有当子节点处理完毕后该方法才会被调用。
finish():当整个parse()方法完成时调用,多用于清理临时数据和缓存数据。
Digester默认支持的处理规则
4. 示例程序
通过一个示例来展示如何使用Digester,Java对象定义如下:
public class Department{
private String name;
private String code;
private Map<String,String>extension=new HashMap<String,String>();
private List<User> users=new ArrayList<User>();
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public String getCode(){
return code;
}
public void setCode(String code){
this.code=code;
}
public void addUser(User user){
this.users.add(User);
}
public void putExtension(String name,String value){
this.extension.put(name,value);
}
}
public class User{
private String name;
private String code;
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public String getCode(){
return code;
}
public void setCode(String code){
this.code=code;
}
Department对象包含name、code两个属性,以及一个User列表、一个表示扩展属性的Map,可以通过addUser()方法添加User对象,通过putExtension()方法添加扩展属性。User对象包含name和code两个属性。
假如要转换一个test.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<department>
<user name="name1" code="code1"></user>
<user name="name2" code="code2"></user>
<extension>
<property-name>director</property-name>
<property-value>zhangsan</property-value>
</extension>
</department>
从XML文件内容可以看出,Department对象包含了两个User对象和一个名为director的扩展属性。我们可以编写如下代码完成XML解析:
Digester digester=new Digester();
digester.setValidation(true);
//匹配department节点时,创建department对象
digester.addObjectCreate("department",Department.class);
//匹配department节点时,设置对象属性
digester.addSetProperties("department");
//匹配department/user节点时,创建User对象
digester.addObjectCreate("department/user",User.class);
//匹配department/user节点时,设置对象属性
digester.addSetProperties("department/user");
//匹配department/user节点时,调用Department对象的addUser方法
digester.addSetNext("department/user","addUser");
//匹配department/extension节点时,调用Department对象的putExtension方法
digester.addCallMethod("department/extension","putExtension",2);
//调用方法的第一个参数为节点department/extension/property-name的内容
digester.addCallParma("department/extension/property-name",0);
//调用方法的第二个参数为节点department/extension/property-value的内容
digester.addCallParma("department/extension/property-value",1);
try{
Department department=(Department) digester.parse(new File("test.xml"));
}cath(Exception e){
e.printStackTrace();
}
创建一个Digester对象,并为其添加匹配模式以及对应的处理规则。由于Digester已经提供了常见处理规则的工厂方法。因此,直接调用相关方法即可。整个处理过程不需要手动维护对象属性和对象间的关系,不需要解析XML Dom。