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

莱莱  金牌会员 | 2024-11-5 16:27:03 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 837|帖子 837|积分 2511

目录
一、QLExpress快速了解
二、QLExpress与常用规则引擎对比
三、快速引用和一般工作原理简诉
(一)引用与根本演示
(二)一般工作原理说明
四、根本语法学习
(一)操作符
(二)Java对象操作
(三) 脚本中定义function
(四)扩展操作符
(五)绑定Java类或对象的methon
(六)宏定义(macro)
(七)编译脚本查询外部必要定义的变量和函数
(八)不定参数的使用
(九)聚集的快捷用法
(十)聚集的遍历
四、总结

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

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

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

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

(一)引用与根本演示

在 Maven 项目中引入 QLExpress,必要在项目的 pom.xml 文件中添加相关的依赖:
  1. <dependencies>
  2.     <dependency>
  3.         <groupId>com.ql</groupId>
  4.         <artifactId>qlExpress</artifactId>
  5.         <version>3.2.2</version> <!-- 使用实际版本号 -->
  6.     </dependency>
  7. </dependencies>
复制代码
以下展示简单演示如何使用 QLExpress 盘算折扣后的金额。在现实项目中,大概必要更复杂的脚本和上下文,以适应业务需求。
  1. package org.zyf.javabasic.qlexpress;
  2. import com.ql.util.express.DefaultContext;
  3. import com.ql.util.express.ExpressRunner;
  4. /**
  5. * @program: zyfboot-javabasic
  6. * @description: 演示如何使用 QLExpress 计算折扣后的金额
  7. * @author: zhangyanfeng
  8. * @create: 2023-11-12 21:40
  9. **/
  10. public class QLExpressExample {
  11.     public static void main(String[] args) {
  12.         try {
  13.             // 创建 QLExpress 引擎
  14.             ExpressRunner runner = new ExpressRunner();
  15.             // 创建上下文并设置变量
  16.             DefaultContext<String, Object> context = new DefaultContext<>();
  17.             context.put("amount", 1000);
  18.             context.put("discount", 0.1);
  19.             // 执行脚本
  20.             String expression = "amount * (1 - discount)";
  21.             Object result = runner.execute(expression, context, null, true, false);
  22.             // 输出结果
  23.             System.out.println("Result: " + result);
  24.         } catch (Exception e) {
  25.             e.printStackTrace();
  26.         }
  27.     }
  28. }
复制代码
(二)一般工作原理说明

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


  • 语法树分析: QLExpress会先将输入的脚本举行词法分析和语法分析,天生一棵语法树。这个语法树表现了脚本的布局,将其组织成可以被实行的形式。
  • 上下文: 在QLExpress中,上下文是一个关键概念。它是脚本实行时的环境,包含变量、函数等信息。在实行脚本之前,你可以向上下文中添加变量,定义函数,设置一些实行参数等。这样,脚本实行时可以引用上下文中的内容。
  • 实行过程: 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 举行一些简单的运算和条件判断。
  1. package org.zyf.javabasic.qlexpress;
  2. import com.ql.util.express.DefaultContext;
  3. import com.ql.util.express.ExpressRunner;
  4. /**
  5. * @program: zyfboot-javabasic
  6. * @description: 一些简单的运算和条件判断
  7. * @author: zhangyanfeng
  8. * @create: 2023-11-12 21:48
  9. **/
  10. public class Calculator {
  11.     public static void main(String[] args) {
  12.         try {
  13.             ExpressRunner runner = new ExpressRunner();
  14.             DefaultContext<String, Object> context = new DefaultContext<>();
  15.             // 设置变量
  16.             context.put("a", 10);
  17.             context.put("b", 5);
  18.             // 算术运算示例
  19.             executeAndPrint(runner, context, "a + b", "Addition");
  20.             // 比较运算示例
  21.             executeAndPrint(runner, context, "a > b", "Greater than");
  22.             // 逻辑运算示例
  23.             executeAndPrint(runner, context, "a > 0 && b > 0", "Logical AND");
  24.             // 三元运算示例
  25.             executeAndPrint(runner, context, "a > b ? 'a is greater' : 'b is greater'", "Ternary Operator");
  26.         } catch (Exception e) {
  27.             e.printStackTrace();
  28.         }
  29.     }
  30.     private static void executeAndPrint(ExpressRunner runner, DefaultContext<String, Object> context, String expression, String operation) throws Exception {
  31.         // 执行脚本
  32.         Object result = runner.execute(expression, context, null, true, false);
  33.         // 输出结果
  34.         System.out.println(operation + ": " + result);
  35.     }
  36. }
复制代码
(二)Java对象操作

根本的 Java 语法和对象操作在 QLExpress 中同样适用,演示在 QLExpress 中使用 Java 语法和举行对象操作:
  1. package org.zyf.javabasic.qlexpress;
  2. import com.ql.util.express.DefaultContext;
  3. import com.ql.util.express.ExpressRunner;
  4. /**
  5. * @program: zyfboot-javabasic
  6. * @description: 基本的 Java 语法和对象操作
  7. * @author: zhangyanfeng
  8. * @create: 2023-11-12 21:54
  9. **/
  10. public class QLExpressJavaSyntaxExample {
  11.     public static void main(String[] args) {
  12.         try {
  13.             ExpressRunner runner = new ExpressRunner();
  14.             DefaultContext<String, Object> context = new DefaultContext<>();
  15.             // 创建一个用户对象
  16.             User user = new User("John", 25);
  17.             context.put("user", user);
  18.             // 使用 Java 语法访问对象属性和调用方法
  19.             executeAndPrint(runner, context, "user.getName()", "Accessing Object Property");
  20.             executeAndPrint(runner, context, "user.getAge() + 5", "Performing Arithmetic with Object Property");
  21.             // 使用 Java 语法进行对象操作
  22.             executeAndPrint(runner, context, "user.age = 30", "Modifying Object Property");
  23.         } catch (Exception e) {
  24.             e.printStackTrace();
  25.         }
  26.     }
  27.     private static void executeAndPrint(ExpressRunner runner, DefaultContext<String, Object> context, String expression, String operation) throws Exception {
  28.         // 执行脚本
  29.         Object result = runner.execute(expression, context, null, true, false);
  30.         // 输出结果
  31.         System.out.println(operation + ": " + result);
  32.     }
  33.     // 用户对象类
  34.     static class User {
  35.         private String name;
  36.         private int age;
  37.         public User(String name, int age) {
  38.             this.name = name;
  39.             this.age = age;
  40.         }
  41.         public String getName() {
  42.             return name;
  43.         }
  44.         public int getAge() {
  45.             return age;
  46.         }
  47.     }
  48. }
复制代码
(三) 脚本中定义function

在QLExpress中,可以通过 function 关键字来定义函数。以下是一个简单的示例:
  1. package org.zyf.javabasic.qlexpress;
  2. import com.ql.util.express.DefaultContext;
  3. import com.ql.util.express.ExpressRunner;
  4. /**
  5. * @program: zyfboot-javabasic
  6. * @description: 通过 function 关键字来定义函数
  7. * @author: zhangyanfeng
  8. * @create: 2023-11-12 22:11
  9. **/
  10. public class QLExpressFunctionExample {
  11.     public static void main(String[] args) {
  12.         try {
  13.             final String express = "function add(int a, int b){\n" +
  14.                     "  return a + b;\n" +
  15.                     "};\n" +
  16.                     "\n" +
  17.                     "function sub(int a, int b){\n" +
  18.                     "  return a - b;\n" +
  19.                     "};\n" +
  20.                     "\n" +
  21.                     "a = 10;\n" +
  22.                     "result = add(a, 4) + sub(a, 9);\n" +
  23.                     "return result;";
  24.             ExpressRunner runner = new ExpressRunner();
  25.             DefaultContext<String, Object> context = new DefaultContext<>();
  26.             // 执行脚本
  27.             Object result = runner.execute(express, context, null, true, false);
  28.             // 输出脚本执行结果
  29.             System.out.println("Result: " + result);
  30.             // 输出函数调用过程中的参数和返回值
  31.             System.out.println("add function call: a + 4 = " + context.get("a") + " + 4 = " + context.get("result"));
  32.         } catch (Exception e) {
  33.             e.printStackTrace();
  34.         }
  35.     }
  36. }
复制代码
(四)扩展操作符

在 QLExpress 中,可以通过自定义操作符(Operator)来扩展语言的功能。
  1. package org.zyf.javabasic.qlexpress;
  2. import com.ql.util.express.DefaultContext;
  3. import com.ql.util.express.ExpressRunner;
  4. import java.util.ArrayList;
  5. import java.util.List;
  6. /**
  7. * @program: zyfboot-javabasic
  8. * @description: 自定义操作符(Operator)来扩展语言的功能
  9. * @author: zhangyanfeng
  10. * @create: 2023-11-12 23:13
  11. **/
  12. public class QLExpressOperatorExample {
  13.     public static void main(String[] args) {
  14.         try {
  15.             // 示例 1:替换 if then else 关键字
  16.             ExpressRunner runner1 = new ExpressRunner();
  17.             DefaultContext<String, Object> context1 = new DefaultContext<>();
  18.             context1.put("语文",120);
  19.             context1.put("数学",23);
  20.             context1.put("英语",23);
  21.             runner1.addOperatorWithAlias("如果", "if", null);
  22.             runner1.addOperatorWithAlias("则", "then", null);
  23.             runner1.addOperatorWithAlias("否则", "else", null);
  24.             String express1 = "如果 (语文 + 数学 + 英语 > 270) 则 {return 1;} 否则 {return 0;}";
  25.             Object result1 = runner1.execute(express1, context1, null, false, false, 100L);
  26.             System.out.println("Result 1: " + result1); // 输出结果 1
  27.             // 示例 2:自定义 Operator
  28.             ExpressRunner runner2 = new ExpressRunner();
  29.             DefaultContext<String, Object> context2 = new DefaultContext<>();
  30.             // 自定义 Operator
  31.             runner2.addOperator("join", new JoinOperator());
  32.             // 示例 2.1:addOperator
  33.             Object result2_1 = runner2.execute("1 join 2 join 3", context2, null, false, false);
  34.             System.out.println("Result 2.1: " + result2_1); // 输出结果 [1, 2, 3]
  35.             // 示例 2.2:replaceOperator
  36.             ExpressRunner runner2_2 = new ExpressRunner();
  37.             runner2_2.replaceOperator("+", new JoinOperator());
  38.             Object result2_2 = runner2_2.execute("1 + 2 + 3", context2, null, false, false);
  39.             System.out.println("Result 2.2: " + result2_2); // 输出结果 [1, 2, 3]
  40.             // 示例 2.3:addFunction
  41.             ExpressRunner runner2_3 = new ExpressRunner();
  42.             runner2_3.addFunction("join", new JoinOperator());
  43.             Object result2_3 = runner2_3.execute("join(1, 2, 3)", context2, null, false, false);
  44.             System.out.println("Result 2.3: " + result2_3); // 输出结果 [1, 2, 3]
  45.         } catch (Exception e) {
  46.             e.printStackTrace();
  47.         }
  48.     }
  49.     // JoinOperator 类的定义
  50.     public static class JoinOperator extends com.ql.util.express.Operator {
  51.         public Object executeInner(Object[] list) throws Exception {
  52.             Object opdata1 = list[0];
  53.             Object opdata2 = list[1];
  54.             if (opdata1 instanceof List) {
  55.                 ((List) opdata1).add(opdata2);
  56.                 return opdata1;
  57.             } else {
  58.                 List result = new ArrayList();
  59.                 for (Object opdata : list) {
  60.                     result.add(opdata);
  61.                 }
  62.                 return result;
  63.             }
  64.         }
  65.     }
  66. }
复制代码
(五)绑定Java类或对象的methon

在 QLExpress 中,可使用 addFunctionOfClassMethod 和 addFunctionOfServiceMethod 方法来绑定 Java 类或对象的方法。
  1. package org.zyf.javabasic.qlexpress;
  2. import com.ql.util.express.DefaultContext;
  3. import com.ql.util.express.ExpressRunner;
  4. /**
  5. * @program: zyfboot-javabasic
  6. * @description: 可以使用 addFunctionOfClassMethod 和 addFunctionOfServiceMethod 方法来绑定 Java 类或对象的方法
  7. * @author: zhangyanfeng
  8. * @create: 2023-11-19 16:13
  9. **/
  10. public class QLExpressFunctionBindingExample {
  11.     public static void main(String[] args) {
  12.         try {
  13.             ExpressRunner runner = new ExpressRunner();
  14.             DefaultContext<String, Object> context = new DefaultContext<>();
  15.             // 绑定 Math 类的 abs 方法
  16.             runner.addFunctionOfClassMethod("取绝对值", Math.class.getName(), "abs", new String[]{"double"}, null);
  17.             // 绑定 BeanExample 类的 upper 方法
  18.             runner.addFunctionOfClassMethod("转换为大写", BeanExample.class.getName(), "upper", new String[]{"String"}, null);
  19.             // 绑定 System.out 的 println 方法
  20.             runner.addFunctionOfServiceMethod("打印", System.out, "println", new String[]{"String"}, null);
  21.             // 绑定 BeanExample 对象的 anyContains 方法
  22.             runner.addFunctionOfServiceMethod("contains", new BeanExample(), "anyContains", new Class[]{String.class, String.class}, null);
  23.             String express = "取绝对值(-100); 转换为大写("hello world"); 打印("你好吗?"); contains("helloworld", "aeiou")";
  24.             Object result = runner.execute(express, context, null, false, false);
  25.             System.out.println("Result: " + result);
  26.         } catch (Exception e) {
  27.             e.printStackTrace();
  28.         }
  29.     }
  30.     public static class BeanExample {
  31.         public static double abs(double value) {
  32.             System.out.println("取绝对值结果: " + value);
  33.             return Math.abs(value);
  34.         }
  35.         public static String upper(String abc) {
  36.             System.out.println("转换为大写结果: " + abc.toUpperCase());
  37.             return abc.toUpperCase();
  38.         }
  39.         public boolean anyContains(String str, String searchStr) {
  40.             char[] s = str.toCharArray();
  41.             for (char c : s) {
  42.                 if (searchStr.contains(c + "")) {
  43.                     return true;
  44.                 }
  45.             }
  46.             return false;
  47.         }
  48.     }
  49. }
复制代码
(六)宏定义(macro)

在QLExpress中,宏定义(macro)答应将一个表达式片段命名为宏,并在其他地方引用这个宏。当必要在多个地方使用雷同的复杂表达式时非常有用,可以进步代码的可读性和维护性。
  1. package org.zyf.javabasic.qlexpress;
  2. import com.ql.util.express.DefaultContext;
  3. import com.ql.util.express.ExpressRunner;
  4. /**
  5. * @program: zyfboot-javabasic
  6. * @description: macro 宏定义
  7. * @author: zhangyanfeng
  8. * @create: 2023-11-19 17:40
  9. **/
  10. public class QLExpressMacroExample {
  11.     public static void main(String[] args) {
  12.         try {
  13.             ExpressRunner runner = new ExpressRunner();
  14.             DefaultContext<String, Object> context = new DefaultContext<>();
  15.             // 定义宏
  16.             runner.addMacro("计算平均成绩", "(语文+数学+英语)/3.0");
  17.             runner.addMacro("是否优秀", "计算平均成绩>90");
  18.             // 设置变量值
  19.             context.put("语文", 88);
  20.             context.put("数学", 99);
  21.             context.put("英语", 95);
  22.             // 执行表达式并打印结果
  23.             Object result = runner.execute("是否优秀", context, null, false, false);
  24.             System.out.println("Result: " + result);
  25.         } catch (Exception e) {
  26.             e.printStackTrace();
  27.         }
  28.     }
  29. }
复制代码
(七)编译脚本查询外部必要定义的变量和函数

在QLExpress中,编译脚本并查询外部必要定义的变量和函数可以通过 ExpressRunner 提供的一些方法来实现。
  1. package org.zyf.javabasic.qlexpress;
  2. import com.ql.util.express.DefaultContext;
  3. import com.ql.util.express.ExpressRunner;
  4. /**
  5. * @program: zyfboot-javabasic
  6. * @description: 编译脚本,查询外部需要定义的变量和函数。
  7. * @author: zhangyanfeng
  8. * @create: 2023-11-19 17:49
  9. **/
  10. public class QLExpressCompileExample {
  11.     public static void main(String[] args) {
  12.         try {
  13.             ExpressRunner runner = new ExpressRunner(true, true);
  14.             DefaultContext<String, Object> context = new DefaultContext<>();
  15.             context.put("语文",120);
  16.             context.put("数学",23);
  17.             context.put("英语",23);
  18.             context.put("综合考试",235);
  19.             // 定义脚本
  20.             String express = "double 平均分 = (语文 + 数学 + 英语 + 综合考试) / 4.0; return 平均分";
  21.             // 查询外部需要定义的变量
  22.             String[] variableNames = runner.getOutVarNames(express);
  23.             System.out.println("外部需要定义的变量:");
  24.             for (String variableName : variableNames) {
  25.                 System.out.println("var : " + variableName);
  26.             }
  27.             // 查询外部需要定义的函数
  28.             String[] functionNames = runner.getOutFunctionNames(express);
  29.             System.out.println("\n外部需要定义的函数:");
  30.             for (String functionName : functionNames) {
  31.                 System.out.println("function : " + functionName);
  32.             }
  33.             // 编译脚本并执行
  34.             Object result = runner.execute(express, context, null, false, false);
  35.             System.out.println("\n脚本执行结果: " + result);
  36.         } catch (Exception e) {
  37.             e.printStackTrace();
  38.         }
  39.     }
  40. }
复制代码
看下对应打印结果:
  1. 执行的表达式:double 平均分 = (语文 + 数学 + 英语 + 综合考试) / 4.0; return 平均分
  2. 单词分解结果:{double},{平均分},{=},{(},{语文},{+},{数学},{+},{英语},{+},{综合考试},{)},{/},{4.0},{;},{return},{平均分}
  3. 预处理后结果:{double},{平均分},{=},{(},{语文},{+},{数学},{+},{英语},{+},{综合考试},{)},{/},{4.0},{;},{return},{平均分}
  4. 单词分析结果:double:CONST_CLASS,平均分:ID,=:=,(:(,语文:ID,+:+,数学:ID,+:+,英语:ID,+:+,综合考试:ID,):),/:/,4.0:CONST_DOUBLE,;:;,return:return,平均分:ID
  5. 最后的语法树:
  6. 1:   STAT_BLOCK:STAT_BLOCK                                                                 STAT_BLOCK
  7. 2:      STAT_SEMICOLON:STAT_SEMICOLON        STAT_SEMICOLON
  8. 3:         =:=        =
  9. 4:            def:def        def
  10. 5:               double:CONST_CLASS        CONST_CLASS
  11. 5:               平均分:CONST_STRING        CONST
  12. 4:            /:/        /
  13. 5:               (:CHILD_EXPRESS        CHILD_EXPRESS
  14. 6:                  +:+        +
  15. 7:                     +:+        +
  16. 8:                        +:+        +
  17. 9:                           语文:ID        ID
  18. 9:                           数学:ID        ID
  19. 8:                        英语:ID        ID
  20. 7:                     综合考试:ID        ID
  21. 5:               4.0:CONST_DOUBLE        CONST
  22. 2:      STAT_SEMICOLON:STAT_SEMICOLON        STAT_SEMICOLON
  23. 3:         return:return        return
  24. 4:            平均分:ID        ID
  25. 1:LoadData Class:double
  26. 2:LoadData 平均分
  27. 3:OP : def OPNUMBER[2]
  28. 4:LoadAttr:语文
  29. 5:LoadAttr:数学
  30. 6:OP : + OPNUMBER[2]
  31. 7:LoadAttr:英语
  32. 8:OP : + OPNUMBER[2]
  33. 9:LoadAttr:综合考试
  34. 10:OP : + OPNUMBER[2]
  35. 11:LoadData 4.0
  36. 12:OP : / OPNUMBER[2]
  37. 13:OP : = OPNUMBER[2]
  38. 14:clearDataStack
  39. 15:LoadAttr:平均分
  40. 16:return [value]
  41. 外部需要定义的变量:
  42. var : 数学
  43. var : 综合考试
  44. var : 英语
  45. var : 语文
  46. 执行的表达式:double 平均分 = (语文 + 数学 + 英语 + 综合考试) / 4.0; return 平均分
  47. 单词分解结果:{double},{平均分},{=},{(},{语文},{+},{数学},{+},{英语},{+},{综合考试},{)},{/},{4.0},{;},{return},{平均分}
  48. 预处理后结果:{double},{平均分},{=},{(},{语文},{+},{数学},{+},{英语},{+},{综合考试},{)},{/},{4.0},{;},{return},{平均分}
  49. 单词分析结果:double:CONST_CLASS,平均分:ID,=:=,(:(,语文:ID,+:+,数学:ID,+:+,英语:ID,+:+,综合考试:ID,):),/:/,4.0:CONST_DOUBLE,;:;,return:return,平均分:ID
  50. 最后的语法树:
  51. 1:   STAT_BLOCK:STAT_BLOCK                                                                 STAT_BLOCK
  52. 2:      STAT_SEMICOLON:STAT_SEMICOLON        STAT_SEMICOLON
  53. 3:         =:=        =
  54. 4:            def:def        def
  55. 5:               double:CONST_CLASS        CONST_CLASS
  56. 5:               平均分:CONST_STRING        CONST
  57. 4:            /:/        /
  58. 5:               (:CHILD_EXPRESS        CHILD_EXPRESS
  59. 6:                  +:+        +
  60. 7:                     +:+        +
  61. 8:                        +:+        +
  62. 9:                           语文:ID        ID
  63. 9:                           数学:ID        ID
  64. 8:                        英语:ID        ID
  65. 7:                     综合考试:ID        ID
  66. 5:               4.0:CONST_DOUBLE        CONST
  67. 2:      STAT_SEMICOLON:STAT_SEMICOLON        STAT_SEMICOLON
  68. 3:         return:return        return
  69. 4:            平均分:ID        ID
  70. 1:LoadData Class:double
  71. 2:LoadData 平均分
  72. 3:OP : def OPNUMBER[2]
  73. 4:LoadAttr:语文
  74. 5:LoadAttr:数学
  75. 6:OP : + OPNUMBER[2]
  76. 7:LoadAttr:英语
  77. 8:OP : + OPNUMBER[2]
  78. 9:LoadAttr:综合考试
  79. 10:OP : + OPNUMBER[2]
  80. 11:LoadData 4.0
  81. 12:OP : / OPNUMBER[2]
  82. 13:OP : = OPNUMBER[2]
  83. 14:clearDataStack
  84. 15:LoadAttr:平均分
  85. 16:return [value]
  86. 外部需要定义的函数:
  87. 执行的表达式:double 平均分 = (语文 + 数学 + 英语 + 综合考试) / 4.0; return 平均分
  88. 单词分解结果:{double},{平均分},{=},{(},{语文},{+},{数学},{+},{英语},{+},{综合考试},{)},{/},{4.0},{;},{return},{平均分}
  89. 预处理后结果:{double},{平均分},{=},{(},{语文},{+},{数学},{+},{英语},{+},{综合考试},{)},{/},{4.0},{;},{return},{平均分}
  90. 单词分析结果:double:CONST_CLASS,平均分:ID,=:=,(:(,语文:ID,+:+,数学:ID,+:+,英语:ID,+:+,综合考试:ID,):),/:/,4.0:CONST_DOUBLE,;:;,return:return,平均分:ID
  91. 最后的语法树:
  92. 1:   STAT_BLOCK:STAT_BLOCK                                                                 STAT_BLOCK
  93. 2:      STAT_SEMICOLON:STAT_SEMICOLON        STAT_SEMICOLON
  94. 3:         =:=        =
  95. 4:            def:def        def
  96. 5:               double:CONST_CLASS        CONST_CLASS
  97. 5:               平均分:CONST_STRING        CONST
  98. 4:            /:/        /
  99. 5:               (:CHILD_EXPRESS        CHILD_EXPRESS
  100. 6:                  +:+        +
  101. 7:                     +:+        +
  102. 8:                        +:+        +
  103. 9:                           语文:ID        ID
  104. 9:                           数学:ID        ID
  105. 8:                        英语:ID        ID
  106. 7:                     综合考试:ID        ID
  107. 5:               4.0:CONST_DOUBLE        CONST
  108. 2:      STAT_SEMICOLON:STAT_SEMICOLON        STAT_SEMICOLON
  109. 3:         return:return        return
  110. 4:            平均分:ID        ID
  111. 1:LoadData Class:double
  112. 2:LoadData 平均分
  113. 3:OP : def OPNUMBER[2]
  114. 4:LoadAttr:语文
  115. 5:LoadAttr:数学
  116. 6:OP : + OPNUMBER[2]
  117. 7:LoadAttr:英语
  118. 8:OP : + OPNUMBER[2]
  119. 9:LoadAttr:综合考试
  120. 10:OP : + OPNUMBER[2]
  121. 11:LoadData 4.0
  122. 12:OP : / OPNUMBER[2]
  123. 13:OP : = OPNUMBER[2]
  124. 14:clearDataStack
  125. 15:LoadAttr:平均分
  126. 16:return [value]
  127. 脚本执行结果: 100.25
复制代码
(八)不定参数的使用

在QLExpress中,可以通过使用不定参数(动态参数)来处理方法的参数。
  1. package org.zyf.javabasic.qlexpress;
  2. import com.ql.util.express.DefaultContext;
  3. import com.ql.util.express.DynamicParamsUtil;
  4. import com.ql.util.express.ExpressRunner;
  5. /**
  6. * @program: zyfboot-javabasic
  7. * @description: 不定参数的使用
  8. * @author: zhangyanfeng
  9. * @create: 2023-11-19 18:00
  10. **/
  11. public class QLExpressDynamicParamsExample {
  12.     public static void main(String[] args) {
  13.         try {
  14.             // 创建 ExpressRunner 实例
  15.             ExpressRunner runner = new ExpressRunner();
  16.             // 创建 DefaultContext 实例
  17.             DefaultContext<String, Object> expressContext = new DefaultContext<>();
  18.             // 在 runner 中添加一个函数,使用不定参数
  19.             runner.addFunctionOfServiceMethod("getTemplate", new QLExpressDynamicParamsExample(), "getTemplate",
  20.                     new Class[]{Object[].class}, null);
  21.             // 调用 getTemplate 方法,传递数组作为参数
  22.             Object resultWithArray = runner.execute("getTemplate([11, '22', 33L, true])",
  23.                     expressContext, null, false, false);
  24.             System.out.println("Result with Array: " + resultWithArray);
  25.             // 打开全局开关,启用动态参数调用
  26.             DynamicParamsUtil.supportDynamicParams = true;
  27.             // 调用 getTemplate 方法,传递多个参数
  28.             Object resultWithDynamicParams = runner.execute("getTemplate(11, '22', 33L, true)", expressContext,
  29.                     null, false, false);
  30.             System.out.println("Result with Dynamic Params: " + resultWithDynamicParams);
  31.         } catch (Exception e) {
  32.             e.printStackTrace();
  33.         }
  34.     }
  35.     // 等价于 getTemplate(Object[] params)
  36.     public Object getTemplate(Object... params) {
  37.         StringBuilder result = new StringBuilder();
  38.         for (Object obj : params) {
  39.             result.append(obj).append(",");
  40.         }
  41.         return result.toString();
  42.     }
  43. }
复制代码
(九)聚集的快捷用法

在QLExpress中,你可以使用一些快捷的语法来操作聚集。
  1. package org.zyf.javabasic.qlexpress;
  2. import com.ql.util.express.DefaultContext;
  3. import com.ql.util.express.ExpressRunner;
  4. import java.util.ArrayList;
  5. import java.util.HashMap;
  6. import java.util.List;
  7. import java.util.Map;
  8. /**
  9. * @program: zyfboot-javabasic
  10. * @description: 集合操作
  11. * @author: zhangyanfeng
  12. * @create: 2023-11-19 19:04
  13. **/
  14. public class QLExpressCollectionOperationsExample {
  15.     public static void main(String[] args) {
  16.         try {
  17.             ExpressRunner runner = new ExpressRunner();
  18.             DefaultContext<String, Object> context = new DefaultContext<>();
  19.             // 使用NewMap创建Map
  20.             String expressMap = "abc = NewMap(1:1, 2:2); return abc.get(1) + abc.get(2);";
  21.             Object resultMap = runner.execute(expressMap, context, null, false, false);
  22.             System.out.println("NewMap Result: " + resultMap);
  23.             // 使用NewList创建List
  24.             String expressList = "abc = NewList(1, 2, 3); return abc.get(1) + abc.get(2);";
  25.             Object resultList = runner.execute(expressList, context, null, false, false);
  26.             System.out.println("NewList Result: " + resultList);
  27.             // 使用方括号[]创建List
  28.             String expressSquareBrackets = "abc = [1, 2, 3]; return abc[1] + abc[2];";
  29.             Object resultSquareBrackets = runner.execute(expressSquareBrackets, context, null, false, false);
  30.             System.out.println("Square Brackets Result: " + resultSquareBrackets);
  31.         } catch (Exception e) {
  32.             e.printStackTrace();
  33.         }
  34.     }
  35. }
复制代码
(十)聚集的遍历

类似java的语法,只是ql不支持for(obj:list){}的语法,只能通过下标访问。
  1. package org.zyf.javabasic.qlexpress;
  2. import com.ql.util.express.DefaultContext;
  3. import com.ql.util.express.ExpressRunner;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. /**
  7. * @program: zyfboot-javabasic
  8. * @description: 使用foreach关键字结合索引遍历集合
  9. * @author: zhangyanfeng
  10. * @create: 2023-11-19 19:36
  11. **/
  12. public class QLExpressCollectionTraversalExample {
  13.     public static void main(String[] args) {
  14.         try {
  15.             ExpressRunner runner = new ExpressRunner();
  16.             DefaultContext<String, Object> context = new DefaultContext<>();
  17.             // 创建一个Map
  18.             Map<String, String> map = new HashMap<>();
  19.             map.put("a", "a_value");
  20.             map.put("b", "b_value");
  21.             // 将Map放入上下文中
  22.             context.put("map", map);
  23.             // 遍历Map
  24.             String express = "keySet = map.keySet();\n" +
  25.                     "objArr = keySet.toArray();\n" +
  26.                     "for (i = 0; i < objArr.length; i++) {\n" +
  27.                     "    key = objArr[i];\n" +
  28.                     "    System.out.println(map.get(key));\n" +
  29.                     "}";
  30.             runner.execute(express, context, null, false, false);
  31.         } catch (Exception e) {
  32.             e.printStackTrace();
  33.         }
  34.     }
  35. }
复制代码
四、总结

本文全面介绍了 QLExpress 作为阿里巴巴开源的一款轻量级动态脚本引擎的紧张功能和应用场景,并深入解说了其在规则引擎和业务场景中的使用优势。通过对比其他规则引擎的性能与灵活性,QLExpress 展现了其在处理动态规则、复杂盘算逻辑、实时决策等方面的独特优势,尤其适用于必要动态配置业务规则的场景。在现实应用中,QLExpress 的简洁语法、快速上手和高性能表现,使其成为了企业动态业务处理的有效工具。
未来,QLExpress 还可以进一步优化和拓展,以支持更多场景和定制化功能。对于盼望在系统中灵活应用规则逻辑、提升业务决策效率的开辟者来说,QLExpress 是一个值得关注和尝试的工具。盼望通过本文的介绍,各人能更好地理解和应用 QLExpress,从而在项目中实现更高效、更灵活的规则管理。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

莱莱

金牌会员
这个人很懒什么都没写!

标签云

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