引入依赖
<dependency><groupId>com.googlecode.aviator</groupId><artifactId>aviator</artifactId><version>5.3.3</version></dependency>
AviatorEvaluator.execute()
Aviator只支持4种数值类型:
- long
- double
- bigint
- decimal
明确表达式
常量表达式
计算
// 数学运算 (Long)String exp1 = "1+2+3";Long result = (Long) AviatorEvaluator.execute(exp1);System.out.println(result); // 6// 数学运算 (Double)String exp4 = "1.1+2.2+3.3";Double result2 = (Double) AviatorEvaluator.execute(exp4);System.out.println(result2); // 6.6// 包含关系运算和逻辑运算String exp2 = "(1>0||0<1)&&1!=0";System.out.println(AviatorEvaluator.execute(exp2)); // true// 三元运算String exp3 = "4 > 3 ? \"4 > 3\" : 999";System.out.println(AviatorEvaluator.execute(exp3)); // 4 > 3
变量表达式
计算,map变量传入
// map 变量传入String exp5 = "a>b";Map<String, Object> map = new HashMap<>();map.put("a", 7);map.put("b", 5);System.out.println(AviatorEvaluator.execute(exp5, map)); // true
AviatorEvaluator.compile()
可以执行自定义函数调用,为了提升性能,可以先编译表达式,再进行表达式求值。
使用步骤
- 自定义函数类
- AviatorEvaluator.addFunction() 注册函数类
- AviatorEvaluator.compile() 生成Expression对象
- Expression.execute() 传入变量值执行获取结果
自定义函数(不可变参数)
- 继承
AbstractFunction
- 实现 getName(): 定义函数名
- 实现 call(): 定义函数逻辑,call()有很多重载方法最多支持20个参数
- 在 AviatorEvaluator 注册 (AviatorEvaluator.addFunction())
自定义函数类
public class AviatorFunc extends AbstractFunction {/*** 实现函数逻辑 */@Overridepublic 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());}/*** 定义函数名*/@Overridepublic String getName() {return "add";}}
定义了一个名为 add 的函数,实现两数相加逻辑
函数调用
// 自定义函数调用AviatorEvaluator.addFunction(new AviatorFunc());String exp6 = "add(a,b)";Map<String, Object> map2 = new HashMap<>();map2.put("a", 7.7d);map2.put("b", 5.5d);// 缓存字符串表达式Expression compileExp = AviatorEvaluator.compile(exp6, true);System.out.println(compileExp.execute(map2)); // 13.2
自定义函数(可变参数)
用法与上述类似,但继承 AbstractVariadicFunction
如下:
public class AviatorFuncWithCustomArgs extends AbstractVariadicFunction {// 可变形参@Overridepublic AviatorObject variadicCall(Map<String, Object> map, AviatorObject... args) {double sum = 0d;for (AviatorObject arg : args) {Number a = FunctionUtils.getNumberValue(arg, map);sum += a.doubleValue();}return new AviatorDouble(sum);}@Overridepublic String getName() {return "customAdd";}
}
函数调用:
// 自定义函数的调用 (可变参数)AviatorEvaluator.addFunction(new AviatorFuncWithCustomArgs());String exp7 = "customAdd(a,b,c,d)";Map<String, Object> map7 = new HashMap<>();Double a7 = 7.7d;Double b7 = 5.5d;Double c7 = 6.6d;map7.put("a", a7);map7.put("b", b7);map7.put("c", c7);map7.put("d", 1.1d);Expression compileExp7 = AviatorEvaluator.compile(exp7, true);System.out.println(compileExp7.execute(map7)); // 20.9
封装工具类
/*** Aviator 工具类 应用于规则条件的判断【规则引擎】*/
public class AviatorUtil {/*** 解析常量字符串表达式** @param str: 字符串表达式,不包含自定义函数, 也不包含变量*/public static Object execute(String str) {// 执行AviatorEvaluator 对象的 execute(),获取字符串表达式运算后结果return AviatorEvaluator.execute(str);}/*** 解析变量字符串表达式** @param str: 字符串表达式, 包含变量* @param map: 变量参数*/public static Object execute(String str,Map<String, Object> map) {// 将字符串表达式解析为 Expression 对象Expression compileExp = AviatorEvaluator.compile(str, true);// 执行Expression 对象的 execute(),获取字符串表达式运算后结果return compileExp.execute(map);}/*** 解析自定义函数字符串表达式** @param str: 字符串表达式, 包含自定义函数* @param func: 自定义函数* @return java.lang.Object*/public static Object execute(String str,AbstractFunction func) {// 注册自定义函数AviatorEvaluator.addFunction(func);// 将字符串表达式解析为 Expression 对象Expression compileExp = AviatorEvaluator.compile(str, true);// 执行Expression 对象的 execute(),获取字符串表达式运算后结果return compileExp.execute();}
}
注意!!!
精度丢失问题
使用浮点数计算的过程中可能有精度问题
【Java问题】
AviatorEvaluator.addFunction(new AviatorFuncWithCustomArgs());String exp7 = "customAdd(a,b,c)";Map<String, Object> map = new HashMap<>();Double a = 7.7d;Double b = 5.5d;Double c = 6.6d;map.put("a", a);map.put("b", b);map.put("c", c);Expression compileExp7 = AviatorEvaluator.compile(exp7, true);System.out.println(compileExp7.execute(map));
还是上述自定义函数例子,输出结果如下:
使用BigDecimal
类改造自定义函数:
public class AviatorFuncWithCustomArgs extends AbstractVariadicFunction {@Overridepublic AviatorObject variadicCall(Map<String, Object> map, AviatorObject... args) {return new AviatorDouble(Arrays.stream(args).map(arg -> FunctionUtils.getNumberValue(arg, map)).map(n -> new BigDecimal(n.toString())) // 直接转换为 BigDecimal.reduce(BigDecimal::add).orElse(BigDecimal.ZERO) // 如果流为空,则返回0.doubleValue() // 如果需要返回double值);}@Overridepublic String getName() {return "customAdd";}
}
另一个例子:
String uiExpression = "if(complete / target >= 0.95){ 8 }else{ 0 }";Map<String, Object> variable = new HashMap<>();variable.put("target", new BigDecimal("100"));variable.put("complete", new BigDecimal("95"));// 是否跟踪运行,打开后将在控制台打印整个表达式的求值过程。请勿在生产环境打开,将极大地降低性能。默认为 false 关闭。AviatorEvaluator.getInstance().setOption(Options.TRACE_EVAL, true);// 执行表达式final Object execute = AviatorEvaluator.execute(uiExpression, variable);System.out.println(execute);
可以看到由于精度问题,导致输出结果0,不是我们想要的8。(0.95 < 0.9500000000000001)
解决方案
AviatorScript 本身有考虑这种计算场景,可以通过配置,强制要求 AviatorScript 框架将整数和浮点数解析为 BigDecimal
类型,而不是坑爹的 Double
类型。
// -- 1. 解析浮点数为 Decimal 类型
AviatorEvaluator.getInstance().setOption(Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL, true);
// -- 2. 解析整数为 Decimal 类型
AviatorEvaluator.getInstance().setOption(Options.ALWAYS_PARSE_INTEGRAL_NUMBER_INTO_DECIMAL, true);
添加配置验证:
String uiExpression = "if(complete / target >= 0.95){ 8 }else{ 0 }";Map<String, Object> variable = new HashMap<>();variable.put("target", new BigDecimal("100"));variable.put("complete", new BigDecimal("95"));// 是否跟踪运行,打开后将在控制台打印整个表达式的求值过程。请勿在生产环境打开,将极大地降低性能。默认为 false 关闭。AviatorEvaluator.getInstance().setOption(Options.TRACE_EVAL, true);// -- 1. 解析浮点数为 Decimal 类型AviatorEvaluator.getInstance().setOption(Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL, true);// -- 2. 解析整数为 Decimal 类型AviatorEvaluator.getInstance().setOption(Options.ALWAYS_PARSE_INTEGRAL_NUMBER_INTO_DECIMAL, true);// 执行表达式final Object execute = AviatorEvaluator.execute(uiExpression, variable);System.out.println(execute);
问题解决,输出结果8:
详细使用参考如下资料:
- https://juejin.cn/post/7273032322546647096
- AviatorScript 编程指南(5.0)