`
dmh920
  • 浏览: 18228 次
  • 性别: Icon_minigender_1
  • 来自: 湖南
社区版块
存档分类
最新评论

浅析Digester

    博客分类:
  • JAVA
阅读更多

         最近在看tomcat源代码,发现tomcat的xml文件都是通过Digester解析自动生成了对象,于是对Digester研究了下,为了防止自己忘记便记录下来。首先通过一个小程序来了解Digester,解析下面的school.xml文件:

<?xml version="1.0" encoding="UTF-8"?>  
<school name="xx大学" address="xx省xx市">  
    <department description="计算机系">  
        <student name="小明" sex="男"/>  
        <student name="小红" sex="女"/>  
        <student name="小王" sex="男"/>  
    </department>  
    <department description="数学系">  
        <student name="小李" sex="男"/>  
        <student name="小新" sex="男"/>  
        <student name="小赵" sex="女"/>  
    </department>  
</school>
public class DigesterForSchool {  
    public static void main(String[] args) throws Exception{  
        String path = System.getProperty("user.dir") + File.separator + "etc";  
        Digester digester = new Digester();  
        digester.addObjectCreate("school", "com.test.digester.School");  
        digester.addSetProperties("school");  
        digester.addObjectCreate("school/department", "com.test.digester.Department");  
        digester.addSetProperties("school/department");  
        digester.addSetNext("school/department", "addDepartment");  
        digester.addObjectCreate("school/department/student",  
                "com.test.digester.Student");  
        digester.addSetNext("school/department/student", "addStudent");  
        digester.addSetProperties("school/department/student");  
        School school = (School)digester.parse(new File(path, "school.xml"));  
        System.out.println(school);  
    }  
}

输出结果如下:

school name: xx大学, address: xx省xx市

departments: [

department description: 计算机系

students: [

student name: 小明 sex: 男, 

student name: 小红 sex: 女, 

student name: 小王 sex: 男], 

department description: 数学系

students: [

student name: 小李 sex: 男, 

student name: 小新 sex: 男, 

student name: 小赵 sex: 女]]

        可以发现Digester将school.xml文件的内容,转换为了相对应的对象。那Digester到底是怎样工作的呢?查看Digester源代码我们发现:

import org.xml.sax.XMLReader;  
public class Digester extends DefaultHandler

        原来Digester是SAX解析器事件处理类DefaultHandler的子类,我们知道SAX是基于事件驱动解析xml文件,当SAX扫描到文档(document)开始、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。所以Digester在事件处理函数中做了相应的处理,让其能按照自己的规则生成对象数据。现在来看看Digester在事件处理函数中做了哪些处理:

public void startElement( String namespaceURI, String localName, String qName, Attributes list ) throws SAXException {
	...
	List<Rule> rules = getRules().match( namespaceURI, match, localName, list );
        matches.push( rules );
	...
	for ( int i = 0; i < rules.size(); i++ ) {
		...
		Rule rule = rules.get(i);
                rule.begin( namespaceURI, name, list );
		...	
	}
}
public void characters( char buffer[], int start, int length ) throws SAXException {
	...
	bodyText.append( buffer, start, length );
}
public void endElement( String namespaceURI, String localName, String qName ) throws SAXException {
	...
	List<Rule> rules = matches.pop();
	...
        for ( int i = 0; i < rules.size(); i++ ) {
                ....
                Rule rule = rules.get( i );
                ....
                rule.body( namespaceURI, name, bodyText );
        }
        ...
        for ( int i = 0; i < rules.size(); i++ ) {
                ....
                Rule rule = rules.get( i );
                ....
                rule.end( namespaceURI, name, bodyText );
        }
        ....
}

         在执行startElement()方法和endElement()方法的时候都用到Rule类的相关方法,现在再看看api中对Rule类的描述:Concrete implementations of this class implement actions to be taken when a corresponding nested pattern of XML elements has been matched. 当匹配到xml某个模式时,Rule的具体实现类的动作将被执行。在解析xml文档时,当Digester实例匹配到某个模式的元素开始标签时,调用相对应的Rule对象的begin方法(见startElement()方法),当匹配到相应元素的结束标签时,调用Rule对象的body()和end()方法(见endElement()方法)。当Digester类可以包含0个或多个Rule对象。在Digester实例中,这些规则和其相关联的模式都存储在Rules接口表示的一类集合中。每当把一条规则添加到Digester实例中时,Rule对象也都会被添加到Rules对象中。当调用addObjectCreate()、addCallMethod()方法、addSetNext()方法或其他方法时,都会间接调用Digester类的addRule()方法。该方法会将一个Rule对象和它所匹配的模式添加到Digester对象的Rules集合中。 

public void addObjectCreate( String pattern, String className ) {   
        addRule( pattern, new ObjectCreateRule( className ) );  
}  
public void addRule( String pattern, Rule rule ) {  
        rule.setDigester( this );  
        getRules().add( pattern, rule );  
}  
public Rules getRules() {  
        if ( this.rules == null ) {  
            this.rules = new RulesBase();  
            this.rules.setDigester( this );  
        }  
        return ( this.rules );  
}

      Digester已经预定义了一些规则,可以直接使用这些规则,如果这些规则仍然不满足需求,可以实现自己的规则,常用的预定义规则如下:

1.创建对象:

        ObjectCreateRule:利用指定类的默认构造函数。创建该类的一个对象,并把对象压入栈,当元素处理结束时,对象被弹出。
        FactoryCreateRule:利用指定的工厂类创建一个对象,用于处理没有提供默认构造函数的类。注意的是用于该规则的工厂类必须实现org.apache.commons.digester.ObjectCreationFactory接口。
2.设置属性:
        SetPropertiesRule:利用指定名称的XML元素属性值,设置顶层Bean的属性。
        BeanPropertySetterRule:把顶层Bean的指定名称的属性设置成当前XML元素包含的字符数据。(通常用来处理<element>10</element>之类的结构)。
        SetPropertyRule:设置顶层Bean的一个属性。无论是Bean属性的名称,还是赋予该属性的值,都在当前XML元素中以属性的形式指定,例如:<student key="age" value="10" />。
3.对象之间关系:
        SetNextRule:peek出栈顶对象和栈顶下一个对象,将栈顶对象作为参数传递给栈顶下一个元素。
        SetTopRule:把栈顶下一个对象传递给顶层对象。
        SetRootRule:调用栈底对象的一个方法,并把栈顶的对象作为参数传入。
4.调用方法:
        CallMethodRule:调用顶层Bean的指定名称的方法。被调用的方法可以有任意多个参数,参数的值通过后继的CallParamRule给出。
        CallParamRule:表示方法调用的参数。参数的值或者取自指定名称的XML元素的属性,或者是当前元素包含的原始字符数据。这个规则要求用一个整数指定它在参数列表中的位置。

      Digester实例内部维护着一个用于临时存储创建对象的栈和一个临时存储参数的栈(这个栈主要是用于CallMethodRule和CallParamRule使用,可以自行查阅源代码),如果实现自己的Rule时使用到了Digester中的栈,在begin()方法中,将一个Object push进去,一定要记得在end()方法中pop出来。Rule的api也说道:If a rule wishes to manipulate a digester stack (the default object stack, a named stack, or the parameter stack) then it should only ever push objects in the rule's begin method and always pop exactly the same number of objects off the stack during the rule's end method. Of course peeking at the objects on the stacks can be done from anywhere. 那现在我们来实现一个自定义规则,将上面的xml文件的student元素修改下命名为school2.xml:

<?xml version="1.0" encoding="UTF-8"?>  
<school name="xx大学" address="xx省xx市">  
    <department id="1" description="计算机系">  
        <student name="小明" sex="男" birthday="1990-01-01 00:00:00"/>  
        <student name="小红" sex="女" birthday="1991-01-01 00:00:00"/>  
        <student name="小王" sex="男" birthday="1992-01-01 00:00:00"/>  
    </department>  
    <department id="2" description="数学系">  
        <student name="小李" sex="男" birthday="1990-01-02 00:00:00"/>  
        <student name="小新" sex="男" birthday="1991-01-02 00:00:00"/>  
        <student name="小赵" sex="女" birthday="1992-01-02 00:00:00"/>  
    </department>  
</school>

        student元素新增了一个属性"birthday",在Student类中新增了一个成员变量Date型的birthday,这时在给birthday属性赋值时就不能直接调用addSetProperties()方法了,因为从xml解析到的birthday是string类型的数据,如果直接调用addSetProperties()方法会抛出类型转换异常,所以写了一个新的设置属性规则和一个将字符串日期类型转换为Date型数据的规则,代码如下:

public class SetAllPropertiesRule extends Rule {
	protected Map<String,String> excludes = new HashMap<String,String>();
    
	public SetAllPropertiesRule() {}

	/**
	 * 构造一个SetAllPropertiesRule
	 * @param exclude 不需要设置属性值的属性名数组
	 */
	public SetAllPropertiesRule(String[] exclude) {
             for (int i=0; i<exclude.length; i++ ) {
        	if (exclude[i]!=null) { 
        		this.excludes.put(exclude[i],exclude[i]);
        	}
             }
        }
	
	@Override
	public void begin(String namespace, String name, Attributes attributes)
			throws Exception {
		Map<String, String> values = new HashMap<String, String>();
		for (int i = 0; i < attributes.getLength(); i++) {
			String attributeName = attributes.getLocalName(i);
                        if ("".equals(attributeName)) {
                            attributeName = attributes.getQName(i);
                        }
                        String value = attributes.getValue(i);
                        if (!excludes.containsKey(attributeName)){
            	            values.put(attributeName, value );
                        }
		}
		populate(getDigester().peek(), values);
	}
}
public class ConvertStringToDateRule extends Rule {
	private SimpleDateFormat format = null;;
	
	private String attributeName = null;
	
	/**
	 * 构造一个ConvertStringToDateRule
	 * @param pattern 需要转换的字符串日期格式
	 */
	public ConvertStringToDateRule(String pattern) {
		this(pattern, null);
	}
	
	/**
	 * 构造一个ConvertStringToDateRule
	 * @param pattern 需要转换的字符串日期格式
	 * @param attributeName 需要转换的元素属性名,如果为null则将元素间的字符串转换,
         * 如:</element>2012-01-01 00:00:00</element>
	 */
	public ConvertStringToDateRule(String pattern, String attributeName) {
		this.attributeName = attributeName;
		format = new SimpleDateFormat(pattern);
	}
	
	@Override
	public void begin(String namespace, String name, Attributes attributes)
			throws Exception {
		if(attributeName == null) {
			return;
		}
		String str = attributes.getValue(attributeName);
		Date date = format.parse(str); 
		getDigester().push(date);
	}
	
	@Override
	public void body(String namespace, String name, String text)
			throws Exception {
		if(attributeName == null) {
			Date date = format.parse(text);
			getDigester().push(date);
		}
	}
	
	@Override
	public void end(String namespace, String name) throws Exception {
		getDigester().pop();
	}
}
public class DigesterForSchool2 {
	public static void main(String[] args) throws Exception {
		String path = System.getProperty("user.dir") + File.separator + "etc";
		Digester digester = new Digester();
		digester.addObjectCreate("school", "com.test.digester.school2.School");
		digester.addSetProperties("school");
		digester.addObjectCreate("school/department",
				"com.test.digester.school2.Department");
		digester.addSetProperties("school/department");
		digester.addSetNext("school/department", "addDepartment");
		digester.addObjectCreate("school/department/student",
				"com.test.digester.school2.Student");
		digester.addSetNext("school/department/student", "addStudent");
		digester.addRule("school/department/student", new SetAllPropertiesRule(
				new String[] { "birthday" }));
		digester.addRule("school/department/student",
				new ConvertStringToDateRule("yyyy-MM-dd hh:mm:ss", "birthday"));
		digester.addSetNext("school/department/student", "setBirthday");
		School school = (School) digester.parse(new File(path, "school2.xml"));
		System.out.println(school);
	}
} 

输出结果如下:

school name: xx大学 address: xx省xx市

departments: [

department id: 1 description: 计算机系

students: [

student name: 小明 sex: 男 birthday: Mon Jan 01 00:00:00 CST 1990, 

student name: 小红 sex: 女 birthday: Tue Jan 01 00:00:00 CST 1991, 

student name: 小王 sex: 男 birthday: Wed Jan 01 00:00:00 CST 1992], 

department id: 2 description: 数学系

students: [

student name: 小李 sex: 男 birthday: Tue Jan 02 00:00:00 CST 1990, 

student name: 小新 sex: 男 birthday: Wed Jan 02 00:00:00 CST 1991, 

student name: 小赵 sex: 女 birthday: Thu Jan 02 00:00:00 CST 1992]]

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics