是一门高性能、轻量级寄宿于 JVM (包括 Android 平台)之上的脚本语言。
它起源于2010年,作者对当时已有的一些产品不是很满意,所以自己撸了一个,它是的一个定制化的子集。
在这里插入图片描述
相比较一些传统的规则引擎,比如、、,它更加轻量级,而且性能更好,同时能力开放,扩展很方便。
我们来看(吹)看(吹)AviatorScript的特点:
-
它支持数字、字符串、正则表达式、布尔值等基本类型,并且可以使用所有 Java 运算符进行运算。
-
还有一个内置的东西叫做 和 ,可以处理超大整数和高精度运算。而且我们还可以通过运算符重载让它们使用普通的算术运算符 。
-
语法非常齐全,可以用它来写多行数据、条件语句、循环语句,还能处理词法作用域和异常处理等等。
-
如果我们喜欢函数式编程,还有一个叫做 Sequence 抽象的东西,可以让你更方便地处理集合。
-
还有一个轻量化的模块系统,方便我们组织代码。
-
如果我们需要调用 Java 方法,也没问题,可以用多种方式方便地调用 Java 方法,还有一个完整的脚本 API可以让你从 Java 调用脚本。
-
性能也是超出想象的好,如果使用 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、调用外部接口……这样就可以像搭积木一样搭建想要的功能。