前言
上一篇對Digester做了基本介紹,也已經了解了Digester的基本使用方法,接下來将繼續學習其相關特性,本篇主要涉及以下幾個内容:
- 規則子產品綁定,通過定義一個RulesModule接口實作類來完成規則的預先綁定,運作時重複使用
- 異步解析xml
- 解析xml中的變量,如${sys.user}
- 使用帶參數的構造方法建立對象,參數來自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" ) ) );
...
}
}
很顯然這樣做是沒有錯誤的(其實,個人覺得還不如直接寫一個私有方法,添加規則,哈哈),但是有如下缺點:
- RuleSet實際上并不是配置,隻是給digester綁定下規則而已;
- digester對象與用戶端耦合度比較高,直接由用戶端建立;
- 每次解析調用前,都需要重複綁定規則
- 規則綁定的時候,語義性很差,可讀性不好;
那麼,最佳實踐是什麼呢,答案是使用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") ) );
...
}
}
好處顯而易見:
- RulesModule規則綁定的API語義化很強,使用簡便,可讀性高;
- 規則綁定的配置移到了啟動階段來完成;
- 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
@Author 風一樣的碼農
@HomePageUrl http://www.cnblogs.com/chenpi/
@Copyright 轉載請注明出處,謝謝~