1. 程式人生 > >Apache Commons Digester 二(規則模塊綁定-RulesModule、異步解析-asyncParse、xml變量Substitutor、帶參構造方法)

Apache Commons Digester 二(規則模塊綁定-RulesModule、異步解析-asyncParse、xml變量Substitutor、帶參構造方法)

對象 property 解決 space getclass bool trace throw object

前言

上一篇對Digester做了基本介紹,也已經了解了Digester的基本使用方法,接下來將繼續學習其相關特性,本篇主要涉及以下幾個內容:

  1. 規則模塊綁定,通過定義一個RulesModule接口實現類來完成規則的預先綁定,運行時重復使用
  2. 異步解析xml
  3. 解析xml中的變量,如${sys.user}
  4. 使用帶參數的構造方法創建對象,參數來自xml節點數據

規則模塊預先綁定 - RulesModule接口

在此之前,我們使用Digester的基本流程都是每次在程序運行時綁定規則,然後解析;

事實上,我們可以改變Digester的解析流程,啟動的時候預先定義規則集,然後在運行的時候重復使用預先定義的規則;

可能這樣說比較空泛,可以看一下如下一個Web應用場景,應該就會有一個比較深刻的理解了;

servlet場景例子

熟悉Web開發的應該都知道servlet了,這裏就不細說了,假設有一個EmployeeServlet,如下所示:

由於servlet是單例的,而且Digester不是線程安全的,所以我們會在每次請求的的時候,new出一個Digester對象,來保證線程安全,寫法如下:

public class EmployeeServlet
  extends HttpServlet
{

    public void doGet(HttpServletRequest req, HttpServletResponse res)
        
throws ServletException, IOException { Digester digester = new Digester(); digester.setNamespaceAware( true ); digester.setXIncludeAware( true ); digester.addObjectCreate( "employee", Employee.class ); digester.addCallMethod( "employee/firstName", "setFirstName", 0 ); digester.addCallMethod(
"employee/lastName", "setLastName", 0 ); digester.addObjectCreate( "employee/address", Address.class ); digester.addCallMethod( "employee/address/type", "setType", 0 ); digester.addCallMethod( "employee/address/city", "setCity", 0 ); digester.addCallMethod( "employee/address/state", "setState", 0 ); digester.addSetNext( "employee/address", "addAddress" ); Employee employee = digester.parse( openStream( req.getParameter( "employeeId" ) ) ); ... }

我們可以很容易發現以上程序的缺點:代碼沒有復用,每次請求都需重復綁定規則;
不過,我們可以使用RuleSet來解決代碼沒有復用的問題,如下所示,定義一個EmployeeRuleSet規則集實現RuleSet接口:

public class EmployeeRuleSet
  implements RuleSet
{

    public void addRuleInstances( Digester digester )
    {
        digester.addObjectCreate( "employee", Employee.class );
        digester.addCallMethod( "employee/firstName", "setFirstName", 0 );
        digester.addCallMethod( "employee/lastName", "setLastName", 0 );

        digester.addObjectCreate( "employee/address", Address.class );
        digester.addCallMethod( "employee/address/type", "setType", 0 );
        digester.addCallMethod( "employee/address/city", "setCity", 0 );
        digester.addCallMethod( "employee/address/state", "setState", 0 );
        digester.addSetNext( "employee/address", "addAddress" );
    }

}

然後在servlet中這樣使用:

public class EmployeeServlet
  extends HttpServlet
{

    private final RuleSet employeeRuleSet = new EmployeeRuleSet();

    public void doGet(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException
    {
        Digester digester = new Digester();
        digester.setNamespaceAware( true );
        digester.setXIncludeAware( true );

        employeeRuleSet.addRuleInstances( digester );

        Employee employee = digester.parse( openStream( req.getParameter( "employeeId" ) ) );
        ...
    }

}

很顯然這樣做是沒有錯誤的(其實,個人覺得還不如直接寫一個私有方法,添加規則,哈哈),但是有如下缺點:

  1. RuleSet實際上並不是配置,只是給digester綁定下規則而已;
  2. digester對象與客戶端耦合度比較高,直接由客戶端創建;
  3. 每次解析調用前,都需要重復綁定規則
  4. 規則綁定的時候,語義性很差,可讀性不好;

那麽,最佳實踐是什麽呢,答案是使用RulesModule接口,幫助我們啟動時預先綁定規則,然後運行的時候,重復使用預先綁定的規則即可,如下所示:

定義一個RulesModule接口實現類:

class EmployeeModule
    extends AbstractRulesModule
{

    @Override
    protected void configure()
    {
        forPattern( "employee" ).createObject().ofType( Employee.class );
        forPattern( "employee/firstName" ).setBeanProperty();
        forPattern( "employee/lastName" ).setBeanProperty();

        forPattern( "employee/address" ).createObject().ofType( Address.class ).then().setNext( "addAddress");
        forPattern( "employee/address/type" ).setBeanProperty();
        forPattern( "employee/address/city" ).setBeanProperty();
        forPattern( "employee/address/state" ).setBeanProperty();
    }

}

然後在servlet這樣使用:

public class EmployeeServlet
    extends HttpServlet
{

    private final DigesterLoader loader = newLoader( new EmployeeModule() )
        .setNamespaceAware( true )
        .setXIncludeAware( true );

    public void doGet(HttpServletRequest req, HttpServletResponse res)
        throws ServletException, IOException
    {
        Digester digester = loader.newDigester()

        Employee employee = digester.parse( openStream( req.getParameter("employeeId") ) );
        ...
    }

}

好處顯而易見:

  1. RulesModule規則綁定的API語義化很強,使用簡便,可讀性高;
  2. 規則綁定的配置移到了啟動階段來完成;
  3. digester對象不是由客戶端來創建,而是通過DigesterLoader創建;

FromXmlRulesModule

除了自己編寫類實現RulesModule接口外,digester自身提供了一個FromXmlRulesModule類,就已經實現了RulesModule接口,我們可以這樣使用

            DigesterLoader loader = DigesterLoader.newLoader( new FromXmlRulesModule()
            {

                @Override
                protected void loadRules()
                {
                    loadXMLRules( DigesterLoaderMain.class.getResource( "myrule.xml" ) );
                }

            } );
       ...
      Digester digester = loader.newDigester(); // myrule.xml already parsed
      ...
      Digester newDigester = loader.newDigester(); // myrule.xml won‘t be parsed again!

完整例子

假設有一個xml如下,待解析

<employee>
    <firstName>Pi</firstName>
    <lastName>Chen</lastName>
    <address>
        <type>CITY</type>
        <city>HangZhou</city>
        <state>2</state>
    </address>
</employee>

開始編碼,首先,定義一個RulesModule接口實現類:

package apache.commons.digester3.example.rulesbinder.module;

import org.apache.commons.digester3.binder.AbstractRulesModule;

import apache.commons.digester3.example.rulesbinder.pojo.Address;
import apache.commons.digester3.example.rulesbinder.pojo.Employee;
/**
 * 
 * 
 * @author    http://www.cnblogs.com/chenpi/
 * @version   2017年6月5日
 */
public class EmployeeModule extends AbstractRulesModule {

    @Override
    protected void configure() {
        forPattern("employee").createObject().ofType(Employee.class);
        forPattern("employee/firstName").setBeanProperty();
        forPattern("employee/lastName").setBeanProperty();

        forPattern("employee/address").createObject().ofType(Address.class).then().setNext("addAddress");
        forPattern("employee/address/type").setBeanProperty();
        forPattern("employee/address/city").setBeanProperty();
        forPattern("employee/address/state").setBeanProperty();
    }

}

編寫客戶端類:

package apache.commons.digester3.example.rulesbinder;

import java.io.IOException;

import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.binder.DigesterLoader;
import org.xml.sax.SAXException;

import apache.commons.digester3.example.rulesbinder.module.EmployeeModule;
import apache.commons.digester3.example.rulesbinder.pojo.Address;
import apache.commons.digester3.example.rulesbinder.pojo.Employee;
import apache.commons.digester3.example.simpletest.ExampleMain;
/**
 * 
 * 
 * @author    http://www.cnblogs.com/chenpi/
 * @version   2017年6月5日
 */
public class DigesterLoaderMain {

    private static DigesterLoader dl = DigesterLoader.newLoader(new EmployeeModule())
            .setNamespaceAware(false);
    public static void main(String[] args) {
        try {
            
            Digester digester = dl.newDigester();
            Employee employee = digester.parse(ExampleMain.class.getClassLoader().getResourceAsStream("employee.xml"));

            System.out.print(employee.getFirstName() + " ");
            System.out.print(employee.getLastName() + ", ");
            for (Address a : employee.getAddressList()) {
                System.out.print(a.getType() + ", ");
                System.out.print(a.getCity() + ", ");
                System.out.println(a.getState());
            }

        } catch (IOException e) {

            e.printStackTrace();
        } catch (SAXException e) {

            e.printStackTrace();
        }
    }
}

結果打印:

Pi Chen, CITY, HangZhou, 2

異步解析XML

異步解析的話,直接調用asyncParse方法即可,不過需要特別註意,因為digester對象並不是線程安全的,如下是一個簡單的API使用示例:

承接上一個例子,使用同樣的xml和RulesModule實現類;

客戶端類:

package apache.commons.digester3.example.rulesbinder;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.binder.DigesterLoader;
import apache.commons.digester3.example.rulesbinder.module.EmployeeModule;
import apache.commons.digester3.example.rulesbinder.pojo.Address;
import apache.commons.digester3.example.rulesbinder.pojo.Employee;
import apache.commons.digester3.example.simpletest.ExampleMain;
/**
 * 
 * @author    http://www.cnblogs.com/chenpi/
 * @version   2017年6月5日
 */
public class AsyncParseMain {
    private static DigesterLoader dl = DigesterLoader.newLoader(new EmployeeModule())
            .setNamespaceAware(false).setExecutorService(Executors.newSingleThreadExecutor());
    public static void main(String[] args) {
        try {
            
            Digester digester = dl.newDigester();
            Future<Employee> future = digester.asyncParse(ExampleMain.class.getClassLoader().getResourceAsStream("employee.xml"));

            Employee employee = future.get();
            
            System.out.print(employee.getFirstName() + " ");
            System.out.print(employee.getLastName() + ", ");
            for (Address a : employee.getAddressList()) {
                System.out.print(a.getType() + ", ");
                System.out.print(a.getCity() + ", ");
                System.out.println(a.getState());
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

xml變量解析-Substitutor抽象類

這個比較簡單,定義一個VariableSubstitutor實現類,用戶轉換屬性和body中定義的變量值;

假設有一個xml如下所示,(其中${type}為變量):

<employee>
    <firstName>Pi</firstName>
    <lastName>Chen</lastName>
    <address>
        <type>${type}</type>
        <city>HangZhou</city>
        <state>2</state>
    </address>
</employee>

那麽可以這樣解析如上xml:

package apache.commons.digester3.example.rulesbinder;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.Substitutor;
import org.apache.commons.digester3.binder.DigesterLoader;
import org.apache.commons.digester3.substitution.MultiVariableExpander;
import org.apache.commons.digester3.substitution.VariableSubstitutor;
import org.xml.sax.SAXException;

import apache.commons.digester3.example.rulesbinder.module.EmployeeModule;
import apache.commons.digester3.example.rulesbinder.pojo.Address;
import apache.commons.digester3.example.rulesbinder.pojo.Employee;
import apache.commons.digester3.example.simpletest.ExampleMain;

/**
 * 
 * 
 * @author http://www.cnblogs.com/chenpi/
 * @version 2017年6月5日
 */
public class SubstitutionMain
{
    private static DigesterLoader dl = DigesterLoader.newLoader(new EmployeeModule())
        .setNamespaceAware(false);

    public static void main(String[] args)
    {

        try
        {
            // set up the variables the input xml can reference
            Map<String, Object> vars = new HashMap<String, Object>();
            vars.put("user.name", "me");
            vars.put("type", "boss");

            // map ${varname} to the entries in the var map
            MultiVariableExpander expander = new MultiVariableExpander();
            expander.addSource("$", vars);

            // allow expansion in both xml attributes and element text
            Substitutor substitutor = new VariableSubstitutor(expander);

            Digester digester = dl.newDigester();
            digester.setSubstitutor(substitutor);

            Employee employee = digester
                .parse(ExampleMain.class.getClassLoader().getResourceAsStream("employee$.xml"));

            System.out.print(employee.getFirstName() + " ");
            System.out.print(employee.getLastName() + ", ");
            for (Address a : employee.getAddressList())
            {
                System.out.print(a.getType() + ", ");
                System.out.print(a.getCity() + ", ");
                System.out.println(a.getState());
            }

        }
        catch (IOException e)
        {

            e.printStackTrace();
        }
        catch (SAXException e)
        {

            e.printStackTrace();
        }
    }
}

帶參構造方法使用示例

簡單地說,就是在使用ObjectCreateRule規則的時候,能夠傳遞xml中的值(屬性值、body值)給構造方法使用;

如下是一個待解析的xml:

<root>
  <bean super="false">
    <rate>9.99</rate>
  </bean>
</root>

那麽可以這樣解析:

package apache.commons.digester3.example.rulesbinder;

import java.io.IOException;

import org.apache.commons.digester3.Digester;
import org.apache.commons.digester3.ObjectCreateRule;
import org.apache.commons.digester3.binder.DigesterLoader;
import org.xml.sax.SAXException;

import apache.commons.digester3.example.rulesbinder.module.EmployeeModule;
import apache.commons.digester3.example.rulesbinder.pojo.Address;
import apache.commons.digester3.example.rulesbinder.pojo.Employee;
import apache.commons.digester3.example.rulesbinder.pojo.MyBean;
import apache.commons.digester3.example.simpletest.ExampleMain;

/**
 * 
 * 
 * @author http://www.cnblogs.com/chenpi/
 * @version 2017年6月5日
 */
public class ConstructorParamsMain
{

    public static void main(String[] args)
    {
        try
        {

            ObjectCreateRule createRule = new ObjectCreateRule(MyBean.class);
            createRule.setConstructorArgumentTypes(Double.class, Boolean.class);

            Digester digester = new Digester();
            digester.addRule("root/bean", createRule);
            digester.addCallParam("root/bean", 1, "super");
            digester.addCallParam("root/bean/rate", 0);

            MyBean myBean = digester.parse(ConstructorParamsMain.class.getClassLoader()
                .getResourceAsStream("constructor-params.xml"));

            System.out.println(myBean.getRate());
            System.out.println(myBean.isSuper_());

        }
        catch (IOException e)
        {

            e.printStackTrace();
        }
        catch (SAXException e)
        {

            e.printStackTrace();
        }
    }
}

結果打印:

9.99
false

參考資料

http://commons.apache.org/proper/commons-digester/

代碼參考

https://github.com/peterchenhdu/apache-commons-digester-example

Apache Commons Digester 二(規則模塊綁定-RulesModule、異步解析-asyncParse、xml變量Substitutor、帶參構造方法)