规则引擎QLExpress基本用法

10,685 阅读3分钟

背景介绍

由阿里的电商业务规则、表达式(布尔组合)、特殊数学公式计算(高精度)、语法分析、脚本二次定制等强需求而设计的一门动态脚本引擎解析工具。 在阿里集团有很强的影响力,同时为了自身不断优化、发扬开源贡献精神,于2012年开源。

QLExpress脚本引擎被广泛应用在阿里的电商业务场景,具有以下的一些特性:

  • 1、线程安全,引擎运算过程中的产生的临时变量都是threadlocal类型。
  • 2、高效执行,比较耗时的脚本编译过程可以缓存在本地机器,运行时的临时变量创建采用了缓冲池的技术,和groovy性能相当。
  • 3、弱类型脚本语言,和groovy,javascript语法类似,虽然比强类型脚本语言要慢一些,但是使业务的灵活度大大增强。
  • 4、安全控制,可以通过设置相关运行参数,预防死循环、高危系统api调用等情况。
  • 5、代码精简,依赖最小,250k的jar包适合所有java的运行环境,在android系统的低端pos机也得到广泛运用。

依赖和调用说明

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>QLExpress</artifactId>
  <version>3.2.0</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 r = runner.execute(express, context, null, true, false);
System.out.println(r);

语法介绍

操作符和Java对象操作

和Java语法相比,要避免的一些ql写法错误

  • 不支持try{}catch{}
  • 注释目前只支持 /** **/,不支持单行注释 //
  • 不支持java8的lambda表达式
  • 不支持for循环集合操作for (GRCRouteLineResultDTO item : list)
  • 弱类型语言,请不要定义类型声明,更不要用Templete(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(GRCRouteLineResultDTO 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[i];
        System.out.println(map.get(key));
  }

脚本中定义function

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);

扩展操作符:Operator

替换 if then else 等关键字

ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("语文", 99);
context.put("数学", 109);
context.put("英语", 120);
runner.addOperatorWithAlias("如果", "if", null);
runner.addOperatorWithAlias("则", "then", null);
runner.addOperatorWithAlias("否则", "else", null);

String exp = "如果 (语文 + 数学 + 英语 > 270) 则 {return 789;} 否则 {return 0;}";
Object execute = runner.execute(exp, context, null, false, false, null);
System.out.println(execute);

如何自定义Operator 首先必须要继承来自com.ql.util.express.Operator的操作符

public class JoinOperator extends Operator {
    @Override
    public Object executeInner(Object[] list) throws Exception {
        Object opData1 = list[0];
        Object opData2 = list[1];

        if (opData1 instanceof java.util.List) {
            ((java.util.List)opData1).add(opData2);
            return opData1;
        } else {
            java.util.List result = new java.util.ArrayList();
            result.add(opData1);
            result.add(opData2);
            return result;
        }

    }
}

然后主程序就可以通过runner.addOperator来对这个操作重命名了!然后我们要解析的字符串就可以成功转义了!!!

public class test1 {
    public static void main(String[] args) throws Exception {
        ExpressRunner runner = new ExpressRunner();
        DefaultContext<String, Object> context = new DefaultContext<>();
        context.put("zou", 1000);
        runner.addOperator("join", new JoinOperator());
        Object r = runner.execute("zou join 2 join 3", context, null, false, false);
        System.out.println(r);
    }
}

上面只是一种操作。具体的操作如下,就不一一举例了

//(1)addOperator
ExpressRunner 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)replaceOperator
ExpressRunner 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)addFunction
ExpressRunner 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]

绑定Java类或者对象的method

public class test1 {

    public static int Abs(int x) {
        if (x < 0) return -x;
        return x;
    }

    public static void main(String[] args) throws Exception {
        ExpressRunner runner = new ExpressRunner();
        DefaultContext<String, Object> context = new DefaultContext<>();
        runner.addFunctionOfClassMethod("取绝对值", test1.class.getName(), "Abs", new String[] {"int"}, null);

        String exp = "if (取绝对值(-32432) == 32432) {return 66666;} else {return 99999;}";
        Object execute = runner.execute(exp, context, null, false, false);
        System.out.println(execute);
    }
}

结果为:66666;

macro宏定义

class test {
    public static void main(String[] args) {
        ExpressRunner runner = new ExpressRunner();
        runner.addMacro("计算语文数学分数之和", "语文 + 数学");
        runner.addMacro("是否优秀", "计算语文数学分数之和 > 230");
        IExpressContext<String, Object> context = new DefaultContext<>();
        context.put("语文", 99);
        context.put("数学", 234);
        Object execute = runner.execute("是否优秀", context, null, false, false);
        System.out.println(execute);
    }
}

返回true

编译脚本,查询外部需要定义的变量和函数

以下脚本有没有int都没有区别

@Test
public void test_script() throws Exception {
    String exp = "int 平均分 = (语文 + 数学) / 2.0; return 平均分";
    ExpressRunner runner = new ExpressRunner();

    String[] names = runner.getOutVarNames(exp);

    for (String s : names) {
        System.out.println("var: " + s);
    }
}

// 输出结果:
var : 数学
var : 语文

关于集合的快捷写法

其实类似Java的语法,只是ql不支持for(obj:list){}的语法,只能通过下表的方式访问。

@Test
public void testSet() throws Exception {
    ExpressRunner runner = new ExpressRunner();
    DefaultContext<String, Object> context = new DefaultContext<>();
    String exp = "abc = NewMap(1:1, 2:2); return abc.get(1) + abc.get(2);";
    Object execute = runner.execute(exp, context, null, false, false);
    System.out.println(execute);

    exp = "abc = NewList(1, 2, 3); return abc.get(1) + abc.get(2);";
    Object execute1 = runner.execute(exp, context, null, false, false);
    System.out.println(execute1);

    exp = "abc = [1, 2, 3]; return abc[1] + abc[2];";
    Object execute2 = runner.execute(exp, context, null, false, false);
    System.out.println(execute2);
    }