Digester 是apache 下 commons常用组件库中的一个工具,其主要功能就是将xml文件转为java bean。它是通过sax的方式解析xml文档。
struts1 中读取配置文件就是使用Digester。今天我们就来解析下Digester的源码。本次解析中使用的是commons-digester-1.7.jar
先弄个xml:
<?xml version="1.0" encoding="UTF-8"?>
<students>
<student id="1001">
<name>Java Boy</name>
<course>JSP</course>
</student>
<student id="1002">
<name>Java Girl</name>
<course>EJB</course>
</student>
</students>
Digester中有俩大概念:匹配模式(matching pattern)和规则(rule)。
匹配模式是以字符串的形式存在,以上面的xml为例,students/student , students/student/name 就是俩个匹配模式。其实每个匹配模式就是一个路径,说明该匹配模式指代的是哪个元素,如students/student 指代的就是元素<student>,students/student/name指代的就是元素<name>.
而规则(rule)则是一系列动作。在Digester中规则是一个抽象类(org.apache.commons.digester.Rule). 其中包括四个方法begin()、body()、end()、finish(),可以看出这四个方法都是时点方法,按照时间顺序执行。目前在Rule中这四个方法都是空方法,需要它的实现者去重写。
Digester提供了一些内置规则(rule),使用这些内置规则就可完全的将xml文档转为java bean了,而不必在定义了。以下五类九个rule较为常用(为了方便,这个是我直接从百度百科上弄下来的):
A:对象创建
1.ObjectCreateRule 当begin()方法被调用时, 此rule创建相应Java对象, 并将其push到Digester的对象栈上。当end()方法被调用时, 栈顶对象将被pop, Digester内所有对该对象的引用都将失效。
2.FactoryCreateRule 创建Java对象的另一种选择。当待创建的Java对象没有无参构造函数,或需要在创建时需要进行额外的设置时,需要用此rule。
B:属性设置
3.SetPropertiesRule 当begin()方法被调用时, Digester使用标准的Java反射API,将栈顶对象的属性设置为XML元素的同名属性值。
4.SetPropertyRule 当begin()方法被调用时, Digester调用栈顶对象某指定属性的设置方法,设置其值。
C:父子关系管理
5.SetNextRule 当end()方法被调用时, Digester将栈顶元素设置进次栈顶元素中(调用相应的设置方法)。
6.SetTopRule 当end()方法被调用时, Digester将次栈顶元素设置进栈顶元素中(调用相应的设置方法)。
D:任意方法调用
7.CallMethodRule 当end()方法被调用时, Digester将调用栈顶元素指定名称的方法。除了方法名外,此rule还需要配置参数数目,参数类型。参数值一般通过CallParamRule得到。
8.CallParamRule 此rule内嵌于CallParamRule中,按顺序(相对于0)定义了CallParamRule中参数值的来源,可选的来源包括当前XML元素的属性或内容。
E:其它
9.NodeCreateRule 将XML文件树的一部分转换为DOM节点,并push到Digester的对象栈上。
有了上面的概念在来讨论下Digester的工作原理或工作过程就比较简单了。由于Digester只是对SAX更高层次上的一个封装,所以也不会脱离"事件"驱动的。具体过程:
当Digester利用SAX解析到XML文件中的某个元素时,通过“事件”驱动调用Digester相应方法(由于Digester继承了DefaultHandler,那么实际就会调用Digeser的startElement()、characters()、endElement()),在startElement()方法中,Digester首先会生成该元素的匹配模式(如:当前处理到student节点 那么匹配模式就是students/student),然后根据这个匹配模式去找到该匹配模式下事先绑定的所有规则(rule),依次调用这些规则rule的begin()方法。之后会通过事件调用characters()方法(取得元素值)、最后调用endElement()方法,在此方法中会先后调用规则的body()、end()方法,从而完成解析xml的。在此过程中如何生成xml元素对应的java bean、以及怎样生成、生成后的java bean如何管理,就需要通过规则Rule及规则间的相互配合来完成了。
具体归纳起来可分4步(这是我自己归纳的,仅供参考):
1.创建Digester对象实例。
2.为每个匹配模式配置规则(rule)(可设置多个规则,实际使用的是List来存放的),以及为该Digester对象设置属性(可选)。
3.用digester.parse()解析XML文档。
4.返回root(根对象,一般是xml根元素的java bean,或者是根元素下的所有子元素的集合,或者其他,)
以下我们从个demo入手:
package com;
public class Student {
private String name;
private String course;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCourse() {
return course;
}
public void setCourse(String course) {
this.course = course;
}
}
package com;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.digester.Digester;
public class DigesterDemo {
private List students;
public DigesterDemo(){
students= new ArrayList();
}
/**
* 填充list
* @param student
*/
public void addStudent( Student student){
students.add( student);
}
/**
* @param path
* xml文件路径
*/
public void parseXml(String path){
//创建实例
Digester digester= new Digester();
//是否进行命名空间校验(可选)
digester.setNamespaceAware(true);
//是否进行dtd合法性检查(可选)
digester.setValidating(false);
//将初始对象(根对象)压入digester的stack(可选)
digester.push(new DigesterDemo());
//指明匹配模式和要创建的类
digester.addObjectCreate( "students/student", Student.class);
//将元素属性也映射到对象中
digester.addSetProperties( "students/student");
//当移动到下一个标签中时的动作
digester.addSetNext( "students/student", "addStudent");
//设置对象属性
digester.addBeanPropertySetter( "students/student/name");
digester.addBeanPropertySetter( "students/student/course"); try {
//解析结束并返回根元素
DigesterDemo ds= (DigesterDemo)digester.parse(path);
} catch (Exception e) {
e.printStackTrace();
}
}
}
上面的 parseXml(...)方法就是使用Digester的解析过程。
首先创建Digester对象,对应步骤1.
在下面俩句是进行设置(digester.setNamespaceAware(true); 和 digester.setValidating(false); )
从
digester.addObjectCreate( "students/student", Student.class);
到
digester.addSetNext( "students/student", "addStudent");
是使用Digester的内置规则(rule)对象为匹配模式添加规则。对应步骤2.
最后是
DigesterDemo ds= (DigesterDemo)digester.parse(path);开始解析并返回跟对象,此处的跟对象就是通过digester.push(new DigesterDemo())中设置的,将一个DigesterDemo实例压入(push)堆栈。对应步骤3、4.
谈到这我们有必要简单的看下Digester使用的堆栈
F5进入"digester.push(new DigesterDemo())",
/**
* Push a new object onto the top of the object stack.
*
* @param object The new object
*/
public void push(Object object) {
if (stack.size() == 0) {
root = object;
}
stack.push(object);
}
其中的属性"stack"就是一个堆栈实例。是org.apache.commons.collections.ArrayStack类的对象。而collections也是apache commons下的一个工具。
看下ArrayStack类的源码定义:
public class ArrayStack extends ArrayList implements Buffer {
.
.
.
}
实际上是在ArrayList 有序表的基础上进行了封装。ArrayStack里边的几个方法我们在下面会用到,所以我这先说下:
/**
* Returns the top item off of this stack without removing it.
*
* @return the top item on the stack
* @throws EmptyStackException if the stack is empty
*/
public Object peek() throws EmptyStackException {
int n = size();
if (n <= 0) {
throw new EmptyStackException();
} else {
return get(n - 1);
}
}
"peek"字面意思是"看"的意思。在此用于查看栈顶元素(注意:此处只是返回栈顶元素的引用,并没有进行出栈操作)。size()是ArrayList的方法,返回元素个数。如果元素个数<=0抛出异常,否则返回下标值最大(n-1)的元素。由此可看ArrayStack是采用下标0对应的元素作为栈底元素,下标最大值的作为栈顶,这样只要通过add()方法就可进行入栈,remove(n-1)就可进行出战操作,很方便。
/**
* Returns the n'th item down (zero-relative) from the top of this
* stack without removing it.
*
* @param n the number of items down to go
* @return the n'th item on the stack, zero relative
* @throws EmptyStackException if there are not enough items on the
* stack to satisfy this request
*/
public Object peek(int n) throws EmptyStackException {
int m = (size() - n) - 1;
if (m < 0) {
throw new EmptyStackException();
} else {
return get(m);
}
}
带参数的peek方法返回指定下标的元素,此处的下标n并非是ArrayList的下标而是堆栈的下标。是从栈顶到栈底的顺便编号的,起始编号为0.即栈顶的编号为0. 这样就需要将堆栈的下标转为ArrayList的下标,通过语句int m = (size() - n) - 1;可实现。
/**
* Pops the top item off of this stack and return it.
*
* @return the top item on the stack
* @throws EmptyStackException if the stack is empty
*/
public Object pop() throws EmptyStackException {
int n = size();
if (n <= 0) {
throw new EmptyStackException();
} else {
return remove(n - 1);
}
}
pop() 出栈方法。通过ArrayList的remove()方法将栈顶(n-1)元素移除。
/**
* Pushes a new item onto the top of this stack. The pushed item is also
* returned. This is equivalent to calling <code>add</code>.
*
* @param item the item to be added
* @return the item just pushed
*/
public Object push(Object item) {
add(item);
return item;
}
push() 入栈方法。通过ArrayList 的add()方法。
之所以对堆栈说得比较详细,是因为后边用到堆栈的地方太多了,可以说Digester是在堆栈上进行操作的。
待续