java规则引擎Aviator

2021/07/05 1758点热度 0人点赞 0条评论

在灰度系列中《基于springcloud的灰度实现方案(二)》,之前规则适配使用数据库+策略模式实现,单个规则还好,多个规则,各种场景使用,还是稍微有点欠缺。就想着用java规则引擎来解决这个问题。

之前在项目中使用过drools,比较重,初始加载复杂,首次执行效率较低,最好预热一下,其次分布式规则处理时的一致性也得自己把控;

之前就了解过aviator,这次就直接用了。

相关资料

# 官网地址
https://github.com/killme2008/aviator
# 开发文档
https://www.yuque.com/boyan-avfmj/aviatorscript/cpow90

简介:

5.0之前aviator只是一个表达式引擎主要用于各
种表达式的动态求值。
5.0以后,aviator 变成了一门通用的脚本语言aviatorScript.
- 直接翻译成对应的java字节码;
- 高性能(最多扫两套)
- 轻量级,只依赖commons-beanutils这个库的反射,整个jar5.0页就430k
相对有特色的点:
- 支持运算重载;
- 原生支持大整数和BigDecimal类型及运算;
- 原生支持正则表达式类型及匹配运算符 =~
- clojureseq库及lambda支持可以灵活地处理各种集合
- 开放能力:包括自定义函数接入以及各种定制选项

ps: 如果用不到5.0以后的功能,使用之前的版本即可,之前的包只有几十kb;

特性:

  • 词法作用域 {...} ,和 let 定义作用域内的变量

  • return 语句,用于从函数或者 script 中返回(值)。

  • if/elsif/else 条件语句

  • for/while 循环语句,以及 break / continue 支持

  • fn 语法用于定义命名函数, 4.0 已经引入了 lambda -> ... end语法专门用于匿名函数定义

  • 单行注释 支持

  • 和 Java Scripting API 更好的集成

  • 字符串插值

  • 异常处理 try...catch...finally 语句等等。

项目中使用

        <dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.2.5</version>
</dependency>

示例:比如,在灰度规则里 手机尾号为9和0,用户id>901,注册时间大于‘2021-06-25’ 的用户进入灰度环境 硬编码好说,策略也还好,但是如果加上或运算,策略也不好处理, 表达式最好了

    @Test
public void grayRule(){
Map<String,Object> map = new HashMap<>();
final Date date = new Date();
String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS").format(date);
map.put("mobileTail","9");
map.put("userId",901);
map.put("registerTime",dateStr);
map.put("mobile","15600000269");
map.put("sex",1);
map.put("age",28);
// 手机尾号
String expression = "(string.startsWith('9,0',mobileTail) && userId>=901 && registerTime>'2021-06-25 00:00:00')";
Boolean flag = (Boolean) AviatorEvaluator.execute(expression, map);
Assert.assertTrue("规则验证通过",flag);
}

用了以后,操作真简单。

执行方式一:直接表达式+参数

AviatorEvaluator.execute() 

AviatorEvaluator.getInstance().execute()

最终底层:
AviatorEvaluatorInstance.execute()

script脚本+ 参数

执行方式二:script脚本+参数

script脚本

str = "";
if (age <=1 ){
str = "婴儿";
} elsif (age>1 && age<=6) {
str = "儿童";
} elsif (age>6 && age<=17) {
str = "青少年";
} elsif (age>18 && age<=40){
str = "青年";
} elsif (age>40 && age<=48){
str = "壮年";
} elsif (age>48 && age<=65){
str = "中年";
} elsif (age>65){
str = "老年";
}
return str="#{name}处在#{str}";
    @Test
public void script2() throws IOException {
//编译脚本
//路径是文件系统的绝对路径或相对路径,
//相对路径的时候,必须项目的根目录开始的相对路径
//classpath下的绝对或相对路径
Expression compiledExp = AviatorEvaluator.getInstance().compileScript("src/test/resources/script.av");
//执行脚本,参数可以map,也可以通过newEnv kv对的方式塞入,最终还是map
final Object o = compiledExp.execute(compiledExp.newEnv("age", 12, "name", "yxk"));
System.out.println(o);
}

输出结果

yxk处在青少年期

内置函数

aviator内置了很多函数,具体可以看官网 https://www.yuque.com/boyan-avfmj/aviatorscript/ashevw

比如:上面我们用的string.startsWith('9,0',mobileTail) 

@Test
public void function_in(){
long num =(Long) AviatorEvaluator.getInstance().execute("math.round(4.3)");
Assert.assertEquals("4.3四舍五入后", 4L ,num);
Map<String,Object> map = new HashMap<>();
map.put("str","yxkong");
map.put("head","yxk");
Boolean flag = (Boolean) AviatorEvaluator.getInstance().execute("string.contains(str,head)",map);
Assert.assertTrue("yxkong包含yxk",flag);
}

自定义函数

//定义函数
class AddFunction extends AbstractFunction {
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {

long num1 = FunctionUtils.getNumberValue(arg1, env).longValue();
long num2 = FunctionUtils.getNumberValue(arg2, env).longValue();
return AviatorLong.valueOf(num1+num2);
}

@Override
public String getName() {
return "add";
}
}

@Test
public void function_my(){
// 注册自定义函数
AviatorEvaluator.addFunction(new AddFunction());
long num =(Long) AviatorEvaluator.getInstance().execute("add(3,4)");
Assert.assertEquals("3+4", 7L,num);
}

yxkong

这个人很懒,什么都没留下

文章评论