1. 概述
解析XML一直都是編寫程序的頭疼問題,不是因爲它難,而是因爲各種地方需要對解析XML的結果要求不同,XML解析和業務邏輯融合在一起,所以每次解析時都感覺是從頭開始,沒有一套好用的類庫。
在大多數應用系統中,都需要保存一個或多個配置信息,這些信息可以保存在數據庫中,也可以保存在文件中,保存XML文件中是一個不錯的選擇,如下:
<?xml version="1.0" encoding="GB18030"?> <SystemConfig> <LinesPerPage>20</LinesPerPage> <ThreadPool> <MaxNum>10</MaxNum> <MinNum>5</MinNum> </ThreadPool> </SystemConfig> |
您可能希望在系統運行時,直接從一個JavaBean中讀取系統配置信息,JavaBean的結構如下(省略了get/set):
package bean;
public class SystemConfig { private int linesPerPage = 0;
private int threadPoolMaxNum = 0;
private int threadPoolMinNum = 0; } |
如果簡單的將SystemConfig.xml和SystemConfig關聯起來呢?commons-digester是一個不錯的選擇。
2. Commons-digester介紹
2.1. 概述
Commons-digester是apache的開源類庫,最初是struts的一部分,目的是讀取struts中的一系列XML文件(如struts-config.xml),後來經過擴充和重構,變成了一個獨立的開源庫。
大家都知道,使用struts作爲表示層的應用程序往往都包含多個XML文件,每個XML文件的內容都不同,如果爲每種文件編寫一個XML解析器,將大大加重程序維護的難度,最終可能因爲XML解析方式無法維護,導致這個開源項目失敗。但struts的開發者很聰明,他們想到了將XML解析抽取出來,統一維護的方式,於是commons-disgester的前身就誕生了。
Commons-digester可以通過幾行簡單的代碼,將XML文件轉換爲JavaBean或其他需要的格式。其中,直接轉換爲JavaBean是最常用的。
2.2. 基本原理
Commons-digester將一個XML文件看作一個抽象的棧,當讀到一個開始標籤時,將標籤內容壓棧,當讀取結束標籤時,將棧頂元素出棧,同時執行所有的規則(Rule),以此類推,完成整個XML文件的讀取。
3. SystemConfig.xml解析方法
基本原理可能會讓廣大同行迷惑,直接看一些SystemConfig.xml文件如何解析吧。
解析XML文件的代碼如下,junit部分只是檢驗解析結果是否正確:
import java.io.File; import java.io.IOException;
import junit.framework.TestCase;
import org.apache.commons.digester.Digester; import org.xml.sax.SAXException;
import bean.SystemConfig;
public class SystemConfigTest extends TestCase { public void testSystemConfig() throws IOException, SAXException { // 生成digester對象 Digester digester = new Digester();
// 當遇到<SystemConfig>標籤時生成SystemConfig對象 digester.addObjectCreate("SystemConfig", SystemConfig.class);
// 當遇到<SystemConfig><LinesPerPage>標籤時爲SystemConfig中的linesPerPage屬性賦值 digester.addBeanPropertySetter("SystemConfig/LinesPerPage","linesPerPage");
// 當遇到<SystemConfig><ThreadPool><MaxNum>標籤時爲SystemConfig中的threadPoolMaxNum屬性賦值 digester.addBeanPropertySetter("SystemConfig/ThreadPool/MaxNum","threadPoolMaxNum");
// 當遇到<SystemConfig><ThreadPool><MinNum>標籤時爲SystemConfig中的threadPoolMinNum屬性賦值 digester.addBeanPropertySetter("SystemConfig/ThreadPool/MinNum","threadPoolMinNum");
// 讀取SystemConfig.xml文件 File systemConfigXml = new File("SystemConfig.xml"); // 進行文件解析 SystemConfig sysConfig = (SystemConfig)digester.parse(systemConfigXml);
// 以下的測試用例用來判斷解析結果是否正確 assertNotNull(sysConfig); assertEquals(20, sysConfig.getLinesPerPage()); assertEquals(10, sysConfig.getThreadPoolMaxNum()); assertEquals(5, sysConfig.getThreadPoolMinNum()); } } |
要解析一個XML文件,需要先定義好解析規則,然後使用這些預定義規則解析XML文件即可。在上面的例子中,因爲解析的結果是將XML中的屬性值保存到Bean中,所以直接使用addBeanPropertySetter()方法就可以了。
從上面的例子可以看出,解析一個XML文件,主要有以下三步:
1. 創建Digester對象。
2. 向Digester對象中添加解析規則。
3. 使用解析規則解析文件。
4. XML文件解析規則
Commons-digester將繁瑣的解析過程變成了簡單的三個步驟,其實解析不同XML文件時,只有添加規則一步有一些差別,其他兩部只要照搬上面的代碼即可。
規則是XML文件的解析方法,也就是Digester對象在遇到不同XML元素時需要執行的操作。比如上面例子中的BeanPropertySetter規則,就是將XML內容保存到Bean的相應元素中。
規則可以自定義,但在一般情況下,默認的一套規則足以滿足大多數場合的需要。
5. 更復雜的例子
下面我們來看一個更復雜的例子,在這個例子中,使用commons-digester解析一個web.xml文件,這個文件是我自己寫的一個真實項目中的一小部分。
Web.xml文件如下:
<?xml version="1.0" encoding="GB18030"?>
<web-app> <display-name>Struts Examples Application</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring-beans-config.xml</param-value> </context-param>
<listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>
<listener> <listener-class> com.lijin.demo.common.listener.SpringConfigListener </listener-class> </listener>
<filter> <filter-name>encodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter>
<servlet> <servlet-name>action</servlet-name> <servlet-class> org.apache.struts.action.ActionServlet </servlet-class>
<init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-configs/common-config.xml</param-value> </init-param> <init-param> <param-name>config/jsp/user</param-name> <param-value>/WEB-INF/struts-configs/user-config.xml</param-value> </init-param> <init-param> <param-name>config/jsp/usergroup</param-name> <param-value>/WEB-INF/struts-configs/usergroup-config.xml</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet>
<servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
<filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>*.html</url-pattern> </filter-mapping>
<filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping>
<filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping>
<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>
<taglib> <taglib-uri>http://www.lijin.com/tags</taglib-uri> <taglib-location>/WEB-INF/tld/lijin.tld</taglib-location> </taglib> </web-app> |
解析該文件的例子如下:
import java.io.File; import java.io.IOException;
import junit.framework.TestCase;
import org.apache.commons.digester.Digester; import org.xml.sax.SAXException;
import bean.Filter; import bean.Servlet; import bean.WebApp;
public class FirstTest extends TestCase { public void testWebXml() throws IOException, SAXException { Digester digester = new Digester(); digester.addObjectCreate("web-app", WebApp.class); digester.addBeanPropertySetter("web-app/display-name", "displayName");
digester.addCallMethod("web-app/context-param", "addContextParam", 2); digester.addCallParam("web-app/context-param/param-name", 0); digester.addCallParam("web-app/context-param/param-value", 1);
digester.addCallMethod("web-app/listener", "addListener", 1); digester.addCallParam("web-app/listener/listener-class", 0);
digester.addObjectCreate("web-app/filter", Filter.class); digester.addSetNext("web-app/filter", "addFilter"); digester.addBeanPropertySetter("web-app/filter/filter-name", "filterName"); digester.addBeanPropertySetter("web-app/filter/filter-class", "filterClass"); digester.addCallMethod("web-app/filter/init-param", "addInitParam", 2); digester.addCallParam("web-app/filter/init-param/param-name", 0); digester.addCallParam("web-app/filter/init-param/param-value", 1);
digester.addObjectCreate("web-app/servlet", Servlet.class); digester.addSetNext("web-app/servlet", "addServlet"); digester.addBeanPropertySetter("web-app/servlet/servlet-name", "servletName"); digester.addBeanPropertySetter("web-app/servlet/servlet-class", "servletClass"); 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); digester.addBeanPropertySetter("web-app/servlet/load-on-startup", "loadOnStartUp");
digester.addCallMethod("web-app/servlet-mapping", "addServletMapping", 2); digester.addCallParam("web-app/servlet-mapping/servlet-name", 0); digester.addCallParam("web-app/servlet-mapping/url-pattern", 1);
digester.addCallMethod("web-app/filter-mapping", "addFilterMapping", 2); digester.addCallParam("web-app/filter-mapping/filter-name", 0); digester.addCallParam("web-app/filter-mapping/url-pattern", 1);
digester.addCallMethod("web-app/welcome-file-list/welcome-file", "addWelcomeFile", 1); digester.addCallParam("web-app/welcome-file-list/welcome-file", 0);
digester.addCallMethod("web-app/taglib", "addTaglib", 2); digester.addCallParam("web-app/taglib/taglib-uri", 0); digester.addCallParam("web-app/taglib/taglib-location", 1);
File webXml = new File("web.xml"); WebApp webApp = (WebApp)digester.parse(webXml);
assertNotNull(webApp); assertEquals("Struts Examples Application", webApp.getDisplayName());
assertEquals(1, webApp.getContextParamList().size()); assertEquals("contextConfigLocation", webApp.getContextParamList().get(0).getParamName()); assertEquals("/WEB-INF/spring-beans-config.xml", webApp.getContextParamList().get(0).getParamValue());
assertEquals(2, webApp.getListenerList().size()); assertEquals("org.springframework.web.context.ContextLoaderListener", webApp.getListenerList().get(0)); assertEquals("com.lijin.demo.common.listener.SpringConfigListener", webApp.getListenerList().get(1));
assertEquals(1, webApp.getFilterMap().size()); assertEquals("encodingFilter", webApp.getFilterMap().get("encodingFilter").getFilterName()); assertEquals("org.springframework.web.filter.CharacterEncodingFilter", webApp.getFilterMap().get("encodingFilter").getFilterClass()); assertEquals("UTF-8", webApp.getFilterMap().get("encodingFilter").getEncoding()); assertEquals(true, webApp.getFilterMap().get("encodingFilter").isForceEncoding());
assertEquals(1, webApp.getServletMap().size()); assertTrue(webApp.getServletMap().containsKey("action")); assertEquals("action", webApp.getServletMap().get("action").getServletName()); assertEquals("org.apache.struts.action.ActionServlet", webApp.getServletMap().get("action").getServletClass()); assertEquals(3, webApp.getServletMap().get("action").getConfigMap().size()); assertTrue(webApp.getServletMap().get("action").getConfigMap().containsKey("config")); assertTrue(webApp.getServletMap().get("action").getConfigMap().containsKey("config/jsp/user")); assertTrue(webApp.getServletMap().get("action").getConfigMap().containsKey("config/jsp/usergroup")); assertEquals("/WEB-INF/struts-configs/common-config.xml", webApp.getServletMap().get("action").getConfigMap().get("config")); assertEquals("/WEB-INF/struts-configs/user-config.xml", webApp.getServletMap().get("action").getConfigMap().get("config/jsp/user")); assertEquals("/WEB-INF/struts-configs/usergroup-config.xml", webApp.getServletMap().get("action").getConfigMap().get("config/jsp/usergroup")); assertEquals(webApp.getServletMap().get("action").getLoadOnStartUp(), 2);
assertEquals(1, webApp.getServletMap().get("action").getMappingUrlList().size()); assertTrue(webApp.getServletMap().get("action").getMappingUrlList().contains("*.do"));
assertEquals(1, webApp.getFilterMap().size()); assertEquals(3, webApp.getFilterMap().get("encodingFilter").getMappingUrlList().size()); assertTrue(webApp.getFilterMap().get("encodingFilter").getMappingUrlList().contains("*.html")); assertTrue(webApp.getFilterMap().get("encodingFilter").getMappingUrlList().contains("*.do")); assertTrue(webApp.getFilterMap().get("encodingFilter").getMappingUrlList().contains("*.jsp"));
assertEquals(1, webApp.getWelcomeFileList().size()); assertTrue(webApp.getWelcomeFileList().contains("index.jsp"));
assertEquals(1, webApp.getTagLibMap().size()); assertTrue(webApp.getTagLibMap().containsKey("http://www.lijin.com/tags")); assertEquals("/WEB-INF/tld/lijin.tld", webApp.getTagLibMap().get("http://www.lijin.com/tags")); }
}
|
其中用到了bean.Filter,bean.Servlet和bean.WebApp三個JavaBean,他們都是配合這個例子的標準JavaBean,都可以根據上述文件的結構推倒出來,在這裏不贅述。
6. 學習這個類庫的心得
Commons-Digester爲我們解析XML文件提供了一個新思路和新方法,在研究的規程中,我曾經想過爲什麼只有讀取文件的類,沒有寫文件的類,後來仔細一想,才明白設計這個類庫的目的是簡化XML文件的讀取,相反,寫XML文件比讀取容易的多,因爲整個寫文件的過程都在自己的控制之下,能輸出什麼格式自己心裏都有數,讀取就沒那麼容易了需要進行各種各樣的校驗。實際使用時,在對這套類庫進行簡單的封裝後,就可以實現讀寫XML配置文件的功能了。