简介
轻量级的规则引擎,易于学习的api
简单来说,规则引擎就是一个函数:y=f(x1,x2,…,xn)
将业务代码和业务规则分离,解耦业务决策和业务代码的绑定关系
入门示例
依赖引入
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.1.0</version>
</dependency>
<!--规则定义文件格式,支持json,yaml等-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-support</artifactId>
<version>4.1.0</version>
</dependency>
<!--支持mvel规则语法库-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-mvel</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-spel</artifactId>
<version>4.1.0</version>
</dependency>
代码示例
public static void main(String[] args) {
Rules rules = new Rules();
Rule rule = new RuleBuilder()
.name("test")
.description("测试").priority(3)
.when(facts -> facts.get("userName").equals("zhangsan"))
.then(facts -> System.out.println("this is test action!"))
.build();
rules.register(rule);
Facts facts = new Facts();
facts.put("userName","zhangsan");
DefaultRulesEngine defaultRulesEngine = new DefaultRulesEngine();
defaultRulesEngine.fire(rules,facts);
}
核心概念
规则(Rule)
由条件和行动构成的推理语句,通俗的讲:当满足某个条件之后,需要做后续的动作(一个或多个动作),一般表示为IF <conditions> THEN <actions>, Rule表达逻辑。一个规则的IF部分称为LHS,THEN部分称为RHS。
除了以上示例方式构建Rule,还可以通过注解构建Rule:
@Rule(name = "repeatRule",description = "重复规则对消息内容去重",priority = 100)
public class RepeatRule {
@Condition
public boolean evaluate(@Fact("sendMessages") Set<String> sendMessages, @Fact("message") String message) {
return sendMessages.contains(message);
}
@Action(order =0)
public void execute(Facts facts) {
//
}
@Action(order = 1)
public void breakRules(Facts facts) {
facts.put("break",true);
facts.put("reason","repeat limit");
}
}
事实(Facts)
一组用户的输入数据,规则将根据输入数据判断是否满足条件,从而触发规则actions
Facts是一组Fact集合,源码示例如下:
而Fact是一个容器,可以简单的理解成一个Map对象:
执行引擎(RulesEngine)
引擎执行器,一般为推理引擎。Rules使用LHS与事实进行模式匹配。当匹配被找到,Rules会执行RHS即执行逻辑,同时actions经常会改变facts的状态,来推理出期望的结果。
常用的执行引擎即:DefaultRulesEngine
结合mvel使用示例
@Test
public void mvelExecute() {
MVELRule repeatRule = new MVELRule().name("repeat Rule")
.priority(100)
.when("!sendMessages.contains(msgId)")
.then("result.put('break',true);result.put('reason','repeat limit')");
Rules rules = new Rules(repeatRule);
Facts facts = new Facts();
facts.put("msgId","123456");
facts.put("sendMessages",new TreeSet<>());
facts.put("msgType","interaction");
facts.put("message","hello world");
facts.put("result",new HashMap<>());
DefaultRulesEngine engine = new DefaultRulesEngine();
//CustomRuleListener customRuleListener = new CustomRuleListener();
//engine.registerRuleListener(customRuleListener);
engine.fire(rules,facts);
Map<String,Object> result = (Map<String,Object>)facts.get("result");
log.info("执行结果是:{}", JSONUtil.toJsonStr(result));
}
MVEL可以支持表达式支持常用的逻辑符:> = < && || 等,还支持java类型api的调用
最直观的使用MVEL表达是API如下:
@Test
public void mvelCompileTest(){
MyTest obj = new MyTest();
Map<String, Object> map = new HashMap<>();
map.put("key1", 1);
map.put("key2", 2);
String expression = "func(map)";
Map<String, Object> vars = new HashMap<>();
vars.put("map", map);
MVEL.executeExpression(MVEL.compileExpression(expression), obj, vars);
System.out.println(map);
}
public void func(Map<String, Object> map) {
map.put("test","lisi");
}
详细的MVEL脚本语言的使用:http://mvel.documentnode.com/
结合spel使用示例
public static void main(String[] args) {
Rules rules = new Rules();
SpELRule spelRule = new SpELRule().name("1")
.description("1")
.priority(1)
.when("#{ ['item'].price >= 100 }")
.then("#{ ['item'].setExpression('折扣1折\n原价为:' + ['item'].price " +
"+ '\n折扣后的价格为:'" +
" + T(java.lang.Float).parseFloat(['item'].price * 0.1+'') ) }");
rules.register(spelRule);
Item item = new Item(500);
Facts facts = new Facts();
facts.put("item", item);
DefaultRulesEngine defaultRulesEngine = new DefaultRulesEngine();
// 触发引擎
defaultRulesEngine.fire(rules, facts);
System.out.println(item.getExpression());
}
企业级应用
如果规则引擎的使用仅仅只如以上硬编码的使用方式,还不足以体现规则引擎的强大。试看以下代码:
@Test
public void factoryTest() throws Exception {
ClassPathResource classPathResource = new ClassPathResource("rule.yml");
String absolutePath = classPathResource.getAbsolutePath();
YamlRuleDefinitionReader ymlReader = new YamlRuleDefinitionReader();
MVELRuleFactory mvelRuleFactory = new MVELRuleFactory(ymlReader);
Rules rules = mvelRuleFactory.createRules(new FileReader(absolutePath));
DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
JSONObject entries = new JSONObject();
entries.set("productId","1");
entries.set("type","1");
Facts facts = new Facts();
facts.put("canteen", entries);
rulesEngine.fire(rules, facts);
}
yml内容如下:
---
name: "规则1"
description: "prouductId = 1 && type = 1 "
condition: "canteen.productId==1&&canteen.type==1"
priority: 1
actions:
- "com.byd.performance.easyruledemo.four.UserInfoService.getNowTime();"
---
name: "规则2"
description: "prouductId = 1 && type = 2"
condition: "canteen.productId == 2 && canteen.type == 1"
priority: 2
actions:
- "System.out.println(2);"
以上通过配置文件定义规则,将规则的触发决策通过文本的方式表述,解耦了业务逻辑代码和规则决策,大大的提升了该引擎的实用性。试想如下业务场景:
公司为了对产品经销商加强管理,需要制定一系列的处罚措施,当触犯某条规则时,将受到相应的惩罚措施。如果采用代码实现决策过程,需要大量的if else来完成,且当需要改变处罚措施时,需要修改代码,重新发布。
如果把决策过程由以上配置文件完成(再配合规则变动刷新等措施或者直接把规则数据由数据库存储加载),可以方便的支持需求变更(此处只是讨论决策的变动,如果需要新增加某种处罚措施也需要代码调整、发布)
源码浅析
easy-rule的源码较为简单(最主要是将触发规则(Condition)和执行逻辑(Action)解耦的思想),以下以简单的示例做源码分析:
org.jeasy.rules.core.DefaultRulesEngine#doFire
可以看出执行引擎的核心逻辑逻辑清晰、简单--就是通过循环Rule,判断是否命中规则,然后执行规则逻辑即可
其中执行引擎有4个参数可设置,分别释义如下:
skipOnFirstAppliedRule:Parameter to skip next applicable rules when a rule is applied(当第一个被命中的规则执行后,是否不执行后续规则)代码line 115就是改功能的释义 skipOnFirstNonTriggeredRule:Parameter to skip next applicable rules when a rule is non triggered(当有规则的判断逻辑未命中或判断是否命中异常,是否跳过后续规则,代码line102,line130 是逻辑释义) isSkipOnFirstFailedRule:Parameter to skip next applicable rules when a rule has failed(当执行某个规则逻辑异常时是否跳过后续规则) priorityThreshold:规则优先级的阈值,超过该阈值的规则不被执行,默认值是Integer.MAX_VALUE
功能展望
在上面的企业级应用中有提到过,将规则决策由代码实现转而由配置文件(或者存储于数据库)中实现,解耦了规则和执行逻辑。(执行逻辑--知识库)知识库的更新需要走发布流程。如果实现以下功能是不是能更好的发挥该框架的功能
1 解藕知识库与业务逻辑的耦合性,维护知识库来进行知识与版本的管理
2 动态增加知识库,让JVM动态加载新的知识库
3 屏蔽语言壁垒,能够让运营、产品支持在线规则配置、发布是最终要实现的重要目标。做到无需研发接入,实时发布