太强了!动态脚本引擎QLExpress,实现各种复杂的业务规则

开发 前端
在众多规则引擎中,QLExpress以其简洁的语法、高性能的执行能力和灵活的配置选项脱颖而出。它不仅能够轻松应对复杂的业务逻辑,还提供了强大的扩展能力,满足多样化的开发需求。

环境:Spring2.7.18

1. 简介

在复杂的业务环境中,规则引擎作为系统决策的核心,扮演着非常重要的角色。它们使业务逻辑能够灵活调整。规则引擎通过解析和执行预设的规则,实现业务逻辑的自动化和智能化处理。这不仅提升了系统的灵活性和可扩展性,还大大简化了业务逻辑的维护和管理。基于这样的需求,寻求一个高效、易用的规则引擎是非常重要的。

在众多规则引擎中,QLExpress以其简洁的语法、高性能的执行能力和灵活的配置选项脱颖而出。它不仅能够轻松应对复杂的业务逻辑,还提供了强大的扩展能力,满足多样化的开发需求。

QLExpress由阿里的电商业务规则、表达式(布尔组合)、特殊数学公式计算(高精度)、语法分析、脚本二次定制等强需求而设计的一门动态脚本引擎解析工具。

QLExpress特性:

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

接下来将详细介绍QLExpress的使用

2. 实战案例

2.1 引入依赖

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>QLExpress</artifactId>
  <version>3.3.3</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 ret = runner.execute(express, context, null, true, false) ;
System.out.printf("%s = %d%n", express, ret) ;

用起来是不是与SpEL表达式差不多?

2.2 语法介绍

基础语法

支持 +,-,*,/,<,>,<=,>=,==,!=,<>【等同于!=】,%,mod【取模等同于%】,++,--,in【类似sql】,like【sql语法】,&&,||,!,等操作符 

支持for,break、continue、if then else 等标准的程序控制逻辑

示例

ExpressRunner runner = ... ;
DefaultContext<String, Object> context = ... ;
context.put("n", 10) ;
context.put("sum", 0) ;
String express = """
    for(i = 1; i <= n; i++) {
       sum = sum + i;
    }
    return sum;
    """ ;
Object ret = runner.execute(express, context, null, true, false) ;
System.out.printf("表达式: %n%s计算结果: %d%n", express, ret) ;

输出结果

图片图片

注意,与java语法相比以下是不支持的:

不支持try{}catch{} 

注释目前只支持 /** **/,不支持单行注释 // 

不支持java8的lambda表达式 

不支持for循环集合操作for (Item item : list) 

弱类型语言,请不要定义类型声明,更不要用Template(Map<String, List>之类的) 

array的声明不一样 

min,max,round,print,println,like,in 都是系统默认函数的关键字,请不要作为变量名

对象操作

ExpressRunner runner = ... ;
DefaultContext<String, Object> context = ... ;
String express = """
      import com.pack.qlexpress.PersonService ;
      import com.pack.qlexpress.Person ;
      
      ps = new PersonService() ;
      ps.save(new Person()) ;
    """ ;
runner.execute(express, context, null, true, false) ;

输出结果

PersonService save method, com.pack.Person@1c3a4799

相当于将java代码以字符串形式表达出来。注意这里要导入所用到的包,即便在同一包中也要如此操作。

2.3 表达式定义函数

context.put("arg1", 10) ;
context.put("arg2", 20) ;
String express = """
      function add(int a, int b) {
        return a + b ;
      }
      return Math.PI + add(arg1, arg2) ;
    """ ;
Object ret = runner.execute(express, context, null, true, false) ;
System.out.printf("计算结果: %s%n", ret) ;

上面表达式中定义了add函数做加法运算,最终整个表达式调用add同时再加上Math.PI。对于java.lang包中的类不需要导入操作。

2.4 操作符

String express = """
    if(a > b) {
      return 1;
    } else {
      return 0;
    }
  """ ;
Object ret = runner.execute(express, context, null, true, false) ;
System.out.printf("计算结果: %s%n", ret) ;

上面表达式做if...else...判断,输出结果:

a <= b
计算结果: null // 没有返回结果,所以为null

对于开发来说写上面的表达式太简单了,如果你非开发人员来写if else或者其它更加复杂的语句那就太难为人了。qlexpress允许我们将这些关键字进行别名的定义,如下示例:

runner.addOperatorWithAlias("如果", "if", null) ;
runner.addOperatorWithAlias("否则", "else", null) ;
runner.addOperatorWithAlias("大于", ">", null) ;
runner.addOperatorWithAlias("返回", "return", null) ;
String express = "如果(a大于b){返回 1;} 否则 {返回 0;}";
Object ret = runner.execute(express, context, null, true, false) ;

上面代码将程序中的关键字都通过汉字来别名化,这更加适应大众应用。

2.5 绑定对象或Method

ExpressRunner runner = new ExpressRunner() ;
runner.addFunctionOfClassMethod("四舍五入", CommonService.class, "roundUp", new Class[] {double.class}, null);
String express = """
      四舍五入(56.54788)
    """ ;
Object ret = runner.execute(express, null, null, true, false) ;
System.out.printf("计算结果: %s%n", ret) ;

通过addFunctionOfClassMethod方法定义一个对象中的方法。输出结果:

计算结果: 56.55

addFunctionOfClassMethod方法就是对类中的方法进行描述。

2.6 宏定义

ExpressRunner runner = new ExpressRunner() ;
runner.addMacro("计算平均成绩", "(语文+数学+英语) / 3.0");
runner.addMacro("是否优秀", "计算平均成绩 > 90");
DefaultContext<String, Object> context = new DefaultContext<String, Object>() ;
context.put("语文", 88) ;
context.put("数学", 99) ;
context.put("英语", 95) ;
Object ret = runner.execute("是否优秀", context, null, false, false);
System.out.printf("是否优秀: %s%n", ret) ;

输出结果

是否优秀: true

以上宏的定义可以嵌套的调用。

2.7 查询表达式变量

ExpressRunner runner = new ExpressRunner() ;
String express = """
      int ret = (a + b + Math.PI * c ) / 4 ;
      return ret ;
    """ ;
String[] vars = runner.getOutVarNames(express) ;
for (String var : vars) {
  System.out.printf("var: %s%n", var) ;
}

输出结果

var: a
var: b
var: c

以上将输出当前表达式在执行时所需要传入的变量。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2025-01-13 13:47:13

2025-01-22 14:02:35

2022-05-30 16:31:08

CSS

2021-03-04 09:31:42

开源技术 项目

2023-12-10 20:33:50

Redis搜索全文

2021-08-05 16:25:37

Windows 11Windows微软

2022-06-08 08:01:28

模板字面量类型

2024-01-30 09:21:29

CSS文字效果文字装饰

2023-03-06 08:03:10

Python可视化工具

2021-09-15 08:45:55

Python文本文件代码

2020-12-31 11:28:09

GitLabCICD

2021-02-03 20:19:08

Istio流量网格

2022-11-23 22:09:10

Drools规则引擎

2022-01-26 07:18:57

ES6WeakSetMap

2024-02-01 12:43:00

模型训练

2022-12-31 18:13:10

2024-04-25 09:14:57

数据库Mysql阿里巴巴

2022-06-06 12:18:44

配置可视化Nginx

2020-06-19 12:59:33

动态脚本Java

2023-10-17 08:55:08

数据库数据业务
点赞
收藏

51CTO技术栈公众号