Digester是apache開源項目Commons中的一個子項目,是一款解析處理XML文檔的工具。現在Java領域中流傳了很多有關處理XML文
檔解析的工具,除官方(Sun)的標準的SAX(最新版本2.0),DOM(最新版本3.0,在Tiger版本中集成)外[JAXP只是Sun定義的一組
規範接口],其他開源不泛多多,比如Jdom,Dom4j,Castor等等,包括這款Apache的digester。說到這裏,你不得不佩服開源組織
的強大智慧的結晶,digester處理XML文檔基於XML節點樹Path的規則,實在給人一種賞心悅目之感,這也是偶一直對其情有獨鍾的最大理由。廢
話免了,轉入正題。
剛說到Digester處理是基本類似於XML文檔樹節點遍歷的規則來進行處理,底層處理是採用了SAX,基於事件驅動的模式。舉個例子:
<Company>
<Technology>
<name length="4">Corx</name>
<date>2005.06.27</date>
</Technology>
<Product>
<name length="4">Kxcp</name>
<date>2004.12.29</date>
</Product>
</Company>
在digester中,定義了一些規則(rule),對遍歷的節點path預先對應好要處理的規則,即當解析器遍歷到某個節點的時候,如果發現當前節點有對應的處理規則,調用相應的rule進行處理。舉個例子:
Company/Technology -> ObjectCreatedRule //對象創建規則
Company/Technology/name -> BeanPropertySetterRule //屬性存取規則
...
對以上的解釋可能還不太明白,不要着急,下面詳細解釋一下digester的基本原理,喝杯咖啡,慢慢來~
首先看看org.apache.commons.digester.Digester這個類,查看source發現Digester本身繼承了
DefaultHandler句柄,DefaultHandler句柄是SAX中基於時間驅動的缺省的句柄實現(包含ContentHandler,
ErrorHandler, EntityResolver,
DTDHandler),這個句柄不用多介紹了吧,相信用過SAX的哥們都明白。:)。剛剛不是說到了Rule了嘛,digester中定義了一個規則處
理接口org.apache.commons.digester.Rule,此接口類似於ContentHandler接口中的方法,稍稍有點不同,主要
有begin(), body(), end(),
finish()方法。而digester缺省定義了許多有效的常用規則,每個規則都實現這個接口,
如果沒有什麼特殊需求,一般這些規則是夠用了,羅列一下:BeanPropertySetterRule, CallMethodRule,
CallParamRule, FactoryCreateRule, NodeCreateRule, ObjectCreateRule,
ObjectParamRule,PathCallParamRule, SetNestedPropertiesRule,
SetNextRule, SetPropertiesRule, SetPropertyRule, SetRootRule,
SetTopRule,這些規則的意思稍後說。同時,對這些規則,digester還定義了一個規則的容器接口Rules(抽象類),這個抽象類接口容器
容納規則,並定義了規則匹配的模式,digester實現了一個基本的匹配模式RulesBase,簡要看看這個實現中的兩個最重要的方法:
.....
public void add(String pattern, Rule rule) {
// to help users who accidently add '/' to the end of their patterns
int patternLength = pattern.length();
if (patternLength>1 && pattern.endsWith("/"
) {
pattern = pattern.substring(0, patternLength-1);
}
List list = (List) cache.get(pattern);
if (list == null) {
list = new ArrayList();
cache.put(pattern, list);
}
list.add(rule);
rules.add(rule);
if (this.digester != null) {
rule.setDigester(this.digester);
}
if (this.namespaceURI != null) {
rule.setNamespaceURI(this.namespaceURI);
}
}
還有一個方法:
...
public List match(String namespaceURI, String pattern) {
// List rulesList = (List) this.cache.get(pattern);
List rulesList = lookup(namespaceURI, pattern);
if ((rulesList == null) || (rulesList.size() < 1)) {
// Find the longest key, ie more discriminant
String longKey = "";
Iterator keys = this.cache.keySet().iterator();
while (keys.hasNext()) {
String key = (String) keys.next();
if (key.startsWith("*/"
) {
if (pattern.equals(key.substring(2)) ||
pattern.endsWith(key.substring(1))) {
if (key.length() > longKey.length()) {
// rulesList = (List) this.cache.get(key);
rulesList = lookup(namespaceURI, key);
longKey = key;
}
}
}
}
}
if (rulesList == null) {
rulesList = new ArrayList();
}
return (rulesList);
}
...
以上基本實現只是digester默認匹配規則,如果你要更換自己的規則匹配模式,則只需要繼承
org.apache.commons.digester.Rules接口,定義自己的匹配方式,digester同時還給我們提供了一個比較複雜,不過
非常常用的匹配模式,那就是通配符匹配模式,引入了”!“、”*“、”?“三個符號進行通配的匹配模式,這個類就是
org.apache.commons.digester.ExtendsBaseRules,後續再說。
digester就是通過以上的幾種接口組件,同時配合操作數棧,進行XML解析。具體說,就是在parse
XML文檔之前,預先向容器集合(默認就是RulesBase容器)對XML文檔中的節點path注入匹配規則,然後在parse文檔的時候,遭遇到節點
時時,調用SAX句柄中相應的方法,配合操作數棧,根據定義好的匹配模式,調用相應規則中的方法,將XML序列化成Java
Object。介紹有點抽象,沿用digester本身帶的例子介紹一下:
...
//對如下的XML文檔
<address-book>
<person id="1" category="acquaintance">
<name>Gonzo</name>
<email type="business">[email protected]</email>
</person>
<person id="2" category="rolemodel">
<name>Kermit</name>
<email type="business">[email protected]</email>
<email type="home">[email protected]</email>
</person>
</address-book>
...
...
Digester digester = new Digester();
AddressBook book = new AddressBook();
d.push(book); //將AddressBook實例壓入堆棧
digester.addObjectCreate("address-book/person",
Person.class);//對person節點注入對象創建規則,即在SAX的事件遭遇到person節點的時候,創建Person類的實例,並壓
入堆棧,此時堆棧中從棧頂到棧底分別爲AddressBook實例,Person類實例。
digester.addSetProperties("address-book/person"
;//
對person節點注入屬性設置規則,即在SAX的事件遭遇到person節點中的Attributes時,根據屬性列表中的屬性值對,這兒就是
id="1",
category="acquaintance",使用Java反射(reflection)機制,調用當前棧頂對象即Person實例類中id、
category屬性的標準的JavaBean方法,setId, setCategory。
digester.addSetNext("address-book/person", "addPerson"
;//
對person節點注入父節點方法調用規則,即在SAX事件遭遇到person節點的時候,調用棧中Person實例的父實例中的addPerson方
法。d.addCallMethod("address-book/person/name", "setName",
0);//對name節點注入方法調用規則,調用當前棧頂對象即Person實例中的setName方法,而此方法的參數即是當前name節點的字符內
容。通常這個規則和addCallParam規則配合使用,這兒是一種特殊情況。
digester.addCallMethod("address-book/person/email", "addEmail",
2);//對email節點注入方法調用規則,調用當前棧頂對象即Person實例中的addEmail方法,此方法需要兩個參數,一個是從屬性值的
type屬性獲取,一個是從email本身的字符內容獲取。
digester.addCallParam("address-book/person/email", 0, "type"
;//對email節點注入參數調用規則,將當前節點的type屬性值壓入方法操作數棧
digester.addCallParam("address-book/person/email", 1);//對email節點注入參數調用規則,將當前節點的字符屬性值壓入方法操作數棧。
System.out.println(book);//打印book中值。。
...
通過以上註釋應該不難理解吧。
下面再對以上所說的幾種常用規則作一個詳細的介紹:
ObjectCreateRule:這個規則比較簡單,此規則就是對指定的模式創建一個類的實例,並將當前實例壓入堆棧,並且在遭遇元素結束
時,將當前的棧頂實例彈出棧。
對應Digester中有關這個規則的Javadoc方法說明:
addObjectCreate(java.lang.String pattern, java.lang.Class clazz) - 方法參數說明了一切
addObjectCreate(java.lang.String pattern, java.lang.String className) - 同上
addObjectCreate(java.lang.String pattern, java.lang.String attributeName, java.lang.Class clazz) - 這個稍微解釋一下,
多了一個參數attributeName,這個參數的意思就是如果在當前匹配模式的節點中定義了屬性,則默認就採用這個attributeName所
對應的值來加載實例。比如以上面的例子加入person元素還有一個屬性, class="test.org.apache.commons.digester.Person1",
則此規則會加載Person1實例而不是Person實例。明白?!
addObjectCreate(java.lang.String pattern, java.lang.String className, java.lang.String attributeName) -同上
FactoryCreateRule:這個規則是基於工廠模式創建指定模式的一個類的實例。跟ObjectCreateRule類似,不同的是其參數Class繼承了
ObjectCreationFactory接口。此接口中有個方法createObject(Attributes attrs),創建類的實例,在規則中將此類的實例壓入堆棧。
對應Digester中有關這個規則的Javadoc方法說明:
addFactoryCreate(java.lang.String pattern, java.lang.Class clazz) - 一目瞭然,不用說了。只是clazz必須是實現了
ObjectCreationFactory接口的類。
addFactoryCreate(java.lang.String pattern, java.lang.Class clazz, boolean ignoreCreateExceptions) - 同上,多了一個參
數,ignoreCreateExceptions表明是否忽略在創建類的過程中忽略拋出的exception。
addFactoryCreate(java.lang.String pattern, java.lang.Class clazz, java.lang.String attributeName) - 稍微介紹一下這裏
的attributeName參數,這個參數跟ObjectCreationRule規則中的attributeName雷同,不同的是這個屬性的值必須是實現了接口
ObjectCreationFactory接口的類。
addFactoryCreate(java.lang.String pattern, java.lang.Class
clazz, java.lang.String attributeName, boolean ignoreCreateExceptions)
- 同上
addFactoryCreate(java.lang.String pattern, ObjectCreationFactory creationFactory) - 同上
addFactoryCreate(java.lang.String pattern, ObjectCreationFactory creationFactory, boolean ignoreCreateExceptions) - 同上
addFactoryCreate(java.lang.String pattern, java.lang.String className) - 同上
addFactoryCreate(java.lang.String pattern, java.lang.String className, boolean ignoreCreateExceptions) - 同上
addFactoryCreate(java.lang.String pattern, java.lang.String className, java.lang.String attributeName) - 同上
addFactoryCreate(java.lang.String pattern, java.lang.String
className, java.lang.String attributeName, boolean
ignoreCreateExceptions)
BeanPropertySetterRule:Bean屬性設置規則,對匹配當前指定模式的元素設置bean屬性同名或者指定屬性的值。
對應Digester中有關這個規則的Javadoc方法說明:
addBeanPropertySetter(java.lang.String pattern) - 對匹配當前指定模式的元素設置bean屬性同名屬性的值。
addBeanPropertySetter(java.lang.String pattern, java.lang.String propertyName) - 對匹配當前指定模式的元素設置bean屬性指定屬性的值。
CallMethodRule:方法調用規則,對匹配當前指定模式的元素,初始化指定的方法類型和方法參數,並壓入堆棧。此規則需要和CallMethodRule
規則配合使用:
對應Digester中有關這個規則的Javadoc方法說明:
addCallMethod(java.lang.String pattern, java.lang.String methodName) - 初始化指定的方法,只是當前方法不需要任何參數。
addCallMethod(java.lang.String pattern, java.lang.String methodName, int paramCount) - 初始化指定的方法,參數個數爲
paramCount,參數類型缺省爲java.lang.String。注意有種特殊情況,就是paramCount爲0的時候,默認使用當前元素的字符數據作爲
參數值。
addCallMethod(java.lang.String pattern, java.lang.String methodName, int paramCount, java.lang.Class[] paramTypes) -
同上,只是有指定的參數類型.
addCallMethod(java.lang.String pattern, java.lang.String methodName, int paramCount, java.lang.String[] paramTypes) -
同上
CallParamRule:提供CallMethodRule規則所需要的參數,必須跟CallMethodRule配合使用。
對應Digester中有關這個規則的Javadoc方法說明:
addCallParam(java.lang.String pattern, int paramIndex) - 使用匹配當前指定模式元素的字符數據作爲索引爲paramIndex的參數值.
addCallParam(java.lang.String pattern, int paramIndex, boolean fromStack) - 從操作數棧中,默認取出棧頂對象作爲索引
爲paramIndex的參數值.
addCallParam(java.lang.String pattern, int paramIndex, int stackIndex) - 從操作數棧中,取出從棧頂數第stackIndex + 1個
對象作爲索引爲paramIndex的參數值.
addCallParam(java.lang.String pattern, int paramIndex, java.lang.String attributeName) - 使用屬性attrbuteName的值作爲
索引爲paramIndex的參數值.
PathCallParamRule:提供當前匹配的模式路徑作爲方法調用所需要的參數,配合CallMethodRule使用。
對應Digester中有關這個規則的Javadoc方法說明:
addCallParamPath(java.lang.String pattern, int paramIndex) - 指定索引爲paramIndex的值爲當前匹配模式的路徑.
ObjectParamRule;指定對象作爲指定索引的值,配合CallMethodRule使用。
對應Digester中有關這個規則的Javadoc方法說明:
addObjectParam(java.lang.String pattern, int paramIndex, java.lang.Object paramObj) - 指定索引爲paramIndex的值爲給定
的對象的值.
SetNestedPropertiesRule:當前匹配模式的直接子元素和對應bean的屬性之間的映射.
對應Digester中有關這個規則的Javadoc方法說明:
addSetNestedProperties(java.lang.String pattern) - 默認當前匹配模式的元素的直接子元素和bean中對應屬性之間值的映射
addSetNestedProperties(java.lang.String pattern, java.lang.String[] elementNames, java.lang.String[] propertyNames) -
當前匹配模式的直接子元素集和bean中屬性集之間的映射
addSetNestedProperties(java.lang.String pattern, java.lang.String elementName, java.lang.String propertyName) -
當前匹配模式的直接子元素和bean中屬性之間的映射
SetNextRule:匹配當前模式時,將棧頂對象作爲次棧頂對象中指定方法的參數。
對應Digester中有關這個規則的Javadoc方法說明:
addSetNext(java.lang.String pattern, java.lang.String methodName) - 指定次棧頂元素的方法名稱,將棧頂對象作爲指定方法的參數。
addSetNext(java.lang.String pattern, java.lang.String methodName,
java.lang.String paramType) - 指定次棧頂元素的方法名稱和參數類型,將棧頂對象作爲指定方法的參數。
SetPropertiesRule:匹配當前模式的元素的屬性與棧頂對象中同名或者指定對應關係的屬性值。
對應Digester中有關這個規則的Javadoc方法說明:
addSetProperties(java.lang.String pattern) - 指定棧頂對象屬性的值爲當前匹配元素中同名元素屬性的值。
addSetProperties(java.lang.String pattern, java.lang.String[]
attributeNames, java.lang.String[] propertyNames) -
對應棧頂對象屬性的值爲指定的當前匹配元素中元素屬性的值
addSetProperties(java.lang.String pattern, java.lang.String attributeName, java.lang.String propertyName) - 同上
SetPropertyRule:不常用的一個規則,主要用於key-value值對。一個元素屬性爲棧頂對象的屬性,一個元素屬性爲棧頂對象的屬性的值。
對應Digester中有關這個規則的Javadoc方法說明:
addSetProperty(java.lang.String pattern, java.lang.String name,
java.lang.String value) -
name和value都是匹配當前模式的元素屬性,name的值是棧頂對象中同名的屬性名稱,而value的值是棧頂對象中屬性名稱爲name的值。(好
像有點繞口)
SetRootRule:將當前棧頂對象作爲根對象中指定方法的參數。
對應Digester中有關這個規則的Javadoc方法說明:
addSetRoot(java.lang.String pattern, java.lang.String methodName) - 將當前棧頂對象作爲根對象中指定爲methodName方法的參數。
addSetRoot(java.lang.String pattern, java.lang.String methodName, java.lang.String paramType) - 同上,只是多了一個方法參數的類型
SetTopRule:與SetNextRule正好想法,是將次棧頂元素作爲棧頂元素指定方法的參數。
對應Digester中有關這個規則的Javadoc方法說明:
addSetTop(java.lang.String pattern, java.lang.String methodName) - 將次棧頂元素作爲棧頂元素指定爲methodName方法的參數。
addSetTop(java.lang.String pattern, java.lang.String methodName, java.lang.String paramType) - 同上,只是多了一個方法參數的類型
前面介紹的基本就是digester的常用主要用法,正常來說足夠了!不過爲了提供一些額外更強大的擴展,digester提供了擴展的通配符匹配規則,
更強大也更方便!那就是ExtendedBaseRules,這個類擴展了基本的匹配規則RulesBase,提供了更通用的通配符匹配規則,以下簡要介
紹一下:
首先說一下基本的匹配模式,有三種:
Parent Match(可以理解爲匹配子元素的精確父匹配):a/b/c/? */a/b/c/?
Ancester Match(可以理解爲匹配那種出身自一個精確序列元素的元素):a/b/* */a/b/*
Universal Wildcard Match(可以理解爲通配符匹配,都以!開頭):!*a/b !a/b/? !*a/b/? !a/b/* !*/a/b/*
Wild Match (可以理解爲更通用更模糊的通配符匹配):* !*
?代表直接子元素
* 代表任意的父或子元素
! 代表以什麼什麼爲開頭
舉個例子:
Digester digester = new Digester();
digester.setRules(new ExtendedBaseRules());
digester.setValidating(false);
digester.addObjectCreate("!*/b", BetaBean.class);
digester.addObjectCreate("!*/a", AlphaBean.class);
digester.addObjectCreate("root", ArrayList.class);
digester.addSetProperties("!*"
;
digester.addSetNext("!*/b/?", "setChild"
;
digester.addSetNext("!*/a/?", "setChild"
;
digester.addSetNext("!root/?", "add"
;
ArrayList root =
(ArrayList) digester.parse(getInputStream("Test4.xml"
);
assertEquals("Wrong array size", 2, root.size());
AlphaBean one = (AlphaBean) root.get(0);
assertTrue(one.getChild() instanceof BetaBean);
BetaBean two = (BetaBean) one.getChild();
assertEquals("Wrong name (1)", two.getName() , "TWO"
;
assertTrue(two.getChild() instanceof AlphaBean);
AlphaBean three = (AlphaBean) two.getChild();
assertEquals("Wrong name (2)", three.getName() , "THREE"
;
BetaBean four = (BetaBean) root.get(1);
assertEquals("Wrong name (3)", four.getName() , "FOUR"
;
assertTrue(four.getChild() instanceof BetaBean);
BetaBean five = (BetaBean) four.getChild();
assertEquals("Wrong name (4)", five.getName() , "FIVE"
;
Test4.xml文件:
<root>
<a name="ONE">
<b name="TWO">
<a name="THREE"/>
</b>
</a>
<b name="FOUR">
<b name="FIVE"/>
</b>
</root>