Commons-digester:规则引擎全实例

mikean 2013-06-29

    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--

相关推荐