轻量级Java表达式引擎Aviator
简介
Aviator是⼀个⾼性能、轻量级的 java 语⾔实现的表达式求值引擎, 主要⽤于各种表达式的动态求值。现在已经有很多开源可⽤的 java 表达式求值引擎,为什么还需要 Avaitor 呢?
Aviator的设计⽬标是轻量级和⾼性能,相⽐于Groovy、JRuby的笨重, Aviator⾮常⼩, 加上依赖包也才 537K,不算依赖包的话只有 70K;
当然, Aviator的语法是受限的, 它不是⼀门完整的语⾔, ⽽只是语⾔的⼀⼩部分集合。
其次, Aviator的实现思路与其他轻量级的求值器很不相同, 其他求值器⼀般都是通过解释的⽅式运⾏, ⽽Aviator则是直接将表达式编译成JVM 字节码, 交给 JVM 去执⾏。简单来说, Aviator的定位是介于 Groovy 这样的重量级脚本语⾔和 IKExpression 这样的轻量级表达式引擎之间。
Aviator 的特性:
1. ⽀持绝⼤多数运算操作符,包括算术操作符、关系运算符、逻辑操作符、位运算符、正则匹配操作符(=~)、三元表达式(?:)
2. ⽀持操作符优先级和括号强制设定优先级
3. 逻辑运算符⽀持短路运算。
4. ⽀持丰富类型,例如nil、整数和浮点数、字符串、正则表达式、⽇期、变量等,⽀持⾃动类型转换。
5. 内置⼀套强⼤的常⽤函数库
6. 可⾃定义函数,易于扩展
7. 可重载操作符
8. ⽀持⼤数运算(BigInteger)和⾼精度运算(BigDecimal)
9. 性能优秀
包依赖
Aviator依赖了commons-beanutils, 使⽤Aviator可以添加下⾯的maven依赖:
<dependency>
<groupId&lecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>{version}</version>
</dependency>
从 3.2.0 版本开始, Aviator 仅⽀持 JDK 7 及其以上版本。 JDK 6 请使⽤ 3.1.1 这个稳定版本。
java配置用户变量使⽤⼿册
执⾏表达式
Aviator的使⽤都是集中通过lecode.aviator.AviatorEvaluator这个⼊⼝类来处理, 最简单的例⼦, 执⾏⼀个计算1+2+3的表达式:
lecode.aviator.AviatorEvaluator;
public class TestAviator {
public static void main(String[] args) {
Long result = (Long) ute("1+2+3");
System.out.println(result);
}
}
细⼼的朋友肯定注意到结果是Long,⽽不是Integer。这是因为Aviator的数值类型仅⽀持Long和Double, 任何整数都将转换成Long, 任何浮点数都将转换为Double, 包括⽤户传⼊的变量数值。这个例⼦的打印结果将是正确答案6。
例外情况是,如果开启了 ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL 选项,那么在表达式中出现的浮点数都将解析为 BigDecimal,这是为了⽅便⼀些⽤户要求⾼精度的计算,⼜不想额外地给浮点数加上 M 后缀标记为 BigDecimal:
AviatorEvaluator.setOption(Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL, true);
Aviator 的⽰例代码可以在 ⾥到。
多⾏表达式
从 4.0.0 开始, aviator⽀持以分号 ; 隔开的多⾏表达式,对于多⾏表达式求值的结果将是最后⼀个表达式的结果,例如ute("print('hello world'); 1+2+3 ; 100-1");
的结果将是最后⼀个表达式 100-1,也就是 99,但是中间的表达式也将执⾏,包括打印 hello world。通过在表达式求值过程中加⼊ println 打印,可以⽅便调试,也可以通过 Options.TRACE_EVAL来跟踪执⾏过程,参见后续章节。
求值器多实例
AviatorEvaluator是⼀个全局静态实例,但是很多场景下,你可能想为不同的场景提供⼀个不同的求值器实例,包括不同的选项配置和⾃定义函数列表等,那么从 4.0.0开始, Aviator提供了多实例的求值器⽀持:
AviatorEvaluatorInstance instance = wInstance();
//接下来使⽤ instance,⼏乎跟 AviatorEvaluator 没有不同,只是换成了实例⽅法
使⽤变量
想让Aviator对你say hello吗? 很简单, 传⼊你的名字, 让Aviator负责字符串的相加:
public class TestAviator {
public static void main(String[] args) {
String yourName = "Michael";
Map<String, Object> env = new HashMap<String, Object>();
env.put("yourName", yourName);
String result = (String) ute(" 'hello ' + yourName ", env);
System.out.println(result); // hello Michael
}
}
上⾯的例⼦演⽰了怎么向表达式传⼊变量值, 表达式中的yourName是⼀个变量, 默认为null, 通过传⼊Map<String,Object>的变量绑定环境,
将yourName设置为你输⼊的名称。 env 的key是变量名, value是变量的值。
上⾯例⼦中的'hello '是⼀个Aviator的String, Aviator的String是任何⽤单引号或者双引号括起来的字符序列, String可以⽐较⼤⼩(基
于unicode顺序), 可以参与正则匹配, 可以与任何对象相加, 任何对象与String相加结果为String。 String中也可以有转义字符,如\n、\\、\' 等。
exec ⽅法
Aviator 2.2 开始新增加⼀个exec⽅法, 可以更⽅便地传⼊变量并执⾏, ⽽不需要构造env这个map了:
String name = "dennis";
<(" 'hello ' + yourName ", name); // hello dennis
只要在exec中按照变量在表达式中的出现顺序传⼊变量值就可以执⾏, 不需要构建Map了。
调⽤函数
Aviator ⽀持函数调⽤, 函数调⽤的风格类似 lua, 下⾯的例⼦获取字符串的长度:
string.length('hello')是⼀个函数调⽤, string.length是⼀个函数, 'hello'是调⽤的参数。
再⽤string.substring来截取字符串:
通过string.substring('hello', 1, 2)获取字符串'e', 然后通过函数ains判断e是否在'test'中。可以看到, 函数可以嵌套调⽤。
Aviator 的内置函数列表请看后⾯。
lambda函数定义
从 4.0.0 开始, aviator ⽀持通过 lambda 关键字定义⼀个匿名函数,并且⽀持闭包捕获:
<("(lambda (x,y) -> x + y end)(x,y)", 1, 2);
上⾯的例⼦我们定义了⼀个接收两个参数的匿名函数 lambda (x,y) -> x + y end,然后直接使⽤ x,y两个变量进⾏调⽤,求值的时候传⼊的x,y 分别是1和2,因此结果为3.
匿名函数的基本定义形式是
lambda (参数1,参数2...) -> 参数体表达式 end
匿名函数可以作为参数使⽤,也可以作为结果返回,例如下⾯这个稍微复杂点的例⼦,也可以看到闭包捕获的效果:
AviatorEvaluator
.exec("(lambda (x) -> lambda(y) -> lambda(z) -> x + y + z end end end)(1)(2)(3)");
我们定义了⼀个函数,它接收参数 x,然后返回了⼀个新的函数,这个新函数接收参数 y,新返回的⼜是另⼀个新函数,最后这个新函数接收参数 z,并且将 x+y+z 三个参数求和并返回。 假设这个匿名函数”名称“为s,那么上述表达式的调⽤结果等价于 s(1)(2)(3),最终就是 1+2+3。
匿名函数更⼤的⽤户在于后⾯的 seq 库配合⾼阶函数使⽤。
⾃定义函数
Aviator 除了内置的函数之外,还允许⽤户⾃定义函数,只要实现lecode.pe.AviatorFunction接⼝, 并注册
到AviatorEvaluator即可使⽤. AviatorFunction接⼝⼗分庞⼤, 通常来说你并不需要实现所有的⽅法, 只要根据你的⽅法的参 数个数, 继
承AbstractFunction类并override相应⽅法即可。
可以看⼀个例⼦,我们实现⼀个add函数来做数值的相加:
public class TestAviator {
public static void main(String[] args) {
//注册函数
AviatorEvaluator.addFunction(new AddFunction());
System.out.ute("add(1, 2)")); // 3.0
System.out.ute("add(add(1, 2), 100)")); // 103.0
}
}
class AddFunction extends AbstractFunction {
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
Number left = NumberValue(arg1, env);
Number right = NumberValue(arg2, env);
return new AviatorDouble(left.doubleValue() + right.doubleValue());
}
public String getName() {
return "add";
}
}
注册函数通过AviatorEvaluator.addFunction⽅法, 移除可以通过removeFunction。另外, FunctionUtils 提供了⼀些⽅便参数类型转换的⽅法。
如果你的参数个数不确定,可以继承 AbstractVariadicFunction 类,只要实现其中的 variadicCall ⽅法即可,⽐如我们实现⼀个到第⼀个参数不为 null 的函数:
public class GetFirstNonNullFunction extends AbstractVariadicFunction {
public AviatorObject variadicCall(Map<String, Object> env, args) {
if (args != null) {
for (AviatorObject arg : args) {
if (Value(env) != null) {
return arg;
}
}
}
return new AviatorString(null);
}
@Override
public String getName() {
return "getFirstNonNull";
}
}
注册后使⽤就可以传⼊不定参数了:
getFirstNonNull(1);
getFirstNonNull(1,2,3,4,nil,5);
getFirstNonNull(a,b,c,d);
当然,同时你仍然覆写特定的 call ⽅法来⾃定义实现。
⾃定义函数在 4.0.0 之后也可以通过 lambda 来定义:
AviatorEvaluator.defineFunction("add", "lambda (x,y) -> x + y end");
<("add(1,2)");
加载⾃定义函数列表
除了通过代码的⽅式 AviatorEvaluator.addFunction 来添加⾃定义函数之外,你可以在 classpath 下放置⼀个配置⽂
件 fig,内容是⼀⾏⼀⾏的⾃定义函数类的完整名称,例如:
# 这是⼀⾏注释
那么 Aviator 将在 JVM 启动的时候⾃动加载这些⾃定义函数,配置⽂件中以 # 开头的⾏将被认为是注释。如果你想⾃定义⽂件路径,可以通过传⼊环境变量
-lecode.aviator.custom_function_config_fig
来设置。
函数加载器
从 4.0.0 开始,Aviator 还⽀持 FunctionLoader接⼝,可以⽤于⾃定义函数加载器:
/**
* Function loader to load function when function not found.
*
* @author dennis
*
*/
public interface FunctionLoader {
/**
* Invoked when function not found
*
* @param name function name
*/
public AviatorFunction onFunctionNotFound(String name);
}
⽤户可以⾃主实现函数加载器,当函数不能从当前求值器中到的时候,将调⽤ loader 的 onFunctionNotFound ⽅法进⾏查。⾃定义的加载器,通过 AviatorEvaluator.addFunctionLoader(loader)注册,可以注册多个加载器,加载顺序将按照添加顺序进⾏查,其中任何⼀个到,都将中断查过程。
重载运算符
Aviator ⽀持的运算符参见⼀节。部分⽤户可能有重载这些内置运算符的需求,例如在 Excel ⾥, & 不是位运算,⽽是字符串连接符,那么你可以通过 3.3.0 版本⽀持的运算符重载来实现:
AviatorEvaluator.addOpFunction(OperatorType.BIT_AND, new AbstractFunction() {
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
return new Value(env).toString() + Value(env).toString());
}
@Override
public String getName() {
return "&";
}
});
AviatorEvaluator.addOpFunction(opType, func) 就可以重载指定的运算符,重载后运⾏即可看到:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论