环境: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
以上将输出当前表达式在执行时所需要传入的变量。