简单高效的轻量级表达式引擎:Aviator 前言
Aviator 是一个高性能、、轻量级的表达式引擎,支持表达式动态求值。其设计目标为轻量级和高性能,相比于 Groovy 和 JRuby 的笨重,Aviator 就显得更加的小巧。与其他的轻量级表达式引擎不同,其他的轻量级表达式引擎基本都是通过解释代码的方式来运行,而 Aviator 则是直接将表达式编译成Java字节码,交给JVM来运行。
使用方式 引入依赖 1 2 3 4 5 <dependency > <groupId > com.googlecode.aviator</groupId > <artifactId > aviator</artifactId > <version > ${version}</version > </dependency >
简单使用 在 Aviator 中,我们可以直接定义一个常量表达式来让 Aviator 执行,下面我们通过一个简单的小案例来熟悉一下 Aviator
1 2 3 4 5 6 7 8 9 import com.googlecode.aviator.AviatorEvaluator;import org.junit.jupiter.api.Test;@Test void simpleTest () { String expression = "3 + 2 * 6" ; Object result = AviatorEvaluator.execute(expression); System.out.println(result); }
可以看到我们成功运行,且结果就是我们预期的15。
变量表达式 在用过了常量表达式后,我们还可以通过声明变量的形式来实现表达式的运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import com.googlecode.aviator.AviatorEvaluator;import org.junit.jupiter.api.Test;import java.util.HashMap;import java.util.Map;@Test void variableTest () { Map<String, Object> map = new HashMap <>(); map.put("name" , "张三" ); map.put("job" , "程序员" ); String exp = "'你好,我是'+ name + ',我的职业是' + job + ',很高兴认识你'" ; Object result = AviatorEvaluator.execute(exp, map); System.out.println(result); }
需要注意的是:在书写表达式的时候,格式跟在Java中书写相差不大。由于我们是在字符串中书写的表达式,需要注意 表达式中的字符串也要用单引号包裹起来 ,否则 Aviator 会将你的字符串判定为一个变量,然后去变量map中去寻找,当找不到的时候就会报错。
自定义函数 在见识过以上两种使用方式后,Aviator 还支持以自定义函数的形式来执行表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import com.googlecode.aviator.runtime.function.AbstractFunction;import com.googlecode.aviator.runtime.type.AviatorLong;import com.googlecode.aviator.AviatorEvaluator;import com.googlecode.aviator.runtime.function.AbstractFunction;import com.googlecode.aviator.runtime.type.AviatorObject;import org.junit.jupiter.api.Test;class CustomFunction extends AbstractFunction { @Override public String getName () { return "customFunc" ; } @Override public AviatorObject call (Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) { Number num1 = arg1.numberValue(env); Number num2 = arg2.numberValue(env); Long sum = num1.longValue() + num2.longValue(); return AviatorLong.valueOf(sum); } @Test void customFuncTest () { AviatorEvaluator.addFunction(new CustomFunction ()); Long result = (Long) AviatorEvaluator.execute("customFunc(50,20)" ); System.out.println(result); } }
我们声明完自定义函数后,需要先注册到 Aviator 中之后才能使用,同时我们也可以发现,自定义函数中也是调用的 AviatorEvaluator.execute()
方法,那么就说明调用自定义函数时也是可以做动态变量传值的,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import com.googlecode.aviator.runtime.function.AbstractFunction;import com.googlecode.aviator.runtime.type.AviatorLong;import com.googlecode.aviator.AviatorEvaluator;import com.googlecode.aviator.runtime.function.AbstractFunction;import com.googlecode.aviator.runtime.type.AviatorObject;import org.junit.jupiter.api.Test;class CustomFunction extends AbstractFunction { @Override public String getName () { return "customFunc" ; } @Override public AviatorObject call (Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) { Number num1 = arg1.numberValue(env); Number num2 = arg2.numberValue(env); Long sum = num1.longValue() + num2.longValue(); return AviatorLong.valueOf(sum); } @Test void customFuncTest () { Map<String, Object> map = new HashMap <>(); map.put("a" , 50 ); map.put("b" , 20 ); AviatorEvaluator.addFunction(new CustomFunction ()); Long result = (Long) AviatorEvaluator.execute("customFunc(a,b)" , map); System.out.println(result); } }
应用案例 既然学习完了上面三种使用方式,那我们就可以来找个简单的应用案例实操一下了。
假如我们做了一个OA系统,员工每完成一个项目会积累一定的积分,积分可以用来兑换一些公司内自定的奖品,公式我们暂定为:[(总数 * num + 已完成项目数量 * 0.5 -未完成项目数量 * 0.5)/总数] * 10
,其中 num
的值是根据项目总数变化的,具体公式为(总数去除个位数) / 1000 + 0.9
,并且这个公式可能在之后的场景里会改变,有了场景我们就来具体实现一下。
建表 首先我们要先建个表来存放我们的公式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0 ;DROP TABLE IF EXISTS `aviator_expression`;CREATE TABLE `aviator_expression` ( `var_id` int NOT NULL AUTO_INCREMENT COMMENT '表达式id' , `var_name` varchar (50 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '表达式名称' , `expression` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '表达式' , `create_user_id` int NULL DEFAULT NULL COMMENT '创建用户id' , `create_user_name` varchar (30 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建用户名称' , `create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' , `update_user_id` int NULL DEFAULT NULL COMMENT '最后修改用户id' , `update_user_name` varchar (30 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '最后修改用户名称' , `update_time` datetime NULL DEFAULT NULL COMMENT '最后修改时间' , PRIMARY KEY (`var_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic ; INSERT INTO `aviator_expression` VALUES (1 , '计算个人积分' , '(total * double(num) + completedNum * 0.5 - unFinishedNum * 0.5) * 10' , 0 , '超级管理员' , '2023-07-22 11:00:48' , NULL , NULL , NULL );INSERT INTO `aviator_expression` VALUES (2 , '计算个人积分子项' , 'totalInt / 1000 + 0.9' , 0 , '超级管理员' , '2023-07-22 14:30:48' , NULL , NULL , NULL );DROP TABLE IF EXISTS `employee_project`;CREATE TABLE `employee_project` ( `emp_id` int NOT NULL AUTO_INCREMENT COMMENT '员工id' , `name` varchar (50 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '员工名称' , `total` int NULL DEFAULT NULL COMMENT '项目总数' , `completed_num` int NULL DEFAULT NULL COMMENT '已完成项目数量' , `unFinished_num` int NULL DEFAULT NULL COMMENT '未完成项目数量' , PRIMARY KEY (`emp_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic ; INSERT INTO `employee_project` VALUES (1 , '张三' , 9 , 7 , 2 );INSERT INTO `employee_project` VALUES (2 , '李四' , 10 , 9 , 1 );INSERT INTO `employee_project` VALUES (3 , '王五' , 23 , 20 , 3 );SET FOREIGN_KEY_CHECKS = 1 ;
实现 Controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import com.aviator.service.AviatorExpressionService;import com.aviator.vo.PerformanceVo;import lombok.RequiredArgsConstructor;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController @RequiredArgsConstructor @RequestMapping("/performance") public class PerformanceController { private final AviatorExpressionService expressionService; @GetMapping("/calculate") public List<PerformanceVo> calculatePerformance () { return expressionService.calculatePerformance(); } }
Service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import com.aviator.entity.AviatorExpression;import com.aviator.vo.PerformanceVo;import com.baomidou.mybatisplus.extension.service.IService;import java.util.List;public interface AviatorExpressionService extends IService <AviatorExpression> { List<PerformanceVo> calculatePerformance () ; }
ServiceImpl 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 import cn.hutool.core.bean.BeanUtil;import com.aviator.entity.EmployeeProject;import com.aviator.service.EmployeeProjectService;import com.aviator.vo.PerformanceVo;import com.baomidou.mybatisplus.core.toolkit.Wrappers;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.googlecode.aviator.AviatorEvaluator;import lombok.RequiredArgsConstructor;import com.aviator.entity.AviatorExpression;import com.aviator.service.AviatorExpressionService;import com.aviator.mapper.AviatorExpressionMapper;import org.springframework.stereotype.Service;import java.text.DecimalFormat;import java.util.HashMap;import java.util.List;import java.util.Map;@Service @RequiredArgsConstructor public class AviatorExpressionServiceImpl extends ServiceImpl <AviatorExpressionMapper, AviatorExpression> implements AviatorExpressionService { private final EmployeeProjectService employeeProjectService; @Override public List<PerformanceVo> calculatePerformance () { List<EmployeeProject> employeeProjectList = employeeProjectService.list(); List<PerformanceVo> performanceList = BeanUtil.copyToList(employeeProjectList, PerformanceVo.class); AviatorExpression performanceExp = this .getOne(Wrappers.lambdaQuery(AviatorExpression.class) .eq(AviatorExpression::getVarName, "团队积分计算" )); AviatorExpression performanceChildExp = this .getOne(Wrappers.lambdaQuery(AviatorExpression.class) .eq(AviatorExpression::getVarName, "计算团队积分子项" )); performanceList.forEach(any -> { int totalInt = any.getTotal() / 10 ; Map<String, Object> map = new HashMap <>(); map.put("totalInt" , totalInt); Double num = (Double) AviatorEvaluator.execute(performanceChildExp.getExpression(), map); Map<String, Object> paramMap = BeanUtil.beanToMap(any); paramMap.put("num" , num); Double doublePerformance = (Double) AviatorEvaluator.execute(performanceExp.getExpression(), paramMap); DecimalFormat df = new DecimalFormat ("#.#" ); String performance = df.format(doublePerformance); any.setPerformance(performance); }); return performanceList; } }
由于num在传入的时候数据类型为String
类型,但是 Aviator 对数据类型要求比较严格,所以我们要在表达式里面将num
转为 double
类型。
总结 我们一共学习了 Aviator 的三种用法:简单表达式、变量表达式 和 自定义函数。
同时也通过一个简单的小案例供大家学习参考,至此教程就结束啦,感谢大家的关注。