AviatorScript 是一门高性能、轻量级寄宿于 JVM (包括 Android 平台)之上的脚本语言。
它起源于2010年,作者对当时已有的一些产品不是很满意,所以自己开发了一个,可以将其视为 Groovy 的一个定制化子集。

相比一些传统的规则引擎,比如 Drools、Jess、JRules,它更加轻量级,性能也更好,同时具备高度的扩展性。
那么,AviatorScript 有哪些核心特点呢?
- 支持数字、字符串、正则表达式、布尔值等基本类型,并且可以使用所有 Java 运算符进行运算。
- 内置
bigint 和 decimal 类型,可以处理超大整数和高精度运算,并且通过运算符重载让它们能使用普通的算术运算符 +-*/。
- 语法齐全,支持多行数据、条件语句、循环语句,还能处理词法作用域和异常处理。
- 提供了名为 Sequence 的抽象,方便喜欢函数式编程的开发者处理集合。
- 具备轻量化的模块系统,便于组织代码。
- 可以方便地调用 Java 方法,同时提供了完整的脚本 API 让你能够从 Java 调用脚本。
- 性能表现出色。在 ASM 模式下,它会直接将脚本翻译成 JVM 字节码;解释模式则可以在 Android 等非标准 Java 平台上运行。
得益于这些特性,AviatorScript 能广泛应用于规则判断、公式计算、动态脚本控制乃至集合数据 ELT 等场景。
快速开始
AviatorScript 是一门寄生在 JVM 上的语言,类似 Clojure、Scala、Kotlin 等。我们从编写一个 “Hello World” 开始。
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.3.3</version>
</dependency>
注意:虽然 aviator 的 groupId 包含 googlecode,但它和 Google 无关。这是因为早期 aviator 托管在 Google Code 开源项目托管平台。
- 在项目的
resource 目录下创建一个 script 文件夹,并在其中创建脚本文件 hello.av。
println("Hello, AviatorScript!");
@Test
void testHello() throws Exception {
// 获取路径
ClassPathResource resource = new ClassPathResource("script/hello.av");
String scriptPath = resource.getPath();
// 编译
Expression exp = AviatorEvaluator.getInstance().compileScript(scriptPath);
// 执行
exp.execute();
}
执行测试后,控制台会输出:
Hello, AviatorScript!
- 你也可以直接将脚本内容定义为字符串,使用
compile() 方法进行编译。
@Test
void testHelloStr() throws Exception {
// 定义脚本
String script="println(\"Hello, AviatorScript!\");";
// 编译
Expression exp = AviatorEvaluator.getInstance().compile(script);
// 执行
exp.execute();
}
AviatorScript 提供了一个 IntelliJ IDEA 插件,支持直接编译运行脚本,使用起来比较方便。

不过需要注意的是,这个插件维护并不活跃,仅兼容到 IDEA 2021 版本。

AviatorScript 脚本的执行分为两个步骤:编译 和 执行。

编译支持脚本文件(compileScript)和脚本文本(compile)。编译产生的 Expression 对象,最终都通过调用 execute() 方法来执行。
这里有一个重要的特性:execute 方法可以接收一个由变量列表组成的 Map,用于向脚本执行上下文注入数据。
String expression = "a-(b-c) > 100";
Expression compiledExp = AviatorEvaluator.compile(expression);
// 上下文
double a=100.3,b=45,c= -199.100;
Map<String, Object> context=new HashMap<>();
context.put("a",a);
context.put("b",b);
context.put("c",c);
// 通过注入的上下文执行
Boolean result = (Boolean) compiledExp.execute(context);
System.out.println(result);
实现业务规则判断正是基于这个能力——将参数作为上下文传入脚本,然后执行逻辑判断。
基本语法
AviatorScript 的语法相当简洁,接近于数学表达式的形式。
基本类型及运算
AviatorScript 支持数字、布尔值、字符串等常见类型,同时将大整数(BigInteger)、高精度数(BigDecimal)、正则表达式也作为基本类型来支持。
数字
数字类型包括整数、浮点数以及用于高精度计算的 BigDecimal。它们支持各种算术运算。
整数和算术运算
整数对应 Java 中的 long 类型,范围是 -9223372036854774808 ~ 9223372036854774807。可以用十进制或十六进制表示。
let a = 99;
let b = 0xFF;
let c = -99;
println(a + b); // 270
println(a / b); // 0
println(a - b + c); // -156
println(a + b * c); // -9801
println(a - (b - c)); // 198
println(a / b * b + a % b); // 99
整数运算遵循整数规则,相除结果仍为整数。可以使用括号来改变运算优先级。
浮点数
浮点数对应 Java 的 double 类型,支持十进制或科学计数法表示。
let a = 1.34159265;
let b = 0.33333;
let c = 1e-2;
println(a + b); // 1.67492265
println(a - b); // 1.00826265
println(a * b); // 0.4471865500145
println(a / b); // 4.0257402772554
println(a + c); // 1.35159265
高精度计算(Decimal)
使用 BigDecimal 类型进行精确计算,适用于金融或科学计算。在数字后加 “M” 后缀表示。
let a = 1.34M;
let b = 0.333M;
let c = 2e-3M;
println(a + b); // 1.673M
println(a - b); // 1.007M
println(a * b); // 0.44622M
println(a / b); // 4.022022022M
println(a + c); // 1.342M
BigDecimal 运算的默认精度是 MathContext.DECIMAL128,可通过引擎配置修改。
数字类型转换
运算时数字类型会自动提升:按照 long -> bigint -> decimal -> double 的顺序。可以使用 long(x) 或 double(x) 函数进行强制转换。
let a = 1;
let b = 2;
println("a/b is " + a/b); // 0
println("a/double(b) is " + a/double(b)); // 0.5
字符串
字符串由单引号或双引号括起,可用 println 打印。
let a = "hello world";
println(a); // hello world
通过 string.length 函数获取长度。
let a = "hello world";
println(string.length(a)); // 11
使用 + 运算符进行拼接。
let a = "hello world";
let b = "AviatorScript";
println(a + ", " + b + "!" + 5); // hello world, AviatorScript!5
字符串的其他操作函数(如 substring)都位于 string 命名空间下。
布尔类型和逻辑运算
布尔类型只有 true 和 false 两个值。比较运算会产生布尔值。
println("3 > 1 is " + (3 > 1)); // 3 > 1 is true
println("3 >= 1 is " + (3 >= 1)); // 3 >= 1 is true
println("3 >= 3 is " + (3 >= 3)); // 3 >= 3 is true
println("3 < 1 is " + (3 < 1)); // 3 < 1 is false
println("3 <= 1 is " + (3 <= 1)); // 3 <= 1 is false
println("3 <= 3 is " + (3 <= 3)); // 3 <= 3 is true
println("3 == 1 is " + (3 == 1)); // 3 == 1 is false
println("3 != 1 is " + (3 != 1)); // 3 != 1 is true
支持的逻辑运算符包括:>(大于)、>=(大于等于)、<(小于)、<=(小于等于)、==(等于)、!=(不等于)。
条件与循环
条件语句
条件语句的语法与其他语言类似。
if(true) {
println("in if body");
}
if(false){
println("in if body");
} else {
println("in else body");
}
let a = rand(1100);
if(a > 1000) {
println("a is greater than 1000.");
} elsif (a > 100) {
println("a is greater than 100.");
} elsif (a > 10) {
println("a is greater than 10.");
} else {
println("a is less than 10 ");
}
循环语句
AviatorScript 提供 for 和 while 循环。
for循环
for ... in 语句用于遍历集合,例如遍历数字范围。
for i in range(0, 10) {
println(i);
}
range(start, end) 函数创建一个从 start(包含)到 end(不包含)的整数序列。可以指定第三个参数作为步长。
for i in range(0, 10, 2) {
println(i);
}
for .. in 可以遍历数组、java.util.List、java.util.Map 等集合。
while循环
当条件为真时,重复执行代码块。
let sum = 1;
while sum < 1000 {
sum = sum + sum;
}
println(sum);
循环中可以使用 continue(跳过本次迭代)、break(跳出整个循环)、return(中断脚本或函数执行并返回)来控制流程。
函数
函数是 AviatorScript 的一个非常重要的特性,为其带来了强大的扩展能力。
函数定义和调用
使用 fn 关键字定义函数。
fn add(x, y) {
return x + y;
}
three = add(1, 2);
println(three); // 输出:3
s = add('hello', ' world');
println(s); // 输出:hello world
AviatorScript 是动态类型语言,无需声明参数和返回值类型,会自动进行类型转换。函数体最后一个表达式的值将作为返回值,也可使用 return 语句显式返回。
自定义函数
可以通过 Java 代码实现自定义函数并注入引擎,脚本中即可使用。事实上,所有的内置函数也是通过这种方式实现的。
public class TestAviator {
public static void main(String[] args) {
// 创建一个AviatorEvaluator的实例
AviatorEvaluatorInstance instance = AviatorEvaluator.getInstance();
// 注册函数
instance.addFunction(new AddFunction());
// 执行脚本,脚本里调用自定义函数
Double result= (Double) instance.execute("add(1, 2)");
// 输出结果
System.out.println(result);
}
}
/**
* 实现AbstractFunction接口,就可以自定义函数
*/
class AddFunction extends AbstractFunction {
/**
* 函数调用
* @param env 当前执行的上下文
* @param arg1 第一个参数
* @param arg2 第二个参数
* @return 函数返回值
*/
@Override
public AviatorObject call(Map<String, Object> env,
AviatorObject arg1, AviatorObject arg2) {
Number left = FunctionUtils.getNumberValue(arg1, env);
Number right = FunctionUtils.getNumberValue(arg2, env);
// 将两个参数进行相加
return new AviatorDouble(left.doubleValue() + right.doubleValue());
}
/**
* 函数的名称
* @return 函数名
*/
public String getName() {
return "add";
}
}
实现步骤:
- 继承
AbstractFunction 类。
- 重写
call 方法定义函数逻辑,可通过 FunctionUtils 获取脚本传递的参数。
- 通过
getName 方法设置函数名称。
- 通过
addFunction 方法注册函数实例。
- 之后即可在 AviatorScript 脚本中编译并执行这个自定义函数。
关于 AviatorScript 更详细的语法,可以参考其官方文档,可读性相当不错。
接下来,我们看看 AviatorScript 在实际项目中的应用,感受它如何提升项目的灵活性。
实战案例
在我们的项目中,主要将 AviatorScript 作为规则引擎使用。我们可以将脚本维护在配置中心或数据库中,实现动态管理和更新。这样一来,规则的修改无需改动代码,更加灵活便捷。

客户端版本控制
在移动端开发中,常常需要兼容不同版本的客户端(Android/iOS),某些功能可能低版本不支持或高版本已废弃。硬编码兼容逻辑非常繁琐,使用规则脚本是更好的选择。
- 自定义版本比较函数:AviatorScript 没有内置版本比较函数,但我们可以利用其自定义函数特性实现一个。
class VersionFunction extends AbstractFunction {
@Override
public String getName() {
return "compareVersion";
}
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
// 获取版本
String version1 = FunctionUtils.getStringValue(arg1, env);
String version2 = FunctionUtils.getStringValue(arg2, env);
int n = version1.length(), m = version2.length();
int i = 0, j = 0;
while (i < n || j < m) {
int x = 0;
for (; i < n && version1.charAt(i) != '.'; ++i) {
x = x * 10 + version1.charAt(i) - '0';
}
++i; // 跳过点号
int y = 0;
for (; j < m && version2.charAt(j) != '.'; ++j) {
y = y * 10 + version2.charAt(j) - '0';
}
++j; // 跳过点号
if (x != y) {
return x > y ? new AviatorBigInt(1) : new AviatorBigInt(-1);
}
}
return new AviatorBigInt(0);
}
}
- 注册自定义函数:通常我们会定义一个单例的
AviatorEvaluatorInstance 并注册为 Spring Bean,方便全局使用。
@Bean
public AviatorEvaluatorInstance aviatorEvaluatorInstance() {
AviatorEvaluatorInstance instance = AviatorEvaluator.getInstance();
// 默认开启缓存
instance.setCachedExpressionByDefault(true);
// 使用LRU缓存,最大值为100个。
instance.useLRUExpressionCache(100);
// 注册内置函数,版本比较函数。
instance.addFunction(new VersionFunction());
return instance;
}
- 在业务代码中传递上下文并执行:将业务参数放入执行上下文,编译并执行脚本。开启编译缓存能显著提升效率。
/**
* 客户端版本过滤
* @param device 设备
* @param version 版本
* @param rule 规则脚本
* @return 是否匹配
*/
public boolean filter(String device,String version,String rule){
// 执行参数
Map<String, Object> env = new HashMap<>();
env.put("device", device);
env.put("version", version);
// 编译脚本,使用MD5作为缓存key
Expression expression = aviatorEvaluatorInstance.compile(DigestUtils.md5DigestAsHex(rule.getBytes()), rule, true);
// 执行脚本
boolean isMatch = (boolean) expression.execute(env);
return isMatch;
}
- 编写和维护规则脚本:脚本通常存放在配置中心或数据库中,支持动态变更。
if(device==bil){
return false;
}
## 控制android的版本
if (device=="Android" && compareVersion(version,"1.38.1")<0){
return false;
}
return true;
通过这种方式,当需要调整客户端版本控制策略时,直接修改脚本即可,甚至可以通过在 env 中注入更多参数(如用户ID)来实现简单的黑白名单功能。自定义函数也可以封装更复杂的逻辑,例如判断用户是否为新用户。
营销活动规则
假设运营需要开展支付优惠活动,最初的规则是“满1000减200,满500减100”。用 if-else 就能实现。
但若规则频繁变更,例如后来改为“首单用户减20”,再后来又变成“随机优惠”,频繁修改代码会非常低效。使用规则脚本可以优雅地解决这个问题。
if (amount>=1000){
return 200;
}elsif(amount>=500){
return 100;
}else{
return 0;
}
public BigDecimal getDiscount(BigDecimal amount,String rule){
// 执行规则并计算最终价格
Map<String, Object> env = new HashMap<>();
env.put("amount", amount);
Expression expression = aviatorEvaluatorInstance.compile(DigestUtils.md5DigestAsHex(rule.getBytes()), rule, true);
return (BigDecimal) expression.execute(env);
}
这样一来,营销规则变更时,只需少量开发工作(例如开发判断首单用户的自定义函数),并以组件化的方式维护营销规则脚本即可。
订单风控规则
在电商等领域,风控规则需要根据交易争议率、表现数据等频繁调整。AviatorScript 能帮助我们快速配置和变更风控规则。
例如,根据订单金额、客户评级、收货地址等属性自动判断风险等级。
if (amount>=1000 || rating <= 2){
return "High";
}elsif(amount >= 500 || rating<=4){
return "Mid";
}else{
return "Low";
}
public String riskLevel(BigDecimal amount,String rating,String rule){
// 执行规则并计算最终价格
Map<String, Object> env = new HashMap<>();
env.put("amount", amount);
env.put("rating", rating);
Expression expression = aviatorEvaluatorInstance.compile(DigestUtils.md5DigestAsHex(rule.getBytes()), rule, true);
return (String) expression.execute(env);
}
这里示例仅返回一个风险等级,实际上可以通过返回 Map 等结构来传递多个风控参数。
以上只是几个简单的例子,AviatorScript 还能应用于审批流程、事件处理、数据质量管理等诸多需要动态逻辑的场景。在轻量级规则引擎需求下,它的表现非常出色。尤其是其强大的扩展性——支持通过 Java 自定义函数,使得我们能在脚本中查询数据库、调用外部接口等,从而像搭积木一样构建复杂功能。
总结
本文介绍了一款轻量级、高性能的规则脚本语言 AviatorScript。它语法丰富、易于扩展,在项目中应用可以有效提升业务灵活性,降低因规则变动带来的开发工作量。
AviatorScript 的核心优势在于其“宿主”于 JVM 的特性,使得它能无缝与 Java 生态集成,并通过编译为字节码获得接近原生 Java 的性能。对于需要动态配置业务逻辑、频繁调整规则的场景,它是一个非常值得考虑的解决方案。
更多技术干货和开发者讨论,欢迎访问 云栈社区 进行交流。
参考链接