一個輕量級的類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/
