xml转对象 1.   概述 2.   Commons-digester介绍 3.   SystemConfig.xml解析方法 4.   XML文件解析规则 5.   更复杂的例子 6.   学习这个类库的心得

解析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配置文件的功能了。