Digester源码解析(1讲)
2013-02-15 20:25 阅读(191)


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是在堆栈上进行操作的。

待续