摘要:Jakarta Commons Digester 学习笔记 |
Digester是Jakarta 子项目Commons下的一个模块,支持基于规则的对任意XML文档的处理。它最初是Structs项目的一部分,后因其通用性而划归Commons. 下载及编译
cvs -d :pserver:[email protected]:/home/cvspublic login
password: anoncvs cvs -d :pserver:[email protected]:/home/cvspublic checkout jakarta-commons/digester cd jakarta-commons/digester ant dist Digester的运行依赖下列包:
一个简单的例子假定有两个JavaBean如下,分别为Foo和Bar
package mypackage;
public class Foo { public void addBar(Bar bar); public Bar findBar(int id); public Iterator getBars(); public String getName(); public void setName(String name); } public mypackage; public class Bar { public int getId(); public void setId(int id); public String getTitle(); public void setTitle(String title); } 用下面的xml文件进行配置
<foo name="The Parent">
<bar id="123" title="The First Child"/> <bar id="456" title="The Second Child"/> </foo> 用下面几行代码即可完成配置文件解析工作:
基本情况熟悉用SAX来处理XML文档的程序员,会发现Digester隐藏了遍历XML元素这些细节,而是提供了更高一层的、更友好的SAX事件接口,从而让程序员的精力放在对数据的处理过程中。使用Digester,须按照以下步骤:
元素匹配模板Digester能自动遍历目标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> 字符”*”表示任意级别,如”*/a”表示任意级别的<a>都可匹配(不包括根元素级的).熟悉XLST的朋友,对这种思路一定不陌生。 从上面的描述,可知某个元素同时满足多个匹配模板是非常可能的,在这种情况下,与各个模板相关联的处理规则(processing rule)的执行顺序如下:对begin或body方法,按照各个rule的注册顺序的先后,对end方法则是注册顺序的反序。 处理规则(processing rule)元素匹配模板用以识别什么时候采取行动,处理规则则用以定义行动的内容。从形式上讲,一个处理规则是一个java类,它扩展了org.apache.commons.digester.Rule类。每个处理规则,实现下列 的一个或几个事件处理方法(event method),当相应的模板匹配成功以后,在已定义的某个时刻,这些事件方法会被触发。
在设置digester时,通过调用addRule()方法,来注册一个特定的元素匹配模板以及相应的一个Rule类的实例。如上所述,Rule类中的事件处理方法,会在适当的时间被调用。这个机制,允许动态地生成Rule的实现。 另外,digester也提供了一些处理常见情况的处理规则类。
对这些标准的规则类,可以创建它们的实例,并调用digester.addRule来注册它们。由于经常使用它们,所以digester定义了一些简便的方法来注册它们。如:
Rule rule = new SetNextRule(digester, "addChild","com.mycompany.mypackage.MyChildClass");
可以用下列代码替换
digester.addRule("a/b/c", rule);
digester.addSetNext("a/b/c", "addChild", "com.mycompany.mypackage.MyChildClass");
对象栈对digester技术最普通的应用,是用来动态创建一个由Java对象构成的树结构,各对象的属性以及对象间的关系,基于 XML文档的内容来设置(XML文档就是一棵树)。为实现这种应用,Digester提供了一个对象栈,以供在相关的模板识别后被激活的处理规则操作。此 栈的基本操作包括:
用栈的原因,就是当识别出一个XML元素的“开始”时,将相关对象生成并压入栈顶,这个对象在处理该元素的子元素的过程中一直在栈中,当所有子元素都处理完后,解析器遇到这个元素的“结束”时,则弹出此对象,并进行相关的处理。 如何描述对象间的关系呢?将栈顶的对象做为一个参数,传递给第二栈顶(即先于栈顶对象入栈的那个对象,在栈顶对象的下面)的一个方法,就可以简单地建 立起一种“父子关系”,从而可以简单地建立起1:1的关系(第二栈顶对象与栈顶对象之间)和1:N的关系(第二栈顶对象不动,N次压栈顶弹栈顶对象). 如果取得生成的第一个对象呢?可以让parse()方法返回,或者在调用parse()方法前,先行压入一个对象,在parse()方法结束后弹出这个对象,则其子对象即为我们想要的第一个对象。 日志(logging)日志是一个调试Digester规则集的非常重要的工具,它可以记录非常丰富的信息,因它在使用Digester之前有必要了解日志是如何工作的。 Digester使用Jakarta Commons Logging,这个模块并不是具体的日志实现,而只是一个可设置的接口。可以设置它将各种日志信息传递它自身带的基本记录器,或者传递给其它的更复杂的 日志工具。具体请参考commons logging的文档,或Jakarta Commons Logging学习笔记 Digester主要使用两个记录器:
假定用commons logging自带的基本日志工具,并以DEBUG级别记录Digester调试信息以及INFO级别记录SAX事件信息,则对logging的配置文件设置如下:
org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog org.apache.commons.logging.simplelog.log.org.apache.commons.digester.Digester=debug org.apache.commons.logging.simplelog.log.org.apache.commons.digester.Digester.sax=info
Digester包中的例子
***********Example.xml**********
运行结果如下(运行时可能需要xml-crimson,一个源sun的XML解析器,可到http://xml.apache.org/crimson/下载)
<address-book> <person id="1" category="acquaintance" try="would be ignored"> <name>Gonzo</name> <email type="business">[email protected]</email> <gender result="the whole tag would be ignored">male</gender> </person> <person id="2" category="rolemodel"> <name>Kermit</name> <email type="business">[email protected]</email> <email type="home">[email protected]</email> </person> </address-book>**********Person.java************ import java.util.HashMap; import java.util.Iterator; public class Person { private int id; private String category; private String name; private HashMap emails = new HashMap(); //下面的两个方法的名字中set以后的部分,与<person>的属性名字对映。当从xml文件中识别出<person> 的属性时,如果有要求(即调用过addSetProperties方法),Digester会依据这种对映关系自动调用相应的方法。 public void setId(int id) { this.id = id; } public void setCategory(String category) { this.category = category; } //对name而言,因为其值来自<name>标签的内容而非属性值,需要用addCallMethod指定识别<name>后的要调用此方法(想自动调用也要可以,需要addBeanPropertySetter,参见第下一个例子)。 public void setName(String name) { this.name = name; } //同name,此时还要一一指定addEmail的参数值的来源。 public void addEmail(String type, String address) { emails.put(type, address); } public void print() { System.out.println("Person #" + id); System.out.println(" category=" + category); System.out.println(" name=" + name); for(Iterator i = emails.keySet().iterator(); i.hasNext(); ) { String type = (String) i.next(); String address = (String) emails.get(type); System.out.println(" email (type " + type + ") : " + address); } } } **********AddressBook.java*********** import java.util.LinkedList; import java.util.Iterator; public class AddressBook { LinkedList people = new LinkedList(); public void addPerson(Person p) { people.addLast(p); } public void print() { System.out.println("Address book has " + people.size() + " entries"); for(Iterator i = people.iterator(); i.hasNext(); ) { Person p = (Person) i.next(); p.print(); } } } ************AddressBookDigester********* import org.apache.commons.digester.Digester; /** * Usage: java Example1 example.xml */ public class AddressBookDigester { public static void main(String[] args) { if (args.length != 1) { usage(); System.exit(-1); } String filename = args[0]; // 创建一个Digester实例 Digester d = new Digester(); // 创建AddressBook实例,并将其压入栈顶。 AddressBook book = new AddressBook(); d.push(book); // 增加规则 addRules(d); // 处理输入的xml文件 try { java.io.File srcfile = new java.io.File(filename); d.parse(srcfile); } catch(java.io.IOException ioe) { System.out.println("Error reading input file:" + ioe.getMessage()); System.exit(-1); } catch(org.xml.sax.SAXException se) { System.out.println("Error parsing input file:" + se.getMessage()); System.exit(-1); } // 将解析出的地址数据打印出来 book.print(); } private static void addRules(Digester d) { // 当遇到<person>时,创建类Person的一个实例,并将其压入栈顶 d.addObjectCreate("address-book/person", Person.class); // 将<person>标签的属性(attribute)与栈顶Person类对象的属性(property)设置方法根 据各自的名字进行映射,(例如,将标签属性id与属性设置方法setId进行映射,将标签属性category与属性设置方法setCategory进行 映射),然后将属性的值作参数传递给执行相应的方法。 // 如果某标签属性没法通过名字找到相应的属性设置方法,则此标签属性被忽略(如example.xml中第一个<person>的try属性)。 d.addSetProperties("address-book/person"); // 调用第二栈顶对象(AddressBook实例)的addPerson方法,以栈对象(Person实例)的对象为参数 d.addSetNext("address-book/person", "addPerson"); // 当遇到<person>的子元素<name>时,调用栈顶对象(Person实例)的setName方法。 // 此处addCallMethod方法的第一参数是规则,第二个参数是方法的名字,第三个是参数的数量(为0时,表示只有一个参数,且参数的值是元素的内容) d.addCallMethod("address-book/person/name", "setName", 0); // 当遇到<person>的子元素<email>时,调用栈顶对象(Person实例)的addEmail 方法,addEmail方法有两个参数,取值分别来自<email>的属性type的值和<email>本身的内容。 // 此处addCallParam方法的第一参数是规则,第二个参数是指明被调用方法(addEmail)参数的序号,第三个是参数为字符串时指属性的名字) d.addCallMethod("address-book/person/email", "addEmail", 2); d.addCallParam("address-book/person/email", 0, "type"); d.addCallParam("address-book/person/email", 1); } private static void usage() { System.out.println("Usage: java Example1 example.xml"); } }
Address book has 2 entries
Person #1 category=acquaintance name=Gonzo email (type business) : [email protected] Person #2 category=rolemodel name=Kermit email (type business) : [email protected] email (type home) : [email protected] 配置属性Digester用来解析应用系统的配置文件,其本身也有很可配置的属性。
除了上述属性外,还可以注册一个本地DTD,以供DOCTYPE声明引用。这样的注册告诉XML解析器,当遇到DOCTYPE声明时,应使用刚注册的DTD的内容,而不是DOCTYPE声明中的标识符(identifier)。 例如,Struect框架控制器中,使用下述的注册,告诉Structs使用一个本地的DTD中的相关内容来处理Structs配置文件,这样可以适 用于那些没有连接到互联网的应用环境,而在连到互联网的环境中可以加快运行速度(因为它避免了通过网络去取相关的资源)。
URL url = new URL("/org/apache/struts/resources/struts-config_1_0.dtd");
digester.register("-//Apache Software Foundation//DTD Struts Configuration 1.0//EN",url.toString()); 规则集打包通常情况下,一个规则被创建后,接着便注册,然后等在event时被调用,这些规则集很难为其它应用程序直接复用。一个解决方法是将所有规则都放在一 个类中,此由这些规则可以很简单地被装载然后被注册使用。RuleSet接口就是为些而设计,一般是通过扩展RuleSetBase类来开发规则集类。如 例:
public class MyRuleSet extends RuleSetBase {
可以这样使用这个规则集
public MyRuleSet() { this(""); } public MyRuleSet(String prefix) { super(); this.prefix = prefix; this.namespaceURI = "http://www.mycompany.com/MyNamespace"; } protected String prefix = null; public void addRuleInstances(Digester digester) { digester.addObjectCreate(prefix + "foo/bar", "com.mycompany.MyFoo"); digester.addSetProperties(prefix + "foo/bar"); } }
Digester digester = new Digester();
... 一些配置Digester ... digester.addRuleSet(new MyRuleSet("baz/")); 带命名空间的XML解析这种情况下,使用Digester的步骤为:
另外,在指明要digester考虑命名空间之后,在定义匹配模板时,可以将命名空间别名加“:”作为元素名称的一部分使用。这与无命名空间时是一致的。 开发定制的匹配处理过程通过实现 org.apache.commons.digester.Rules接口或扩展org.apache.commons.digester.RulesBase类来达到定制匹配过程的目的。 Digester提供ExtendedBaseRules来扩展了匹配模板的定义,引入了特殊通配字符?和*以及!,提供RegexRules来支持 以正则式的语法定义匹配模板,提供WithDefaultsRulesWrapper来支持默认规则(即其它规则都不匹配时的处理规则)。 一些认识通过看说明材料,尤其在学习Digester包中的Catalog例子以后,有一些认识: 1、由于xml对属性名字的定义要求,与Java中对方法名字的定义要求不一致,导致出现不能自动映射的情况,如year-made标签属性,就不可能有方法setYear-made; 2、对于根元素,与其子元素建立联系,有几种办法:一种是先生成根元素实例,压入栈,然后解析,将调用方法规则建立联系;另一种是解析的过程中第一个创建它,然后用getRoot的方法得到。 3、如果某对象类构造都要参数,则此时需要扩展AbstractObjectCreationFactory类为这种对象建立一个Factory,在这个Factory中取得初始化参数值然后再创建一个对象实例。 4、设有某个标签,要想自动用该标签子元素的内容填充该标签对应的对象的属性,则需要用digester.setRules(new ExtendedBaseRules()),然后addRules(),然后再调用addBeanPropertySetter("bala/lala /?");进行规则定义,注意此模板中有通配符。 5、如果对象的属性是整型,则Digester自动将xml文件中字符串值转换为整型。 6、在指明要digester考虑命名空间之后,如果不会引起歧义,完全可以忽略命名空间的存在,除非你要针对特定的命名空间进行特定的处理。 原作者:Hilton |