前言
在許多需要處理XML格式數據的應用環境中, 如果能夠以“事件驅動”的方式來處理XML文檔,比如,當識別出特定的XML元素時,觸發“創建對象”操作事件(或者觸發調用對象的方法事件),這對於應用程序開發來說,是非常有用的;
熟悉以SAX(Simple API for XML Parsing)方式來處理XML文檔的開發人員會認識到,Digester為SAX事件提供了更高層次,對開發者更加友好的接口,它隱藏了大部分導航XML元素層次結構的細節,以便於開發者更加專注於要執行的處理操作;
使用Digester的基本步驟
- 創建一個 org.apache.commons.digester3.Digester 類的實例對象。這里補充說明下,只要我們已經完成XML解析操作,並且不在多個線程中使用同一個Digester對象,那么就可以安全的重復使用我們預先創建的這個Digester實例;不過重用Digester實例並不是非常推薦,最好每個XML解析對應一個單獨的Digester實例;
- 為Digester實例配置屬性值,通過配置屬性值,我們可以改變Digester 的解析行為,具體有哪些屬性值可以配置,待會會介紹;
- 可選的, 可以將我們的一些初始對象push到Digester棧里;.
- 在輸入的XML文檔中,給所有需要觸發規則(rule)處理的元素匹配模式(pattern)注冊規則;針對任何一個模式,你可以注冊任意數量的規則;補充說明下,如果一個模式對應多個規則,則begin和body事件方法會按照它們注冊的順序依次執行,而end事件方法是倒序執行的;
- 最后,調用digester.parse()方法,該方法需要傳入XML文件的引用作為參數,該參數支持多種格式的文件流;另外需要注意的是,該方法會拋出IOException or SAXException異常,以及各種可能的在規則解析處理時遇到的異常,如NoSuchMethodException、IllegalAccessException…
了解基本步驟后,來看一個簡單的示例,如下所示,是我們即將要解析的xml文件:
<foo name="The Parent"> <bar id="123" title="The First Child" /> <bar id="456" title="The Second Child" /> <bar id="789" title="The Second Child" /> </foo>
首先,創建兩個java bean對應xml中的元素信息:
Foo類

package apache.commons.digester3.example.pojo; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * @author http://www.cnblogs.com/chenpi/ * @version 2017年6月3日 */ public class Foo { private String name; private List<Bar> barList = new ArrayList<Bar>(); public void addBar(Bar bar) { barList.add(bar); } public Bar findBar(int id) { for (Bar bar : barList) { if (bar.getId() == id) { return bar; } } return null; } public Iterator<Bar> getBars() { return barList.iterator(); } /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } /** * @return the barList */ public List<Bar> getBarList() { return barList; } /** * @param barList the barList to set */ public void setBarList(List<Bar> barList) { this.barList = barList; } }
Bar類

package apache.commons.digester3.example.pojo; /** * @author http://www.cnblogs.com/chenpi/ * @version 2017年6月3日 */ public class Bar { private int id; private String title; /** * @return the id */ public int getId() { return id; } /** * @param id the id to set */ public void setId(int id) { this.id = id; } /** * @return the title */ public String getTitle() { return title; } /** * @param title the title to set */ public void setTitle(String title) { this.title = title; } }
使用Digester解析xml:
package apache.commons.digester3.example.simpletest; import java.io.IOException; import org.apache.commons.digester3.Digester; import org.xml.sax.SAXException; import apache.commons.digester3.example.pojo.Bar; import apache.commons.digester3.example.pojo.Foo; /** * * @author http://www.cnblogs.com/chenpi/ * @version 2017年6月3日 */ public class Main { public static void main(String[] args) { try { //1、創建Digester對象實例 Digester digester = new Digester(); //2、配置屬性值 digester.setValidating(false); //3、push對象到對象棧 //digester.push(new Foo()); //4、設置匹配模式、規則 digester.addObjectCreate("foo", "apache.commons.digester3.example.pojo.Foo"); digester.addSetProperties("foo"); digester.addObjectCreate("foo/bar", "apache.commons.digester3.example.pojo.Bar"); digester.addSetProperties("foo/bar"); digester.addSetNext("foo/bar", "addBar", "apache.commons.digester3.example.pojo.Bar"); //5、開始解析 Foo foo = digester.parse(Main.class.getClassLoader().getResourceAsStream("example.xml")); //6、打印解析結果 System.out.println(foo.getName()); for (Bar bar : foo.getBarList()) { System.out.println(bar.getId() + "," + bar.getTitle()); } } catch (IOException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } } }
結果打印:
The Parent
123,The First Child
456,The Second Child
789,The Second Child
注意以上代碼涉及類型的自動轉換,如id屬性,由字符串類型轉為整型,這里所有的類型轉換都是由commons-beanutils包的ConvertUtils來完成的。
Digester屬性配置
org.apache.commons.digester3.Digester實例對象包含若干成員屬性,這些屬性值是可以設置的,以便我們自定義解析操作;
為了讓這些配置在XML解析前生效,這些屬性值的更改一定要在parse方法調用之前設置;
如下是一些可以配置的屬性
Property | Description |
---|---|
classLoader | 通過配置這個屬性值,我們可以指定ObjectCreateRule和FactoryCreateRule規則使用的類加載器;另外,在沒指定該值的時候,如果useContextClassLoader屬性值設為true,則會使用當前線程上下文類加載器,否則直接使用加載Digester類的同一個類加載器; |
errorHandler | 通過配置這個屬性值,我們可以指定一個SAX ErrorHandler,當解析錯誤出現的時候,該Handler會收到通知; |
namespaceAware | 通過設置該boolean值,可以讓Digester在解析的時候,識別出XML命名空間; |
xincludeAware | 通過設置該boolean值,可以讓Digester在解析的時候,識別出語法 ,注意該設置只有在namespaceAware設為true的前提下才有效. |
ruleNamespaceURI | 該值一般配合namespaceAware使用,在namespaceAware設為true的情況下,設置ruleNamespaceURI,那么接下來的規則只會匹配ruleNamespaceURI命名空間下的元素; |
rules | 通過該屬性,我們可以額外添加一些自定義解析匹配規則; |
useContextClassLoader | 如果useContextClassLoader屬性值設為true,則會使用當前線程上下文類加載器,否則直接使用加載Digester類的同一個類加載器 注意 - 如果設置了classLoader屬性值,改屬性配置將被忽略; |
validating | 通過設置該boolean值, 可以配置Digester是否做DTD檢查; |
另外,我們可以通過Digester的register方法,讓Digester在遇到DOCTYPE聲明時,使用本地dtd,而不是從網上獲取,如下所示:
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());
Digester對象棧
Digester使用的一個核心技術就是動態構建一顆java對象樹,在構建的過程中,一個重要的輔助數據結構即對象棧;
以如下xml為例:
<foo name="The Parent"> <bar id="123" title="The First Child" /> <bar id="456" title="The Second Child" /> <bar id="789" title="The Second Child" /> </foo>
在解析的時候:
首先會創建一個foo對象,並壓入對象棧,然后設置foo屬性值name,緊接着,創建bar對象並壓入棧,然后設置bar的屬性值,然后將該bar對象添加的到foo對象的barlist屬性集合中,然后bar對象彈出對象棧;
以此類推,遇到起始標記的元素創建對象入棧,遇到結尾標記的元素做出棧操作,出棧前,需要將出棧對象並關聯到上一個棧頂對象;
最終,解析完xml后,留在棧頂的就關聯了所有在xml解析中創建的動態對象了;
Digester暴露出的與對象棧操作API如下所示:
Digester元素匹配模式
Digester的一個關鍵特性是可以自動識別xml的層次結構,程序員只需要關心遇到匹配到某個元素后需要做哪些操作即可;
如下是一個示例,其中a, a/b, a/b/c為匹配模式,對應xml中特定位置的元素:
<a> -- Matches pattern "a" <b> -- Matches pattern "a/b" <c/> -- Matches pattern "a/b/c" <c/> -- Matches pattern "a/b/c" </b> <b> -- Matches pattern "a/b" <c/> -- Matches pattern "a/b/c" <c/> -- Matches pattern "a/b/c" <c/> -- Matches pattern "a/b/c" </b> </a>
Digester規則處理
當匹配到模式時,會觸發規則處理,具體的規則處理機制是由這個org.apache.commons.digester3.Rule接口封裝的,該接口定義了以下幾個方法:
- begin() - 匹配到xml元素開始標記時,調用該方法;
- body() - 匹配到xml元素body時,調用該方法;
- end() - 匹配到xml元素結束標記時,調用該方法;
- finish() - 當所有解析方法解析完畢后,調用該方法,用於清楚臨時數據等;
默認情況下,Digester提供了以下Rule接口的實現類,我們在編碼的時候可以直接使用,詳見API文檔:
- ObjectCreateRule
- FactoryCreateRule
- SetPropertiesRule
- SetPropertyRule
- SetNextRule
- SetTopRule
- CallMethodRule
- CallParamRule
- NodeCreateRule
如下是一個SetNextRule規則實現類的示例(兩種寫法):
Rule rule = new SetNextRule("addBar",Bar.class); digester.addRule("foo/bar", rule ); //digester.addSetNext("foo/bar", "addBar", Bar.class.getName());
Digester日志
日志是調試、排查錯誤非常關鍵的一個環節,Digester記錄了非常詳細的日志,我們可以按如下方式來開啟日志打印功能;
這里的日志實現選擇log4j,
首先,在pom.xml加上如下依賴:
<!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
然后,編寫一個配置文件log4j.properties放到resources路徑下:
### set log levels ###
log4j.rootLogger = debug, stdout
### \u8F93\u51FA\u5230\u63A7\u5236\u53F0 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
運行程序,發現已經可以看到DEBUG調試日志了日志:
2017-06-04 18:26:33 [ main:51 ] - [ DEBUG ] Fire body() for SetPropertiesRule[aliases={}, ignoreMissingProperty=true]
2017-06-04 18:26:33 [ main:51 ] - [ DEBUG ] Popping body text ''
2017-06-04 18:26:33 [ main:51 ] - [ DEBUG ] Fire end() for SetPropertiesRule[aliases={}, ignoreMissingProperty=true]
2017-06-04 18:26:33 [ main:52 ] - [ DEBUG ] Fire end() for ObjectCreateRule[className=apache.commons.digester3.example.pojo.Foo, attributeName=null]
2017-06-04 18:26:33 [ main:52 ] - [ DEBUG ] [ObjectCreateRule]{foo} Pop 'apache.commons.digester3.example.pojo.Foo'
2017-06-04 18:26:33 [ main:52 ] - [ DEBUG ] endDocument()
The Parent
123,The First Child
456,The Second Child
789,The Second Child
Digester例子
前面我們已經舉了一個Digester的簡單使用例子,這里將繼續展示幾個示例;
解析xml元素body值
如下XML文檔就是我們要解析內容:
<web-app> <servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>application</param-name> <param-value>org.apache.struts.example.ApplicationResources</param-value> </init-param> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> </servlet> </web-app>
首先,定義一個ServletBean,存儲以上xml信息,如下所示:
/* * File Name: ServletBean.java * Description: * Author: http://www.cnblogs.com/chenpi/ * Create Date: 2017年6月4日 */ package apache.commons.digester3.example.pojo; import java.util.HashMap; import java.util.Map; /** * * @author http://www.cnblogs.com/chenpi/ * @version 2017年6月4日 */ public class ServletBean { private String servletName; private String servletClass; private Map<String, String> initParams = new HashMap<String, String>(); public void addInitParam(String paramName, String paramValue){ initParams.put(paramName, paramValue); } /** * @return the servletName */ public String getServletName() { return servletName; } /** * @param servletName the servletName to set */ public void setServletName(String servletName) { this.servletName = servletName; } /** * @return the servletClass */ public String getServletClass() { return servletClass; } /** * @param servletClass the servletClass to set */ public void setServletClass(String servletClass) { this.servletClass = servletClass; } /** * @return the initParams */ public Map<String, String> getInitParams() { return initParams; } /** * @param initParams the initParams to set */ public void setInitParams(Map<String, String> initParams) { this.initParams = initParams; } }
編寫規則解析xml,如下所示:
/* * File Name: Main2.java * Description: * Author: http://www.cnblogs.com/chenpi/ * Create Date: 2017年6月4日 */ package apache.commons.digester3.example.simpletest; import java.io.IOException; import org.apache.commons.digester3.Digester; import org.apache.commons.digester3.Rule; import org.apache.commons.digester3.SetNextRule; import org.xml.sax.SAXException; import apache.commons.digester3.example.pojo.Bar; import apache.commons.digester3.example.pojo.Foo; import apache.commons.digester3.example.pojo.ServletBean; /** * * @author http://www.cnblogs.com/chenpi/ * @version 2017年6月4日 */ public class WebMain { public static void main(String[] args) { try { // 1、創建Digester對象實例 Digester digester = new Digester(); // 2、配置屬性值 digester.setValidating(false); // 3、push對象到對象棧 // 4、設置匹配模式、規則 digester.addObjectCreate("web-app/servlet", "apache.commons.digester3.example.pojo.ServletBean"); digester.addCallMethod("web-app/servlet/servlet-name", "setServletName", 0); digester.addCallMethod("web-app/servlet/servlet-class", "setServletClass", 0); digester.addCallMethod("web-app/servlet/init-param", "addInitParam", 2); digester.addCallParam("web-app/servlet/init-param/param-name", 0); digester.addCallParam("web-app/servlet/init-param/param-value", 1); // 5、開始解析 ServletBean servletBean = digester .parse(ExampleMain.class.getClassLoader().getResourceAsStream("web.xml")); // 6、打印解析結果 System.out.println(servletBean.getServletName()); System.out.println(servletBean.getServletClass()); for(String key : servletBean.getInitParams().keySet()){ System.out.println(key + ": " + servletBean.getInitParams().get(key)); } } catch (IOException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } } }
結果打印:
action
org.apache.struts.action.ActionServlet
application: org.apache.struts.example.ApplicationResources
config: /WEB-INF/struts-config.xml
解析XML命名空間
對於沒有使用命名空間的xml來說,Digester默認的處理機制已經足夠滿足我們的需求了。但是當XML文檔使用命名空間的時候,對於不同命名空間的元素來說,有時候我們希望使用不同的規則去解析它。
Digester 沒有提供對命名空間的完全支持, 但已經足夠完成大多數任務了. 開啟Digester的命名空間支持只需要以下幾個步驟即可:
1、通過配置以下屬性值,告訴 Digester,需要開啟命名空間解析:
digester.setNamespaceAware( true );
2、聲明接下來的規則關聯的命名空間,注意我們這里沒有指明任何前綴,XML文檔的作者是可以使用任何他們喜歡的前綴的
digester.setRuleNamespaceURI( "http://www.mycompany.com/MyNamespace" );
3、添加該命名空間下的規則, 通常會調用 addObjectCreate() 或者 addSetProperties()這類方法. 注意,這里匹配的模式是不需要加上前綴的:
digester.addObjectCreate( "foo/bar", "com.mycompany.MyFoo" );
digester.addSetProperties( "foo/bar");
4. 重復2、3步驟,解析其它命名空間的元素.
如下,是一個示例,使用以上步驟即可完成解析:
<m:foo xmlns:m="http://www.mycompany.com/MyNamespace" xmlns:y="http://www.yourcompany.com/YourNamespace"> <m:bar name="My Name" value="My Value"/> <y:bar id="123" product="Product Description"/> </m:foo>
由於我們給Digester指定的命名空間為http://www.mycompany.com/MyNamespace,所以以上xml只有第一個bar會被解析出來。
如下是一個完整示例(XML及對應解析代碼):
<m:foo xmlns:m="http://www.mycompany.com/MyNamespace" xmlns:y="http://www.yourcompany.com/YourNamespace" name="The Parent"> <m:bar id="123" title="The First Child" /> <y:bar id="456" title="The Second Child" /> <m:bar id="789" title="The Second Child" /> </m:foo>
/* * File Name: Main.java * Description: * Author: http://www.cnblogs.com/chenpi/ * Create Date: 2017年6月3日 */ package apache.commons.digester3.example.simpletest; import java.io.IOException; import org.apache.commons.digester3.Digester; import org.xml.sax.SAXException; import apache.commons.digester3.example.pojo.Bar; import apache.commons.digester3.example.pojo.Foo; /** * * @author http://www.cnblogs.com/chenpi/ * @version 2017年6月3日 */ public class ExampleNSMain { public static void main(String[] args) { try { Digester digester = new Digester(); digester.setValidating(false); digester.setNamespaceAware(true); digester.setRuleNamespaceURI("http://www.mycompany.com/MyNamespace"); digester.addObjectCreate("foo", Foo.class); digester.addSetProperties("foo"); digester.addObjectCreate("foo/bar", Bar.class); digester.addSetProperties("foo/bar"); digester.addSetNext("foo/bar", "addBar", Bar.class.getName()); Foo foo = digester .parse(ExampleNSMain.class.getClassLoader().getResourceAsStream("example_ns.xml")); System.out.println(foo.getName()); for (Bar bar : foo.getBarList()) { System.out.println(bar.getId() + "," + bar.getTitle()); } } catch (IOException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } } }
使用命名空間前綴用於模式匹配
當一個命名空間下的規則集合與另一個命名空間的規則集合相互獨立的話,使用帶命名空間的規則非常有用,但是當我們的規則邏輯需要使用到不同命名空間下的元素時,那么使用帶命名空間前綴的模式將會是一個更好的策略;
很簡單,我們只需要,設置NamespaceAware 屬性為false,然后在模式前面帶上命名空間前綴即可。
比如, (將 NamespaceAware 設為false), 那么模式 m:bar' 只會匹配命名空間前綴為m的bar元素.
如下是一個完整demo:
/* * File Name: Main.java * Description: * Author: http://www.cnblogs.com/chenpi/ * Create Date: 2017年6月3日 */ package apache.commons.digester3.example.simpletest; import java.io.IOException; import org.apache.commons.digester3.Digester; import org.xml.sax.SAXException; import apache.commons.digester3.example.pojo.Bar; import apache.commons.digester3.example.pojo.Foo; /** * * @author http://www.cnblogs.com/chenpi/ * @version 2017年6月3日 */ public class ExampleNS2Main { public static void main(String[] args) { try { Digester digester = new Digester(); digester.setValidating(false); digester.setNamespaceAware(false); //digester.setRuleNamespaceURI("http://www.mycompany.com/MyNamespace"); digester.addObjectCreate("m:foo", Foo.class); digester.addSetProperties("m:foo"); digester.addObjectCreate("m:foo/m:bar", Bar.class); digester.addSetProperties("m:foo/m:bar"); digester.addSetNext("m:foo/m:bar", "addBar", Bar.class.getName()); digester.addObjectCreate("m:foo/y:bar", Bar.class); digester.addSetProperties("m:foo/y:bar"); digester.addSetNext("m:foo/y:bar", "addBar", Bar.class.getName()); Foo foo = digester .parse(ExampleNS2Main.class.getClassLoader().getResourceAsStream("example_ns.xml")); System.out.println(foo.getName()); for (Bar bar : foo.getBarList()) { System.out.println(bar.getId() + "," + bar.getTitle()); } } catch (IOException e) { e.printStackTrace(); } catch (SAXException e) { e.printStackTrace(); } } }
錯誤排查
Digester 是基於 SAX 開發的. Digestion 會拋出兩種類型的 Exception:
- java.io.IOException
- org.xml.sax.SAXException
第一個異常很少會拋出,且該異常眾所周知。 通常我們遇到最多的是第二個異常,當SAX解析無法完成的時候會拋出該異常,所以熟悉SAX的錯誤處理方式對診斷SAXException很有幫助。
當一個SAX 解析器 遇到xml問題時 (哈哈,有時候,會在遇到xml問題之后),會拋出SAXParseException異常. 該異常是SAXException 的子類,並且包含了一些額外信息(哪里出錯,出了什么錯誤),如果我們捕獲了這類異常,那么就可以明確知道問題是XML引起的,而不是Digester或者我們的解析規則。通常來說,捕獲該異常並記錄詳細信息到日志對診斷錯誤非常有幫助。
一般情況下 SAXException 異常內部會組合另一個異常,換句話說,就是當Digester遇到異常的時候,會首先將該異常封裝成一個SAXException異常,然后將該SAXException重新拋出 。所以,捕獲SAXException異常,並仔細檢查被封裝的內部異常,有助於排查錯誤;
錯誤示例:
org.xml.sax.SAXParseException; lineNumber: 4; columnNumber: 44; 元素類型 "y:bar" 必須后跟屬性規范 ">" 或 "/>"。
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1239)
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:648)
at org.apache.commons.digester3.Digester.parse(Digester.java:1642)
at org.apache.commons.digester3.Digester.parse(Digester.java:1701)
at apache.commons.digester3.example.simpletest.ExampleNS2Main.main(ExampleNS2Main.java:48)
參考資料
http://commons.apache.org/proper/commons-digester/guide/core.html
示例代碼
https://github.com/peterchenhdu/apache-commons-digester-example