Apache Commons Digester是对SAX的封装,用于直接通过XML文件来生成Java类实例
通常在使用SAX解析XML的时候,需要覆盖DefaultHandler类的startDocument、startElement、characters、endElement、endDocument等方法。这样编程过于复杂不友好,而且对于不同的XML格式需要编写不同的DefaultHandler子类来处理
Digester提供了一套更为友好优雅的接口,使开发者不再需要去重写这些方法,而是直接通过Digester定义的标签格式,自动生成所需要的Java类实例
标签格式
Digester的方法中大部分都含有一个String类型的参数pattern,它表示的就是当前方法所需要处理的标签的格式。该格式通过 / 来分隔标签,父标签总是位于子标签的左侧;需要匹配的目标标签位于最右侧,它的左侧必须逐层标明每层的父标签。如:
<a><b><c></c></b></a>
要匹配到<c>标签,则pattern="a/b/c";同理,要匹配到<b>标签,则pattern="a/b"
运行原理
Digester内部通过SAX实现,SAX需要扫描整个XML文档。Digester通过核心方法
void addRule(String pattern, Rule rule);
将标签格式pattern与对应的规则处理类rule注册到其内部;当SAX扫描到pattern对应的目标标签时,由rule来决定其行为
规则处理类统一实现Rule接口,它的4个主要方法如下:
begin():对应SAX的startElement方法,标签开始时被触发,同时将解析该标签的所有参数(如<a name="xxx">中的name)
body():对应SAX的characters方法,扫描到标签内容时被触发,并提取内容字符串(trim后)(如<a>content</a>中的content)
end():对应SAX的endElement方法,标签结束时被触发
finish():对应SAX的endDocument方法,扫描结束时被触发,通常用于清理各规则的临时变量、关闭流、清空缓存数据结构等操作
此外,Digester内置了一个实例栈(Stack),用于缓存各rule生成的Java类实例。通常,栈顶元素都是当前正在处理的Java实例
内置规则
Digester内置了一系列Rule接口的实现类来帮助更加简洁高效地完成XML文件的解析工作:
ObjectCreateRule:在begin()方法中根据传入的类名实例化一个Java实例,并将它压入内置栈中;在end()方法中将该实例弹出
FactoryCreateRule:ObjectCreateRule的变型,传入的用于生成Java实例的类名参数必须是继承自ObjectCreateFactory的类;ObjectCreateFactory能在简单初始化一个实例后做更多的操作
SetPropertiesRule:在begin()方法中,将标签的全部参数通过反射填写到内置栈当前栈顶实例的成员变量中
SetPropertyRule:在begin()方法中,设置内置栈栈顶实例的一个成员变量,其中变量名为一个标签参数的值,变量值是另一个标签参数的值
SetNestedPropertiesRule:将当前标签的子标签及子标签的内容作为变量名和变量值反射填写到内置栈的栈顶实例中
BeanPropertySetterRule:将当前标签及其内容作为变量名和变量值反射填写到内置栈的栈顶实例中
SetNextRule:在end()方法中,根据传入的方法名调用内置栈仅次于栈顶的实例的对应方法,并以栈顶实例作为参数
SetTopRule:在end()方法中,根据传入的方法名调用内置栈栈顶实例的对应方法,并以仅次于栈顶的实例作为参数
CallMethodRule:在end()方法中,根据传入的方法名调用栈顶实例的某个方法,方法的参数视情况而定
CallParamRule:配置CallMethodRule使用,用于指定参数
另外,除了addRule(String pattern, Rule rule)这个核心规则注册方法以外,Digester还提供了一套简化的方法,以便简化对上述内置规则的调用。例如:
addRule(pattern, new ObjectCreateRule(clazzName))
可以用下面的调用取代:
addObjectCreate(pattern, clazzName)
其它规则也同理。需要注意的是,这些扩展方法的内部实际还是调用的addRule
用法示例
以下是对网上一个公交查询API所返回的XML文件的解析的示例
XML文件:
<?xml version="1.0" encoding="UTF-8"?>
<buses>
<bus>
<dist>15465</dist>
<time>35</time>
<foot_dist>162</foot_dist>
<last_foot_dist>162</last_foot_dist>
<segments>
<segment name="地铁1号线(升仙湖-广都)">
<start_stat>火车北站</start_stat>
<end_stat>天府广场</end_stat>
<stats>火车北站;人民北路;文殊院;骡马市;天府广场</stats>
<line_dist>7005</line_dist>
<foot_dist>0</foot_dist>
</segment>
<segment name="地铁2号线(龙泉驿-犀浦)">
<start_stat>天府广场</start_stat>
<end_stat>茶店子客运站</end_stat>
<stats>天府广场;人民公园;通惠门;中医大省医院;白果林;蜀汉路东;一品天下;羊犀立交;茶店子客运站</stats>
<line_dist>8298</line_dist>
<foot_dist>0</foot_dist>
</segment>
</segments>
</bus>
<bus>
<dist>9131</dist>
<time>34</time>
<foot_dist>402</foot_dist>
<last_foot_dist>0</last_foot_dist>
<segments>
<segment name="86路(北湖公交站-茶店子公交站)">
<start_stat>火车北站东</start_stat>
<end_stat>茶店子公交站</end_stat>
<stats>
火车北站东;人民北路二段北;人民北路;西北桥;一环路九里堤路口西;沙湾;西门车站;营门口立交桥东;五里村;茶店子;茶店子西口;茶店子公交站
</stats>
<line_dist>8729</line_dist>
<foot_dist>402</foot_dist>
</segment>
</segments>
</bus>
</buses>
Bus类(对应<bus>标签):
public class Bus {
private int dist;
private int time;
private int footDist;
private int lastFootDist;
private List<Segment> segments = new ArrayList<Segment>();
public int getDist() {
return dist;
}
public void setDist(int dist) {
this.dist = dist;
}
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
public int getFootDist() {
return footDist;
}
public void setFootDist(int footDist) {
this.footDist = footDist;
}
public int getLastFootDist() {
return lastFootDist;
}
public void setLastFootDist(int lastFootDist) {
this.lastFootDist = lastFootDist;
}
public List<Segment> getSegments() {
return segments;
}
public void addSegment(Segment segment) {
this.segments.add(segment);
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
Segment类(对应<segment>标签):
public class Segment {
private String name;
private String startStat;
private String endStat;
private List<String> stats;
private int lineDist;
private int footDist;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getStartStat() {
return startStat;
}
public void setStartStat(String startStat) {
this.startStat = startStat;
}
public String getEndStat() {
return endStat;
}
public void setEndStat(String endStat) {
this.endStat = endStat;
}
public List<String> getStats() {
return stats;
}
public void setStats(String stats) {
this.stats = Arrays.asList(stats.split(";"));
}
public int getLineDist() {
return lineDist;
}
public void setLineDist(int lineDist) {
this.lineDist = lineDist;
}
public int getFootDist() {
return footDist;
}
public void setFootDist(int footDist) {
this.footDist = footDist;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
测试类:
public class DigesterTest {
private List<Bus> buses = new ArrayList<Bus>();
public void addBus(Bus bus) {
this.buses.add(bus);
}
public void run() throws IOException, SAXException {
Digester digester = new Digester();
digester.push(this);
digester.addObjectCreate("buses/bus", Bus.class);
digester.addSetNestedProperties("buses/bus", new String[] {"foot_dist", "last_foot_dist"}, new String[] {"footDist", "lastFootDist"});
// digester.addBeanPropertySetter("buses/bus/dist");
digester.addObjectCreate("buses/bus/segments/segment", Segment.class);
digester.addSetProperties("buses/bus/segments/segment");
digester.addSetNestedProperties("buses/bus/segments/segment",
new String[] {"start_stat", "end_stat", "line_dist", "foot_dist"},
new String[] {"startStat", "endStat", "lineDist", "footDist"});
digester.addCallMethod("buses/bus/segments/segment/stats", "setStats", 0);
digester.addSetNext("buses/bus/segments/segment", "addSegment");
digester.addSetNext("buses/bus", "addBus");
digester.parse(new File("digester-test.xml"));
System.out.println(this.buses.size());
System.out.println(this.buses);
}
public static void main(String[] args) throws IOException, SAXException {
new DigesterTest().run();
}
}