0


掌握 QLExpress:阿里巴巴开源的业务规则动态解析神器

干货分享,感谢您的阅读!

在现代业务系统中,如何实现快速、灵活的规则配置和动态决策,成为了企业提升响应速度和智能化水平的关键。阿里巴巴开源的 QLExpress 引擎,以其轻量、高效、简洁的优势,为复杂业务逻辑的动态处理提供了一种创新的解决方案。无论是需要实时调整规则的电商促销,还是依赖规则动态性的金融风控,QLExpress 都能以其灵活的表达式和易用的规则配置,实现高效而精准的业务决策。如果你正在寻找一款强大、灵活的动态脚本引擎,那么 QLExpress 可能正是你需要的工具!官网学习地址:https://github.com/alibaba/QLExpress

一、QLExpress快速了解

QLExpress(Quick Language Express)是阿里巴巴开源的一门动态脚本引擎解析工具,起源于阿里巴巴的电商业务,旨在解决业务规则、表达式、数学计算等动态脚本的解析问题。其具有以下基本特点:

  1. 线程安全:QLExpress被设计为线程安全的动态脚本引擎,它使用threadlocal类型的临时变量,确保在引擎运算过程中的并发场景下的线程安全性。
  2. 高效执行:为了提高执行效率,QLExpress在编译过程中可以将比较耗时的脚本编译结果缓存到本地机器。此外,运行时的临时变量创建采用了缓冲池技术,以确保高效的运行时性能,使其与一些性能优秀的脚本引擎(如Groovy)相当。
  3. 弱类型脚本语言:QLExpress采用弱类型脚本语言,语法类似于Groovy和JavaScript。这使得业务规则的表达更加灵活,虽然相对于强类型脚本语言可能略慢,但在业务的灵活性方面提供了很大的优势。
  4. 安全控制:QLExpress提供了一些运行时参数的设置,以进行安全控制。通过这些参数,可以预防一些潜在的安全问题,如死循环或对高危系统API的调用。
  5. 代码精简、依赖最小:QLExpress的设计追求代码的精简和最小依赖,其jar包大小为250k,适用于所有Java的运行环境。这使得它在各种环境中都能轻松部署和运行,包括在Android系统的低端POS机上广泛应用。

总体而言,这些特性使QLExpress成为一个在阿里电商业务场景中得到广泛应用的强大工具,具有高效、灵活和安全的特点。

二、QLExpress与常用规则引擎对比

特性 / 规则引擎DroolsAviatorEasyRuleQLExpress语言Drools规则语言 (DRL)Aviator表达式语言Java弱类型脚本语言性能适用于复杂规则,可能较慢高性能表达式求值引擎相对较高性能,适用于简单规则高效执行,适用于业务规则和表达式计算灵活性非常灵活,支持动态修改规则灵活,支持丰富的运算符和函数简单易用,适合非专业开发人员灵活,支持业务规则、表达式和数学计算语法专门的规则语言表达式语言Java编写规则弱类型脚本语言,类似于Groovy和JavaScript应用场景复杂的业务规则简单的表达式计算和规则简单规则场景,面向非专业开发人员业务规则、表达式、数学计算,适用于电商业务开发者社区大型开发者社区相对较小的社区规模相对较小的社区规模相对较小的社区规模,阿里巴巴内部有影响力文档详尽的文档文档相对较少文档相对较少文档相对较少,可能需要深入源代码理解开源是是是是
Drools适用于复杂的业务规则,而Aviator和QLExpress适用于相对简单的表达式计算和规则。EasyRule更适合简单规则场景,特别是面向非专业开发人员的情况。最终选择取决于具体需求,包括业务规则的复杂性、性能要求、开发人员技能水平以及项目的特定场景。

三、快速引用和一般工作原理简诉

(一)引用与基本演示

在 Maven 项目中引入 QLExpress,需要在项目的

pom.xml

文件中添加相关的依赖:

<dependencies>
    <dependency>
        <groupId>com.ql</groupId>
        <artifactId>qlExpress</artifactId>
        <version>3.2.2</version> <!-- 使用实际版本号 -->
    </dependency>
</dependencies>

以下展示简单演示如何使用 QLExpress 计算折扣后的金额。在实际项目中,可能需要更复杂的脚本和上下文,以适应业务需求。

package org.zyf.javabasic.qlexpress;

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

/**
 * @program: zyfboot-javabasic
 * @description: 演示如何使用 QLExpress 计算折扣后的金额
 * @author: zhangyanfeng
 * @create: 2023-11-12 21:40
 **/
public class QLExpressExample {
    public static void main(String[] args) {
        try {
            // 创建 QLExpress 引擎
            ExpressRunner runner = new ExpressRunner();

            // 创建上下文并设置变量
            DefaultContext<String, Object> context = new DefaultContext<>();
            context.put("amount", 1000);
            context.put("discount", 0.1);

            // 执行脚本
            String expression = "amount * (1 - discount)";
            Object result = runner.execute(expression, context, null, true, false);

            // 输出结果
            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

(二)一般工作原理说明

QLExpress的一般工作原理,包括语法树分析、上下文和执行过程。

  1. 语法树分析: QLExpress会先将输入的脚本进行词法分析和语法分析,生成一棵语法树。这个语法树表示了脚本的结构,将其组织成可以被执行的形式。
  2. 上下文: 在QLExpress中,上下文是一个关键概念。它是脚本执行时的环境,包含变量、函数等信息。在执行脚本之前,你可以向上下文中添加变量,定义函数,设置一些执行参数等。这样,脚本执行时可以引用上下文中的内容。
  3. 执行过程: QLExpress的执行过程包括编译和运行两个主要阶段。在编译阶段,脚本被解析并生成可以执行的指令序列。在运行阶段,这些指令被执行,从而实现脚本的功能。

在这个框架下,二次定制的功能扩展可以包括:

  • 自定义操作符和函数: 可以通过实现自定义的操作符和函数,使得脚本能够执行特定的业务逻辑。
  • 修改执行流程: 可以在执行阶段插入自定义的逻辑,改变脚本执行的流程。
  • 定制编译过程: 在编译阶段定制特定的优化或变换,以满足特殊需求。
  • 扩展上下文功能: 可以添加一些上下文的拦截器,使得在脚本执行前后可以执行额外的逻辑。

这样的扩展点允许更好地适应特定的业务场景和需求。

四、基本语法学习

(一)操作符

QLExpress 支持一系列操作符,包括算术运算符、比较运算符、逻辑运算符等。
类别操作符示例描述算术运算符+

a + b

加法,将两个数字相加-

a - b

减法,从第一个数字中减去第二个数字*

a * b

乘法,将两个数字相乘/

a / b

除法,将第一个数字除以第二个数字%

a % b

取余,返回第一个数字除以第二个数字的余数比较运算符==

a == b

等于,判断两个值是否相等!=

a != b

不等于,判断两个值是否不相等>

a > b

大于,判断第一个值是否大于第二个值<

a < b

小于,判断第一个值是否小于第二个值>=

a >= b

大于等于,判断第一个值是否大于或等于第二个值<=

a <= b

小于等于,判断第一个值是否小于或等于第二个值逻辑运算符&&

condition1 && condition2

逻辑与,两个条件都为真时结果为真||

condition1 || condition2

逻辑或,两个条件中有一个为真时结果为真!

!condition

逻辑非,将真变为假,假变为真三元运算符? :

condition ? valueIfTrue : valueIfFalse

用于根据条件选择两个值中的一个
定义一个

Calculator

类,其中包含一些数字和一个用户对象,然后使用 QLExpress 进行一些简单的运算和条件判断。

package org.zyf.javabasic.qlexpress;

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

/**
 * @program: zyfboot-javabasic
 * @description: 一些简单的运算和条件判断
 * @author: zhangyanfeng
 * @create: 2023-11-12 21:48
 **/
public class Calculator {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 设置变量
            context.put("a", 10);
            context.put("b", 5);

            // 算术运算示例
            executeAndPrint(runner, context, "a + b", "Addition");

            // 比较运算示例
            executeAndPrint(runner, context, "a > b", "Greater than");

            // 逻辑运算示例
            executeAndPrint(runner, context, "a > 0 && b > 0", "Logical AND");

            // 三元运算示例
            executeAndPrint(runner, context, "a > b ? 'a is greater' : 'b is greater'", "Ternary Operator");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void executeAndPrint(ExpressRunner runner, DefaultContext<String, Object> context, String expression, String operation) throws Exception {
        // 执行脚本
        Object result = runner.execute(expression, context, null, true, false);

        // 输出结果
        System.out.println(operation + ": " + result);
    }
}

(二)Java对象操作

基本的 Java 语法和对象操作在 QLExpress 中同样适用,演示在 QLExpress 中使用 Java 语法和进行对象操作:

package org.zyf.javabasic.qlexpress;

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

/**
 * @program: zyfboot-javabasic
 * @description: 基本的 Java 语法和对象操作
 * @author: zhangyanfeng
 * @create: 2023-11-12 21:54
 **/
public class QLExpressJavaSyntaxExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 创建一个用户对象
            User user = new User("John", 25);
            context.put("user", user);

            // 使用 Java 语法访问对象属性和调用方法
            executeAndPrint(runner, context, "user.getName()", "Accessing Object Property");
            executeAndPrint(runner, context, "user.getAge() + 5", "Performing Arithmetic with Object Property");

            // 使用 Java 语法进行对象操作
            executeAndPrint(runner, context, "user.age = 30", "Modifying Object Property");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void executeAndPrint(ExpressRunner runner, DefaultContext<String, Object> context, String expression, String operation) throws Exception {
        // 执行脚本
        Object result = runner.execute(expression, context, null, true, false);

        // 输出结果
        System.out.println(operation + ": " + result);
    }

    // 用户对象类
    static class User {
        private String name;
        private int age;

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public int getAge() {
            return age;
        }
    }
}

(三) 脚本中定义function

在QLExpress中,可以通过

function

关键字来定义函数。以下是一个简单的示例:

package org.zyf.javabasic.qlexpress;

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

/**
 * @program: zyfboot-javabasic
 * @description: 通过 function 关键字来定义函数
 * @author: zhangyanfeng
 * @create: 2023-11-12 22:11
 **/
public class QLExpressFunctionExample {
    public static void main(String[] args) {
        try {
            final String express = "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" +
                    "result = add(a, 4) + sub(a, 9);\n" +
                    "return result;";
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 执行脚本
            Object result = runner.execute(express, context, null, true, false);

            // 输出脚本执行结果
            System.out.println("Result: " + result);

            // 输出函数调用过程中的参数和返回值
            System.out.println("add function call: a + 4 = " + context.get("a") + " + 4 = " + context.get("result"));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

(四)扩展操作符

在 QLExpress 中,可以通过自定义操作符(Operator)来扩展语言的功能。

package org.zyf.javabasic.qlexpress;

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

import java.util.ArrayList;
import java.util.List;

/**
 * @program: zyfboot-javabasic
 * @description: 自定义操作符(Operator)来扩展语言的功能
 * @author: zhangyanfeng
 * @create: 2023-11-12 23:13
 **/
public class QLExpressOperatorExample {
    public static void main(String[] args) {
        try {
            // 示例 1:替换 if then else 关键字
            ExpressRunner runner1 = new ExpressRunner();
            DefaultContext<String, Object> context1 = new DefaultContext<>();
            context1.put("语文",120);
            context1.put("数学",23);
            context1.put("英语",23);

            runner1.addOperatorWithAlias("如果", "if", null);
            runner1.addOperatorWithAlias("则", "then", null);
            runner1.addOperatorWithAlias("否则", "else", null);

            String express1 = "如果 (语文 + 数学 + 英语 > 270) 则 {return 1;} 否则 {return 0;}";
            Object result1 = runner1.execute(express1, context1, null, false, false, 100L);
            System.out.println("Result 1: " + result1); // 输出结果 1

            // 示例 2:自定义 Operator
            ExpressRunner runner2 = new ExpressRunner();
            DefaultContext<String, Object> context2 = new DefaultContext<>();

            // 自定义 Operator
            runner2.addOperator("join", new JoinOperator());

            // 示例 2.1:addOperator
            Object result2_1 = runner2.execute("1 join 2 join 3", context2, null, false, false);
            System.out.println("Result 2.1: " + result2_1); // 输出结果 [1, 2, 3]

            // 示例 2.2:replaceOperator
            ExpressRunner runner2_2 = new ExpressRunner();
            runner2_2.replaceOperator("+", new JoinOperator());
            Object result2_2 = runner2_2.execute("1 + 2 + 3", context2, null, false, false);
            System.out.println("Result 2.2: " + result2_2); // 输出结果 [1, 2, 3]

            // 示例 2.3:addFunction
            ExpressRunner runner2_3 = new ExpressRunner();
            runner2_3.addFunction("join", new JoinOperator());
            Object result2_3 = runner2_3.execute("join(1, 2, 3)", context2, null, false, false);
            System.out.println("Result 2.3: " + result2_3); // 输出结果 [1, 2, 3]

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // JoinOperator 类的定义
    public static class JoinOperator extends com.ql.util.express.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;
            }
        }
    }
}

(五)绑定Java类或对象的methon

在 QLExpress 中,可使用

addFunctionOfClassMethod

addFunctionOfServiceMethod

方法来绑定 Java 类或对象的方法。

package org.zyf.javabasic.qlexpress;

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

/**
 * @program: zyfboot-javabasic
 * @description: 可以使用 addFunctionOfClassMethod 和 addFunctionOfServiceMethod 方法来绑定 Java 类或对象的方法
 * @author: zhangyanfeng
 * @create: 2023-11-19 16:13
 **/
public class QLExpressFunctionBindingExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 绑定 Math 类的 abs 方法
            runner.addFunctionOfClassMethod("取绝对值", Math.class.getName(), "abs", new String[]{"double"}, null);

            // 绑定 BeanExample 类的 upper 方法
            runner.addFunctionOfClassMethod("转换为大写", BeanExample.class.getName(), "upper", new String[]{"String"}, null);

            // 绑定 System.out 的 println 方法
            runner.addFunctionOfServiceMethod("打印", System.out, "println", new String[]{"String"}, null);

            // 绑定 BeanExample 对象的 anyContains 方法
            runner.addFunctionOfServiceMethod("contains", new BeanExample(), "anyContains", new Class[]{String.class, String.class}, null);

            String express = "取绝对值(-100); 转换为大写(\"hello world\"); 打印(\"你好吗?\"); contains(\"helloworld\", \"aeiou\")";
            Object result = runner.execute(express, context, null, false, false);

            System.out.println("Result: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static class BeanExample {
        public static double abs(double value) {
            System.out.println("取绝对值结果: " + value);
            return Math.abs(value);
        }

        public static String upper(String abc) {
            System.out.println("转换为大写结果: " + abc.toUpperCase());
            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;
        }
    }
}

(六)宏定义(macro)

在QLExpress中,宏定义(macro)允许将一个表达式片段命名为宏,并在其他地方引用这个宏。当需要在多个地方使用相同的复杂表达式时非常有用,可以提高代码的可读性和维护性。

package org.zyf.javabasic.qlexpress;

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

/**
 * @program: zyfboot-javabasic
 * @description: macro 宏定义
 * @author: zhangyanfeng
 * @create: 2023-11-19 17:40
 **/
public class QLExpressMacroExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 定义宏
            runner.addMacro("计算平均成绩", "(语文+数学+英语)/3.0");
            runner.addMacro("是否优秀", "计算平均成绩>90");

            // 设置变量值
            context.put("语文", 88);
            context.put("数学", 99);
            context.put("英语", 95);

            // 执行表达式并打印结果
            Object result = runner.execute("是否优秀", context, null, false, false);
            System.out.println("Result: " + result);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

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

在QLExpress中,编译脚本并查询外部需要定义的变量和函数可以通过

ExpressRunner

提供的一些方法来实现。

package org.zyf.javabasic.qlexpress;

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

/**
 * @program: zyfboot-javabasic
 * @description: 编译脚本,查询外部需要定义的变量和函数。
 * @author: zhangyanfeng
 * @create: 2023-11-19 17:49
 **/
public class QLExpressCompileExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner(true, true);
            DefaultContext<String, Object> context = new DefaultContext<>();
            context.put("语文",120);
            context.put("数学",23);
            context.put("英语",23);
            context.put("综合考试",235);

            // 定义脚本
            String express = "double 平均分 = (语文 + 数学 + 英语 + 综合考试) / 4.0; return 平均分";

            // 查询外部需要定义的变量
            String[] variableNames = runner.getOutVarNames(express);
            System.out.println("外部需要定义的变量:");
            for (String variableName : variableNames) {
                System.out.println("var : " + variableName);
            }

            // 查询外部需要定义的函数
            String[] functionNames = runner.getOutFunctionNames(express);
            System.out.println("\n外部需要定义的函数:");
            for (String functionName : functionNames) {
                System.out.println("function : " + functionName);
            }

            // 编译脚本并执行
            Object result = runner.execute(express, context, null, false, false);
            System.out.println("\n脚本执行结果: " + result);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

看下对应打印结果:

执行的表达式:double 平均分 = (语文 + 数学 + 英语 + 综合考试) / 4.0; return 平均分
单词分解结果:{double},{平均分},{=},{(},{语文},{+},{数学},{+},{英语},{+},{综合考试},{)},{/},{4.0},{;},{return},{平均分}
预处理后结果:{double},{平均分},{=},{(},{语文},{+},{数学},{+},{英语},{+},{综合考试},{)},{/},{4.0},{;},{return},{平均分}
单词分析结果:double:CONST_CLASS,平均分:ID,=:=,(:(,语文:ID,+:+,数学:ID,+:+,英语:ID,+:+,综合考试:ID,):),/:/,4.0:CONST_DOUBLE,;:;,return:return,平均分:ID
最后的语法树:
1:   STAT_BLOCK:STAT_BLOCK                                                             STAT_BLOCK
2:      STAT_SEMICOLON:STAT_SEMICOLON    STAT_SEMICOLON
3:         =:=    =
4:            def:def    def
5:               double:CONST_CLASS    CONST_CLASS
5:               平均分:CONST_STRING    CONST
4:            /:/    /
5:               (:CHILD_EXPRESS    CHILD_EXPRESS
6:                  +:+    +
7:                     +:+    +
8:                        +:+    +
9:                           语文:ID    ID
9:                           数学:ID    ID
8:                        英语:ID    ID
7:                     综合考试:ID    ID
5:               4.0:CONST_DOUBLE    CONST
2:      STAT_SEMICOLON:STAT_SEMICOLON    STAT_SEMICOLON
3:         return:return    return
4:            平均分:ID    ID

1:LoadData Class:double
2:LoadData 平均分
3:OP : def OPNUMBER[2]
4:LoadAttr:语文
5:LoadAttr:数学
6:OP : + OPNUMBER[2]
7:LoadAttr:英语
8:OP : + OPNUMBER[2]
9:LoadAttr:综合考试
10:OP : + OPNUMBER[2]
11:LoadData 4.0
12:OP : / OPNUMBER[2]
13:OP : = OPNUMBER[2]
14:clearDataStack
15:LoadAttr:平均分
16:return [value]

外部需要定义的变量:
var : 数学
var : 综合考试
var : 英语
var : 语文
执行的表达式:double 平均分 = (语文 + 数学 + 英语 + 综合考试) / 4.0; return 平均分
单词分解结果:{double},{平均分},{=},{(},{语文},{+},{数学},{+},{英语},{+},{综合考试},{)},{/},{4.0},{;},{return},{平均分}
预处理后结果:{double},{平均分},{=},{(},{语文},{+},{数学},{+},{英语},{+},{综合考试},{)},{/},{4.0},{;},{return},{平均分}
单词分析结果:double:CONST_CLASS,平均分:ID,=:=,(:(,语文:ID,+:+,数学:ID,+:+,英语:ID,+:+,综合考试:ID,):),/:/,4.0:CONST_DOUBLE,;:;,return:return,平均分:ID
最后的语法树:
1:   STAT_BLOCK:STAT_BLOCK                                                             STAT_BLOCK
2:      STAT_SEMICOLON:STAT_SEMICOLON    STAT_SEMICOLON
3:         =:=    =
4:            def:def    def
5:               double:CONST_CLASS    CONST_CLASS
5:               平均分:CONST_STRING    CONST
4:            /:/    /
5:               (:CHILD_EXPRESS    CHILD_EXPRESS
6:                  +:+    +
7:                     +:+    +
8:                        +:+    +
9:                           语文:ID    ID
9:                           数学:ID    ID
8:                        英语:ID    ID
7:                     综合考试:ID    ID
5:               4.0:CONST_DOUBLE    CONST
2:      STAT_SEMICOLON:STAT_SEMICOLON    STAT_SEMICOLON
3:         return:return    return
4:            平均分:ID    ID

1:LoadData Class:double
2:LoadData 平均分
3:OP : def OPNUMBER[2]
4:LoadAttr:语文
5:LoadAttr:数学
6:OP : + OPNUMBER[2]
7:LoadAttr:英语
8:OP : + OPNUMBER[2]
9:LoadAttr:综合考试
10:OP : + OPNUMBER[2]
11:LoadData 4.0
12:OP : / OPNUMBER[2]
13:OP : = OPNUMBER[2]
14:clearDataStack
15:LoadAttr:平均分
16:return [value]

外部需要定义的函数:
执行的表达式:double 平均分 = (语文 + 数学 + 英语 + 综合考试) / 4.0; return 平均分
单词分解结果:{double},{平均分},{=},{(},{语文},{+},{数学},{+},{英语},{+},{综合考试},{)},{/},{4.0},{;},{return},{平均分}
预处理后结果:{double},{平均分},{=},{(},{语文},{+},{数学},{+},{英语},{+},{综合考试},{)},{/},{4.0},{;},{return},{平均分}
单词分析结果:double:CONST_CLASS,平均分:ID,=:=,(:(,语文:ID,+:+,数学:ID,+:+,英语:ID,+:+,综合考试:ID,):),/:/,4.0:CONST_DOUBLE,;:;,return:return,平均分:ID
最后的语法树:
1:   STAT_BLOCK:STAT_BLOCK                                                             STAT_BLOCK
2:      STAT_SEMICOLON:STAT_SEMICOLON    STAT_SEMICOLON
3:         =:=    =
4:            def:def    def
5:               double:CONST_CLASS    CONST_CLASS
5:               平均分:CONST_STRING    CONST
4:            /:/    /
5:               (:CHILD_EXPRESS    CHILD_EXPRESS
6:                  +:+    +
7:                     +:+    +
8:                        +:+    +
9:                           语文:ID    ID
9:                           数学:ID    ID
8:                        英语:ID    ID
7:                     综合考试:ID    ID
5:               4.0:CONST_DOUBLE    CONST
2:      STAT_SEMICOLON:STAT_SEMICOLON    STAT_SEMICOLON
3:         return:return    return
4:            平均分:ID    ID

1:LoadData Class:double
2:LoadData 平均分
3:OP : def OPNUMBER[2]
4:LoadAttr:语文
5:LoadAttr:数学
6:OP : + OPNUMBER[2]
7:LoadAttr:英语
8:OP : + OPNUMBER[2]
9:LoadAttr:综合考试
10:OP : + OPNUMBER[2]
11:LoadData 4.0
12:OP : / OPNUMBER[2]
13:OP : = OPNUMBER[2]
14:clearDataStack
15:LoadAttr:平均分
16:return [value]

脚本执行结果: 100.25

(八)不定参数的使用

在QLExpress中,可以通过使用不定参数(动态参数)来处理方法的参数。

package org.zyf.javabasic.qlexpress;

import com.ql.util.express.DefaultContext;
import com.ql.util.express.DynamicParamsUtil;
import com.ql.util.express.ExpressRunner;

/**
 * @program: zyfboot-javabasic
 * @description: 不定参数的使用
 * @author: zhangyanfeng
 * @create: 2023-11-19 18:00
 **/
public class QLExpressDynamicParamsExample {
    public static void main(String[] args) {
        try {
            // 创建 ExpressRunner 实例
            ExpressRunner runner = new ExpressRunner();

            // 创建 DefaultContext 实例
            DefaultContext<String, Object> expressContext = new DefaultContext<>();

            // 在 runner 中添加一个函数,使用不定参数
            runner.addFunctionOfServiceMethod("getTemplate", new QLExpressDynamicParamsExample(), "getTemplate",
                    new Class[]{Object[].class}, null);

            // 调用 getTemplate 方法,传递数组作为参数
            Object resultWithArray = runner.execute("getTemplate([11, '22', 33L, true])",
                    expressContext, null, false, false);
            System.out.println("Result with Array: " + resultWithArray);

            // 打开全局开关,启用动态参数调用
            DynamicParamsUtil.supportDynamicParams = true;

            // 调用 getTemplate 方法,传递多个参数
            Object resultWithDynamicParams = runner.execute("getTemplate(11, '22', 33L, true)", expressContext,
                    null, false, false);
            System.out.println("Result with Dynamic Params: " + resultWithDynamicParams);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 等价于 getTemplate(Object[] params)
    public Object getTemplate(Object... params) {
        StringBuilder result = new StringBuilder();
        for (Object obj : params) {
            result.append(obj).append(",");
        }
        return result.toString();
    }
}

(九)集合的快捷用法

在QLExpress中,你可以使用一些快捷的语法来操作集合。

package org.zyf.javabasic.qlexpress;

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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @program: zyfboot-javabasic
 * @description: 集合操作
 * @author: zhangyanfeng
 * @create: 2023-11-19 19:04
 **/
public class QLExpressCollectionOperationsExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 使用NewMap创建Map
            String expressMap = "abc = NewMap(1:1, 2:2); return abc.get(1) + abc.get(2);";
            Object resultMap = runner.execute(expressMap, context, null, false, false);
            System.out.println("NewMap Result: " + resultMap);

            // 使用NewList创建List
            String expressList = "abc = NewList(1, 2, 3); return abc.get(1) + abc.get(2);";
            Object resultList = runner.execute(expressList, context, null, false, false);
            System.out.println("NewList Result: " + resultList);

            // 使用方括号[]创建List
            String expressSquareBrackets = "abc = [1, 2, 3]; return abc[1] + abc[2];";
            Object resultSquareBrackets = runner.execute(expressSquareBrackets, context, null, false, false);
            System.out.println("Square Brackets Result: " + resultSquareBrackets);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

(十)集合的遍历

类似java的语法,只是ql不支持for(obj:list){}的语法,只能通过下标访问。

package org.zyf.javabasic.qlexpress;

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

import java.util.HashMap;
import java.util.Map;

/**
 * @program: zyfboot-javabasic
 * @description: 使用foreach关键字结合索引遍历集合
 * @author: zhangyanfeng
 * @create: 2023-11-19 19:36
 **/
public class QLExpressCollectionTraversalExample {
    public static void main(String[] args) {
        try {
            ExpressRunner runner = new ExpressRunner();
            DefaultContext<String, Object> context = new DefaultContext<>();

            // 创建一个Map
            Map<String, String> map = new HashMap<>();
            map.put("a", "a_value");
            map.put("b", "b_value");

            // 将Map放入上下文中
            context.put("map", map);

            // 遍历Map
            String express = "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(express, context, null, false, false);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

四、总结

本文全面介绍了 QLExpress 作为阿里巴巴开源的一款轻量级动态脚本引擎的主要功能和应用场景,并深入讲解了其在规则引擎和业务场景中的使用优势。通过对比其他规则引擎的性能与灵活性,QLExpress 展现了其在处理动态规则、复杂计算逻辑、实时决策等方面的独特优势,尤其适用于需要动态配置业务规则的场景。在实际应用中,QLExpress 的简洁语法、快速上手和高性能表现,使其成为了企业动态业务处理的有效工具。

未来,QLExpress 还可以进一步优化和拓展,以支持更多场景和定制化功能。对于希望在系统中灵活应用规则逻辑、提升业务决策效率的开发者来说,QLExpress 是一个值得关注和尝试的工具。希望通过本文的介绍,大家能更好地理解和应用 QLExpress,从而在项目中实现更高效、更灵活的规则管理。

标签: express java 后端

本文转载自: https://blog.csdn.net/xiaofeng10330111/article/details/134363363
版权归原作者 张彦峰ZYF 所有, 如有侵权,请联系我们删除。

“掌握 QLExpress:阿里巴巴开源的业务规则动态解析神器”的评论:

还没有评论