AviatorScript
AviatorScript 是一门依赖于JVM虚拟机的高性能和轻量级的动态脚本语言。Aviator从原来的表达式引擎到现在脚本语言,支持了更多的特性特性。
原理和特点
Aviator 的基本过程是将表达式直接翻译成对应的 java 字节码执行,除了依赖 commons-beanutils 这个库之外(用于做反射)不依赖任何第三方库,因此整体非常轻量级,整个 jar 包大小哪怕发展到现在 5.0 这个大版本,也才 430K。同时, Aviator 内置的函数库非常“节制”,除了必须的字符串处理、数学函数和集合处理之外,类似文件 IO、网络等等你都是没法使用的,这样能保证运行期的安全,如果你需要这些高阶能力,可以通过开放的自定义函数来接入。
编译过程
Hello World示例
hello world
1、创建maven项目,添加以下依赖,目前最新版本是5.3.3。
com.googlecode.aviator
aviator
5.3.3
获取一个AviatorEvaluator实例,默认是编译执行,如果指定EvalMode.INTERPRETER,怎解析执行,这种模式生产指令并解释执行。解释执行的效率远远低于编译执行,编译执行可以使用getInstance()方法或者指定EvalMode=ASM。
Expression exp = AviatorEvaluator.newInstance(EvalMode.ASM).compile("(a+b)>c");
Map env = new HashMap<>();
env.put("a", 12);
env.put("b", 5);
env.put("c", 20);
Boolean execute = (Boolean) exp.execute(env);
System.out.println(execute);
//执行结果 flase
数据类型及运算
Aviator支持的数据类型有:整数(long),浮点数(Double),大整数(bigInt),高精度(BigDecimal),字符串,布尔值(Boolean)。单一类型参与的运算,结果仍然为该类型,多种类型参与的运算,按照下列顺序: long -> bigint -> decimal -> double 自动提升。通过一个例子演示这些数据类型及运算。
## av/exp.av
## 数据类型和运算
let l = 123456789; ## long
let d = 12.0123; ## double
let bi = 10223372036854774807N; ##bigint以N结尾
let bd = 112.112M; ##BigDecimal以M结尾
let str = "hello zhangsan"; ##只要以单引号或者双引号括起来的连续字符就是一个完整的字符串对象
println(l+l); ##结果为long,如果超过long限制自动转成bigInt
println(d*d);
println(bi*bi);
println(l+d);
println(bi*d);
println(bd/l);
let newStr = str+"!"; ## 拼接字符串使用+
println(newStr);
println(string.length(newStr)); ## 计算字符串的长度
println("3<4 = "+(3<4)) ##boolean
let score = 99;
let result = (score>85)?"优秀":score; ## 三元运算符可以返回不同类型的值
println(result)
对于运算处理基本的加减乘除,还有幂运算、比较运算、逻辑运算、三元运算和正则匹配和位运算等
- 幂运算使用math.pow(a, b)或者a**b
- 比较运算:>,>=,<,<=,!=,==
- 逻辑运算:逻辑与&&、逻辑或||、逻辑否! ,对于逻辑与和逻辑非可使用别名and和or。
- 位运算:位与&,或|,异或^,左移<<,右移>>,无符号右移>>>
- 三元运算:a?b:c当a的值为true时表达式的值为b,否则为c
变量声明和作用域:
声明一个变量可以使用let关键字,let声明的变量是全局变量,在整个脚本有效。如果使用let 在子代码块中定义一个变量,并且其父作用域有同名的变量,将“掩盖”父作用域的变量,如果不使用 let ,读写的将仍然是父作用域的变量。
声明变量时要对变量赋值,如果不想赋值请使用nil,nil相当于Java中的null,表示没有赋值,可以对nil做比较运算,nil以外的都大于nil,但是不能对nil做其他的运算。
创建一个av文件,声明一个变量a赋值为nil,返回一个三元运算符计算结果,最后脚本应该返回c的值。注意,Aviator默认返回最后表达式的结果,但是最后有分号的情况下,整个脚本返回nil。下面脚本最后使用了return语句返回结果,如果没有return,则整个脚本返回nil,除非把最后的分案去掉。
## av/variable.av
let a = nil;
return a > 1? b : c;
测试上面的脚本结果,使用exp.getVariableNames()获取所有的变量名字,发现只用b和c。最后通过newEnv生成map对象并存储key和value值。
Expression exp = AviatorEvaluator.getInstance().compileScript("av/variable.av");
System.out.println(exp.getVariableNames());
//打印 [b, c]
System.out.println(exp.execute(exp.newEnv("b", 20, "c", 30)));
//打印 30
在 AviatorScript 中,每个语句都有一个值,可以使用if和循环对一个变量赋值,请看下面的例子。
let ifResult = if(a == 10){
a+100
};
println(ifResult);
let forResult = for x in range(0, 10) {
x
};
println(forResult);
let whileResult = while true {
sum = sum + sum;
if sum > 1000 {
return sum;
}
};
println(whileResult);
最后简单介绍下循环,循环可以使用for和while,for的语法为for .. in 后面是数组,list或者map等集合,wihile循环为while关键字和条件的结合,例子如下:
for i in range(0,10){
println(i);
}
let i = 1;
while i < 8{
i = i+1;
println(i)
}
在循环中可以使用break跳出循环,或者使用continue跳过本次循环继续执行,类似Java语言。
函数,数组、集合和 Sequence
函数
通过 fn 语法来定义一个命名函数或者使用lamdba定义一个匿名的函数。请看下面的例子,特殊情况已在代码中说明。
##fn的语法,&args表示不定长参数
fn concat(a, b, &args){
let ret = a + b;
let r = for arg in args{
ret = ret + arg;
}
return ret;
}
##匿名函数 lambda (参数1,参数2...) -> 参数体表达式 end
let three = (lambda (x,y) -> x + y end)(1, 2);
## 对于匿名的函数,可以赋值给一个变量,
let add = lambda (x,y) -> x + y end;
three = add(1,2);
## 从 5.2.4 开始,匿名函数的定义也可以用 fn 语法
let add =fn (x,y) {x+y};
在AviatorScript 中将数组和集合都抽象成一个序列集合 Sequence,在此之上可以用同一套高阶函数方便地操作任意的数组或者集合。
1、可以使用tuple创建固定大小的数组
2、使用seq.array方法创建数组并填充,或者使用array_of创建空数组
3、创建list使用seq.list方法
4、创建map使用seq.map(k1, v1, k2, v2 ...)方法
5、创建set使用seq.set,set是不重复的集合
let t = tuple(1, 2, "hello", 3.14);
let a1 = seq.array(int, 1, 2, 3, 4);
let a2 = seq.array_of(int, 3);
let a3 = seq.array_of(long, 3, 2);
let list = seq.list(1, 2, 3);
## 使用add添加元素
seq.add(list, 4);
let map = seq.map("a", 1, "b", 2, "c", 3);
## 使用remove删除元素
seq.remove(map, 0);
let s = seq.set(1, 2, 3, "hello","hello");
## 使用get获取元素
let mg = seq.get(map, 0);
Sequence 是 AviatorScript 对“集合”的抽象。Sequence接口继承了Iterable接口,代表可遍历的对象。接口中的两个方法:hintSize()用于返回集合的元素数量,newCollector() 用于收集 sequence 里的元素经过某种“变化”后的结果。
public interface Sequence extends Iterable {
Collector newCollector(int var1);
int hintSize();
}
创建对象和引用Java类
使用new创建对象,对于java.lang包的类可以直接使用,对于之外的需要添加完成的包名和类名,从 版本5.2 开始,aviatorscript 支持 use 语句导入类。
let d = new java.util.Date();
let s = new String("zhangsan");
use java.util.Date; ##导入单个类
let d = new Date();
use java.util.*;##导入util下的所有类
use java.util.{Date,ArrayList};## 导入指定的类 use 包名.{类1, 类2...}
使用场景
总结
1、Aviator一开始的定位为表达式语言,首先能用作表达式的运算
2、在业务复杂规则配置的时候可以动态生成脚本并执行规则判断
3、使用在规则引擎中