規則引擎適合於作業務規則頻繁變化的場景,咱們的業務在應用過程當中,也常常要處理大量的業務規則,固然,也但願能有一套規則引擎來支撐,這樣是再好不過的。 對一些經常使用的商業規則引擎作一下了解,感受很是不錯,可是太貴了。看一些開源的引擎吧,也不錯,可是感受相對於咱們本身這麼簡單的需求,太複雜了。 框架
因而就想着本身作個,試試看能不能解決了本身的這些簡單的業務規則頻繁變化的業務場景,嗯嗯,腦子裏大概過了一下電影,感受路是通的,主要有以下業務需求: this
因爲業務規則執行器須要支持擴展,固然須要設計一個接口了: spa
<span style="font-size:14px;">/** * 規則執行器,能夠有多種實現 */ public interface RuleExecutor<T extends Rule> { /** * 返回執行器類型 * * @return */ String getType(); /** * 執行規則,並把結果放到上下文上 * * @param context * @return 返回條件是否成立 */ boolean execute(Context context, T rule); } </span>
一共就兩方法,getType用來返回規則執行器的類型,以肯定它是解決哪一種類型的規則的。 execute方法用來執行規則,執行的結果是一個布爾值,表示此條規則是否有執行。 設計
接下來就是設計規則引擎的接口了: code
<span style="font-size:14px;">public interface RuleEngine { /** * 對指定上下文執行指定類型的規則 * * @param context * @param ruleSetName */ void execute(Context context, String ruleSetName); /** * 添加一組規則 * * @param ruleSet */ void addRules(RuleSet ruleSet); /** * 刪除一組規則 * * @param ruleSet */ void removeRules(RuleSet ruleSet); /** * 添加規則執行器列表 * * @param ruleExecutors */ void addRuleExecutors(List<RuleExecutor> ruleExecutors); /** * 添加一個規則執行器 * * @param ruleExecutor */ void addRuleExecutor(RuleExecutor ruleExecutor); /** * 刪除規則執行器列表 * * @param ruleExecutors */ void removeRuleExecutors(List<RuleExecutor> ruleExecutors); /** * 刪除一個規則執行器 * * @param ruleExecutor */ void removeRuleExecutor(RuleExecutor ruleExecutor); /** * 設置一批規則執行器 * @param ruleExecutors */ void setRuleExecutors(List<RuleExecutor> ruleExecutors); } </span>
如上面的代碼同樣,仍是很是簡單的。 execute用來執行一個規則集,其它的方法就是對規則集和規則執行器的管理,只要看一遍就一清二楚了。 orm
<span style="font-size:14px;">@XStreamAlias("rule-set") public class RuleSet { /** * 同種名稱的規則集會自動合併 */ @XStreamAsAttribute private String name; @XStreamImplicit private List<Rule> rules; public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Rule> getRules() { if(rules==null){ rules = new ArrayList<Rule>(); } return rules; } public void setRules(List<Rule> rules) { this.rules = rules; } } </span>
規則集就兩屬性,一個屬性是規則集的名稱,另一個屬性就是一組規則。規則集的名稱用來表示一組相關的業務規則。 對象
根據上面的業務需求,抽象類Rule的結構以下: 接口
它裏面只有基本的幾個屬性:優先級,標識,是否排他,是否容許重複,描述,標題,類型,有效性。 說好的業務規則呢,怎麼描述? ip
因爲不一樣的規則執行器,它能夠支持的規則也不同,所以這裏的規則抽象類只有基本的一些屬性,怎麼樣描述規則由其子類決定。 ci
MVEL方式的規則及其執行器 Mvel規則
<span style="font-size:14px;">/** * 採用MVEL表達式做爲條件實現 * @author yancheng11334 * */ @XStreamAlias("mvel-rule") public class MvelRule extends Rule{ //匹配條件 private String condition; //後續操做 private String action; public String getCondition() { return condition; } public void setCondition(String condition) { this.condition = condition; } public String getAction() { return action; } public void setAction(String action) { this.action = action; } public String getType(){ return "mvel"; } public String toString() { return "MvelRule [condition=" + condition + ", action=" + action + ", type=" + getType() + ", id=" + getId() + ", priority="+ getPriority() +", multipleTimes="+isMultipleTimes()+",exclusive="+isExclusive()+"]"; } /** * 驗證mvel規則的合法性 */ public boolean isVaild() { if(StringUtil.isEmpty(getCondition())){ throw new RuntimeException(String.format("規則[%s]的匹配條件爲空", getId())); } if(StringUtil.isEmpty(getAction())){ throw new RuntimeException(String.format("規則[%s]的後續操做爲空", getId())); } return true; } } </span>
上面表示,這個規則的類型都是mvel,這個規則包含了兩個屬性:condition和action,condition表示條件,只有條件執行結果爲真的時候,才執行action中的處理。
<span style="font-size:14px;">public class MvelRuleExecutor implements RuleExecutor<MvelRule>{ private EL el; public EL getEl() { return el; } public void setEl(EL el) { this.el = el; } public String getType() { return "mvel"; } public boolean execute(Context context, MvelRule rule) { try{ if(executeCondition(rule.getCondition(),context)){ executeAction(rule.getAction(),context); return true; }else{ return false; } }catch(RuntimeException e){ throw e; }catch(Exception e){ throw new RuntimeException("Mvel規則引擎執行器發生異常:",e); } } /** * 判斷條件是否匹配 * @param condition * @param context * @return */ protected boolean executeCondition(String condition,Context context){ try{ MvelContext mvelContext=null; if(context instanceof MvelContext){ mvelContext=(MvelContext) context; }else{ mvelContext=new MvelContext(context); } return (Boolean)el.execute(condition, mvelContext); }catch(Exception e){ throw new RuntimeException(String.format("條件[%s]匹配發生異常:", condition),e); } } /** * 執行條件匹配後的操做 * @param action * @param context */ protected void executeAction(String action,Context context) { try { MvelContext mvelContext = null; if (context instanceof MvelContext) { mvelContext = (MvelContext) context; } else { mvelContext = new MvelContext(context); } el.execute(action, mvelContext); } catch (Exception e) { throw new RuntimeException(String.format("後續操做[%s]執行發生異常:", action), e); } } } </span>
execute方法的意思是:若是執行條件表達式且返回真,那麼就執行action中的處理,並返回true,不然就返回false。 呵呵,這個邏輯也太簡單了。對,tiny框架的一大特色就是用很是簡單的邏輯來實現相對複雜的處理。
前面講到,要方便的在表達式中調用Spring中託管的對象,這個的實現就要從上下文上做文章了:
<span style="font-size:14px;">public <T> T get(String name) { if(context.exist(name)){ return (T)context.get(name); }else{ //必須保存到上下文,不然每次返回不必定是同一個對象(scope多是屬性) T t = (T)beanContainer.getBean(name); context.put(name, t); return t; } } </span>
主要的邏輯在上面,也就是說:若是上下文中有對像,那麼就從上下文中取;若是沒有,那麼就從Spring容器中取。呵呵,這麼高大上的功能,實現起來也這麼簡單。
到上面爲止,相關的準備工做都就緒了,規則引擎的實現類也能夠現身了。其實這個類不貼吧,看文章的同窗們必定說我藏着掖着,可是貼出來吧,真的沒有啥技術含量:
<span style="font-size:14px;">public class RuleEngineDefault implements RuleEngine { private Map<String, List<Rule>> ruleSetMap = new ConcurrentHashMap<String, List<Rule>>(); private List<RuleExecutor> ruleExecutors = null; private Map<String, RuleExecutor> ruleExecutorMap = new ConcurrentHashMap<String, RuleExecutor>(); protected static Logger logger = LoggerFactory .getLogger(RuleEngineDefault.class); public void execute(Context context, String ruleSetName) { List<Rule> ruleSet = ruleSetMap.get(ruleSetName); if (ruleSet != null) { Vector<Rule> newSet = new Vector<Rule>(ruleSet); processRuleSet(context, newSet); } } private void processRuleSet(Context context, Vector<Rule> newSet) { //若是沒有後續規則,則退出 if (newSet.size() == 0) { return; } Rule rule = newSet.get(0); RuleExecutor ruleExecutor = ruleExecutorMap.get(rule.getType()); if (ruleExecutor != null) { boolean executed = ruleExecutor.execute(context, rule); if (executed) { //若是 if (rule.isExclusive()) { //若是條件成立,則是獨佔條件,則直接返回 return; } else if (!rule.isMultipleTimes()) { //若是不是可重複執行的規則,則刪除之 newSet.remove(0); } } else { //若是不匹配,則刪除之 newSet.remove(0); } } else { throw new RuntimeException("找不到對應" + rule.getType() + "的執行器"); } processRuleSet(context, newSet); } public void addRules(RuleSet ruleSet) { List<Rule> rules = ruleSetMap.get(ruleSet.getName()); if (rules == null) { rules = new Vector<Rule>(); ruleSetMap.put(ruleSet.getName(), rules); } //檢查規則 for(Rule rule:ruleSet.getRules()){ if(rule.isVaild()){ rules.add(rule); }else{ logger.logMessage(LogLevel.ERROR, String.format("規則[%s]檢查無效.", rule.getId())); } rule.isVaild(); } Collections.sort(rules); } public void removeRules(RuleSet ruleSet) { List<Rule> rules = ruleSetMap.get(ruleSet.getName()); if (rules != null) { rules.removeAll(ruleSet.getRules()); } } public void setRuleExecutors(List<RuleExecutor> ruleExecutors) { this.ruleExecutors = ruleExecutors; for (RuleExecutor ruleExecutor : ruleExecutors) { ruleExecutorMap.put(ruleExecutor.getType(), ruleExecutor); } } public void addRuleExecutor(RuleExecutor ruleExecutor) { if (ruleExecutors == null) { ruleExecutors = new ArrayList<RuleExecutor>(); } ruleExecutors.add(ruleExecutor); ruleExecutorMap.put(ruleExecutor.getType(), ruleExecutor); } public void addRuleExecutors(List<RuleExecutor> ruleExecutors) { if(ruleExecutors!=null){ for(RuleExecutor ruleExecutor:ruleExecutors){ addRuleExecutor(ruleExecutor); } } } public void removeRuleExecutors(List<RuleExecutor> ruleExecutors) { if(ruleExecutors!=null){ for(RuleExecutor ruleExecutor:ruleExecutors){ removeRuleExecutor(ruleExecutor); } } } public void removeRuleExecutor(RuleExecutor ruleExecutor) { if (ruleExecutors == null) { ruleExecutors = new ArrayList<RuleExecutor>(); } ruleExecutors.remove(ruleExecutor); ruleExecutorMap.remove(ruleExecutor.getType()); } } </span>
一大堆維護規則和規則執行器的代碼就不講了,關鍵的幾個講下:
<span style="font-size:14px;">public void execute(Context context, String ruleSetName) { List<Rule> ruleSet = ruleSetMap.get(ruleSetName); if (ruleSet != null) { Vector<Rule> newSet = new Vector<Rule>(ruleSet); processRuleSet(context, newSet); } } </span>
查找規則集,若是能找到就執行規則集,不然啥也不幹。
<span style="font-size:14px;">private void processRuleSet(Context context, Vector<Rule> newSet) { //若是沒有後續規則,則退出 if (newSet.size() == 0) { return; } Rule rule = newSet.get(0); RuleExecutor ruleExecutor = ruleExecutorMap.get(rule.getType()); if (ruleExecutor != null) { boolean executed = ruleExecutor.execute(context, rule); if (executed) { //若是 if (rule.isExclusive()) { //若是條件成立,則是獨佔條件,則直接返回 return; } else if (!rule.isMultipleTimes()) { //若是不是可重複執行的規則,則刪除之 newSet.remove(0); } } else { //若是不匹配,則刪除之 newSet.remove(0); } } else { throw new RuntimeException("找不到對應" + rule.getType() + "的執行器"); } processRuleSet(context, newSet); } </span>
執行規則集的邏輯是: 若是規則集合中沒有規則了,表示規則集已經執行完畢,直接返回。不然獲取優先級最高的規則,首先檢查是否有對象的規則執行器,若是沒有,則拋異常。若是有就開始執行。 若是執行返回true,說明此規則被成功執行,則判斷其是不是排他規則,若是是,則返回;不然檢查是不是可重複執行規則,若是是則返回繼續執行,不然把此條規則刪除,繼續執行下一條規則。
這裏假定作一個計算我的所得稅的規則實例
定義規則
<span style="font-size:14px;"><rule-set name="feerule" > <!-- 獨佔類條件(執行順序交互不影響執行結果) --> <!--優先級,數值越小優先級越高,用戶設置優先級必須大於0;若是沒有設置,系統會隨機分配一個優先級;同一個規則集不能出現兩個相同優先級的規則--> <mvel-rule id="step1" multipleTimes="false" exclusive="true"> <condition><![CDATA[salary<=3500]]></condition> <action><![CDATA[fee=0]]></action> </mvel-rule> <mvel-rule id="step2" multipleTimes="false" exclusive="true"> <condition><![CDATA[salary>3500 && salary<=5000]]></condition> <action><![CDATA[fee=(salary-3500)*0.03]]></action> </mvel-rule> <mvel-rule id="step3" multipleTimes="false" exclusive="true"> <condition><![CDATA[salary>5000 && salary<=8000]]></condition> <action><![CDATA[fee=(salary-3500)*0.1-105]]></action> </mvel-rule> <mvel-rule id="step4" multipleTimes="false" exclusive="true"> <condition><![CDATA[salary>8000 && salary<=12500]]></condition> <action><![CDATA[fee=(salary-3500)*0.2-555]]></action> </mvel-rule> <mvel-rule id="step5" multipleTimes="false" exclusive="true"> <condition><![CDATA[salary>12500 && salary<=38500]]></condition> <action><![CDATA[fee=(salary-3500)*0.25-1005]]></action> </mvel-rule> <mvel-rule id="step6" multipleTimes="false" exclusive="true"> <condition><![CDATA[salary>38500 && salary<=58500]]></condition> <action><![CDATA[fee=(salary-3500)*0.3-2755]]></action> </mvel-rule> <mvel-rule id="step7" multipleTimes="false" exclusive="true"> <condition><![CDATA[salary>58500 && salary<=83500]]></condition> <action><![CDATA[fee=(salary-3500)*0.35-5505]]></action> </mvel-rule> <mvel-rule id="step8" multipleTimes="false" exclusive="true"> <condition><![CDATA[salary>83500]]></condition> <action><![CDATA[fee=(salary-3500)*0.45-13505]]></action> </mvel-rule> </rule-set> </span>
編寫TestCase
<span style="font-size:14px;">public void testFeeRule(){ Context context = new MvelContext(); context.put("fee", 0.0); context.put("salary", 1000); ruleEngine.execute(context, "feerule"); assertEquals(0, context.get("fee")); context.put("salary", 4000); ruleEngine.execute(context, "feerule"); assertEquals(15.0, context.get("fee")); context.put("salary", 7000); ruleEngine.execute(context, "feerule"); assertEquals(245.0, context.get("fee")); context.put("salary", 21000); ruleEngine.execute(context, "feerule"); assertEquals(3370.0, context.get("fee")); context.put("salary", 40005); ruleEngine.execute(context, "feerule"); assertEquals(8196.50, context.get("fee")); context.put("salary", 70005); ruleEngine.execute(context, "feerule"); assertEquals(17771.75, context.get("fee")); context.put("salary", 100000); ruleEngine.execute(context, "feerule"); assertEquals(29920.00, context.get("fee")); } </span>
看到這裏的時候,我惟一的想法是:啥時我才能夠一個月繳3萬塊的稅呀。 總結 呵呵,按照Tiny慣例,傳上代碼統計數據:
至此,一個簡單的規則引擎就實現了,總共代碼行數不包含註釋爲:462行。能夠較好的適應各類簡單的業務邏輯頻繁變化的業務場景。