Aviator是一门高性能、轻量级的Java语言实现的表达式动态求值引擎。其设计目标是轻量级和高性能,相对于Groovy、JRuby的笨重,Aviator非常小,不过Aviator的语法受限,它并不是一门完整的语言,只是语言的一小部分集合。定位是介于Groovy这样重量级脚本语言和IKExpression这样轻量级表达式引擎之间。
Aviator的实现思路与其它轻量级的求值器不同,其它求值器是通过解释的方式运行,而Aviator是直接将表达式编译成Java字节码,交给JVM去执行。
功能
a.b.c
的嵌套变量访问限制:没有if else、do while等语句,没有赋值语句,没有位运算符;
使用场景
pom文件引入:
<dependency>
<groupId>com.googlecode.aviatorgroupId>
<artifactId>aviatorartifactId>
dependency>
Aviator的数值类型只支持Long和Double,任何整数都将会转换成Long,任何浮点数都会转换成Double,包括用户传入的变量数值。不支持科学计数法,仅支持十进制。
nil对象
nil是Aviator内置的常量,类似Java中的null,空值。nil跟null不同在于,Java中null只能使用在==
、!=
的比较运算符,而nil还可以使用>、>=、<、<=等比较运算符。Aviator规定,任何对象都比nil大,除nil本身。用户传入的变量如果为null,将自动以nil替代。nil与String相加时,跟Java一样显示为null。
Aviator并不支持日期类型,如果要比较日期,你需要将日期写字符串的形式,并且要求是形如yyyy-MM-dd HH:mm:ss:SS
的字符串,否则都将报错。字符串跟java.util.Date
比较时将自动转换为Date对象进行比较。
要访问变量a中的某个属性b,可通过a.b
访问到,即Aviator支持变量的嵌套访问。
Aviator在表达式级别支持正则表达式,通过//
括起来的字符序列构成一个正则表达式,可用于匹配(作为=~
操作符的右操作数)、比较大小,匹配仅能与字符串进行匹配。匹配成功后,Aviator会自动将匹配成功的分组放入$num
的变量中,$0
指代整个匹配的字符串,$1
表示第一个分组,以此类推。正则表达式规则跟Java完全一样,内部使用java.util.regex.Pattern
编译。
类型转换规则
yyyy-MM-dd HH:mm:ss:SS
的字符串,在与java.util.Date
做比较时将尝试转换成java.util.Date
对象比较访问数组和集合
可以通过中括号去访问数组和java.util.List
对象,可以通过map.key
访问java.util.Map
中key对应的value。
两种模式
AviatorEvaluator.setOptimize(AviatorEvaluator.COMPILE);
AviatorEvaluator.setOptimize(AviatorEvaluator.EVAL);
@Test
public void testAviator() {
log.info(AviatorEvaluator.execute("1 + 2 + 3") + "");
log.info(AviatorEvaluator.execute("3 > 1 && 2 != 4 || true") + "");
log.info((String) AviatorEvaluator.execute("3 > 0 ? 'yes': no"));// 三元表达式对于两个分支的结果类型并不要求一致,可以是任何类型
// 以下5个皆为true
AviatorEvaluator.execute("nil == nil");
AviatorEvaluator.execute("3 > nil");
AviatorEvaluator.execute("true != nil");
AviatorEvaluator.execute("' ' > nil");
AviatorEvaluator.execute("a == nil");
// 内置函数
log.info("string.length('hello') = " + AviatorEvaluator.execute("string.length('hello')"));
log.info("string.contains('hello', 'h') = " + AviatorEvaluator.execute("string.contains('hello', 'h')"));
log.info("math.pow(-3, 2) = " + AviatorEvaluator.execute("math.pow(-3, 2)"));
log.info("math.sqrt(9.0) = " + AviatorEvaluator.execute("math.sqrt(9.0)"));
final List<String> list = new ArrayList<String>();
list.add("hello");
list.add(" world");
final int[] array = new int[3];
array[0] = 0;
array[1] = 1;
array[2] = 3;
final Map<String, Date> map = new HashMap<>();
map.put("date", new Date());
Map<String, Object> env = new HashMap<>();
env.put("name", "johnny");
String str = "'hello ' + name";
log.info((String) AviatorEvaluator.execute(str, env));
// 对比, 被@Deprecated的方法
log.info((String) AviatorEvaluator.exec(str, "johnny"));
env.put("date", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS").format(new Date()));
env.put("foo", new Foo(100, new Date()));
env.put("email", "[email protected]");
env.put("list", list);
env.put("array", array);
env.put("map", map);
// 正则
log.info((String) AviatorEvaluator.execute("email=~/\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}/ ? $0:'unknow'", env));
// 嵌套引用
log.info((String) AviatorEvaluator.execute("'[foo i='+ foo.i + ' year='+(foo.date.year+1900)+ ' month='+foo.date.month +']' ", env));
// 日期对比
log.info(AviatorEvaluator.execute("date > '2009-12-20 00:00:00:00' ", env) + "");
// 数组和列表
log.info(AviatorEvaluator.execute("list[0]+list[1]+'\nsum of array='+(array[0]+array[1]+array[2]) +'\ntoday is '+map.date ", env) + "");
}
依赖的实体Bean类为:
@Data
@AllArgsConstructor
private static class Foo {
int i;
Date date;
}
+ - * / %
五个二元运算符,和一元运算符-
。其中- * / %
和一元的-
仅能作用于Number类型。"+"不仅能用于Number类型,还可以用于String的相加,或字符串与其他对象的相加。任何类型与String相加,结果为String。!
,逻辑与&&
,逻辑或||
。逻辑运算符的操作数只能为Boolean。&&
和||
都执行短路规则<,<=,>,>=,==,!=
,不仅可以用于数值,也可以用于Number、String、Pattern、Boolean等,甚至是用户传入的任何两个都实现java.lang.Comparable
接口的对象之间。变量之间以及其他类型与nil之间的关系比较,不同类型除了nil之外不能相互比较。任何对象都比nil大,除nil之外。函数名称 | 说明 |
---|---|
sysdate() | 返回当前日期对象java.util.Date |
rand() | 返回一个介于0-1的随机数,double类型 |
print([out],obj) | 打印对象,如果指定out,向out打印,否则输出到控制台 |
now() | 返回System.currentTimeMillis |
string.substring(s,begin[,end]) | 截取字符串s,从begin到end,end如果忽略的话,将从begin到结尾,与java.util.String.substring 一样。 |
math.log(d) | 求d的自然对数 |
math.log10(d) | 求d以10为底的对数 |
map(seq,fun) | 将函数fun作用到集合seq每个元素上,返回新元素组成的集合 |
filter(seq,predicate) | 将谓词predicate作用在集合的每个元素上,返回谓词为true的元素组成的集合 |
include(seq,element) | 判断element是否在集合seq中,返回boolean值 |
sort(seq) | 排序集合,仅对数组和List有效,返回排序后的新集合 |
reduce(seq,fun,init) | fun接收两个参数,集合元素,累积init,用于将fun作用在集合每个元素和初始值上面,返回最终的init值 |
seq.eq(value) | 返回一个谓词,用来判断传入的参数是否跟value相等,一般用于filter函数,如filter(seq,seq.eq(3)) 过滤返回等于3的元素组成的集合 |
seq.exists() | 返回判断存在,即不为nil的谓词 |
实现com.googlecode.aviator.runtime.function.AbstractFunction
接口,并注册到AviatorEvaluator即可使用
public class UserDefinedFunction extends AbstractFunction {
/**
* 自定义函数名称
*/
@Override
public String getName() {
return "add";
}
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
Number num1 = FunctionUtils.getNumberValue(arg1, env);
Number num2 = FunctionUtils.getNumberValue(arg2, env);
return new AviatorDouble(num1.doubleValue() + num2.doubleValue());
}
}
使用自定义函数之前需要通过addFunction()
方式注册,否则报错com.googlecode.aviator.exception.FunctionNotFoundException: Function not found: add
。
// 注册函数
AviatorEvaluator.addFunction(new UserDefinedFunction());
log.info("add(1, 1) = " + AviatorEvaluator.execute("add(1, 1)"));
AviatorEvaluator.removeFunction("add");
// log.info("add(1, 1) = " + AviatorEvaluator.execute("add(1, 1)"));
每次执行Aviator.execute()
时,背后都经过编译和执行的操作。那可以先编译表达式,拿到编译后结果,然后传入不同的env来重复使用编译结果,提高性能。
/**
* 编译表达式和未编译表达式性能测试
*/
@Test
public void testCompile() {
String expression = "a * (b - c)";
Map<String, Object> env = new HashMap<>();
env.put("a", 3.3);
env.put("b", 2.2);
env.put("c", 1.1);
int num = 10000;
// 编译表达式
Expression compliedExp = AviatorEvaluator.compile(expression);
Stopwatch watch = Stopwatch.createStarted();
for (int i = 0; i < num; i++) {
compliedExp.execute(env);
}
log.info(String.format("预编译耗时为%dms", watch.elapsed(TimeUnit.MILLISECONDS)));
watch.stop().start();
for (int i = 0; i < num; i++) {
AviatorEvaluator.execute(expression, env);
}
log.info(String.format("无编译耗时为%dms", watch.elapsed(TimeUnit.MILLISECONDS)));
}
输出:
ExpressionTest [testCompile:65] 预编译耗时为13ms
ExpressionTest [testCompile:70] 无编译耗时为2435ms
通过complie方法可以将表达式编译成Expression的中间对象,当要执行表达式时传入Map对象直接调用Expression的execute方法即可。
编译后结果可以自己缓存,也可以交给Aviator缓存,AviatorEvaluator内部维护有一个全局缓存池:
/**
* 预编译缓存举例
*/
@Test
public void testCompileCache() {
String expression1 = "a + b + c";
Expression exp1 = AviatorEvaluator.compile(expression1, true);
Expression exp2 = AviatorEvaluator.compile(expression1, true);
Expression exp3 = AviatorEvaluator.compile(expression1, false);
log.info("exp1 == exp2 : " + (exp1 == exp2));
log.info("exp1 == exp3 : " + (exp1 == exp3));
}
输出:
ExpressionTest [testCompileCache:79] exp1 == exp2: true
ExpressionTest [testCompileCache:80] exp1 == exp3: false
将cached设置为true时,那下次编译同一个表达式时将直接返回上一次编译结果。使缓存失效:public static void invalidateCache(String expression)
。
没有规则引擎时,有些逻辑复杂的业务代码,只能通过不断的增添if - else
来满足复杂的业务场景,当if - else
过多时,会导致代码极难阅读,虽然能通过策略模式来优化if - else
,但依旧存在开发周期缓慢、发布上线的问题。
所以需要规则引擎,尤其是风控系统。
规则引擎的优势:
Aviator也可用于规则引擎
将业务人员配置的规则转换成一个规则字符串,然后将该规则字符串保存进数据库中,当使用该规则时,只传递该规则所需要的参数,便可以直接计算出结果,开发人员无需再为这些规则编写任何代码。
public class AviatorRuleEngine {
// 规则可以保存在MySQL或Redis中
Map<Integer, String> ruleMap = new HashMap<>();
public AviatorRuleEngine() {
// 判断是不是资深顾客
ruleMap.put(1, "age >= 18 && sumConsume > 2000 && vip");
// 资深顾客要求修改
ruleMap.put(2, "age > 10 && sumConsume >= 8000 && vip && avgYearConsume >= 1000");
}
public static void main(String[] args) {
AviatorRuleEngine aviator = new AviatorRuleEngine();
// 选择规则,传入规则所需要的参数
log.info("公式1:" + aviator.getResult(1, 20, 3000, false));
log.info("公式2:" + aviator.getResult(2, 23, 8000, true, 2000));
}
public Object getResult(int ruleId, Object... args) {
String rule = ruleMap.get(ruleId);
return AviatorEvaluator.exec(rule, args);
}
}
输出:
公式1:false
公式2:true