背景和特性
背景
由阿里的電商業(yè)務(wù)規(guī)則、表達式(布爾組合)歼跟、特殊數(shù)學(xué)公式計算(高精度)和媳、語法分析、腳本二次定制等強需求而設(shè)計的一門動態(tài)腳本引擎解析工具哈街。 在阿里集團有很強的影響力留瞳,同時為了自身不斷優(yōu)化针炉、發(fā)揚開源貢獻精神胶征,于2012年開源棚贾。
特性
1夺英、線程安全茉唉,引擎運算過程中的產(chǎn)生的臨時變量都是threadlocal類型浩销。
2妈经、高效執(zhí)行临梗,比較耗時的腳本編譯過程可以緩存在本地機器胞得,運行時的臨時變量創(chuàng)建采用了緩沖池的技術(shù)荧止,和groovy性能相當。
3、弱類型腳本語言罩息,和groovy嗤详,javascript語法類似,雖然比強類型腳本語言要慢一些瓷炮,但是使業(yè)務(wù)的靈活度大大增強葱色。
4、安全控制,可以通過設(shè)置相關(guān)運行參數(shù)娘香,預(yù)防死循環(huán)苍狰、高危系統(tǒng)api調(diào)用等情況。
5烘绽、代碼精簡淋昭,依賴最小,250k的jar包適合所有java的運行環(huán)境安接,在android系統(tǒng)的低端pos機也得到廣泛運用翔忽。
基本概念
架構(gòu)圖
三個核心組件
表達式express
直接寫在java類中 注意使用ql語法
寫在ql文件中 ql文件中讀取表達式
上下文context
運行時 runner
- 單例模式
快速上手
引入pom依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.2.0</version>
</dependency>
寫個簡單的例子計算a+b+c
public void quick_start() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
context.put("a", 1);
context.put("b", 2);
context.put("c", 3);
//下面五個參數(shù)意義分別是 表達式,上下文盏檐,errorList歇式,是否緩存,是否輸出日志
Object result = runner.execute("a+b+c", context, null, true, false);
System.out.println("a+b+c=" + result);
}
步驟總結(jié)
- 生成Runner
實際使用建議是單例的胡野,看下上面的架構(gòu)圖
- 上下文傳參
生成DefaultContext材失,并往里面添加數(shù)據(jù)
- 定義表達式
- 執(zhí)行得到結(jié)果
語法
操作符和java對象操作
普通java語法
支持 +,-,*,/,<,>,<=,>=,==,!=,<>【等同于!=】,%,mod【取模等同于%】,++,--,
in【類似sql】,like【sql語法】,&&,||,!,等操作符
支持for,break硫豆、continue龙巨、if then else 等標準的程序控制邏輯
三目運算符
下面展示一些基本的例子
- 計算1-100的和
public void test_basic_use_for() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
String express = "n=100;sum=0;" +
"for(i=1;i<=n;i++){" +
"sum = sum+i;" +
"}" +
"return sum;";
Object result = runner.execute(express, context, null, true, false);
System.out.println("1...100的和是: " + result);
}
- 三目運算符
public void test_basic_use_three_var() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
context.put("a", 5);
context.put("b", 10);
String express = "a>b?a:b";
Object max = runner.execute(express, context, null, true, false);
System.out.println("a和b中較大的指是:" + max);
}
注意點
和java語法相比,要避免ql的一些寫法錯誤
不支持try{}catch{}
不支持java8的lambda表達式
-
不支持for循環(huán)集合操作for (GRCRouteLineResultDTO item : list)
- 通過數(shù)組方式訪問
弱類型語言熊响,請不要定義類型聲明,更不要用Templete(Map<String,List>之類的)
-
array的聲明不一樣
- 使用[]
min,max,round,print,println,like,in 都是系統(tǒng)默認函數(shù)的關(guān)鍵字旨别,請不要作為變量名
數(shù)組
- 聲明的方式類似arr=new int[3];
- 聲明并賦值的時候和java有點不同
- mins=[5,30];
- 迭代的時候?qū)傩越凶鰈ength
public void test_array_use() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
String express = "arr=new int[3];" +
"arr[0]=1;arr[1]=2;arr[2]=3;" +
"sum=arr[0]+arr[1]+arr[2];" +
"return sum;";
Object arrSum = runner.execute(express, context, null, true, false);
System.out.println("arrSum: " + arrSum);
}
list
new ArrayList(); 不用范型
迭代的時候?qū)傩詌ist.size()
取的時候用list.get(i)
public void test_list_use() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
String express = "list = new ArrayList();" +
"list.add(3);list.add(4);list.add(5);" +
"sum=0;" +
"for(i=0;i<list.size();i++){" +
"sum = sum+list.get(i);" +
"}" +
"return sum;";
Object listSum = runner.execute(express, context, null, true, false);
System.out.println("listSum: " + listSum);
}
map
不使用范型 new HashMap()
迭代的時候先生成keySet
keySet.toArray()獲取keyArray
迭代keyArray獲取key
通過key獲取value
public void test_map_use() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
String express = "map=new HashMap();" +
"map.put('a',2);map.put('b',2);map.put('c',2);" +
"sum=0;" +
"keySet=map.keySet();" +
"keyArray=keySet.toArray();" +
"for(i=0;i<keyArray.length;i++){" +
"sum=sum+map.get(keyArray[i]);" +
"}" +
"return sum;";
Object mapValueSum = runner.execute(express, context, null, true, false);
System.out.println("mapValueSum: " + mapValueSum);
}
語法糖
可以使用NewList和NewMap快速創(chuàng)建list和map
- NewList
public void test_NewList() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
String express = "abc=NewList(1,2,3);return abc.get(0)+abc.get(1)+abc.get(2);";
Object listSum = runner.execute(express, context, null, true, false);
System.out.println("listSum: " + listSum);
}
- NewMap
public void test_NewMap() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
String express = "abc=NewMap('a':1,'b':2,'c':3);return abc.get('a')+abc.get('b')+abc.get('c');";
Object mapSum = runner.execute(express, context, null, true, false);
System.out.println("mapSum: " + mapSum);
}
java bean
- 需要import引入依賴
- 系統(tǒng)自動會import java.lang.,import java.util.;
- 創(chuàng)建對象后可以調(diào)用靜態(tài)和非靜態(tài)的方法
package com.ql.util.express.zihao;
/**
* @author tangzihao
* @Date 2021/1/20 9:17 下午
*/
public class Person {
public void sayHello() {
System.out.println("hello,world! this is non static method");
}
public void sayHelloStatic() {
System.out.println("hello,world! this is static method");
}
}
public void test_java_bean() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
//qlexpress只會引入java.util.*和java.lang.*
String express = "import com.ql.util.express.zihao.Person;" +
"person=new Person();" +
"person.sayHello();" +
"person.sayHelloStatic();";
runner.execute(express, context, null, true, false);
}
腳本中定義function
寫法和js非常相似。
注意點
- 在入?yún)⒌牡胤叫枰暶黝愋?/li>
- function的右}需要添加;
public void test_add_func() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
String express = "function add(int a,int b){" +
"return a+b;" +
"};" +
"a=2;b=2;" +
"return add(a,b);";
Object funcResult = runner.execute(express, context, null, true, false);
System.out.println("add(a,b)=" + funcResult);
}
擴展操作符Operator
替換if,then,else關(guān)鍵字
注意then和else后需要使用;
public void test_replace_if_then_else() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
runner.addOperatorWithAlias("如果", "if", null);
runner.addOperatorWithAlias("則", "then", null);
runner.addOperatorWithAlias("否則", "else", null);
context.put("語文", 100);
context.put("數(shù)學(xué)", 100);
context.put("英語", 100);
String express = "如果 ((語文+數(shù)學(xué)+英語)>270) 則 return 1;否則 return 0;";
Object result = runner.execute(express, context, null, true, false);
System.out.println(result);
}
定義自己的operator
public class JoinOperator extends Operator {
public Object executeInner(Object[] list) throws Exception {
java.util.List result = new java.util.ArrayList();
Object opdata1 = list[0];
if(opdata1 instanceof java.util.List){
result.addAll((java.util.List)opdata1);
}else{
result.add(opdata1);
}
for(int i=1;i<list.length;i++){
result.add(list[i]);
}
return result;
}
}
使用Operator
addOperator
輸出結(jié)果[1,2,3]
public void test_add_operator() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
String express = "1 join 2 join 3";
runner.addOperator("join", new JoinOperator());
Object result = runner.execute(express, context, null, true, false);
System.out.println(result);
}
replaceOperator
替換原有的操作符汗茄,比如將+替換為join
public void test_replace_operator() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
String express = "1 + 2 + 3";
runner.replaceOperator("+", new JoinOperator());
Object result = runner.execute(express, context, null, true, false);
System.out.println(result);
}
addFunction
public void test_add_function() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
String express = "join(1,2,3)";
runner.addFunction("join", new JoinOperator());
Object result = runner.execute(express, context, null, true, false);
System.out.println(result);
}
綁定java類型或者對象的method
- addFunctionOfClassMethod 綁定靜態(tài)方法
- addFunctionOfServiceMethod 綁定實例方法
參數(shù)說明:
- 函數(shù)名稱
- 類名稱或者對象實例
- 方法名稱
- 方法的參數(shù)類型
- 錯誤信息 如果函數(shù)執(zhí)行的結(jié)果是false昼榛,需要輸出的錯誤信息
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
runner.addFunctionOfClassMethod("取絕對值", Math.class.getName(), "abs",
new String[]{"double"}, null);
runner.addFunctionOfClassMethod("轉(zhuǎn)換為大寫", BeanExample.class.getName(),
"upper", new String[]{"String"}, null);
runner.addFunctionOfServiceMethod("打印", System.out, "println", new String[]{"String"}, null);
runner.addFunctionOfServiceMethod("contains", new BeanExample(), "anyContains",
new Class[]{String.class, String.class}, null);
String exp = "取絕對值(-100);轉(zhuǎn)換為大寫(\"hello world\");打印(\"你好嗎?\");contains(\"helloworld\",\"aeiou\");";
runner.execute(exp, context, null, false, false);
- addFunctionAndClassMethod 給現(xiàn)有的類增加方法
public void test_function_and_class_method() throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
//給類增加方法
runner.addFunctionAndClassMethod("isBlank", String.class, new Operator() {
@Override
public Object executeInner(Object[] list) throws Exception {
Object obj = list[0];
if (obj == null) {
return true;
}
String str = String.valueOf(obj);
return str.length() == 0;
}
});
String express = "a=\"\".isBlank()";
Object r = runner.execute(express, context, null, true, false);
System.out.println(r);
}
宏定義
public void test_macro_use() throws Exception {
ExpressRunner runner = new ExpressRunner();
IExpressContext<String, Object> context = new DefaultContext<String, Object>();
runner.addMacro("計算平均成績", "(語文+數(shù)學(xué)+英語)/3.0");
runner.addMacro("是否優(yōu)秀", "計算平均成績>90");
context.put("語文", 88);
context.put("數(shù)學(xué)", 99);
context.put("英語", 95);
Object result = runner.execute("是否優(yōu)秀", context, null, false, false);
System.out.println(result);
}
編譯腳本剔难,查詢外部需要定義的變量和函數(shù)
public void test_compile_script() throws Exception {
String express = "int 平均分 = (語文+數(shù)學(xué)+英語+綜合考試.科目2)/4.0;return 平均分";
ExpressRunner runner = new ExpressRunner(true, true);
String[] names = runner.getOutVarNames(express);
for (String s : names) {
System.out.println("var : " + s);
}
String[] functions = runner.getOutFunctionNames(express);
for (String s : functions) {
System.out.println("function : " + s);
}
}
關(guān)于不定參數(shù)的使用
比如BeanExample類的getTemplate方法的入?yún)⑹遣欢▍?shù)。
public class BeanExample {
public Object getTemplate(Object... params) throws Exception{
String result = "";
for(Object obj:params){
result = result+obj+",";
}
return result;
}
}
使用數(shù)組
@Test
public void test_multi_params_use_array() throws Exception {
ExpressRunner runner = new ExpressRunner();
IExpressContext<String, Object> expressContext = new DefaultContext<String, Object>();
runner.addFunctionOfServiceMethod("getTemplate", new BeanExample(), "getTemplate", new Class[]{Object[].class}, null);
Object r = runner.execute("getTemplate([11,'22',33L,true])", expressContext, null, false, false);
System.out.println(r);
}
使用動態(tài)參數(shù)
注意需要打開動態(tài)開關(guān) DynamicParamsUtil.supportDynamicParams = true;否則會拋出異常
public void test_multi_params_dynamic() throws Exception {
DynamicParamsUtil.supportDynamicParams = true;
ExpressRunner runner = new ExpressRunner();
IExpressContext<String, Object> expressContext = new DefaultContext<String, Object>();
runner.addFunctionOfServiceMethod("getTemplate", new BeanExample(), "getTemplate", new Class[]{Object[].class}, null);
Object r = runner.execute("getTemplate(11,'22',33L,true)", expressContext, null, false, false);
System.out.println(r);
}
集合的遍歷
類似java的語法奥喻,只是ql不支持for(obj:list){}的語法偶宫,只能通過下標訪問。
詳細見 操作符和java對象操作 這一小節(jié)的示例
運行參數(shù)和api介紹
屬性開關(guān)
isPrecise
高精度計算在會計財務(wù)中非常重要环鲤,java的float纯趋、double、int、long存在很多隱式轉(zhuǎn)換吵冒,做四則運算和比較的時候其實存在非常多的安全隱患纯命。 所以類似匯金的系統(tǒng)中,會有很多BigDecimal轉(zhuǎn)換代碼痹栖。而使用QLExpress亿汞,你只要關(guān)注數(shù)學(xué)公式本身 訂單總價 = 單價 * 數(shù)量 + 首重價格 + ( 總重量 - 首重) * 續(xù)重單價 ,然后設(shè)置這個屬性即可揪阿,所有的中間運算過程都會保證不丟失精度疗我。
/**
* 是否需要高精度計算
*/
private boolean isPrecise = false;
public void is_precise() throws Exception {
ExpressRunner runner = new ExpressRunner(true, false);
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
//訂單總價 = 單價 * 數(shù)量 + 首重價格 + (總重量 - 首重) * 續(xù)重單價
context.put("單價", 1.25);
context.put("數(shù)量", 100);
context.put("首重價格", 125.25);
context.put("總重量", 20.55);
context.put("首重", 10.34);
context.put("續(xù)重單價", 3.33);
String express = "單價*數(shù)量+首重價格+(總重量-首重)*續(xù)重單價";
Object totalPrice = runner.execute(express, context, null, true, false);
System.out.println("totalPrice:" + totalPrice);
}
上面在創(chuàng)建ExpressRunner的時候第一個參數(shù)是是否開啟高精度默認false,第二個參數(shù)是是否開啟trace南捂。
開啟高精度的情況下輸出是284.2493
沒有開啟高精度的情況下輸出是284.2493
isShortCicuit
/**
* 是否使用邏輯短路特性
*/
private boolean isShortCircuit = true;
在很多業(yè)務(wù)決策系統(tǒng)中吴裤,往往需要對布爾條件表達式進行分析輸出,普通的java運算一般會通過邏輯短路來減少性能的消耗溺健。例如規(guī)則公式: star>10000 and shoptype in('tmall','juhuasuan') and price between (100,900) 假設(shè)第一個條件 star>10000 不滿足就停止運算麦牺。但業(yè)務(wù)系統(tǒng)卻還是希望把后面的邏輯都能夠運算一遍,并且輸出中間過程鞭缭,保證更快更好的做出決策剖膳。
具體可以參見ShortCircuitLogicTest測試類
runner.setShortCircuit(true);
runner.setShortCircuit(false);//顯式指定,默認是true
isTrace
/**
* 是否輸出所有的跟蹤信息缚去,同時還需要log級別是DEBUG級別
*/
private boolean isTrace = false;
ExpressRunner runner = new ExpressRunner(true, false);//第二個參數(shù)是否開啟trace
這個主要是是否輸出腳本的編譯解析過程潮秘,一般對于業(yè)務(wù)系統(tǒng)來說關(guān)閉之后會提高性能。
調(diào)用入?yún)?/h2>
對于runner.execute執(zhí)行方法的參數(shù)入?yún)⒄f明
/**
* 執(zhí)行一段文本
* @param expressString 程序文本
* @param context 執(zhí)行上下文易结,可以擴展為包含ApplicationContext
* @param errorList 輸出的錯誤信息List
* @param isCache 是否使用Cache中的指令集,建議為true
* @param isTrace 是否輸出詳細的執(zhí)行指令信息枕荞,建議為false
* @param aLog 輸出的log
* @return
* @throws Exception
*/
Object execute(String expressString, IExpressContext<String,Object> context,List<String> errorList, boolean isCache, boolean isTrace, Log aLog);
功能擴展API列表
QLExpress主要通過子類實現(xiàn)Operator.java提供的以下方法來最簡單的操作符定義,然后可以被通過addFunction或者addOperator的方式注入到ExpressRunner中搞动。
具體參見上面的JoinOperator的例子躏精。
如果你使用Operator的基類OperatorBase.java將獲得更強大的能力,基本能夠滿足所有的要求鹦肿。
function相關(guān)API
//通過name獲取function的定義
OperatorBase getFunciton(String name);
//通過自定義的Operator來實現(xiàn)類似:fun(a,b,c)
void addFunction(String name, OperatorBase op);
//fun(a,b,c) 綁定 object.function(a,b,c)對象方法
void addFunctionOfServiceMethod(String name, Object aServiceObject,
String aFunctionName, Class<?>[] aParameterClassTypes,
String errorInfo);
//fun(a,b,c) 綁定 Class.function(a,b,c)類方法
void addFunctionOfClassMethod(String name, String aClassName,
String aFunctionName, Class<?>[] aParameterClassTypes,
String errorInfo);
//給Class增加或者替換method矗烛,同時 支持a.fun(b) ,fun(a,b) 兩種方法調(diào)用
//比如擴展String.class的isBlank方法:“abc”.isBlank()和isBlank("abc")都可以調(diào)用
void addFunctionAndClassMethod(String name,Class<?>bindingClass, OperatorBase op);
Operator相關(guān)API
提到腳本語言的操作符箩溃,優(yōu)先級瞭吃、運算的目數(shù)、覆蓋原始的操作符(+,-,*,/等等)都是需要考慮的問題涣旨,QLExpress統(tǒng)統(tǒng)幫你搞定了歪架。
//添加操作符號,可以設(shè)置優(yōu)先級
void addOperator(String name,Operator op);
void addOperator(String name,String aRefOpername,Operator op);
//替換操作符處理
//比如將+替換成自定義的operator 見上面的例子
OperatorBase replaceOperator(String name,OperatorBase op);
//添加操作符和關(guān)鍵字的別名,比如 if..then..else -> 如果霹陡。和蚪。那么止状。。否則攒霹。怯疤。
//具體見上面的例子
void addOperatorWithAlias(String keyWordName, String realKeyWordName,
String errorInfo);
宏相關(guān)API
QLExpress的宏定義比較簡單,就是簡單的用一個變量替換一段文本催束,和傳統(tǒng)的函數(shù)替換有所區(qū)別集峦。
見上面使用宏計算是否優(yōu)秀的例子
//比如addMacro("天貓賣家","userDO.userTag &1024 ==1024")
void addMacro(String macroName,String express)
java class相關(guān)API
QLExpress可以通過給java類增加或者改寫一些method和field,比如鏈式調(diào)用:"list.join("1").join("2")"泣崩,比如中文屬性:"list.長度"少梁。
//添加類的屬性字段
void addClassField(String field,Class<?>bindingClass,Class<?>returnType,Operator op);
//添加類的方法
void addClassMethod(String name,Class<?>bindingClass,OperatorBase op);
public void testArrayOrMapJoinMethod() throws Exception {
ExpressRunner runner = new ExpressRunner();
IExpressContext<String,Object> context = new DefaultContext<String, Object>();
runner.addClassMethod("join", java.util.List.class, new Operator() {
@Override
public Object executeInner(Object[] list) throws Exception {
ArrayList arrayList = (ArrayList) list[0];
return StringUtils.join(arrayList,(String) list[1]);
}
});
runner.addClassMethod("join", java.util.Map.class, new Operator() {
@Override
public Object executeInner(Object[] list) throws Exception {
HashMap map = (HashMap) list[0];
StringBuilder sb = new StringBuilder();
for(Object key: map.keySet()){
sb.append(key).append("=").append(map.get(key)).append((String) list[1]);
}
return sb.substring(0,sb.length()-1);
}
});
Object result = runner.execute("list=new ArrayList();list.add(1);list.add(2);list.add(3);return list.join(' , ');",context,null,false,false);
System.out.println(result);
result = runner.execute("list=new HashMap();list.put('a',1);list.put('b',2);list.put('c',3);return list.join(' , ');",context,null,false,false);
System.out.println(result);
}
public void testAop() throws Exception {
ExpressRunner runner = new ExpressRunner();
IExpressContext<String,Object> context = new DefaultContext<String, Object>();
runner.addClassMethod("size", java.util.List.class, new Operator() {
@Override
public Object executeInner(Object[] list) throws Exception {
ArrayList arrayList = (ArrayList) list[0];
System.out.println("攔截到List.size()方法");
return arrayList.size();
}
});
runner.addClassField("長度", java.util.List.class, new Operator() {
@Override
public Object executeInner(Object[] list) throws Exception {
ArrayList arrayList = (ArrayList) list[0];
System.out.println("攔截到List.長度 字段的計算");
return arrayList.size();
}
});
Object result = runner.execute("list=new ArrayList();list.add(1);list.add(2);list.add(3);return list.size();",context,null,false,false);
System.out.println(result);
result = runner.execute("list=new ArrayList();list.add(1);list.add(2);list.add(3);return list.長度;",context,null,false,false);
System.out.println(result);
//bugfix 沒有return 的時候可能會多次調(diào)用getType,并且返回錯誤
Object result2 = runner.execute("list=new ArrayList();list.add(1);list.add(2);list.add(3);list.長度;",context,null,false,false);
System.out.println(result2);
}
注意矫付,這些類的字段和方法是執(zhí)行器通過解析語法執(zhí)行的凯沪,而不是通過字節(jié)碼增強等技術(shù),所以只在腳本運行期間生效买优,不會對jvm整體的運行產(chǎn)生任何影響妨马,所以是絕對安全的。
語法樹解析變量杀赢、函數(shù)的API
這些接口主要是對一個腳本內(nèi)容的靜態(tài)分析烘跺,可以作為上下文創(chuàng)建的依據(jù),也可以用于系統(tǒng)的業(yè)務(wù)處理脂崔。 比如:計算 “a+fun1(a)+fun2(a+b)+c.getName()” 包含的變量:a,b,c 包含的函數(shù):fun1,fun2
//獲取一個表達式需要的外部變量名稱列表
String[] getOutVarNames(String express);
String[] getOutFunctionNames(String express);
語法解析校驗API
腳本語法是否正確滤淳,可以通過ExpressRunner編譯指令集的接口來完成。
String expressString = "for(i=0;i<10;i++){sum=i+1}return sum;";
InstructionSet instructionSet = expressRunner.parseInstructionSet(expressString);
//如果調(diào)用過程不出現(xiàn)異常砌左,指令集instructionSet就是可以被加載運行(execute)了脖咐!
指令集緩存相關(guān)API
因為QLExpress對文本到指令集做了一個本地HashMap緩存,通常情況下一個設(shè)計合理的應(yīng)用腳本數(shù)量應(yīng)該是有限的汇歹,緩存是安全穩(wěn)定的屁擅,但是也提供了一些接口進行管理。
//優(yōu)先從本地指令集緩存獲取指令集产弹,沒有的話生成并且緩存在本地
InstructionSet getInstructionSetFromLocalCache(String expressString);
//清除緩存
void clearExpressCache();
安全風(fēng)險控制
防止死循環(huán)
try {
express = "sum=0;for(i=0;i<1000000000;i++){sum=sum+i;}return sum;";
//可通過timeoutMillis參數(shù)設(shè)置腳本的運行超時時間:1000ms
Object r = runner.execute(express, context, null, true, false, 1000);
System.out.println(r);
throw new Exception("沒有捕獲到超時異常");
} catch (QLTimeOutException e) {
System.out.println(e);
}
防止調(diào)用不安全系統(tǒng)API
public void test_invoke_risk_api() {
ExpressRunner runner = new ExpressRunner();
QLExpressRunStrategy.setForbiddenInvokeSecurityRiskMethods(true);
DefaultContext<String, Object> context = new DefaultContext<String, Object>();
try {
String express = "System.exit(1);";
Object r = runner.execute(express, context, null, true, false);
System.out.println(r);
throw new Exception("沒有捕獲到不安全的方法");
} catch (QLException e) {
System.out.println(e);
} catch (Exception e) {
e.printStackTrace();
}
}
增強上下文參數(shù)Context相關(guān)的API
與spirng集成
上下文參數(shù) IExpressContext context 非常有用派歌,它允許put任何變量,然后在腳本中識別出來痰哨。
在實際中我們很希望能夠無縫的集成到spring框架中胶果,可以仿照下面的例子使用一個子類。
public class QLExpressContext extends HashMap<String, Object> implements
IExpressContext<String, Object> {
private ApplicationContext context;
//構(gòu)造函數(shù)斤斧,傳入context和 ApplicationContext
public QLExpressContext(Map<String, Object> map,
ApplicationContext aContext) {
super(map);
this.context = aContext;
}
/**
* 抽象方法:根據(jù)名稱從屬性列表中提取屬性值
*/
public Object get(Object name) {
Object result = null;
result = super.get(name);
try {
if (result == null && this.context != null
&& this.context.containsBean((String) name)) {
// 如果在Spring容器中包含bean稽物,則返回String的Bean
result = this.context.getBean((String) name);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}
public Object put(String name, Object object) {
return super.put(name, object);
}
}
完整的demo參照 SpringDemoTest.java
自定義函數(shù)操作符獲取原始context控制上下文
自定義的Operator需要直接繼承OperatorBase,獲取到parent即可折欠,可以用于在運行一組腳本的時候,直接編輯上下文信息,業(yè)務(wù)邏輯處理上也非常有用锐秦。
public class ContextMessagePutTest {
class OperatorContextPut extends OperatorBase {
public OperatorContextPut(String aName) {
this.name = aName;
}
@Override
public OperateData executeInner(InstructionSetContext parent, ArraySwap list) throws Exception {
String key = list.get(0).toString();
Object value = list.get(1);
parent.put(key,value);
return null;
}
}
@Test
public void test() throws Exception{
ExpressRunner runner = new ExpressRunner();
OperatorBase op = new OperatorContextPut("contextPut");
runner.addFunction("contextPut",op);
String exp = "contextPut('success','false');contextPut('error','錯誤信息');contextPut('warning','提醒信息')";
IExpressContext<String, Object> context = new DefaultContext<String, Object>();
context.put("success","true");
Object result = runner.execute(exp,context,null,false,true);
System.out.println(result);
System.out.println(context);
}
}
輸出內(nèi)容是
也就是說原始的上下文沒有被污染
{success=false, warning=提醒信息, error=錯誤信息}
其他
腳本預(yù)熱
可以通過提前加載腳本的方式來進行預(yù)熱咪奖,防止初次編譯腳本帶來的耗時。
動態(tài)規(guī)則推送
可以通過接入配置中心來實現(xiàn)規(guī)則的動態(tài)推送酱床。
流程設(shè)計器
因為這個流程引擎沒有流程設(shè)計器羊赵,所以可以看下http://www.reibang.com/p/9bb2f01ad816 我寫的另外一篇文章。