找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2385

积分

0

好友

333

主题
发表于 昨天 00:08 | 查看: 4| 回复: 0

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

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

AviatorScript 与 Groovy 的关系

相比一些传统的规则引擎,比如 DroolsJessJRules,它更加轻量级,性能也更好,同时具备高度的扩展性。

那么,AviatorScript 有哪些核心特点呢?

  1. 支持数字、字符串、正则表达式、布尔值等基本类型,并且可以使用所有 Java 运算符进行运算。
  2. 内置 bigintdecimal 类型,可以处理超大整数和高精度运算,并且通过运算符重载让它们能使用普通的算术运算符 +-*/
  3. 语法齐全,支持多行数据、条件语句、循环语句,还能处理词法作用域和异常处理。
  4. 提供了名为 Sequence 的抽象,方便喜欢函数式编程的开发者处理集合。
  5. 具备轻量化的模块系统,便于组织代码。
  6. 可以方便地调用 Java 方法,同时提供了完整的脚本 API 让你能够从 Java 调用脚本。
  7. 性能表现出色。在 ASM 模式下,它会直接将脚本翻译成 JVM 字节码;解释模式则可以在 Android 等非标准 Java 平台上运行。

得益于这些特性,AviatorScript 能广泛应用于规则判断、公式计算、动态脚本控制乃至集合数据 ELT 等场景。

快速开始

AviatorScript 是一门寄生在 JVM 上的语言,类似 Clojure、Scala、Kotlin 等。我们从编写一个 “Hello World” 开始。

  • 创建一个 Spring Boot 项目,并引入依赖(这里选择最新版本)。
<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 插件,支持直接编译运行脚本,使用起来比较方便。
AviatorScript 插件代码示例
不过需要注意的是,这个插件维护并不活跃,仅兼容到 IDEA 2021 版本。
IntelliJ IDEA 插件兼容性提示

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

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 命名空间下。

布尔类型和逻辑运算

布尔类型只有 truefalse 两个值。比较运算会产生布尔值。

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 语句
if(true) {
   println("in if body");
}
  • if-else 语句
if(false){
   println("in if body");
} else {
   println("in else body");
}
  • if-elsif-else 语句
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 提供 forwhile 循环。

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.Listjava.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";
    }
}

实现步骤:

  1. 继承 AbstractFunction 类。
  2. 重写 call 方法定义函数逻辑,可通过 FunctionUtils 获取脚本传递的参数。
  3. 通过 getName 方法设置函数名称。
  4. 通过 addFunction 方法注册函数实例。
  5. 之后即可在 AviatorScript 脚本中编译并执行这个自定义函数。

关于 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 的性能。对于需要动态配置业务逻辑、频繁调整规则的场景,它是一个非常值得考虑的解决方案。

更多技术干货和开发者讨论,欢迎访问 云栈社区 进行交流。


参考链接




上一篇:PostgreSQL pg_hba.conf配置详解:如何安全设置远程数据库连接?
下一篇:Claude Code SKILL实战:前端开发中AI代码可用率99%的真实体验
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-18 15:03 , Processed in 0.318360 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表