太強了!動態(tài)腳本引擎QLExpress,實現(xiàn)各種復(fù)雜的業(yè)務(wù)規(guī)則
環(huán)境:Spring2.7.18
1. 簡介
在復(fù)雜的業(yè)務(wù)環(huán)境中,規(guī)則引擎作為系統(tǒng)決策的核心,扮演著非常重要的角色。它們使業(yè)務(wù)邏輯能夠靈活調(diào)整。規(guī)則引擎通過解析和執(zhí)行預(yù)設(shè)的規(guī)則,實現(xiàn)業(yè)務(wù)邏輯的自動化和智能化處理。這不僅提升了系統(tǒng)的靈活性和可擴展性,還大大簡化了業(yè)務(wù)邏輯的維護和管理。基于這樣的需求,尋求一個高效、易用的規(guī)則引擎是非常重要的。
在眾多規(guī)則引擎中,QLExpress以其簡潔的語法、高性能的執(zhí)行能力和靈活的配置選項脫穎而出。它不僅能夠輕松應(yīng)對復(fù)雜的業(yè)務(wù)邏輯,還提供了強大的擴展能力,滿足多樣化的開發(fā)需求。
QLExpress由阿里的電商業(yè)務(wù)規(guī)則、表達式(布爾組合)、特殊數(shù)學公式計算(高精度)、語法分析、腳本二次定制等強需求而設(shè)計的一門動態(tài)腳本引擎解析工具。
QLExpress特性:
- 線程安全,引擎運算過程中的產(chǎn)生的臨時變量都是threadlocal類型。
- 高效執(zhí)行,比較耗時的腳本編譯過程可以緩存在本地機器,運行時的臨時變量創(chuàng)建采用了緩沖池的技術(shù),和groovy性能相當。
- 弱類型腳本語言,和groovy,javascript語法類似,雖然比強類型腳本語言要慢一些,但是使業(yè)務(wù)的靈活度大大增強。
- 安全控制,可以通過設(shè)置相關(guān)運行參數(shù),預(yù)防死循環(huán)、高危系統(tǒng)api調(diào)用等情況。
- 代碼精簡,依賴最小,250k的jar包適合所有java的運行環(huán)境。
接下來將詳細介紹QLExpress的使用
2. 實戰(zhàn)案例
2.1 引入依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.3.3</version>
</dependency>
無需任何配置,可在項目中直接使用了
簡單示例
ExpressRunner runner = new ExpressRunner() ;
DefaultContext<String, Object> context = new DefaultContext<String, Object>() ;
context.put("a", 1) ;
context.put("b", 2) ;
context.put("c", 3) ;
String express = "a + b * c" ;
Object ret = runner.execute(express, context, null, true, false) ;
System.out.printf("%s = %d%n", express, ret) ;
用起來是不是與SpEL表達式差不多?
2.2 語法介紹
基礎(chǔ)語法
支持 +,-,*,/,<,>,<=,>=,==,!=,<>【等同于!=】,%,mod【取模等同于%】,++,--,in【類似sql】,like【sql語法】,&&,||,!,等操作符
支持for,break、continue、if then else 等標準的程序控制邏輯
示例
ExpressRunner runner = ... ;
DefaultContext<String, Object> context = ... ;
context.put("n", 10) ;
context.put("sum", 0) ;
String express = """
for(i = 1; i <= n; i++) {
sum = sum + i;
}
return sum;
""" ;
Object ret = runner.execute(express, context, null, true, false) ;
System.out.printf("表達式: %n%s計算結(jié)果: %d%n", express, ret) ;
輸出結(jié)果
圖片
注意,與java語法相比以下是不支持的:
不支持try{}catch{}
注釋目前只支持 /** **/,不支持單行注釋 //
不支持java8的lambda表達式
不支持for循環(huán)集合操作for (Item item : list)
弱類型語言,請不要定義類型聲明,更不要用Template(Map<String, List>之類的)
array的聲明不一樣
min,max,round,print,println,like,in 都是系統(tǒng)默認函數(shù)的關(guān)鍵字,請不要作為變量名
對象操作
ExpressRunner runner = ... ;
DefaultContext<String, Object> context = ... ;
String express = """
import com.pack.qlexpress.PersonService ;
import com.pack.qlexpress.Person ;
ps = new PersonService() ;
ps.save(new Person()) ;
""" ;
runner.execute(express, context, null, true, false) ;
輸出結(jié)果
PersonService save method, com.pack.Person@1c3a4799
相當于將java代碼以字符串形式表達出來。注意這里要導(dǎo)入所用到的包,即便在同一包中也要如此操作。
2.3 表達式定義函數(shù)
context.put("arg1", 10) ;
context.put("arg2", 20) ;
String express = """
function add(int a, int b) {
return a + b ;
}
return Math.PI + add(arg1, arg2) ;
""" ;
Object ret = runner.execute(express, context, null, true, false) ;
System.out.printf("計算結(jié)果: %s%n", ret) ;
上面表達式中定義了add函數(shù)做加法運算,最終整個表達式調(diào)用add同時再加上Math.PI。對于java.lang包中的類不需要導(dǎo)入操作。
2.4 操作符
String express = """
if(a > b) {
return 1;
} else {
return 0;
}
""" ;
Object ret = runner.execute(express, context, null, true, false) ;
System.out.printf("計算結(jié)果: %s%n", ret) ;
上面表達式做if...else...判斷,輸出結(jié)果:
a <= b
計算結(jié)果: null // 沒有返回結(jié)果,所以為null
對于開發(fā)來說寫上面的表達式太簡單了,如果你非開發(fā)人員來寫if else或者其它更加復(fù)雜的語句那就太難為人了。qlexpress允許我們將這些關(guān)鍵字進行別名的定義,如下示例:
runner.addOperatorWithAlias("如果", "if", null) ;
runner.addOperatorWithAlias("否則", "else", null) ;
runner.addOperatorWithAlias("大于", ">", null) ;
runner.addOperatorWithAlias("返回", "return", null) ;
String express = "如果(a大于b){返回 1;} 否則 {返回 0;}";
Object ret = runner.execute(express, context, null, true, false) ;
上面代碼將程序中的關(guān)鍵字都通過漢字來別名化,這更加適應(yīng)大眾應(yīng)用。
2.5 綁定對象或Method
ExpressRunner runner = new ExpressRunner() ;
runner.addFunctionOfClassMethod("四舍五入", CommonService.class, "roundUp", new Class[] {double.class}, null);
String express = """
四舍五入(56.54788)
""" ;
Object ret = runner.execute(express, null, null, true, false) ;
System.out.printf("計算結(jié)果: %s%n", ret) ;
通過addFunctionOfClassMethod方法定義一個對象中的方法。輸出結(jié)果:
計算結(jié)果: 56.55
addFunctionOfClassMethod方法就是對類中的方法進行描述。
2.6 宏定義
ExpressRunner runner = new ExpressRunner() ;
runner.addMacro("計算平均成績", "(語文+數(shù)學+英語) / 3.0");
runner.addMacro("是否優(yōu)秀", "計算平均成績 > 90");
DefaultContext<String, Object> context = new DefaultContext<String, Object>() ;
context.put("語文", 88) ;
context.put("數(shù)學", 99) ;
context.put("英語", 95) ;
Object ret = runner.execute("是否優(yōu)秀", context, null, false, false);
System.out.printf("是否優(yōu)秀: %s%n", ret) ;
輸出結(jié)果
是否優(yōu)秀: true
以上宏的定義可以嵌套的調(diào)用。
2.7 查詢表達式變量
ExpressRunner runner = new ExpressRunner() ;
String express = """
int ret = (a + b + Math.PI * c ) / 4 ;
return ret ;
""" ;
String[] vars = runner.getOutVarNames(express) ;
for (String var : vars) {
System.out.printf("var: %s%n", var) ;
}
輸出結(jié)果
var: a
var: b
var: c
以上將輸出當前表達式在執(zhí)行時所需要傳入的變量。