案例研究之聊聊 QLExpress 源碼 (一)

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"QLExpress是動態腳本引擎解析工具,由於工作環境中經常會遇到一些規則,但是不想硬編碼到系統中!這樣便會用類似規則引擎模塊來將這些規則從業務剝離出去,而我們系統中的底層因爲使用了QLExpress,所以希望從源碼中一窺究竟。","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本次希望從以下幾個模塊來剝離出QLExpress的面紗:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一、官網解讀","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"二、架構圖","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"三、配置模塊(config)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"四、異常模塊(Exception)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"五、匹配模塊(match)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"六、解析模塊(parse)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"七、規則模塊(rule)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"八、指令模塊(instruction)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"九、其他","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"十、小結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"一、官網解讀","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"QLExpress基本語法","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1.1、背景介紹","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由阿里的電商業務規則、表達式(布爾組合)、特殊數學公式計算(高精度)、語法分析、腳本二次定製等強需求而設計的一門動態腳本引擎解析工具。 在阿里集團有很強的影響力,同時爲了自身不斷優化、發揚開源貢獻精神,於2012年開源。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"QLExpress腳本引擎被廣泛應用在阿里的電商業務場景,具有以下的一些特性:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、線程安全,引擎運算過程中的產生的臨時變量都是threadlocal類型。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、高效執行,比較耗時的腳本編譯過程可以緩存在本地機器,運行時的臨時變量創建採用了緩衝池的技術,和groovy性能相當。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、弱類型腳本語言,和groovy,javascript語法類似,雖然比強類型腳本語言要慢一些,但是使業務的靈活度大大增強。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4、安全控制,可以通過設置相關運行參數,預防死循環、高危系統api調用等情況。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5、代碼精簡,依賴最小,250k的jar包適合所有java的運行環境,在android系統的低端pos機也得到廣泛運用。","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1.2、依賴和調用說明","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"maven引入依賴jar包","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n com.alibaba\n QLExpress\n 3.2.0\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"ExpressRunner runner = new ExpressRunner();\nDefaultContext context = new DefaultContext();\ncontext.put(\"a\",1);\ncontext.put(\"b\",2);\ncontext.put(\"c\",3);\nString express = \"a+b*c\";\nObject r = runner.execute(express, context, null, true, false);\nSystem.out.println(r);","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1.3、語法介紹","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3.1、操作符和java對象操作","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"普通java語法","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//支持 +,-,*,/,,<=,>=,==,!=,<>【等同於!=】,%,mod【取模等同於%】,++,--,\n//in【類似sql】,like【sql語法】,&&,||,!,等操作符\n//支持for,break、continue、if then else 等標準的程序控制邏輯\nn=10;\nfor(sum=0,i=0;ib?a:b;","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"和java語法相比,要避免的一些ql寫法錯誤","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不支持try{}catch{}","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不支持java8的lambda表達式","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不支持for循環集合操作for (GRCRouteLineResultDTO item : list)","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"弱類型語言,請不要定義類型聲明,更不要用Templete(Map之類的)","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"array的聲明不一樣","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"min,max,round,print,println,like,in 都是系統默認函數的關鍵字,請不要作爲變量名","attrs":{}}]}],"attrs":{}}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"弱類型和不使用範型以及關鍵字","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//java語法:使用泛型來提醒開發者檢查類型\nkeys = new ArrayList();\ndeviceName2Value = new HashMap(7);\nString[] deviceNames = {\"ng\",\"si\",\"umid\",\"ut\",\"mac\",\"imsi\",\"imei\"};\nint[] mins = {5,30};\n\n//ql寫法:\nkeys = new ArrayList();\ndeviceName2Value = new HashMap();\ndeviceNames = [\"ng\",\"si\",\"umid\",\"ut\",\"mac\",\"imsi\",\"imei\"];\nmins = [5,30]; ","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"對象和數組以及map的遍歷","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//java語法:對象類型聲明\nFocFulfillDecisionReqDTO reqDTO = param.getReqDTO();\n//ql寫法:\nreqDTO = param.getReqDTO();\n\n//java語法:數組遍歷\nfor(GRCRouteLineResultDTO item : list) {\n}\n//ql寫法:\nfor(i=0;i270) 則 {return 1;} 否則 {return 0;}\";\nDefaultContext context = new DefaultContext();\nrunner.execute(exp,context,null,false,false,null);","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"如何自定義Operator","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//定義一個繼承自com.ql.util.express.Operator的操作符\npublic class JoinOperator extends Operator{\n\tpublic Object executeInner(Object[] list) throws Exception {\n\t\tObject opdata1 = list[0];\n\t\tObject opdata2 = list[1];\n\t\tif(opdata1 instanceof java.util.List){\n\t\t\t((java.util.List)opdata1).add(opdata2);\n\t\t\treturn opdata1;\n\t\t}else{\n\t\t\tjava.util.List result = new java.util.ArrayList();\n\t\t\tresult.add(opdata1);\n\t\t\tresult.add(opdata2);\n\t\t\treturn result;\t\t\t\t\n\t\t}\n\t}\n}\n\n","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"如何使用Operator","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//(1)addOperator\nExpressRunner runner = new ExpressRunner();\nDefaultContext context = new DefaultContext();\nrunner.addOperator(\"join\",new JoinOperator());\nObject r = runner.execute(\"1 join 2 join 3\", context, null, false, false);\nSystem.out.println(r);\n//返回結果 [1, 2, 3]\n\n//(2)replaceOperator\nExpressRunner runner = new ExpressRunner();\nDefaultContext context = new DefaultContext();\nrunner.replaceOperator(\"+\",new JoinOperator());\nObject r = runner.execute(\"1 + 2 + 3\", context, null, false, false);\nSystem.out.println(r);\n//返回結果 [1, 2, 3]\n\n//(3)addFunction\nExpressRunner runner = new ExpressRunner();\nDefaultContext context = new DefaultContext();\nrunner.addFunction(\"join\",new JoinOperator());\nObject r = runner.execute(\"join(1,2,3)\", context, null, false, false);\nSystem.out.println(r);\n//返回結果 [1, 2, 3]","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3.4、綁定Java類或者對象的method","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"addFunctionOfClassMethod+addFunctionOfServiceMethod","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class BeanExample {\n\tpublic static String upper(String abc) {\n\t\treturn abc.toUpperCase();\n\t}\n\tpublic boolean anyContains(String str, String searchStr) {\n\n char[] s = str.toCharArray();\n for (char c : s) {\n if (searchStr.contains(c+\"\")) {\n return true;\n }\n }\n return false;\n }\n}\n\nrunner.addFunctionOfClassMethod(\"取絕對值\", Math.class.getName(), \"abs\",\n\t\t\t\tnew String[] { \"double\" }, null);\nrunner.addFunctionOfClassMethod(\"轉換爲大寫\", BeanExample.class.getName(),\n\t\t\t\t\"upper\", new String[] { \"String\" }, null);\n\nrunner.addFunctionOfServiceMethod(\"打印\", System.out, \"println\",new String[] { \"String\" }, null);\nrunner.addFunctionOfServiceMethod(\"contains\", new BeanExample(), \"anyContains\",\n new Class[] { String.class, String.class }, null);\n\nString exp = “取絕對值(-100);轉換爲大寫(\\\"hello world\\\");打印(\\\"你好嗎?\\\");contains(\"helloworld\",\\\"aeiou\\\")”;\nrunner.execute(exp, context, null, false, false);","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3.5、macro宏定義","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"runner.addMacro(\"計算平均成績\", \"(語文+數學+英語)/3.0\");\nrunner.addMacro(\"是否優秀\", \"計算平均成績>90\");\nIExpressContext context =new DefaultContext();\ncontext.put(\"語文\", 88);\ncontext.put(\"數學\", 99);\ncontext.put(\"英語\", 95);\nObject result = runner.execute(\"是否優秀\", context, null, false, false);\nSystem.out.println(r);\n//返回結果true","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3.6、編譯腳本,查詢外部需要定義的變量和函數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"注意以下腳本int和沒有int的區別","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"String express = \"int 平均分 = (語文+數學+英語+綜合考試.科目2)/4.0;return 平均分\";\nExpressRunner runner = new ExpressRunner(true,true);\nString[] names = runner.getOutVarNames(express);\nfor(String s:names){\n System.out.println(\"var : \" + s);\n}\n\n//輸出結果:\n\nvar : 數學\nvar : 綜合考試\nvar : 英語\nvar : 語文","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3.7、關於不定參數的使用","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Test\n public void testMethodReplace() throws Exception {\n ExpressRunner runner = new ExpressRunner();\n IExpressContext expressContext = new DefaultContext();\n runner.addFunctionOfServiceMethod(\"getTemplate\", this, \"getTemplate\", new Class[]{Object[].class}, null);\n\n //(1)默認的不定參數可以使用數組來代替\n Object r = runner.execute(\"getTemplate([11,'22',33L,true])\", expressContext, null,false, false);\n System.out.println(r);\n //(2)像java一樣,支持函數動態參數調用,需要打開以下全局開關,否則以下調用會失敗\n DynamicParamsUtil.supportDynamicParams = true;\n r = runner.execute(\"getTemplate(11,'22',33L,true)\", expressContext, null,false, false);\n System.out.println(r);\n }\n //等價於getTemplate(Object[] params)\n public Object getTemplate(Object... params) throws Exception{\n String result = \"\";\n for(Object obj:params){\n result = result+obj+\",\";\n }\n return result;\n }","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3.8、關於集合的快捷寫法","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Test\n public void testSet() throws Exception {\n ExpressRunner runner = new ExpressRunner(false,false);\n DefaultContext context = new DefaultContext();\n String express = \"abc = NewMap(1:1,2:2); return abc.get(1) + abc.get(2);\";\n Object r = runner.execute(express, context, null, false, false);\n System.out.println(r);\n express = \"abc = NewList(1,2,3); return abc.get(1)+abc.get(2)\";\n r = runner.execute(express, context, null, false, false);\n System.out.println(r);\n express = \"abc = [1,2,3]; return abc[1]+abc[2];\";\n r = runner.execute(express, context, null, false, false);\n System.out.println(r);\n }","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3.9、集合的遍歷","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其實類似java的語法,只是ql不支持for(obj:list){}的語法,只能通過下標訪問。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//遍歷map\n map = new HashMap();\n map.put(\"a\", \"a_value\");\n map.put(\"b\", \"b_value\");\n keySet = map.keySet();\n objArr = keySet.toArray();\n for (i=0;i10000 and shoptype in('tmall','juhuasuan') and price between (100,900)","attrs":{}},{"type":"text","text":" 假設第一個條件 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"star>10000","attrs":{}},{"type":"text","text":" 不滿足就停止運算。但業務系統卻還是希望把後面的邏輯都能夠運算一遍,並且輸出中間過程,保證更快更好的做出決策。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"isTrace","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n\t * 是否輸出所有的跟蹤信息,同時還需要log級別是DEBUG級別\n\t */\n\tprivate boolean isTrace = false;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個主要是是否輸出腳本的編譯解析過程,一般對於業務系統來說關閉之後會提高性能。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.4.2、調用入參","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * 執行一段文本\n * @param expressString 程序文本\n * @param context 執行上下文,可以擴展爲包含ApplicationContext\n * @param errorList 輸出的錯誤信息List\n * @param isCache 是否使用Cache中的指令集,建議爲true\n * @param isTrace 是否輸出詳細的執行指令信息,建議爲false\n * @param aLog 輸出的log\n * @return\n * @throws Exception\n */\n\tObject execute(String expressString, IExpressContext context,List errorList, boolean isCache, boolean isTrace, Log aLog);","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.4.3、功能擴展API列表","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"QLExpress主要通過子類實現Operator.java提供的以下方法來最簡單的操作符定義,然後可以被通過addFunction或者addOperator的方式注入到ExpressRunner中。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public abstract Object executeInner(Object[] list) throws Exception;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如我們幾行代碼就可以實現一個功能超級強大、非常好用的join操作符:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"list = 1 join 2 join 3;","attrs":{}},{"type":"text","text":" -> [1,2,3] ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}}],"text":"list = join(list,4,5,6);","attrs":{}},{"type":"text","text":" -> [1,2,3,4,5,6]","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class JoinOperator extends Operator{\n\tpublic Object executeInner(Object[] list) throws Exception {\n java.util.List result = new java.util.ArrayList();\n Object opdata1 = list[0];\n if(opdata1 instanceof java.util.List){\n result.addAll((java.util.List)opdata1);\n }else{\n result.add(opdata1);\n }\n for(int i=1;i[] aParameterClassTypes,\n\t\t\tString errorInfo);\n//fun(a,b,c) 綁定 Class.function(a,b,c)類方法\nvoid addFunctionOfClassMethod(String name, String aClassName,\n\t\t\tString aFunctionName, Class>[] aParameterClassTypes,\n\t\t\tString errorInfo);\n//給Class增加或者替換method,同時 支持a.fun(b) ,fun(a,b) 兩種方法調用\n//比如擴展String.class的isBlank方法:“abc”.isBlank()和isBlank(\"abc\")都可以調用\nvoid addFunctionAndClassMethod(String name,Class>bindingClass, OperatorBase op);","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"(2)Operator相關API","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提到腳本語言的操作符,優先級、運算的目數、覆蓋原始的操作符(+,-,*,/等等)都是需要考慮的問題,QLExpress統統幫你搞定了。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//添加操作符號,可以設置優先級\nvoid addOperator(String name,Operator op);\nvoid addOperator(String name,String aRefOpername,Operator op);\n\t\n\t//替換操作符處理\nOperatorBase replaceOperator(String name,OperatorBase op);\n \n //添加操作符和關鍵字的別名,比如 if..then..else -> 如果。。那麼。。否則。。\nvoid addOperatorWithAlias(String keyWordName, String realKeyWordName,\n\t\t\tString errorInfo);","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"(3)宏定義相關API","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"QLExpress的宏定義比較簡單,就是簡單的用一個變量替換一段文本,和傳統的函數替換有所區別。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//比如addMacro(\"天貓賣家\",\"userDO.userTag &1024 ==1024\")\nvoid addMacro(String macroName,String express) ","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"(4)java class的相關api","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"QLExpress可以通過給java類增加或者改寫一些method和field,比如 鏈式調用:\"list.join(\"1\").join(\"2\")\",比如中文屬性:\"list.長度\"。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//添加類的屬性字段\nvoid addClassField(String field,Class>bindingClass,Class>returnType,Operator op);\n\n//添加類的方法\nvoid addClassMethod(String name,Class>bindingClass,OperatorBase op);","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意,這些類的字段和方法是執行器通過解析語法執行的,而不是通過字節碼增強等技術,所以只在腳本運行期間生效,不會對jvm整體的運行產生任何影響,所以是絕對安全的。","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"(5)語法樹解析變量、函數的API","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這些接口主要是對一個腳本內容的靜態分析,可以作爲上下文創建的依據,也可以用於系統的業務處理。 比如:計算 “a+fun1(a)+fun2(a+b)+c.getName()” 包含的變量:a,b,c 包含的函數:fun1,fun2","attrs":{}}]}],"attrs":{}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"(6)語法解析校驗api","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"腳本語法是否正確,可以通過ExpressRunner編譯指令集的接口來完成。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"String expressString = \"for(i=0;i<10;i++){sum=i+1}return sum;\";\nInstructionSet instructionSet = expressRunner.parseInstructionSet(expressString);\n//如果調用過程不出現異常,指令集instructionSet就是可以被加載運行(execute)了!","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"(7)指令集緩存相關的api","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲QLExpress對文本到指令集做了一個本地HashMap緩存,通常情況下一個設計合理的應用腳本數量應該是有限的,緩存是安全穩定的,但是也提供了一些接口進行管理。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\t//優先從本地指令集緩存獲取指令集,沒有的話生成並且緩存在本地\n\tInstructionSet getInstructionSetFromLocalCache(String expressString);\n\t//清除緩存\n\tvoid clearExpressCache();","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"(8)安全風險控制","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"8.1 防止死循環","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"try {\n \texpress = \"sum=0;for(i=0;i<1000000000;i++){sum=sum+i;}return sum;\";\n\t//可通過timeoutMillis參數設置腳本的運行超時時間:1000ms\n\tObject r = runner.execute(express, context, null, true, false, 1000);\n\tSystem.out.println(r);\n\tthrow new Exception(\"沒有捕獲到超時異常\");\n } catch (QLTimeOutException e) {\n\tSystem.out.println(e);\n }","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"8.2 防止調用不安全的系統api","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" ExpressRunner runner = new ExpressRunner();\n QLExpressRunStrategy.setForbiddenInvokeSecurityRiskMethods(true);\n \n DefaultContext context = new DefaultContext();\n try {\n \texpress = \"System.exit(1);\";\n\tObject r = runner.execute(express, context, null, true, false);\n\tSystem.out.println(r);\n\tthrow new Exception(\"沒有捕獲到不安全的方法\");\n } catch (QLException e) {\n\tSystem.out.println(e);\n }","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"(9)增強上下文參數Context相關的api","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"9.1 與spring框架的無縫集成","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上下文參數 IExpressContext context 非常有用,它允許put任何變量,然後在腳本中識別出來。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在實際中我們很希望能夠無縫的集成到spring框架中,可以仿照下面的例子使用一個子類。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class QLExpressContext extends HashMap implements\n\t\tIExpressContext {\n\n\tprivate ApplicationContext context;\n\n\t//構造函數,傳入context和 ApplicationContext\n\tpublic QLExpressContext(Map map,\n ApplicationContext aContext) {\n\t\tsuper(map);\n\t\tthis.context = aContext;\n\t}\n\n\t/**\n\t * 抽象方法:根據名稱從屬性列表中提取屬性值\n\t */\n\tpublic Object get(Object name) {\n\t\tObject result = null;\n\t\tresult = super.get(name);\n\t\ttry {\n\t\t\tif (result == null && this.context != null\n\t\t\t\t\t&& this.context.containsBean((String) name)) {\n\t\t\t\t// 如果在Spring容器中包含bean,則返回String的Bean\n\t\t\t\tresult = this.context.getBean((String) name);\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\tthrow new RuntimeException(e);\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic Object put(String name, Object object) {\n\t\treturn super.put(name, object);\n\t}\n\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"完整的demo參照 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/alibaba/QLExpress/blob/master/src/test/java/com/ql/util/express/test/spring/SpringDemoTest.java","title":null},"content":[{"type":"text","text":"SpringDemoTest.java","attrs":{}}]}]},{"type":"heading","attrs":{"align":null,"level":5},"content":[{"type":"text","text":"9.2 自定義函數操作符獲取原始的context控制上下文","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"自定義的Operator需要直接繼承OperatorBase,獲取到parent即可,可以用於在運行一組腳本的時候,直接編輯上下文信息,業務邏輯處理上也非常有用。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public class ContextMessagePutTest {\n \n \n class OperatorContextPut extends OperatorBase {\n \n public OperatorContextPut(String aName) {\n this.name = aName;\n }\n \n @Override\n public OperateData executeInner(InstructionSetContext parent, ArraySwap list) throws Exception {\n String key = list.get(0).toString();\n Object value = list.get(1);\n parent.put(key,value);\n return null;\n }\n }\n \n @Test\n public void test() throws Exception{\n ExpressRunner runner = new ExpressRunner();\n OperatorBase op = new OperatorContextPut(\"contextPut\");\n runner.addFunction(\"contextPut\",op);\n String exp = \"contextPut('success','false');contextPut('error','錯誤信息');contextPut('warning','提醒信息')\";\n IExpressContext context = new DefaultContext();\n context.put(\"success\",\"true\");\n Object result = runner.execute(exp,context,null,false,true);\n System.out.println(result);\n System.out.println(context);\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"參考資料:","attrs":{}},{"type":"link","attrs":{"href":"https://yq.aliyun.com/album/130","title":null},"content":[{"type":"text","text":"https://yq.aliyun.com/album/130","attrs":{}}]},{"type":"text","text":" ,","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/alibaba/QLExpress","title":""},"content":[{"type":"text","text":"https://github.com/alibaba/QLExpress","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本篇主要是根據官網和github上的信息進行概覽瞭解,對於基本特性功能有所掌握!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章