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文件,主要有以下三步:
- 创建Digester对象。
- 向Digester对象中添加解析规则。
- 使用解析规则解析文件。
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配置文件的功能了。