学用commons digester
学用commons digester
Translated By xiaotaoliang 04.12
把xml文档转换为java bean对象层次结构是很常见的任务。标准SAX和DOM API很强大,也很灵活,只是对某些任务来说太底层了,此外xml解包代码工作量挺大的:SAX要维护一个分析栈,而DOM要操纵DOM树。
因此出现了Jakarta commons digester框架。
Jakarta Digester 框架
Jakarta digester框架是从struts项目中出现和演化的,原本开发的目的是拿来分析struts-config.xml文件的,不过很快被认识到其通用价值,于是移到jakarta commons项目,Commons项目的目标是提供“可重用java组件库”。Digester已经发展到1.6版了。
Digester类让开发者指定一个动作集,当它的分析器碰到xml文档中某些指定的简单模式的时候,动作集将会被执行。Digester框架预先提供了10个“规则包”,涵盖了大部分对xml文档进行解包的工作,如创建bean、设置bean属性等。有必要的话,开发者也可以自定义规则。
例子xml文档和相应的java Bean
<?xml version="1.0"?>
<catalog library="somewhere">
<book>
<author>Author 1</author>
<title>Title 1</title>
</book>
<book>
<author>Author 2</author>
<title>His One Book</title>
</book>
<magazine>
<name>Mag Title 1</name>
<article page="5">
<headline>Some Headline</headline>
</article>
<article page="9">
<headline>Another Headline</headline>
</article>
</magazine>
<book>
<author>Author 2</author>
<title>His Other Book</title>
</book>
<magazine>
<name>Mag Title 2</name>
<article page="17">
<headline>Second Headline</headline>
</article>
</magazine>
</catalog>
Java Bean:
import java.util.Vector;
public class Catalog {
private Vector books;
private Vector magazines;
public Catalog() {
books = new Vector();
magazines = new Vector();
}
public void addBook( Book rhs ) {
books.addElement( rhs );
}
public void addMagazine( Magazine rhs ) {
magazines.addElement( rhs );
}
public String toString() {
String newline = System.getProperty( "line.separator" );
StringBuffer buf = new StringBuffer();
buf.append( "--- Books ---" ).append( newline );
for( int i=0; i<books.size(); i++ ){
buf.append( books.elementAt(i) ).append( newline );
}
buf.append( "--- Magazines ---" ).append( newline );
for( int i=0; i<magazines.size(); i++ ){
buf.append( magazines.elementAt(i) ).append( newline );
}
return buf.toString();
}
}
public class Book {
private String author;
private String title;
public Book() {}
public void setAuthor( String rhs ) { author = rhs; }
public void setTitle( String rhs ) { title = rhs; }
public String toString() {
return "Book: Author='" + author + "' Title='" + title + "'";
}
}
import java.util.Vector;
public class Magazine {
private String name;
private Vector articles;
public Magazine() {
articles = new Vector();
}
public void setName( String rhs ) { name = rhs; }
public void addArticle( Article a ) {
articles.addElement( a );
}
public String toString() {
StringBuffer buf = new StringBuffer( "Magazine: Name='" + name + "' ");
for( int i=0; i<articles.size(); i++ ){
buf.append( articles.elementAt(i).toString() );
}
return buf.toString();
}
}
public class Article {
private String headline;
private String page;
public Article() {}
public void setHeadline( String rhs ) { headline = rhs; }
public void setPage( String rhs ) { page = rhs; }
public String toString() {
return "Article: Headline='" + headline + "' on page='" + page + "' ";
}
}
指定模式和规则:
Digester类基于模式和规则处理XML文档。模式必须匹配xml元素(elements)--基于其名称和在文档树中的位置,描述匹配模式的语法有点像xpath,模式“catalog”匹配顶层的<catalog>元素,模式“catalog/book”匹配直接嵌套在<catalog>元素中的一个<book>元素。
所有模式都使用“绝对路径”,即要从根元素开始指定,除了一个例外:模式包含通配符 * 的情形,比如模式“*/name”可以匹配文档中的任何<name>元素。注意,没有必要专门制订根元素,因为所有路径都使用绝对路径。
当Digester碰到一个指定规则时,将执行与规则相关联的动作集,这其中Digester框架当然是要跟一个SAX分析器相关的(而实际上,Digester类实现了org.xml.sax.ContentHandler接口并维护分析栈)。所有Digester用到的规则必须继承自org.apache.commons.digester.Rule类,这个类公开的方法类似SAX contentHandler回调函数:begin()和end()会分别在分析到“<”和“>”的时候被调用。
The
body()
method is called for the content nested inside of the matched element, and
在处理匹配元素的内嵌内容的时候,body()方法会被回调,最后,一旦处理完关闭标签,finish()方法被调用,这提供了一个挂钩,以完成可能必要的清理工作。然而,大多数开发者不必关心这些函数,因为框架所提供的标准的规则很可能已经提供了所有必需的功能。
要对一个XML文档进行解包,首先创建org.apache.commons.digester.Digester对象,作必要的配置,指定所需模式和规则,最后在parse方法中传入一个XML文件的引用。下面的DigesterDrive类将作示范(此例中输入的XML文档文件名要在命令行中指定)
import org.apache.commons.digester.*;
import java.io.*;
import java.util.*;
public class DigesterDriver {
public static void main( String[] args ) {
try {
Digester digester = new Digester();
digester.setValidating( false );
digester.addObjectCreate( "catalog", Catalog.class );
digester.addObjectCreate( "catalog/book", Book.class );
digester.addBeanPropertySetter( "catalog/book/author", "author" );
digester.addBeanPropertySetter( "catalog/book/title", "title" );
digester.addSetNext( "catalog/book", "addBook" );
digester.addObjectCreate( "catalog/magazine", Magazine.class );
digester.addBeanPropertySetter( "catalog/magazine/name", "name" );
digester.addObjectCreate( "catalog/magazine/article", Article.class );
digester.addSetProperties( "catalog/magazine/article", "page", "page" );
digester.addBeanPropertySetter( "catalog/magazine/article/headline" );
digester.addSetNext( "catalog/magazine/article", "addArticle" );
digester.addSetNext( "catalog/magazine", "addMagazine" );
File input = new File( args[0] );
Catalog c = (Catalog)digester.parse( input );
System.out.println( c.toString() );
} catch( Exception exc ) {
exc.printStackTrace();
}
}
}
创建Digester对象后,我们设定其不使用DTD验证XML文档—因为我们没有为这个文档定义DTD。然后指定模式和相关的规则:objectCreateRule规则负责创建一个指定类的实例并压入分析栈,setPropertiesRule规则把bean的属性成员设置为当前元素的XML属性值 –- 规则的第一个参数是XML属性名,第二个是bean的属性成员名。
Whereas
SetPropertiesRule
takes the value from an attribute,
与setPropertiesRule从某个属性得到值的方式相反,BeanPropertySetterRule会从当前元素的内嵌纯字符数据取得值,使用BeanPropertySetterRule不必指定要赋值的bean属性成员名:它缺省取当前XML元素名。在上面的例子中,匹配catalog/magazine/article/headline模式时的规则定义就是用的这种缺省方式。最后,SetNextRule将对象从栈顶弹出并作为参数传给上一个入栈的对象(目前在栈顶)的指定方法 – 通常用于将一个已经完成构建的bean插入其父对象中。
要注意的是,允许为同一个模式注册几个规则,这种情况下,规则将会按照其被加入到Digester中的先后顺序被依次执行。例如,处理<article>元素的时候,即catalog/magazine/article,我们首先创建合适的article bean,设置其page属性成员,最后从分析栈弹出完整的article bean并将其插入magazine父对象。
执行任意函数:
不仅可以设置bean的属性成员,还可以执行栈中的对象的任意方法。方式是使用CallMethodRule指定方法名及可选的方法的参数个数和类型,其随后的CallParamRule规则定义要传递给方法的参数值,参数值可以从当前XML元素的命名属性中或者当前元素包含的纯字符数据中取得。例如,不使用上面的DigesterDriver实现中用的BeanPropertySetterRule规则,我们也可以通过显式调用属性成员的设置器(setter)并把数据作为参数来达到同样的效果:
digester.addCallMethod( "catalog/book/author", "setAuthor", 1 );
digester.addCallParam( "catalog/book/author", 0 );
第一行指出了要调用的方法名(
setAuthor()
)
,
需要的参数数量(1),第二行是说:从<author>元素中包含的字符数据取得参数并将其作为函数参数数组中的第一个元素,(即数组元素下标为0)。我们也制定了一个属性名(例如,digester.addCallParam( "catalog/book/author", 0, "author" );
),属性值则从当前元素的各个属性中获取。
这里有个重要的警告:很令人迷惑的一点是:digester.addCallMethod(“pattern”,”methodName”,0);并不是指定一个无参数方法调用,而是指定一个有一个参数的方法调用!参数值从当前的XML元素的字符数据取得!因而我们还有另外一个方法替换BeanPropertySetterRule,即:digester.addCallMethod(“catalog/book/author”, “SetAuthor”,0);要调用一个无参数方法,要使用 digester.addCallMethod(“pattern”, “methodname”);
标准规则概要:
下面是所有标准规则的简要描述:
对象创建相关
· objectCreateRule:使用指定类的缺省构造函数创建一个对象,并压入堆栈,元素分析完成后从栈中弹出。要构造的类可以由一个class对象或全类名提供。
· FactoryCreateRule:使用指定的工厂类创建一个对象并压入堆栈,这对那些没有缺省构造函数的类有用。工厂类必须实现org.apache.commons.digester.ObjectCreateFactory接口。
属性设置相关
· SetPropertiesRule:在顶层的bean中使用指定名称的XML元素属性值设置一个或多个指定名称的bean属性成员的值.属性名和属性成员名是放在String[]数组中传递给规则的。(典型地,用于处理如<article page=“10”>这种XML构造)
· BeanPropertySetterRule:对顶层Bean,将命名属性成员的值设置为当前XML元素的附加字符数据。(例如:<page>10</page>)
· SetPropertyRule:设置顶层bean的一个属性成员。属性成员名称和要设置的值都通过当前XML元素给出。(例如:<article key=”page” value=”10”/>)
父子关系管理
· SetNextRule:将对象从栈顶弹出并作为参数传给上一个入栈的对象(目前在栈顶)的指定方法 – 通常用于将一个已经完成构建的bean插入其父对象中。
· SetTopRule:把栈中栈顶往下第二个对象传给栈顶的对象。此规则对于那些公开一个setParent方法的对象有用,比起其他方法的话。
· SetRootRule:调用栈底对象的一个方法,栈顶的对象作为参数。
任意方法调用
· callMethodRule:调用栈顶对象的任意方法,可以使用任意参数集,参数集合将在后续的CallParamRule规则使用中给出。
· CallParamRule:描述方法的参数值。参数值要么从命名的XML元素属性提取,要么从当前元素内含的纯字符数据提取。此规则需要在参数列表中通过整数下标指定其位置。
后略。。。