在灰度系列中《基于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这个库的反射,整个jar到5.0页就430k;
相对有特色的点:
- 支持运算重载;
- 原生支持大整数和BigDecimal类型及运算;
- 原生支持正则表达式类型及匹配运算符 =~;
- 类clojure的seq库及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);
}
![公众号图片](https://img-blog.csdnimg.cn/20210407102553361.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y4MDQwNzUxNQ==,size_16,color_FFFFFF,t_70#pic_center)