零、RuleEngine
github:Hale-Lee/RuleEngine
一、Aviator
1、maven
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>${aviator.version}</version>
</dependency>
2、特性
- (1) 支持大部分运算操作符:包括算术操作符、关系运算符、逻辑操作符、正则匹配操作符
=~
、三元表达式?:
注:支持操作符的优先级和括号强制优先级
- (2) 支持函数调用和自定义函数
- (3) 支持正则表达式匹配,类 Ruby 的
$digit
指向匹配分组、自动类型转换注:当执行操作时,会自动判断操作数类型并做相应转换,无法转换即抛异常
- (4) 支持传入变量,支持类似
a.b.c
的嵌套变量访问 - (5) 性能优秀
- (6) 劣势:没有
if else
、do while
等语句;没有赋值语句;仅支持逻辑表达式、算术表达式、三元表达式和正则匹配;没有位运算符
3、执行方式
两种表达式执行方式:
execute()
:需要传递 Map 格式参数exec()
:不需要传递 Map
public class Test {
public static void main(String[] args) {
// exec执行方式,无需传递Map格式
String age = "18";
System.out.println(AviatorEvaluator.exec("'His age is '+ age +'!'", age));
// execute执行方式,需传递Map格式
Map<String, Object> map = new HashMap<String, Object>();
map.put("age", "18");
System.out.println(AviatorEvaluator.execute("'His age is '+ age +'!'", map));
}
}
AviatorEvaluator
两种模式:
- 以编译速度优先(默认):
AviatorEvaluator.setOptimize(AviatorEvaluator.COMPILE)
- 以运行速度优先:
AviatorEvaluator.setOptimize(AviatorEvaluator.EVAL)
AviatorEvaluator.execute 内存溢出 OOM
:
- 背景:循环调用
AviatorEvaluator.execute
时,JVM 非堆内存会慢慢增长,随着时间的推移,那段循环的代码被执行次数越多,非堆内存最终会因为只有增加没有释放而爆了 - 解决:如果表达式规模有限,都建议启用 cache 模式,除了 class 内存占用之外还可以提升性能
public static Object execute(String expression, Map<String, Object> env, boolean cached) //cached = true
详情链接:循环调用AviatorEvaluator.execute的时候会出现问题
4、内置函数与自定义函数
(1) 内置函数
- 详情可参考:https://juejin.cn/post/7002606430519853069
public class Test {
public static void main(String[] args) {
Map<String,Object> map = new HashMap<>();
map.put("s1","123qwer");
map.put("s2","123");
System.out.println(AviatorEvaluator.execute("string.startsWith(s1,s2)",map));
}
}
(2) 自定义函数
自定义函数要继承AbstractFunction类,重写目标方法
可阅读:https://ixyzero.com/blog/archives/3884.html
public class Test {
public static void main(String[] args) {
AviatorEvaluator.addFunction(new MultiplyFunction()); // 注册自定义函数
// 方式1
System.out.println(AviatorEvaluator.execute("multiply(12.23, -2.3)"));
// 方式2
Map<String, Object> params = new HashMap<>();
params.put("a", 12.23);
params.put("b", -2.3);
System.out.println(AviatorEvaluator.execute("multiply(a, b)", params));
}
}
class MultiplyFunction extends AbstractFunction{
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
double num1 = FunctionUtils.getNumberValue(arg1, env).doubleValue();
double num2 = FunctionUtils.getNumberValue(arg2, env).doubleValue();
return new AviatorDouble(num1 * num2);
}
@Override
public String getName() {
return "multiply";
}
}
5、数据类型
Number
类型:数字类型,支持两种类型,分别对应 Java 的 Long 和 Double- 整数被转换为 Long,浮点数被转换为 Double
- 不支持科学计数法,仅支持十进制
String
类型: 字符串类型,单引号或双引号括起来的文本串,如’hello world’变量传入的 String 或 Character 将被转为 String 类型
Bool
类型: 常量 true 和 false,表示真值和假值,与 java 的Boolean.TRUE
和Boolean.False
对应Pattern
类型: 正则表达式,以//
括起来的字符串,如//d+/
,内部实现为java.util.Pattern
- 变量类型: 与 Java 的变量命名规则相同,变量的值由用户传入
nil
类型: 类似 java 的null
,但nil
可以参与==、!=、>、>=、<、<=
的比较Aviator 规定:
- 除了
nil
本身,任何类型都大于 nil nil == nil
返回 true- 用户传入的变量值若为 null,则将作为 nil 处理,nil 打印为 null
- 除了
6、常用操作符
(1) 操作符列表
(2) 常量和变量
(3) 算术运算符
算术运算符:+、-、<tt></tt>、/、%
其中:
-、<tt></tt>、/、%
仅能作用于Number类型+
能用于 Number 类型和 String 类型
注:任何类型与String 相加,结果为 String
(4) 逻辑运算符
逻辑运算符包括:一元否定运算符 !
、逻辑与 &&
、逻辑或 ||
注:逻辑运算符的操作数只能为 Boolean
(5) 关系运算符
关系运算符包括:<、<=、>、>=、==、!=
注:
&&
和||
都执行短路规则- 可以用作 Number之间、String之间、Pattern之间、Boolean之间、变量之间、其他类型与 nil 之间的关系比较
(6) 匹配运算符
匹配运算符 =~
用于 String 和 Pattern 的匹配
注:
- 左操作数必须为 String,右操作数必须为 Pattern
- 匹配成功后,Pattern 的分组将存于变量
$num
,num 为分组索引
(7) 三元运算符
三元运算符 ?:
,形式为 bool ? exp1: exp2
其中 bool 必须为结果为 Boolean 类型的表达式,而 exp1 和 exp2 可以为任何合法的 Aviator 表达式,并且不要求 exp1 和 exp2 返回的结果类型一致
Aviator 没有提供
if else
语句
8、案例
(1) 案例一:编译表达式
public class Test {
public static void main(String[] args) {
String expression = "a+(b-c)>100";
// 编译表达式
Expression compiledExp = AviatorEvaluator.compile(expression);
Map<String, Object> env = new HashMap<>();
env.put("a", 100.3);
env.put("b", 45);
env.put("c", -199.100);
// 执行表达式
Boolean result = (Boolean) compiledExp.execute(env);
System.out.println(result);
}
}
(2) 案例二:访问数组和集合
List
和数组用list[0]
和array[0]
,Map
用map.date
public class Test {
public static void main(String[] args) {
final List<String> list = new ArrayList<>();
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("list", list);
env.put("array", array);
env.put("map", map);
System.out.println(AviatorEvaluator.execute("list[0]+':'+array[0]+':'+'today is '+map.date", env));
}
}
(3) 案例三:三元比较符
public class Test {
public static void main(String[] args) {
Map<String, Object> env = new HashMap<String, Object>();
env.put("a", -5);
String result = (String) AviatorEvaluator.execute("a>0? 'yes':'no'", env);
System.out.println(result);
}
}
(4) 案例四:正则表达式匹配
public class Test {
public static void main(String[] args) {
String email = "hello2018@gmail.com";
Map<String, Object> env = new HashMap<String, Object>();
env.put("email", email);
String username = (String) AviatorEvaluator.execute("email=~/([\\w0-8]+)@\\w+[\\.\\w+]+/ ? $1 : 'unknow' ", env);
System.out.println(username);
}
}
(5) 案例五:变量的语法糖衣
public class Test {
public static void main(String[] args) {
User user = new User(1,"jack","18");
Map<String, Object> env = new HashMap<>();
env.put("user", user);
String result = (String) AviatorEvaluator.execute(" '[user id='+ user.id + ',name='+user.name + ',age=' +user.age +']' ", env);
System.out.println(result);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private String age;
}
(6) 案例六:nil 对象
- nil 是 Aviator 的内置常量,表示空值
- nil 与 null 的区别:null 只能使用
==、!=
,而 nil 还可以使用>、>=、<、<=
- 用户传入的变量若为
null
,将自动以nil
替代- 除了 nil 本身,任何对象都比 nil 大
AviatorEvaluator.execute("nil == nil"); //true
AviatorEvaluator.execute(" 3 > nil"); //true
AviatorEvaluator.execute(" true != nil"); //true
AviatorEvaluator.execute(" ' ' > nil "); //true
AviatorEvaluator.execute(" a == nil "); //true, a is null
(7) 案例七:日期比较
public class Test {
public static void main(String[] args) {
Map<String, Object> env = new HashMap<String, Object>();
final Date date = new Date();
String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS").format(date);
env.put("date", date);
env.put("dateStr", dateStr);
Boolean result = (Boolean) AviatorEvaluator.execute("date==dateStr", env);
System.out.println(result);
result = (Boolean) AviatorEvaluator.execute("date > '2009-12-20 00:00:00:00' ", env);
System.out.println(result);
result = (Boolean) AviatorEvaluator.execute("date < '2200-12-20 00:00:00:00' ", env);
System.out.println(result);
result = (Boolean) AviatorEvaluator.execute("date ==date ", env);
System.out.println(result);
}
}
(8) 案例八:变量定义
Map<String, Object> expParams = Maps.newHashMap();
expParams.put("work", 13);
Object execute = AviatorUtils.execute("let year = (work <= 0 ? 0 : work/12); let month = work%12; let s0 = \"null\"; let s1 = \"#{month}月\"; let s2 = \"#{year}年\";let s3 = \"#{year}年#{month}月\";work <= 0 ? s0 : (work < 12 ? s1 : (month == 0)?s2:s3)", expParams, true);
System.out.println(execute.toString());
二、Drools
1、优点
- (1) 简化系统架构,优化应用
- (2) 提高系统的可维护性和维护成本
- (3) 方便系统的整合
- (4) 减少编写“硬代码”业务规则的成本和风险
2、使用
(1) maven
<dependencies>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>6.5.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>6.5.0.Final</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
(2) 配置文件 /src/resources/META-INF/kmodule.xml
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
<kbase name="rules" packages="rules">
<ksession name="myAgeSession"/>
</kbase>
</kmodule>
(3) drools规则文件 /src/resources/rules/age.drl
import com.lrq.wechatDemo.domain.User // 导入类
dialect "mvel"
rule "age" // 规则名,唯一
when
$user : User(age<15 || age>60) //规则的条件部分
then
System.out.println("年龄不符合要求!");
end
(4) 案例
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:applicationContext.xml"})
public class TestUser {
private static KieContainer container = null;
private KieSession statefulKieSession = null;
@Test
public void test(){
KieServices kieServices = KieServices.Factory.get();
container = kieServices.getKieClasspathContainer();
statefulKieSession = container.newKieSession("myAgeSession");
User user = new User("duval yang",12);
statefulKieSession.insert(user);
statefulKieSession.fireAllRules();
statefulKieSession.dispose();
}
}
三、MVEL
MVEL 用于执行使用 Java 语法编写的表达式
<dependency>
<groupId>org.mvel</groupId>
<artifactId>mvel2</artifactId>
<version>2.3.1.Final</version>
</dependency>
1、特性
MVEL 是一个功能强大的基于 Java 应用程序的表达式语言
特性:
- (1) 动态 JIT 优化器:当负载超过一个确保代码产生的阈值时,选择性地产生字节代码,减少内存使用量
新的静态类型检查和属性支持,允许集成类型安全表达
- (2) 错误报告的改善:包括行和列的错误信息
- (3) 新的脚本语言特征:包含函数定义,如:闭包、lambda定义、标准循环构造(for, while, do-while, do-until…)、空值安全导航操作、内联 with-context 运营 、易变的(isdef)的测试运营等等
- (4) 改进的集成功能:支持基础类型的个性化属性处理器,集成到 JIT 中
- (5) 更快的模板引擎:支持线性模板定义,宏定义和个性化标记定义
- (6) 新的交互式shell
MVELSH
- (7) 缺少可选类型安全
- (8) 集成不良:通过映射填入内容
- 没有字节码不能运作用字节码生成编译时间慢,还增加了可扩展性问题
- 不用字节码生成运行时执行非常慢
- (9) 内存消耗过大
- (10) Jar 巨大/依赖规模
与 java 不同,
MVEL
是动态类型,即在源文件中没有类型限制
如:一条MVEL
表达式,可以是单个标识符,也可以是一个充满了方法调用和内部集合创建的庞大的布尔表达式
2、案例
public class MvelUtils {
public static void main(String[] args) {
String expression = "a == null && b == nil ";
Map<String,Object> map = Maps.newHashMap();
map.put("a",null);
map.put("b",null);
Object object = MVEL.eval(expression,map);
System.out.println(object);
}
}
四、EasyRules
easy-rules
已集成了mvel
规则表达式,后续可能集成SpEL
1、特性
easy rules 是一个简单而强大的 java 规则引擎,有以下特性:
- 轻量级框架,学习成本低
- 基于 POJO
- 为定义业务引擎提供有用的抽象和简便的应用
- 从原始的规则组合成复杂的规则
主要的类或接口:Rule、RulesEngine、RuleListener、Facts
主要的注解:@Action、@Condition、@Fact、@Priority、@Rule
@Rule
:可以标注 name 和 description 属性,每个rule的name要唯一,如果没有指定,则RuleProxy则默认取类名@Condition
:条件判断,要求返回 boolean 值,表示是否满足条件@Action
:标注条件成立之后触发的方法@Priority
:标注该 rule 的优先级,默认Integer.MAX_VALUE - 1
,值越小越优先@Fact
:在客户端使用 put 方法向 Facts 中添加数据,在规则文件中通过 key 来得到相应的数据Facts 的用法很像 Map,是客户端和规则文件之间通信的桥梁
2、使用
(1) Java 方式
- 先创建规则并标注属性
public class RuleClass {
@Rule(priority = 1) //规则设定优先级
public static class FizzRule {
@Condition
public boolean isFizz(@Fact("number") Integer number) {
return number % 5 == 0;
}
@Action
public void printFizz() {
System.out.print("fizz\n");
}
}
@Rule(priority = 2)
public static class BuzzRule {
@Condition
public boolean isBuzz(@Fact("number") Integer number) {
return number % 7 == 0;
}
@Action
public void printBuzz() {
System.out.print("buzz\n");
}
}
public static class FizzBuzzRule extends UnitRuleGroup {
public FizzBuzzRule(Object... rules) {
for (Object rule : rules) {
addRule(rule);
}
}
@Override
public int getPriority() {
return 0;
}
}
@Rule(priority = 3)
public static class NonFizzBuzzRule {
@Condition
public boolean isNotFizzNorBuzz(@Fact("number") Integer number) {
return number % 5 != 0 || number % 7 != 0;
}
@Action
public void printInput(@Fact("number") Integer number) {
System.out.print(number+"\n");
}
}
}
- 然后客户端调用
public class RuleJavaClient {
public static void main(String[] args) {
// 创建规则引擎
RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);
// 创建规则集并注册规则
Rules rules = new Rules();
rules.register(new RuleClass.FizzRule());
rules.register(new RuleClass.BuzzRule());
rules.register(new RuleClass.FizzBuzzRule(new RuleClass.FizzRule(), new RuleClass.BuzzRule()));
rules.register(new RuleClass.NonFizzBuzzRule());
// 执行规则
Facts facts = new Facts();
for (int i = 1; i <= 100; i++) {
facts.put("number", i);
fizzBuzzEngine.fire(rules, facts);
System.out.println();
}
}
}
(2) yml 方式
resources
目录下新建fizzbuzz.yml
---
name: "fizz rule"
description: "print fizz if the number is multiple of 5"
priority: 1
condition: "number % 5 == 0"
actions:
- "System.out.println(\"fizz\")"
---
name: "buzz rule"
description: "print buzz if the number is multiple of 7"
priority: 2
condition: "number % 7 == 0"
actions:
- "System.out.println(\"buzz\")"
---
name: "fizzbuzz rule"
description: "print fizzbuzz if the number is multiple of 5 and 7"
priority: 0
condition: "number % 5 == 0 && number % 7 == 0"
actions:
- "System.out.println(\"fizzbuzz\")"
---
name: "non fizzbuzz rule"
description: "print the number itself otherwise"
priority: 3
condition: "number % 5 != 0 || number % 7 != 0"
actions:
- "System.out.println(number)"
- 客户端调用
public class RuleYmlClient {
public static void main(String[] args) throws FileNotFoundException {
// create a rules engine
RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);
// create rules
Rules rules = MVELRuleFactory.createRulesFrom(new FileReader("fizzbuzz.yml"));
// fire rules
Facts facts = new Facts();
for (int i = 1; i <= 100; i++) {
facts.put("number", i);
fizzBuzzEngine.fire(rules, facts);
System.out.println();
}
}
}