Commons-digester:规则引擎全实例
Apache Commons-digester是一个非常便捷的XML-Java映射工具包,我们常用它作为规则引擎来使用,其中xml文件用来定义“规则”,通过digester解析之后,将会和javabean对象建立关系。其中struts/spring等第三方项目都使用digester作为xml-java映射工具;digester很好的实现了“xml定义规则”+“java业务处理”的桥接能力;今天我就给大家分享一下如何使用Digester工具设计“数据库分表”策略。
Digester的API非常简单,在此就不再赘言,直接使用实例讲解。
一.XML与规则:
你需要在XML文件中,定义“规则”,其中“规则”就类似于javabean的结构,告诉digester需要实例化什么javabean,不过xml格式必须是结构严谨的。【table-rules.xml】
<?xml version='1.0'?> <!-- <rule id="this is the rule ID,it is required,cant be null."> <table>this is table name,such as 'order'.</table> <delimiter> as the meaning of label,it's delimiter,'_' is the default value, you can specify any readable words as delimiter. </delimiter> <size> the total number of table' shards. such as 64,it means there are 64 table-shards of the table. </size> <strategy> you can see something about RouteStrategyEnum.java. </strategy> </rule> --> <rules> <rule id="order"> <table>order</table> <strategy>hash_code_mod</strategy> <delimiter>_</delimiter> <size>256</size> </rule> <rule id="order_rule"> <table>rder_rule</table> <strategy>number_mod</strategy> <delimiter>_</delimiter> <size>256</size> </rule> </rules>
此XML定义了一个root节点为“rules”,其子节点为“rule”;既然是“数据库分表”,那么每个rule我们可以认为是一种table的声明,其中<table>表示表的实际名称,<strategy>表示此表根据什么策略分表的(比如根据“用户名”hash,根据“订单ID”取模等);<delimiter>表示用于生成表全名时各个参数的“链接符”,比如“_”;<size>表示此table总共有多少个子表,比如256张子表,那么数据将根据“strategy”将数据散列到256张表中,最终子表的实际格式为:<table><delimiter><[size]>,例如“order”表根据“订单id”取模共有256张子表,那么每个子表的名称为order_0 ~ order_255.
二.XML与“规则”模式:【table-rule-pattern.xml】
<?xml version="1.0"?> <!DOCTYPE digester-rules PUBLIC "-//Jakarta Apache //DTD digester-rules XML V1.0//EN" "http://jakarta.apache.org/commons/digester/dtds/digester-rules.dtd"> <digester-rules> <pattern value="rules"> <pattern value="rule"> <object-create-rule classname="com.test.tableRule.object.TableRule"/> <set-properties-rule/> <set-next-rule methodname="put"/> <pattern value="table"> <call-method-rule methodname="setTable" paramcount="0" paramtypes="java.lang.String"/> </pattern> <pattern value="size"> <call-method-rule methodname="setSize" paramcount="0" paramtypes="java.lang.Integer"/> </pattern> <pattern value="type"> <call-method-rule methodname="setType" paramcount="0" paramtypes="java.lang.String"/> </pattern> <pattern value="delimiter"> <call-method-rule methodname="setDelimiter" paramcount="0" paramtypes="java.lang.String"/> </pattern> <pattern value="strategy"> <call-method-rule methodname="setStrategy" paramcount="0" paramtypes="java.lang.String"/> </pattern> </pattern> </pattern> </digester-rules>
这个文件是告知digester如何解析和映射"table-rules.xml",它的主要作用就是声明XML与javabean映射关系。这个xml中的格式是已经约定的,你不能随意创建自己的节点元素。
1) <pattern>的value属性为需要匹配的table-rules.xml中的元素节点名称,比如“rules”。
2) <object-create-rule>表示“遇到”指定pattern的节点,将会创建一个java对象,此对象的为className类型。被创建的对象,将放在解析stack的顶部。
3) <set-properties-rule>表示“对stack顶”的对象进行“属性赋值”操作,比如<rule>节点使用“set-properties”将会导致“id”属性的setter方法被调用。
4) <set-next-rule>获取将“解析stack顶”的对象(stack.pop()),并执行“top-element”对象的“methodname”指定的方法;(实例中讲解)
5) <call-method-rule>调用“解析stack顶”的对象的指定方法,参数值为当前pattern匹配的节点的值,paramcount为“方法的参数列表的个数”,0表示“只有一个参数”的方法,paramtypes为“方法的参数列表”的类型(","分割),如果paramtypes未声明,将会执行无参的方法。
三.Java实例:
1) 测试类:
public class XmlRuleParseTestMain { public static void main(String[] args){ ClassLoader loader = Thread.currentThread().getContextClassLoader(); //ָclasspath Digester digester = DigesterLoader.createDigester(loader.getResource("table-rule-pattern.xml")); digester.setClassLoader(loader); final TableRulePool rulePool = TableRulePool.getInstance(); digester.push(rulePool);//top-element try { digester.parse(loader.getResource("table-rules.xml")); }catch(Exception e){ e.printStackTrace(); } List<TableRule> rules = rulePool.getAllRules(); for(TableRule rule : rules){ System.out.println(rule.getId() + ":" + rule.getTable() + rule.getDelimiter() + rule.getSize()); } } }
2) TableRulePool.java:用来保存XML解析的结果,所有的rules列表,它做为digester解析时的“top-element”,当解析table-rules.xml时,遇到“<rule>”节点时,将会创建TableRule对象,并根据<set-next-rule>中指定的put方法,将tableRule实例放入列表中。
/** * @author liuguanqing * hold all tablerules,client can get anything from it */ public class TableRulePool{ private final Map<String, TableRule> ruleMap = new ConcurrentHashMap<String, TableRule>(32); private static TableRulePool instance; private TableRulePool(){}; public void put(TableRule rule) { if(rule.getId() == null) { throw new NullPointerException("Rule's ID cannt be null!"); } if(rule.getDelimiter() == null){ rule.setDelimiter(TableRule.DEFAULT_DELIMITER); } if(rule.getStrategy() == null){ rule.setStrategy(TableRule.DEFAULT_STRATEGY); } rule.init(); ruleMap.put(rule.getId(), rule); } public synchronized static TableRulePool getInstance(){ if(instance == null){ instance = new TableRulePool(); } return instance; } public TableRule getRule(String ruleId){ return ruleMap.get(ruleId); } public List<TableRule> getAllRules(){ return new ArrayList<TableRule>(ruleMap.values()); } }
3) TableRule.java:一个简单的POJO类,在解析table-rules.xml时,任何一个<rule>节点都会被映射成一个TableRule对象;在“规则模式”XML中,<object-create-rule>中指定。为了能够使用<set-properties-rule>,节点的属性(例如:id)需要有setter方法。<call-method-rule>中指定了执行tableRule实例的方法。
/** * @author liuguanqing * full name : table + delimiter + size,such as "mall_order_256" */ public class TableRule implements Serializable{ /** * */ private static final long serialVersionUID = -5770269844973816977L; public static final String DEFAULT_DELIMITER = "_";// public static final String DEFAULT_STRATEGY = RouteStrategyEnum.NUMBER_MOD.getKey();// private String id;// //the strategy of data routing. such as "hash","number_mod" private String strategy; // prefix of the db-table,not included the "foot_number" private String table; private String delimiter;// private Integer size = 0;//the num of subtables //key is foot_number of subtable //value is the fullname of subtable //such as,0:"order_0",1:"order_1" //I think it can be better than create a new subtable-name everytime. private Map<Integer,String> indexes = new HashMap<Integer,String>(); public TableRule(){} public String getTable() { return table; } public void setTable(String table) { this.table = table; } public String getDelimiter() { return delimiter; } public void setDelimiter(String delimiter) { this.delimiter = delimiter; } public Integer getSize() { return size; } public void setSize(Integer size) { this.size = size; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getStrategy() { return strategy; } public void setStrategy(String strategy) { this.strategy = strategy; } /** * build inner map */ public void init(){ for(int i = 0; i < size; i++){ indexes.put(i, table + delimiter + i); } } /** * route by key,return the fullname of subtable * @param key * @return */ public String getTable(Object key){ if(key == null){ throw new NullPointerException("route key cant be null1"); } int index; if(key instanceof Number){ index = ModStrategy.numberMod((Number)key, this.size); }else { index = ModStrategy.hashCodeMod(key.toString(), this.size); } return indexes.get(index); } }
四.RuleSet:
在上述例子中,我们看到,如果实现XML-Javabean的映射,需要2个xml文件:table-rules.xml定义“规则”列表,table-rule-pattern.xml定义“规则”解析的方式;digester还提供了兼容性的模式,使用java设定“规则”解析的方式,我们可以将不需要table-rule-pattern.xml。
1) 测试类:
public class JavaRuleParseTestMain { public static void main(String[] args){ ClassLoader loader = Thread.currentThread().getContextClassLoader(); Digester digester = new Digester(); digester.setClassLoader(loader); final TableRulePool rulePool = TableRulePool.getInstance(); digester.push(rulePool); digester.addRuleSet(new ConfigRuleSet()); try { digester.parse(loader.getResource("table-rules.xml")); }catch(Exception e){ e.printStackTrace(); } List<TableRule> rules = rulePool.getAllRules(); for(TableRule rule : rules){ System.out.println(rule.getId() + ":" + rule.getTable() + rule.getDelimiter() + rule.getSize()); } //if you find something about username,you can do such as follow. String username = "zhangsan"; TableRule orderRule = rulePool.getRule("order"); System.out.println("Full tablename is:" + orderRule.getTable(username)); } }
2) ConfigRuleSet.java:使用java设定“规则”解析方式,取代table-rule-pattern.xml
public class ConfigRuleSet extends RuleSetBase { @Override public void addRuleInstances(Digester digester) { //digester.push(TableRulePool.getInstance()); digester.addObjectCreate("*/rule", "com.test.tableRule.object.TableRule"); digester.addSetProperties("*/rule"); digester.addSetNext("*/rule", "put"); digester.addCallMethod("*/rule/table", "setTable",0,new String[]{"java.lang.String"}); digester.addCallMethod("*/rule/size", "setSize",0,new String[]{"java.lang.Integer"}); digester.addCallMethod("*/rule/type", "setSize",0,new String[]{"java.lang.String"}); digester.addCallMethod("*/rule/delimiter", "setDelimiter",0,new String[]{"java.lang.String"}); digester.addCallMethod("*/rule/strategy", "setStrategy",0,new String[]{"java.lang.String"}); } }
可以看出java代码和xml的声明的方式很类似,顺序也需要一样。
--END--