QLExpress规则引擎根本语法

源代码 2024-9-14 23:29:20 73 0 来自 中国
开源地点(https://gitee.com/cuibo119/QLExpress)

一、配景先容

由阿里的电贸易务规则、表达式(布尔组合)、特殊数学公式盘算(高精度)、语法分析、脚本二次定制等强需求而筹划的一门动态脚本引擎剖析工具。 在阿里团体有很强的影响力,同时为了自身不绝优化、发扬开源贡献精力,于2012年开源。
QLExpress脚本引擎被广泛应用在阿里的电贸易务场景,具有以下的一些特性:

  • 1、线程安全,引擎运算过程中的产生的暂时变量都是threadlocal范例。
  • 2、高效实行,比力耗时的脚本编译过程可以缓存在当地呆板,运行时的暂时变量创建采取了缓冲池的技能,和groovy性能相当。
  • 3、弱范例脚本语言,和groovy,javascript语法类似,固然比强范例脚本语言要慢一些,但是使业务的机动度大大增强。
  • 4、安全控制,可以通过设置干系运行参数,防备死循环、高危体系api调用等环境。
  • 5、代码精简,依赖最小,250k的jar包恰当全部java的运行环境,在android体系的低端pos机也得到广泛运用。
二、依赖和调用阐明

<pre><dependency> <groupId>com.alibaba</groupId> <artifactId>QLExpress</artifactId> <version>3.2.0</version></dependency></pre>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 r = runner.execute(express, context, null, true, false);System.out.println(r);如果应用有让终端用户输入与实行 QLExpress 的功能,务必关注 多级别安全控制,将 QLExpress 的安全级别设置在 2 或以上。
三、语法先容

1、利用符和java对象利用

平凡java语法

//支持 +,-,*,/,<,>,<=,>=,==,!=,<>【等同于!=】,%,mod【取模等同于%】,++,--,//in【类似sql】,like【sql语法】,&&,||,!,等利用符//支持for,break、continue、if then else 等标准的程序控制逻辑n = 10;sum = 0;for(i = 0; i < n; i++) { sum = sum + i;}return sum;//逻辑三元利用a = 1;b = 2;maxnum = a > b ? a : b;</pre>和java语法相比,要克制的一些ql写法错误


  • 不支持try{}catch{}
  • 解释现在只支持 /** **/,不支持单行解释 //
  • 不支持java8的lambda表达式
  • 不支持for循环聚集利用for (Item item : list)
  • 弱范例语言,请不要界说范例声明,更不要用Template(Map<String, List>之类的)
  • array的声明不一样
  • min,max,round,print,println,like,in 都是体系默认函数的关键字,请不要作为变量名
//java语法:利用泛型来提醒开辟者查抄范例keys = new ArrayList<String>();deviceName2Value = new HashMap<String, String>(7);String[] deviceNames = {"ng", "si", "umid", "ut", "mac", "imsi", "imei"};int[] mins = {5, 30};//ql写法:keys = new ArrayList();deviceName2Value = new HashMap();deviceNames = ["ng", "si", "umid", "ut", "mac", "imsi", "imei"];mins = [5, 30];//java语法:对象范例声明FocFulfillDecisionReqDTO reqDTO = param.getReqDTO();//ql写法:reqDTO = param.getReqDTO();//java语法:数组遍历for(Item item : list) {}//ql写法:for(i = 0; i < list.size(); i++){ item = list.get(i);}//java语法:map遍历for(String key : map.keySet()) { System.out.println(map.get(key));}//ql写法:keySet = map.keySet();objArr = keySet.toArray();for (i = 0; i < objArr.length; i++) { key = objArr; System.out.println(map.get(key));}java的对象利用

import com.ql.util.express.test.OrderQuery;//体系主动会import java.lang.*,import java.util.*;query = new OrderQuery();           // 创建class实例,主动补全类路径query.setCreateDate(new Date());    // 设置属性query.buyer = "张三";                // 调用属性,默认会转化为setBuyer("张三")result = bizOrderDAO.query(query);  // 调用bean对象的方法System.out.println(result.getId()); // 调用静态方法</pre>## [](https://gitee.com/cuibo119/QLExpress#2%E8%84%9A%E6%9C%AC%E4%B8%AD%E5%AE%9A%E4%B9%89function)2、脚本中界说function<pre>function add(int a, int b){ return a + b;};function sub(int a, int b){ return a - b;};a = 10;return add(a, 4) + sub(a, 9);3、扩展利用符:Operator

更换 if then else 等关键字

runner.addOperatorWithAlias("如果", "if", null);runner.addOperatorWithAlias("则", "then", null);runner.addOperatorWithAlias("否则", "else", null);express = "如果 (语文 + 数学 + 英语 > 270) 则 {return 1;} 否则 {return 0;}";DefaultContext<String, Object> context = new DefaultContext<String, Object>();runner.execute(express, context, null, false, false, null);怎样自界说Operator

import java.util.ArrayList;import java.util.List;/** * 界说一个继承自com.ql.util.express.Operator的利用符 */public class JoinOperator extends Operator { public Object executeInner(Object[] list) throws Exception { Object opdata1 = list[0]; Object opdata2 = list[1]; if (opdata1 instanceof List) { ((List)opdata1).add(opdata2); return opdata1; } else { List result = new ArrayList(); for (Object opdata : list) { result.add(opdata); } return result; } }}怎样利用Operator

//(1)addOperatorExpressRunner runner = new ExpressRunner();DefaultContext<String, Object> context = new DefaultContext<String, Object>();runner.addOperator("join", new JoinOperator());Object r = runner.execute("1 join 2 join 3", context, null, false, false);System.out.println(r); // 返回结果 [1, 2, 3]//(2)replaceOperatorExpressRunner runner = new ExpressRunner();DefaultContext<String, Object> context = new DefaultContext<String, Object>();runner.replaceOperator("+", new JoinOperator());Object r = runner.execute("1 + 2 + 3", context, null, false, false);System.out.println(r); // 返回结果 [1, 2, 3]//(3)addFunctionExpressRunner runner = new ExpressRunner();DefaultContext<String, Object> context = new DefaultContext<String, Object>();runner.addFunction("join", new JoinOperator());Object r = runner.execute("join(1, 2, 3)", context, null, false, false);System.out.println(r); // 返回结果 [1, 2, 3]4、绑定java类大概对象的method

addFunctionOfClassMethod + addFunctionOfServiceMethod
public class BeanExample { public static String upper(String abc) { return abc.toUpperCase(); } public boolean anyContains(String str, String searchStr) { char[] s = str.toCharArray(); for (char c : s) { if (searchStr.contains(c+"")) { return true; } } return false; }}runner.addFunctionOfClassMethod("取绝对值", Math.class.getName(), "abs", new String[] {"double"}, null);runner.addFunctionOfClassMethod("转换为大写", 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 express = "取绝对值(-100); 转换为大写(\"hello world\"); 打印(\"你好吗?\"); contains("helloworld",\"aeiou\")";runner.execute(express, context, null, false, false);5、macro 宏界说

runner.addMacro("盘算均匀结果", "(语文+数学+英语)/3.0");runner.addMacro("是否良好", "盘算均匀结果>90");IExpressContext<String, Object> context = new DefaultContext<String, Object>();context.put("语文", 88);context.put("数学", 99);context.put("英语", 95);Object result = runner.execute("是否良好", context, null, false, false);System.out.println(r);//返回结果true6、编译脚本,查询外部必要界说的变量和函数。

留意以下脚本int和没有int的区别
String express = "int 均匀分 = (语文 + 数学 + 英语 + 综合考试.科目2) / 4.0; return 均匀分";ExpressRunner runner = new ExpressRunner(true, true);String[] names = runner.getOutVarNames(express);for(String s:names){ System.out.println("var : " + s);}//输出结果:var : 数学var : 综合考试var : 英语var : 语文)7、关于不定参数的利用

@Testpublic void testMethodReplace() throws Exception { ExpressRunner runner = new ExpressRunner(); IExpressContext<String, Object> expressContext = new DefaultContext<String, Object>(); runner.addFunctionOfServiceMethod("getTemplate", this, "getTemplate", new Class[]{Object[].class}, null); //(1)默认的不定参数可以利用数组来取代 Object r = runner.execute("getTemplate([11,'22', 33L, true])", expressContext, null, false, false); System.out.println(r); //(2)像java一样,支持函数动态参数调用,必要打开以下全局开关,否则以下调用会失败 DynamicParamsUtil.supportDynamicParams = true; r = runner.execute("getTemplate(11, '22', 33L, true)", expressContext, null, false, false); System.out.println(r);}//等价于getTemplate(Object[] params)public Object getTemplate(Object... params) throws Exception{ String result = ""; for(Object obj:params){ result = result + obj + ","; } return result;}8、关于聚集的快捷写法

@Testpublic void testSet() throws Exception { ExpressRunner runner = new ExpressRunner(false, false); DefaultContext<String, Object> context = new DefaultContext<String, Object>(); String express = "abc = NewMap(1:1, 2:2); return abc.get(1) + abc.get(2);"; Object r = runner.execute(express, context, null, false, false); System.out.println(r); express = "abc = NewList(1, 2, 3); return abc.get(1) + abc.get(2)"; r = runner.execute(express, context, null, false, false); System.out.println(r); express = "abc = [1, 2, 3]; return abc[1] + abc[2];"; r = runner.execute(express, context, null, false, false); System.out.println(r);}9、聚集的遍历

其实类似java的语法,只是ql不支持for(obj:list){}的语法,只能通过下标访问。
//遍历mapmap = new HashMap();map.put("a", "a_value");map.put("b", "b_value");keySet = map.keySet();objArr = keySet.toArray();for (i = 0; i < objArr.length; i++) { key = objArr; System.out.println(map.get(key));}四、运行参数和API列表先容

QLExpressRunner如下图所示,从语法树分析、上下文、实行过程三个方面提供二次定制的功能扩展。
1、属性开关

isPrecise

/** * 是否必要高精度盘算 */private boolean isPrecise = false;
高精度盘算在管帐财务中非常告急,java的float、double、int、long存在很多隐式转换,做四则运算和比力的时间其实存在非常多的安全隐患。 以是类似汇金的体系中,会有很多BigDecimal转换代码。而利用QLExpress,你只要关注数学公式自己 订单总价 = 单价 * 数目 + 首重代价 + ( 总重量 - 首重) * 续重单价 ,然后设置这个属性即可,全部的中心运算过程都会包管不丢失精度。
isShortCircuit

/** * 是否利用逻辑短路特性 */private boolean isShortCircuit = true;在很多业务决议体系中,每每必要对布尔条件表达式举行分析输出,平凡的java运算一样平常会通过逻辑短路来淘汰性能的斲丧。比方规则公式: star > 10000 and shopType in ('tmall', 'juhuasuan') and price between (100, 900) 假设第一个条件 star>10000 不满足就停止运算。但业务体系却照旧盼望把反面的逻辑都可以或许运算一遍,而且输出中心过程,包管更快更好的做出决议。
参照单元测试:ShortCircuitLogicTest.java
isTrace

/** * 是否输出全部的跟踪信息,同时还必要log级别是DEBUG级别 */private boolean isTrace = false;这个告急是是否输出脚本的编译剖析过程,一样平常对于业务体系来说关闭之后会进步性能。
2、调用入参

/** * 实行一段文本 * @param expressString 程序文本 * @param context 实行上下文,可以扩展为包罗ApplicationContext * @param errorList 输出的错误信息List * @param isCache 是否利用Cache中的指令集,发起为true * @param isTrace 是否输出具体的实行指令信息,发起为false * @param aLog 输出的log * @return * @throws Exception */Object execute(String expressString, IExpressContext<String, Object> context, List<String> errorList, boolean isCache, boolean isTrace, Log aLog);3、功能扩展API列表

QLExpress告急通过子类实现Operator.java提供的以下方法来最简单的利用符界说,然后可以被通过addFunction大概addOperator的方式注入到ExpressRunner中。
public abstract Object executeInner(Object[] list) throws Exception;好比我们几行代码就可以实现一个功能超等强大、非常好用的join利用符:
_list = 1 join 2 join 3;_         -> [1,2,3]_list = join(list, 4, 5, 6);_     -> [1,2,3,4,5,6]import java.util.ArrayList;import java.util.List;public class JoinOperator extends Operator { public Object executeInner(Object[] list) throws Exception { List result = new ArrayList(); Object opdata1 = list[0]; if (opdata1 instanceof List) { result.addAll((List)opdata1); } else { result.add(opdata1); } for (int i = 1; i < list.length; i++) { result.add(list); } return result; }}如果你利用Operator的基类OperatorBase.java将获得更强大的本领,根本可以或许满足全部的要求。
(1)function干系API

//通过name获取function的界说OperatorBase getFunciton(String name);//通过自界说的Operator来实现类似: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) 两种方法调用//好比扩展String.class的isBlank方法:"abc".isBlank()和isBlank("abc")都可以调用void addFunctionAndClassMethod(String name, Class<?> bindingClass, OperatorBase op);(2)Operator干系API

提到脚本语言的利用符,优先级、运算的目数、覆盖原始的利用符(+,-,*,/等等)都是必要思量的标题,QLExpress齐备帮你搞定了。
//添加利用符号,可以设置优先级void addOperator(String name, Operator op);void addOperator(String name, String aRefOpername, Operator op);//更换利用符处置惩罚OperatorBase replaceOperator(String name, OperatorBase op);//添加利用符和关键字的别名,好比 if..then..else -> 如果。。那么。。否则。。void addOperatorWithAlias(String keyWordName, String realKeyWordName, String errorInfo);(3)宏界说干系API

QLExpress的宏界说比力简单,就是简单的用一个变量更换一段文本,和传统的函数更换有所区别。
//好比addMacro("天猫卖家", "userDO.userTag &1024 == 1024")void addMacro(String macroName, String express);(4)java class的干系api

QLExpress可以通过给java类增长大概改写一些method和field,好比 链式调用:"list.join("1").join("2")",好比中文属性:"list.长度"。
//添加类的属性字段void addClassField(String field, Class<?>bindingClass, Class<?>returnType, Operator op);//添加类的方法void addClassMethod(String name, Class<?>bindingClass, OperatorBase op);
留意,这些类的字段和方法是实行器通过剖析语法实行的,而不是通过字节码增强等技能,以是只在脚本运行期间见效,不会对jvm团体的运行产生任何影响,以是是绝对安全的。
(4)语法树剖析变量、函数的API

这些接口告急是对一个脚本内容的静态分析,可以作为上下文创建的依据,也可以用于体系的业务处置惩罚。 好比:盘算 "a + fun1(a) + fun2(a + b) + c.getName()" 包罗的变量:a,b,c 包罗的函数:fun1,fun2
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-19 02:19, Processed in 0.166224 second(s), 32 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表