QLExpress 規則引擎使用介紹
一個輕量級的類java語法規則引擎,作為一個嵌入式規則引擎在業務系統中使用。讓業務規則定義簡便而不失靈活。讓業務人員就可以定義業務規則。支持標準的JAVA語法,還可以支持自定義操作符號、操作符號重載、函數定義、宏定義、數據延遲加載等
QLExpress的特性
1、編譯執行:
編譯生成基礎指令後執行,性能能得到基本保障。執行過程:單詞分解-->單詞類型分析-->語法分析-->生成運行期指令集合-->執行生成的指令集合
runner.execute("10 * 10 + 1 + 2 * 3 + 5 * 2", null, true,null); 最後生成的指令: 1:LoadData 10 2:LoadData 10 3:OP : * OPNUMBER[2] 4:LoadData 1 5:OP : + OPNUMBER[2] 6:LoadData 2 7:LoadData 3 8:OP : * OPNUMBER[2] 9:OP : + OPNUMBER[2] 10:LoadData 5 11:LoadData 2 12:OP : * OPNUMBER[2] 13:OP : + OPNUMBER[2]
2、支持標準的java語法、JAVA運算符號和關鍵字
import:引入一個包或者類,例如:import java.util.*;需要放在腳本的最前面 new:創建一個對象,例如:new ArrayList(); for:操作符號 if:操作符號 then:操作符號 else:操作符號 break: 終止循環 continue: 績效循環 return: 返回
A、四則運算 : 10 * 10 + 1 + 2 * 3 + 5 * 2 B、boolean運算: 3 > 2 and 2 > 3 C、創建對象,對象方法調用,靜態方法調用:new com.ql.util.express.test.BeanExample("張三").unionName("李四") D、變量賦值:a = 3 + 5 E、支持 in,max,min: (a in (1,2,4)) and (b in("abc","bcd","efg"))
3、自定義的關鍵字
include:在一個表達式中引入其它表達式。例如: include com.taobao.upp.b; 資源的轉載可以自定義接口IExpressResourceLoader來實現,缺省是從文件中裝載 []:匿名創建數組.int[][] abc = [[11,12,13],[21,22,23]]; NewMap:創建HashMap. Map abc = NewMap(1:1,2:2);Map abc = NewMap("a":1,"b":2) NewList:串接ArrayList.List abc = NewList(1,2,3); exportDef: 將局部變量轉換為全局變量,例如:exportDef long userId alias:創建別名,例如: alias 用戶ID user.userId exportAlias: 創建別名,並轉換為全局別名 macro: 定義宏,例如: macro 降級 {level = level - 1} function: 定義函數,例如: function add(int a,int b){ return a+b; }; in: 操作符號,例如: 3 in (3,4,5) mod:操作符號,例如: 7 mod 3 like:操作符號,例如: "abc" like ‘ab%‘
4、自定義的系統函數,後續還會不斷的添加
max:取最大值max(3,4,5) min:最最小值min(2,9,1) round:四舍五入round(19.08,1) print:輸出信息不換行print("abc") println:輸出信息並換行 println("abc")
5、提供表達式上下文,屬性的值不需要在初始的時候全部加入,而是在表達式計算的時候,需要什麽信息才通過上下文接口獲取。
避免因為不知道計算的需求,而在上下文中把可能需要的數據都加入。
runner.execute("三星賣家 and 消保用戶",errorList,true,expressContext) "三星賣家"和"消保用戶"的屬性是在需要的時候通過接口去獲取。
6、可以將計算結果直接存儲到上下文中供後續業務使用。例如:
runner.execute("c = 1000 + 2000",errorList,true,expressContext); 則在expressContext中會增加一個屬性c=3000,也可以在expressContext實現直接的數據庫操作等。
7、支持高精度浮點運算,只需要在創建執行引擎的時候指定參數即可:new ExpressRunner(true,false);
8、可以將Class和Spring對象的方法映射為表達式計算中的別名,方便其他業務人員的立即和配置。例如
將 Math.abs() 映射為 "取絕對值"。 runner.addFunctionOfClassMethod("取絕對值", Math.class.getName(), "abs",new String[] { "double" }, null); runner.execute("取絕對值(-5.0)",null,true,null);
9、可以為已經存在的boolean運算操作符號設置別名,增加錯誤信息同步輸出,在計算結果為false的時候,同時返回錯誤信息,減少業務系統相關的處理代碼
runner.addOperatorWithAlias("屬於", "in", "用戶$1不在允許的範圍")。 用戶自定義的函數同樣也可以設置錯誤信息:例如: runner.addFunctionOfClassMethod("isOk", BeanExample?.class.getName(),"isOk", new String[] { "String" }, "$1 不是VIP用戶"); 則在調用: List errorList = new ArrayList?(); Object result =runner.execute("( (1+1) 屬於 (4,3,5)) and isOk("玄難")",errorList,true,null); 執行結果 result = false.同時在errorList中還會返回2個錯誤原因: 1、"用戶 2 不在允許的範圍" 2、玄難 不是VIP用戶
10、可以自定義函數,自定一個操作函數 group
class GroupOperator extends Operator { public GroupOperator(String aName) { this.name= aName; } public Object executeInner(Object[] list)throws Exception { Object result = new Integer(0); for (int i = 0; i < list.length; i++) { result = OperatorOfNumber.Add.execute(result, list[i]); } return result; } }
則執行:
runner.addFunction("累加", new GroupOperator("累加")); runner.addFunction("group", new GroupOperator("group")); //則執行:group(2,3,4) = 9 ,累加(1,2,3)+累加(4,5,6)=21
11、可以自定操作符號。自定義的操作符號優先級設置為最高。例如自定一個操作函數 love:
class LoveOperator extends Operator { public LoveOperator(String aName) { this.name= aName; } public Object executeInner(Object[] list) throws Exception { String op1 = list[0].toString(); String op2 = list[1].toString(); String result = op2 +"{" + op1 + "}" + op2; return result; } }
然後增加到運算引擎:
runner.addOperator("love", new LoveOperator("love")); //則執行:‘a‘ love ‘b‘ love ‘c‘ love ‘d‘ = "d{c{b{a}b}c}d"
12、可以重載已有的操作符號。例如替換“+”的執行邏輯。參見:com.ql.util.express.test.ReplaceOperatorTest
13、可以延遲運算需要的數據
14、一個腳本可以調用其它腳本定義的宏和函數.參見com.ql.util.express.test.DefineTest
15、可以類似VB的語法來使用操作符號和函數。print abc; 等價於 print(abc).參見 com.ql.util.express.test.OpCallTest
16、支持類定義
17、對 in 操作支持後面的是一個數組或者List變量義
最典型的應用場景
在業務系統中存在一些邏輯判斷,例如"商品A"只能是三星賣家,而且已經開通淘寶店鋪的用戶才能訂購。
那麽我們期望業務人員看到的配置信息就是:"三星賣家 而且 已經開店"
則通過下列步驟可能實現這個功能:
1、定義一個用戶信息對象:
class UserInfo { long id; long tag; String name; public UserInfo(long aId,String aName, long aUserTag) { this.id = aId; this.tag = aUserTag; this.name = aName; } public String getName(){ return name; } public long getUserId() { return id; } public long getUserTag() { return tag; } }
2、定義兩個基礎的功能函數:
/** * 判斷一個用戶TAG的第X位是否為1。這個的demo,其實現合理性不考慮 * @param user * @param tagBitIndex * @return */ public boolean userTagJudge(UserInfo user,int tagBitIndex){ boolean r = (user.getUserTag() & ((long)Math.pow(2, tagBitIndex))) > 0; return r; } /** * 判斷一個用戶是否訂購過某個商品 * @param user * @param goodsId * @return */ public boolean hasOrderGoods(UserInfo user,long goodsId){ //隨機模擬一個 if(user.getUserId() % 2 == 1){ return true; }else{ return false; } }
3、初始化語句執行器
public void initial() throws Exception{ runner.addOperatorWithAlias("而且","and",null); runner.addFunctionOfClassMethod("userTagJudge", Demo.class.getName(), "userTagJudge", new String[] {UserInfo.class.getName(),"int"}, "你不是三星賣家"); runner.addFunctionOfClassMethod("hasOrderGoods", Demo.class.getName(), "hasOrderGoods", new String[] {UserInfo.class.getName(),"long"}, "你沒有開通淘寶店鋪"); runner.addMacro("三星賣家", "userTagJudge(userInfo,3)");//3表示三星賣家的標誌位 runner.addMacro("已經開店", "hasOrderGoods(userInfo,100)");//100表示旺鋪商品的ID }
4、定義一個邏輯判斷函數:
/** * 判斷邏輯執行函數 * @param userInfo * @param expression * @return * @throws Exception */ public String hasPermission(UserInfo userInfo,String expression) throws Exception { IExpressContext<String,Object> expressContext = new DefaultContext<String,Object>(); expressContext.put("userInfo",userInfo); List<String> errorInfo = new ArrayList<String>(); Boolean result = (Boolean)runner.execute(expression, expressContext, errorInfo, true, false); String resultStr =""; if(result.booleanValue() == true){ resultStr = "可以訂購此商品"; }else{ for(int i=0;i<errorInfo.size();i++){ if(i > 0){ resultStr = resultStr + "," ; } resultStr = resultStr + errorInfo.get(i); } resultStr = resultStr + ",所以不能訂購此商品"; } return "親愛的" + userInfo.getName() + " : " + resultStr; }
5、調用執行器執行判斷邏輯:
public static void main(String args[]) throws Exception{ Demo demo = new Demo(); demo.initial(); System.out.println(demo.hasPermission(new UserInfo(100,"xuannan",7), "三星賣家 而且 已經開店")); System.out.println(demo.hasPermission(new UserInfo(101,"qianghui",8), "三星賣家 而且 已經開店")); System.out.println(demo.hasPermission(new UserInfo(100,"張三",8), "三星賣家 and 已經開店")); System.out.println(demo.hasPermission(new UserInfo(100,"李四",7), "三星賣家 and 已經開店"));
參考資料
http://code.taobao.org/p/QLExpress/wiki/index/
QLExpress 規則引擎使用介紹