QLExpress使用说明

一个轻量级的类java语法规则引擎,作为一个嵌入式规则引擎在业务系统中使用。让业务规则定义简便而不失灵活。让业务人员就可以定义业务规则。支持标准的JAVA语法,还可以支持自定义操作符号、操作符号重载、函数定义、宏定义、数据延迟加载等。

QLExpress在阿里集团内部有很强的影响力,被广泛应用在电商场景中,在业务灵活多变的场景下常常需要规则引擎的支持。

官方地址为:https://github.com/alibaba/QLExpress

项目已经给了很多的使用案例,但是如果不亲自运行一下,不容易体会到其用法与强大之处,本次主要给一个QLExpress的运行案例,可以真正的跑起来官方给的各种案例代码。

案例

说明:

/**
 * 执行一段文本
 * @param expressString 程序文本
 * @param context 执行上下文,执行上下文在执行的过程中会产生中间数据,上下文不要重复使用
 * @param errorList 输出的错误信息List
 * @param isCache 是否使用Cache中的指令集
 * @param isTrace 是否输出详细的执行指令信息
 * @return
 * @throws Exception
 */
   public Object execute(String expressString, IExpressContext<String,Object> context,
         List<String> errorList, boolean isCache, boolean isTrace) throws Exception {
      return this.execute(expressString, context, errorList, isCache, isTrace, null);
   }

运行案例:

package me.aihe.demo.qlexpress;

import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import com.ql.util.express.IExpressContext;
import com.ql.util.express.Operator;


import java.util.HashMap;

public class QLExpressionDemo {
    public static void main(String[] args) throws Exception {
        ExpressRunner runner = new ExpressRunner();
//        getStarted(runner);

//        basicStatement(runner);

//        collectionStatement(runner);

//        objectStatement(runner);

//        functionStatement(runner);

//        macronStatement(runner);

//        workWithJavaStatement(runner);

//        extendOperatorStatement(runner);

        customOperatorStatement(runner);
    }

    /**
     * 自定义操作符的使用
     * @param runner
     */
    private static void customOperatorStatement(ExpressRunner runner) throws Exception {
         class JoinOperator extends Operator {
            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;
                }
            }
        }

        // 返回结果  [1, 2, 3]
        //(1)addOperator
        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);

        // (2) addFunction
        // 返回结果:[1 , 2 ]
        runner.addFunction("joinfunc",new JoinOperator());
        r = runner.execute("joinfunc(1,2,3)", context, null, false, false);
        System.out.println(r);

        //(3)replaceOperator
        // 返回结果  [1, 2, 3]
        runner.replaceOperator("+",new JoinOperator());
        r = runner.execute("1 + 2 + 3", context, null, false, false);
        System.out.println(r);


    }

    /**
     * 操作符处理,一般不太常用,但可以自定义一些操作符
     * @param runner
     */
    private static void extendOperatorStatement(ExpressRunner runner) throws Exception {
        runner.addOperatorWithAlias("如果", "if",null);
        runner.addOperatorWithAlias("则", "then",null);
        runner.addOperatorWithAlias("否则", "else",null);

        IExpressContext<String, Object> context =new DefaultContext<String, Object>();
        context.put("语文", 88);
        context.put("数学", 99);
        context.put("英语", 95);

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

    /**
     * 将Java中已经写好的一些方法,绑定到我们自定义的变量上,在业务中最常用的部分。
     * @param runner
     */
    private static void workWithJavaStatement(ExpressRunner runner) throws Exception {
        // 在使用的时候会创建对象
        runner.addFunctionOfClassMethod("取绝对值", Math.class.getName(), "abs",
                new String[] { "double" }, null);

        // 对象已经存在,直接调用对象中的方法
        runner.addFunctionOfServiceMethod("打印", System.out, "println",new String[] { "String" }, null);

        String exp = "a=取绝对值(-100);打印(\"Hello World\");打印(a.toString())";
        DefaultContext<String, Object> context = new DefaultContext<>();
        runner.execute(exp, context,null,false,false);
        System.out.println(context);
    }

    /**
     * Macro定义, 即预先定义一些内容,在使用的时候直接替换Macro中的变量为上下文的内容
     * @param runner
     */
    private static void macronStatement(ExpressRunner runner) throws Exception {
        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(result);
    }

    /**
     * 自定义在QLexpress中的函数
     * 一般语句的最后一句话是返回结果
     * @param runner
     */
    private static void functionStatement(ExpressRunner runner) throws Exception {
        String functionStatement = "function add(int a,int b){\n" +
                "  return a+b;\n" +
                "};\n" +
                "\n" +
                "function sub(int a,int b){\n" +
                "  return a - b;\n" +
                "};\n" +
                "\n" +
                "a=10;\n" +
                "add(a,4) + sub(a,9);";

        Object result = runner.execute(functionStatement, new DefaultContext<>(), null, false, false);
//        runner.execute(functionStatement, new DefaultContext<>(), null, true, false, 1000);
        System.out.println(result);
    }

    /**
     * 对Java对象的操作
     * 系统自动会import java.lang.*,import java.util.*;
     * @param runner
     */
    private static void objectStatement(ExpressRunner runner) throws Exception {
//        TradeEvent tradeEvent = new TradeEvent();
//        tradeEvent.setPrice(20.0);
//        tradeEvent.setName("购物");
//        tradeEvent.setId(UUID.randomUUID().toString());//
//
        String objectStatement = "import me.aihe.demo.trade.TradeEvent;\n" +
                "        tradeEvent = new TradeEvent();\n" +
                "        tradeEvent.setPrice(20.0);\n" +
                "        tradeEvent.id=UUID.randomUUID().toString();\n" +
                "        System.out.println(tradeEvent.getId());\n" +
                "        System.out.println(tradeEvent.price);";
        runner.execute(objectStatement, new DefaultContext<>(), null, false, false);

    }

    /**
     * 在定义集合,数组相关的操作,ql有自己的语法,不能简单的使用Java语法
     * //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));
     *   }
     * @param runner
     */
    private static void collectionStatement(ExpressRunner runner) throws Exception {
        DefaultContext<String, Object> defaultContext = new DefaultContext<>();
        HashMap<String, String> mapData = new HashMap(){{
            put("a","hello");
            put("b","world");
            put("c","!@#$");
        }};
        defaultContext.put("map",mapData);
        //ql不支持for(obj:list){}的语法,只能通过下标访问。
        String mapTraverseStatement = " keySet = map.keySet();\n" +
                "  objArr = keySet.toArray();\n" +
                "  for (i=0;i<objArr.length;i++) {\n" +
                "  key = objArr[i];\n" +
                "   System.out.println(map.get(key));\n" +
                "  }";
        runner.execute(mapTraverseStatement,defaultContext,null,false,false);
        System.out.println(defaultContext);

        // 集合的快捷写法
        /**
         *       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);
         */

    }

    /**
     * 语法基本说明:
     * 不支持try{}catch{}
     * 不支持java8的lambda表达式
     * 不支持for循环集合操作for (GRCRouteLineResultDTO item : list)
     * 弱类型语言,请不要定义类型声明
     * 不要用Templete(Map<String,List>之类的)
     * array的声明不一样
     * min,max,round,print,println,like,in 都是系统默认函数的关键字,请不要作为变量名
     * @param runner
     */
    private static void basicStatement(ExpressRunner runner) throws Exception {
        DefaultContext<String, Object> defaultContext = new DefaultContext<>();

//        defaultContext.put("n",10);  //直接从Java中传递上下文等于在表达式中传递上下文
        String loopStatement = "sum=0;n=10;" +
                "for(i=0;i<n;i++){\n" +
                "sum=sum+i;\n" +
                "}\n" +
                "return sum;";
        Object result = runner.execute(loopStatement, defaultContext, null, false, false);
        System.out.println(result);


        // 注意使用同一个defaultContext,上一步语句执行的中间变量会被传递到下一个语句中
        String maxmiumStatement = "a=1;\n" +
                "b=2;\n" +
                "maxnum = a>b?a:b;";
        result = runner.execute(maxmiumStatement, defaultContext, null, false, false);
        System.out.println(result);

    }

    // 入门展示express使用部分
    private static void getStarted(ExpressRunner runner) throws Exception {
        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, true);
        System.out.println(r);
    }
}

image-20191011080218251

说明

绑定java类或者对象的method,在业务中经常用到,可以重点关注,其余的亲自跑一下看看运行效果。

参考:

https://github.com/alibaba/QLExpress

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容