EasyRules动态规则实现
之前写了easyRules的基本使用方法,可以说还是非常方便而且操作也不复杂,个人觉得相较于Drools来说是简便了不少。接下来就需要更深入的了解一下规则的动态使用方法。
动态规则
动态规则就是由于业务场景的变化,之前的规则已经不适用现在的业务场景,需要更改相对应的规则,但是又不想每次都去修改代码,例如:某宝做活动,之前是满100块才能减免,但是现在只用满10块就能减免了,按照以前的逻辑,我们需要去一下规则文件里面的比较参数就可以,但是这样就需要重新编译打包才能生效,如果使用动态规则,就是我传一个规则文件,只需要运用这个规则文件,就能达到效果,也不需要重新编译打包,何乐而不为呢!!!
要实现这个效果,可以利用easyRules规则定义的两种方式来实现,ymal文件定义规则和json文件定义规则。
存在的问题
这里先说一下在使用过程中遇到的问题(这几个问题都是在运行代码时发现的,可以结合下面的代码实现来看):
-
使用规则文件定义规则,condition和action中只能操作facts中有的对象
在规则文件中虽然可以在conditon和action中使用java代码来写逻辑,但是无法向规则类(pojo类定义的规则)那样的输入参数,因为规则文件中是没办法设置传入参数,例如,在规则类中的action注解对应的方法中我们存入了facts参数,可以使用facts.get()方法获取内容,但是在规则文件中就没办法使用facts这个参数,因为规则文件中无法传入facts这个参数;
-
通过规则监听处理自定义规则的逻辑处理
基于上一个问题,我想到了使用规则监听来进行处理,可以在规则监听类中的afterEvaluate方法中进行相关的逻辑编写,evaluationResult参数时布尔类型的,表示规则匹配是否通过,通过值为true,未通过值未false,也可以使用facts和rule,还是很不错的;
-
使用监听则会作用到所有执行的规则,如何准确处理
基于第二个问题,发现使用了规则监听后,如果你设置的了多个规则进行匹配,那么每次规则执行都会走到afterEvaluate方法中,所以这里的代码逻辑就可能被重复执行多次,这里我处理方法是通过规则的名称来匹配,只有是指定名称的规则才会走逻辑处理。
代码实现
场景简述
接下来使用的测试场景是,输入一个人的信息,信息中包含了这个人的学历等级,根据学历等级去查询他的学历证书情况(集合存储),查询完学历证书后,在检测学历证书与他的学历等级是否匹配,匹配规则为,学历证书数量与学历等级相同,并且最大学历证书的学历等级与人的学历等级一致,则匹配通过,最终人的信息中会添加学历匹配结果,匹配通过的为学历真实,未匹配通过的为存在学历造假嫌疑,并且会保存人的学历资质集合信息。
图解
java规则类实现
规则类
CheckIt:通过检测学历真实
package entity.rules.education;
import entity.pojo.Education;
import entity.pojo.Person;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
import org.jeasy.rules.api.Facts;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author 莫须有
* @Date 2021/12/31 15:45
* @Description 检测学历真实性
*/
@Rule(name = "check", description = "检测学历真实性")
public class CheckIt {
@Condition
public boolean check(@Fact("person")Person person, Facts facts){
List<Education> educationList = person.getEducationList();
List<Education> collect = educationList.stream().sorted(Comparator.comparing(Education::getGrade).reversed()).collect(Collectors.toList());
person.setEducationList(collect);
facts.put("person", person);
return person.getQualifications() == educationList.size() && collect.get(0).getGrade() == person.getQualifications();
}
@Action
public void set(Facts facts){
Person person = facts.get("person");
person.setDec("学历真实!");
facts.put("person", person);
}
}
EducationAdd:添加学历资质集合
package entity.rules.education;
import entity.pojo.Person;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
import org.jeasy.rules.api.Facts;
import utils.AddEducation;
import java.util.List;
/**
* @author 莫须有
* @Date 2021/12/31 15:22
* @Description 根据学历添加学历集合
*/
@Rule(name = "EducationAdd", description = "添加学历规则匹配")
public class EducationAdd {
@Condition
public boolean educationAdd(@Fact("person") Person person){
return person.getQualifications() >=0 && person.getQualifications() <= 10;
}
@Action
public void add(Facts facts){
Person person = facts.get("person");
List list = AddEducation.getQualification(person);
person.setEducationList(list);
facts.put("person", person);
}
}
QualificationNotMach:不符合要求的学历,即造假学历
package entity.rules.education;
import entity.pojo.Education;
import entity.pojo.Person;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
import org.jeasy.rules.api.Facts;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author 莫须有
* @Date 2021/12/31 17:51
* @Description 学历造假规则匹配
*/
@Rule(name = "QualificationNotMach", description = "学历检测未通过匹配规则")
public class QualificationNotMach {
@Condition
public boolean qualificationNotMach(@Fact("person")Person person, Facts facts){
List<Education> educationList = person.getEducationList();
List<Education> collect = educationList.stream().sorted(Comparator.comparing(Education::getGrade).reversed()).collect(Collectors.toList());
person.setEducationList(collect);
facts.put("person", person);
return !(person.getQualifications() == educationList.size() && collect.get(0).getGrade() == person.getQualifications());
}
@Action
public void add(Facts facts){
Person person = facts.get("person");
person.setDec("存在学历造假的嫌疑!");
facts.put("person", person);
}
}
AddEducation :添加学历资质集合工具类
package utils;
import entity.pojo.Education;
import entity.pojo.Person;
import java.util.ArrayList;
import java.util.List;
/**
* @author 莫须有
* @Date 2021/12/31 14:32
* @Description 添加学历集合
*/
public class AddEducation {
public static List getQualification(Person person){
List qList = new ArrayList();
switch (person.getQualifications()){
case 0:
qList.add(getGrade0());
break;
case 1:
qList.add(getGrade1());
break;
case 2:
qList.add(getGrade1());
qList.add(getGrade3());
break;
case 3:
····
default:
break;
}
return qList;
}
private static Education getGrade0() {
Education Education = new Education();
Education.setGrade(0);
Education.setQualificationsName("文盲");
Education.setSchoolName("无教育经历");
return Education;
}
····
}
测试类:
package test;
import config.MyRulesListener;
import entity.pojo.Person;
import entity.rules.education.CheckIt;
import entity.rules.education.EducationAdd;
import entity.rules.education.QualificationNotMach;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.core.DefaultRulesEngine;
/**
* @author 莫须有
* @Date 2021/12/31 15:11
* @Description 学历规则匹配测试
*/
public class EducationTest {
public static void main(String[] args) {
Person person = new Person();
person.setName("莫须有");
person.setAge(25);
person.setQualifications(6);
Facts facts = new Facts();
facts.put("person", person);
Rules rules = new Rules();
rules.register(new EducationAdd());
rules.register(new CheckIt());
rules.register(new QualificationNotMach());
DefaultRulesEngine engine = new DefaultRulesEngine();
engine.fire(rules, facts);
System.out.println("----------------------------");
System.out.println(person);
}
}
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FkiYzHOc-1642052913427)(E:\suredata\markdown\img\规则类执行结果.png)]
以上时java规则类实现学历检测的代码实现。
自定义规则文件实现
这里加入了一个学历资质11,因为之前是只能匹配到10,由于是测试,所以在学历资质添加类里面也加入了对应11的资质添加代码,如果是真实场景就可以是从数据库查询,就不需要进行修改。
AddEducation :添加学历资质集合工具类(添加11)
package utils;
import entity.pojo.Education;
import entity.pojo.Person;
import java.util.ArrayList;
import java.util.List;
/**
* @author 莫须有
* @Date 2021/12/31 14:32
* @Description 添加学历集合
*/
public class AddEducation {
public static List getQualification(Person person){
List qList = new ArrayList();
switch (person.getQualifications()){
case 0:
qList.add(getGrade0());
break;
case 1:
qList.add(getGrade1());
break;
case 2:
qList.add(getGrade1());
qList.add(getGrade3());
break;
····
default:
}
return qList;
}
private static Education getGrade0() {
Education Education = new Education();
Education.setGrade(0);
Education.setQualificationsName("文盲");
Education.setSchoolName("无教育经历");
return Education;
}
····
}
规则文件(这里与json文件为例,yml文件类似)
EducationAddJsonFile.json
[
{
"name": "newEducationAdd",
"description": "修改学历添加列表",
"condition": "person.getQualifications() >= 0 && person.getQualifications()<=11",
"priority": 3,
"actions": [
"System.out.println(\"新规则执行了\")"
]
}
]
在规则文件中actions是规则执行的内容,由于上面问题2中的问题,所以文件中只是进行condition条件的编写,actions中的逻辑处理放到监听类的afterEvaluate方法中进行处理,但是actions内容不能未空,所以随便写一个打印语句。
监听类 MyRulesListener:
package config;
import entity.pojo.Person;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
import org.jeasy.rules.api.RuleListener;
import utils.AddEducation;
import java.util.List;
/**
* @author 莫须有
* @Date 2021/12/30 18:20
* @Description 规则监听器
*/
public class MyRulesListener implements RuleListener {
@Override
public boolean beforeEvaluate(Rule rule, Facts facts) {
System.out.println("beforeEvaluate 触发");
return true;
}
@Override
public void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult) {
if (evaluationResult && rule.getName().equals(facts.get("ruleName"))){
Person person = facts.get("person");
List list = AddEducation.getQualification(person);
person.setEducationList(list);
facts.put("person", person);
}
System.out.println("afterEvaluate 触发");
}
@Override
public void onEvaluationError(Rule rule, Facts facts, Exception exception) {
System.out.println("onEvaluationError 触发");
}
@Override
public void beforeExecute(Rule rule, Facts facts) {
System.out.println("beforeExecute 触发");
}
@Override
public void onSuccess(Rule rule, Facts facts) {
System.out.println("onSuccess 触发");
}
@Override
public void onFailure(Rule rule, Facts facts, Exception exception) {
System.out.println("onFailure 触发");
}
}
测试类 TestUploudFile:
package test;
import config.MyEngineListener;
import config.MyRulesListener;
import entity.pojo.Person;
import entity.rules.education.CheckIt;
import entity.rules.education.QualificationNotMach;
import lombok.extern.slf4j.Slf4j;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.mvel.MVELRuleFactory;
import org.jeasy.rules.support.reader.JsonRuleDefinitionReader;
import org.jeasy.rules.support.reader.RuleDefinitionReader;
import org.jeasy.rules.support.reader.YamlRuleDefinitionReader;
import java.io.FileReader;
/**
* @author 莫须有
* @Date 2022/1/10 9:46
* @Description 测试文件上传
*/
@Slf4j
public class TestUploudFile {
public static void main(String[] args) {
// MVELRuleFactory factory = new MVELRuleFactory(new YamlRuleDefinitionReader());
MVELRuleFactory factory = new MVELRuleFactory(new JsonRuleDefinitionReader());
// 读取json文件
String path = JsonTest.class.getResource("/rules/EducationAddJsonFile.json").getPath();
// String path = JsonTest.class.getResource("/rules/EducationAddYmlFile.yml").getPath();
try {
FileReader jsonFile = new FileReader(path);
Rule rule = factory.createRule(jsonFile);
String ruleName = rule.getName();
Rules rules = new Rules();
rules.register(rule);
rules.register(new CheckIt());
rules.register(new QualificationNotMach());
Person person = new Person();
person.setName("莫须有");
person.setAge(25);
person.setQualifications(11);
Facts facts = new Facts();
facts.put("person", person);
facts.put("ruleName", ruleName);
DefaultRulesEngine engine = new DefaultRulesEngine();
engine.registerRuleListener(new MyRulesListener());
engine.fire(rules, facts);
System.out.println(person);
}catch (Exception e){
e.printStackTrace();
}
}
}
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8A9NSyRL-1642052913428)(E:\suredata\markdown\img\json规则文件执行结果.png)]
以上是自定义规则文件执行规则实现。
条件语句调用方法处理
以上内容完成后,在老大的引导下对自定义文件中condition内容进行探讨,是否可以调用方法来进行判断,测试后发现,可以调用方法进行判断,但是对调用方法的位置有要求,方法只能是facts对象的参数对象内部定义的方法,也就是说在上述例子中,只能使用person中定义的方法,后续测试中尝试了通过person中的方法在调用外部方法也是可以的,所以虽然condition中需要强依赖于facts中的内容,但是可以通过facts中的对象调用其他外部方法实现规则判断。
测试代码
json文件:
[{
"name": "newEducationAdd",
"description": "修改学历添加列表",
"condition": "person.getResult(person.getQualifications())",
"priority": 3,
"actions": [
"System.out.println(\"新规则执行了\")"
]
}]
person类
package entity.pojo;
import entity.rules.education.EducationAdd;
import lombok.Data;
import utils.AddEducation;
import java.util.List;
/**
* @author 莫须有
* @Date 2021/12/31 13:59
* @Description
*/
@Data
public class Person {
// 姓名
private String name;
// 年龄
private int age;
// 描述
private String dec;
// 学历等级
private int qualifications;
private List<Education> educationList;
public boolean getResult(int i){
return AddEducation.getResult(i);
// return i >=0 && i <= 11;
}
}
AddEducation类中添加getResult方法:
/**
* @author 莫须有
* @Date 2021/12/31 14:32
* @Description 添加学历集合
*/
public class AddEducation {
public static boolean getResult(int i){
System.out.println("111111");
return i >= 0 && i <= 11;
}
}
启动测试类:
package test;
import config.MyEngineListener;
import config.MyRulesListener;
import entity.pojo.Person;
import entity.rules.education.CheckIt;
import entity.rules.education.QualificationNotMach;
import lombok.extern.slf4j.Slf4j;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.mvel.MVELRuleFactory;
import org.jeasy.rules.support.reader.JsonRuleDefinitionReader;
import org.jeasy.rules.support.reader.RuleDefinitionReader;
import org.jeasy.rules.support.reader.YamlRuleDefinitionReader;
import java.io.FileReader;
/**
* @author 莫须有
* @Date 2022/1/10 9:46
* @Description 测试文件上传
*/
@Slf4j
public class TestUploudFile {
public static void main(String[] args) {
// MVELRuleFactory factory = new MVELRuleFactory(new YamlRuleDefinitionReader());
MVELRuleFactory factory = new MVELRuleFactory(new JsonRuleDefinitionReader());
// 读取json文件
String path = JsonTest.class.getResource("/rules/EducationAddJsonFile.json").getPath();
// String path = YmlTest.class.getResource("/rules/EducationAddYmlFile.yml").getPath();
try {
FileReader jsonFile = new FileReader(path);
Rule rule = factory.createRule(jsonFile);
String ruleName = rule.getName();
Rules rules = new Rules();
rules.register(rule);
rules.register(new CheckIt());
rules.register(new QualificationNotMach());
Person person = new Person();
person.setName("莫须有");
person.setAge(25);
person.setQualifications(11);
Facts facts = new Facts();
facts.put("person", person);
facts.put("ruleName", ruleName);
DefaultRulesEngine engine = new DefaultRulesEngine();
engine.registerRuleListener(new MyRulesListener());
engine.fire(rules, facts);
System.out.println(person);
}catch (Exception e){
e.printStackTrace();
}
}
}
总结
easyrules自定义规则通过json和yml文件来实现,可以适应变化的需求,有效避免重复修改代码逻辑,是一个很好的if-else替代品;并且它的condition判断可以通过调用方法来进行处理,这对业务的可扩展性也有很大的帮助,后续可以将规则存入数据库,存储内容可以以json字符串格式存储,只是在使用规则的时候需要将规则字符串转换为json文件,需要注意的是,json文件中的规则需要以数组形式存放,无论是一个还是多个。