轻量级规则引擎

   日期:2024-12-28     作者:ykdv4       评论:0    移动:http://mip.riyuangf.com/mobile/news/9846.html
核心提示: 是一门高性能、轻量级寄宿于 JVM (包括 Android 平台)之上的脚本语言。它起源于2010年,作者对当时已有的

 是一门高性能、轻量级寄宿于 JVM (包括 Android 平台)之上的脚本语言。

它起源于2010年,作者对当时已有的一些产品不是很满意,所以自己撸了一个,它是的一个定制化的子集。

在这里插入图片描述

相比较一些传统的规则引擎,比如、、,它更加轻量级,而且性能更好,同时能力开放,扩展很方便。

我们来看(吹)看(吹)AviatorScript的特点

  1. 它支持数字、字符串、正则表达式、布尔值等基本类型,并且可以使用所有 Java 运算符进行运算。

  2. 还有一个内置的东西叫做  和 ,可以处理超大整数和高精度运算。而且我们还可以通过运算符重载让它们使用普通的算术运算符 。

  3. 语法非常齐全,可以用它来写多行数据、条件语句、循环语句,还能处理词法作用域和异常处理等等。

  4. 如果我们喜欢函数式编程,还有一个叫做 Sequence 抽象的东西,可以让你更方便地处理集合。

  5. 还有一个轻量化的模块系统,方便我们组织代码。

  6. 如果我们需要调用 Java 方法,也没问题,可以用多种方式方便地调用 Java 方法,还有一个完整的脚本 API可以让你从 Java 调用脚本。

  7. 性能也是超出想象的好,如果使用 ASM 模式,它会直接将脚本翻译成 JVM 字节码,解释模式还可以在 Android 等非标准 Java 平台上运行。

可以用在各种场景,比如规则判断和规则引擎、公式计算、动态脚本控制,甚至集合数据 ELT 等等。可以说相当全能了。

AviatorScript 是一门寄生在 JVM (Hosted on the JVM)上的语言,类似 clojure/scala/kotlin 等等,我们从写个Hello World开始。

  • 创建一个SpringBoot项目,引入依赖,这里选择的是最新版本

 

PS:可以看到aviator的groupId有一个,但是它和Google可没什么关系,这是因为早期aviator托管在Google的一个开源项目托管平台Google Code。

  • 在项目的resource目录下创建一个目录script,在script目录下创建脚本

 
  • 编写一个单元测试,运行脚本

 

最后执行一下,就可以看到输出

 
  • 我们也可以直接把脚本定义成字符串,用来进行编译

 

有一个Idea插件,支持直接编译运行Aviator脚本,比较方便。

Aviator插件

但不足之处,这个插件已经不怎么维护了,只兼容到了Idea2021版本。

Idea插件

脚本的运行,分为两步,和。

编译执行

编译支持编译脚本文件和脚本文本,分别使用和方法。

编译产生的   对象,最终都是调用  方法执行。

这里有个重要能力, 方法可以接受一个变量列表组成的 map,来注入执行的上下文

 

我们实现一些规则的判断就是基于这个能力,把一些参数下上下文传进去,然后进行逻辑判断。

我们在来看看的基本语法,它的语法相当简洁,比较接近于数学表达式的形式。

AviatorScript 支持常见的类型,如数字、布尔值、字符串等等,同时将大整数、BigDecimal、正则表达式也作为一种基本类型来支持。

数字

AviatorScript 支持数字类型,包括整数和浮点数,以及高精度计算(BigDecimal)。数字类型可以进行各种算术运算。

整数和算术运算

整数类型,对应Java中的long类型,可以表示范围为 -9223372036854774808 ~ 9223372036854774807 的整数。整数可以使用十进制或十六进制表示。

 

整数可以进行加减乘除和取模运算。需要注意的是,整数相除的结果仍然是整数,遵循整数运算规则。可以使用括号来指定运算的优先级。

浮点数

浮点数类型对应Java中的double类型,表示双精度 64 位浮点数。浮点数可以使用十进制或科学计数法表示。

 

浮点数可以进行加减乘除运算,结果仍然为浮点数。

高精度计算(Decimal)

高精度计算使用 BigDecimal 类型,可以进行精确的数值计算,适用于货币运算或者物理公式运算的场景。可以通过在数字后面添加 "M" 后缀来表示 BigDecimal 类型。

 

BigDecimal 类型可以进行加减乘除运算,结果仍然为 BigDecimal 类型。默认的运算精度是 MathContext.DECIMAL128,可以通过修改引擎配置项 Options.MATH_CONTEXT 来改变。

数字类型转换

数字类型在运算时会自动进行类型转换

  • 单一类型参与的运算,结果仍然为该类型。

  • 多种类型参与的运算,按照 long -> bigint -> decimal -> double 的顺序自动提升,结果为提升后的类型。

可以使用 long(x) 函数将数字强制转换为 long 类型,使用 double(x) 函数将数字强制转换为 double 类型。

 

a 和 b 都是 long 类型,它们相除的结果仍然是整数。使用 double(b) 将 b 转换为 double 类型后,相除的结果为浮点数。

字符串

字符串类型由单引号或双引号括起来的连续字符组成。可以使用 println 函数来打印字符串。

 

字符串的长度可以通过 string.length 函数获取。

 

字符串可以通过 + 运算符进行拼接。

 

字符串还包括其他函数,如截取字符串 ,都在  这个 namespace 下,具体见函数库列表。

布尔类型和逻辑运算

布尔类型用于表示真和假,它只有两个值  和  分别表示真值和假值。

比较运算如大于、小于可以产生布尔值

 

上面演示了所有的逻辑运算符

  •  大于

  •  大于等于

  •  小于

  •  小于等于

  •  等于

  •  不等于

也支持条件语句和循环语句。

条件语句

AviatorScript 中的条件语句和其他语言没有太大区别

 
  • -

 
  • --

 

循环语句

AviatorScript提供了两种循环语句:和。

for循环:遍历集合

 语句通常用于遍历一个集合,例如下面是遍历 0 到 9 的数字

 

在这里, 函数用于创建一个整数集合,包括起始值 ,但不包括结束值 。在循环迭代过程中,变量  绑定到集合中的每个元素,并执行大括号  中的代码块。

 函数还可以接受第三个参数,表示递增的步长大小(默认步长为 1)。例如,我们可以打印出0到9之间的偶数

 

 可以用于任何集合结构,比如数组、  、  等等。

while循环

 循环本质上是将条件语句与循环结合在一起。当条件为真时,不断执行一段代码块,直到条件变为假。

例如,下面的示例中,变量  从 1 开始,不断累加自身,直到超过 1000 才停止,然后进行打印输出

 

循环可以用这三个关键字结束——continue/break/return

  • 用于跳过当前迭代,继续下一次迭代。

  • 用于跳出整个循环。

  • 用于中断整个脚本(或函数)的执行并返回。

函数

我们再来看看一个非常重要的特性——函数。

函数

函数定义和调用

AviatorScript中使用语法来定义函数

 

我们这里通过关键字来定义了一个函数,函数名为,它接受两个参数和,并返回它们的和。

需要注意的是,AviatorScript是动态类型系统,不需要定义参数和返回值的类型,它会根据实际传入和返回的值进行自动类型转换。因此,我们可以使用字符串来调用函数。

函数的返回值可以通过语句来指定,也可以省略不写。在函数体内,如果没有明确的语句,最后一个表达式的值将被作为返回值。

自定义函数

再来给大家介绍一个里非常好的特性,支持自定义函数,这给带来了非常强的扩展性。

可以通过 java 代码实现并往引擎中注入自定义函数,在 中就可以使用,事实上所有的内置函数也是通过同样的方式实现的

 

我们看到

  • 继承类,就可以自定义一个函数

  • 重写方法,就可以定义函数的逻辑,可以通过获取脚本传递的参数

  • 通过可以设置函数的名称

  • 通过添加一个自定义函数类的实例,就可以注册函数

  • 最后就可以在的脚本里编译执行我们自定义的函数

好了,关于的语法我们就不过多介绍了,大家可以直接查看官方文档[1],可读性相当不错。

接下来我们就来看看的实际应用,看看它到底怎么提升项目的灵活性。

标题带了规则引擎,在我们的项目里也主要是拿AviatorScript作为规则引擎使用——我们可以把的脚本维护在配置中心或者数据库,进行动态地维护,这样一来,一些规则的修改,就不用大动干戈地去修改代码,这样就更加方便和灵活了。

Aviator应用

在日常的开发中,我们很多时候可能面临这样的情况,兼容客户端的版本,尤其是Android和iPhone,有些功能是低版本不支持的,或者说有些功能到了高版本就废弃掉,这时候如果硬编码去兼容就很麻烦,那么就可以考虑使用规则脚本的方式。

  • 自定义版本比较函数:没有内置版本比较函数,但是可以利用它的自定义函数特性,自己定义一个版本比较函数

 
  • 注册自定义函数:为了方便使用各种自定义函数,我们一般定义一个单例的,把它注册成Bean

 
  • 在代码里传递上下文:接下来,就可以在业务代码里将一些参数放进执行上下文,然然后进行编译执行,注意编译的时候最好要开启缓存,这样效率会高很多

 
  • 编写脚本:接下来就可以编写和维护对应的规则脚本,这些规则脚本通常放在在配置中心或者数据库,方便进行动态变更

 

这样一来,假如某天,客户端Bug或者产品原因,需要修改客户端和客户端的版本控制,直接修改脚本就好了。

甚至我们可以在里放进更多参数,比如,可以实现简单的黑白名单。

我们的自定义函数除了这种简单的比较版本,我们还可以放一些复杂的逻辑,比如判断是否新用户等等。

假如现在我们的运营希望进行一场营销活动,对用户进行一定的支付优惠,最开始的一版活动规则

  • 满1000减200,满500减100

这个好写,一顿if-else就完事了。

但是没过几天,又改了活动规则

  • 首单用户统一减20

,啪啪改代码。

又过去几天,活动规则又改了

  • 随机优惠不同金额

为了一些多变的营销规则,大动干戈,不停修改代码,耗时费力,那么不如用规则脚本实现

  • 定义脚本

 
  • 业务代码调用

 

接下来,再发生营销规则变更,就可以少量开发(自定义函数,比如判断首单用户,并且可以组件化地维护营销规则。

Aviator我在订单风控里应用也很香,风控的规则调整是相当频繁的,比如一个电商网站,常常要根据交易的争议率、交易表现等等,来收紧和放松风控规则,这就要求我们能对一风控规则进行快速地配置变更。

例如,根据订单金额、客户评级、收货地址等属性,自动判断是否有风险并触发相应的风控操作。

  • 规则脚本

 
  • 代码调用:这里只是简单返回了一个风控等级,其实可以通过Map的方式返回多个参数。

 

上面随手列出了几个简单的例子,还可以用在一些审批流程、事件处理、数据质量管理等等场景……

在一些轻量级的需要规则引擎的场景下,真的太香了,尤其是它的扩展性,支持通过Java自定义函数,我甚至可以在脚本里查询数据库、查询Redis、调用外部接口……这样就可以像搭积木一样搭建想要的功能。

 
特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。

举报收藏 0打赏 0评论 0
 
更多>同类最新资讯
0相关评论

相关文章
最新文章
推荐文章
推荐图文
最新资讯
点击排行
{
网站首页  |  关于我们  |  联系方式  |  使用协议  |  隐私政策  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号