前言
上一篇对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