场景描述:
需要判断的规则有上千上万个或者更多。
举个例子:法律案件或者交通法规,像这类场景,每一个法条都相当于是一个条件。(如:是什么原因导致违章的?是闯了红灯啊,还是超速了呀还是超载了呀等等等)各种if-else或者switch-case判断。
这些是已有的规则判断,好说,弄个Excel表格或者插入到库中,一次性读出来,用kie提供的方式直接用就行了。但是如果后期有新增怎么办?
本文只分享新增的场景解决的方案。更新和删除部分目前还没找解决方法。
pom文件
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.2.5.RELEASE
com.***.drools
rule-engine
1.0.0
rule-engine
Demo project for Spring Boot
1.8
7.33.0.Final
javax.inject
javax.inject
1
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.2
mysql
mysql-connector-java
8.0.19
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.kie
kie-api
${drools.version}
org.drools
drools-core
${drools.version}
org.drools
drools-compiler
${drools.version}
org.drools
drools-decisiontables
${drools.version}
org.drools
drools-templates
${drools.version}
org.kie
kie-internal
${drools.version}
org.mvel
mvel2
2.4.4.Final
com.thoughtworks.xstream
xstream
1.4.11.1
org.slf4j
slf4j-api
org.springframework.boot
spring-boot-maven-plugin
实现思路:
1. 使用KieHelper来build()。
故此KieHelper要注册成一个Bean。能保持单利,保证后期动态编译的时候不会丢失已编译过的规则。
@Bean
public KieHelper kieHelper() {
return new KieHelper();
}
DataObject-实体封装类
@Data
public class DataObject {
private String ly;
}
RuleTemplate.java--规则封装类
public class RuleTemplate {
private String template;
public RuleTemplate(String template) {
this.template = template;
}
public String getTemplate() {
return template;
}
public void setTemplate(String template) {
this.template = template;
}
}
RuleTemplate的构造器里的参数其实就是Drl文件的字符串形式。根据自己的业务去拼。我这块是直接去读的库。
@Bean
public RuleTemplate ruleTemplate() {
RuleTemplate ruleTemplate = new RuleTemplate(CreateRule.getInstance().ruleTemplate(IRulesService.queryAll()));
return ruleTemplate;
}
2. 容器初始化的时候去读取已有的规则数据(数据库或者Excel都行),我是直接读的库。
2.1 查完拼接成规则字符串(拼完后可以弄到drl文件里试试规则生成的是否有问题)。
2.2 使用kieHelper来添加已经拼好的rule字符串。然后在build。
@Bean
@PostConstruct
public KieBase kieBase() {
//动态加载的核心实现方式
kieHelper.addContent(ruleTemplate.getTemplate(), ResourceType.DRL);//添加规则
KieBase kieBase = kieHelper.build();//手动编译规则
return kieBase;
}
到这块是项目启动阶段完成的,预加载已知的规则。
以下部分是项目运行阶段来动态的去添加规则后编译并查找。
1. 新规则字符串。
String test = "package com.pkulaw.drools.rules;\n" +
"\n" +
"import com.pkulaw.drools.entity.DataObject\n" +
"\n" +
"global java.util.List lyList;\n" +
"\n" +
"rule \"ly-rule-0000\"\n" +
"agenda-group \"ly-group\"\n" +
"dialect \"java\"\n" +
"when\n" +
" d : DataObject(ly == \"测试\")\n" +
"then\n" +
" lyList.add(\"这是测试\"); \n" +
"end";
2. KieHelper来添加和编译。
kieHelper.addContent(ruleContent, ResourceType.DRL);
//重编译
KieBase kieBase1 = kieHelper.build();
//重新注册单利
beanRegister(kieBase1);
重新注册单利的原因是每次kieHelper去build的时候会重新生成一个KieBase,而KieBase是去获取session进行fact匹配的关键。故此,要去替换Spring容器中的bean的实例。
3.1.1 替换单利bean(Deprecated)-针对Kiebase
//这个方式不够优雅,但也不是不可行,故此留着这段。
public synchronized void beanRegister(KieBase newKBase) {
DefaultListableBeanFactory factory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
factory.removeBeanDefinition("kieBase");
BeanDefinitionBuilder beanDefinitionBuilder =
BeanDefinitionBuilder.genericBeanDefinition(newKBase.getClass().getName());
// get the BeanDefinition
BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
factory.registerBeanDefinition("kieBase", beanDefinition);
factory.registerSingleton("kieBase", newKBase);
}
3.1.2 替换KieBase
上面对直接Spring容器的bean替换不够好,死脑筋没转过来。群友一提醒立马醒悟。
public class KieBaseTemplate {
private KieBase kieBase;
public KieBaseTemplate(KieBase kieBase) {
this.kieBase = kieBase;
}
public KieBase getKieBase() {
return kieBase;
}
public void update(KieBase kieBase) {
this.kieBase = kieBase;
}
}
原来是Kiebase注册成bean现在改成把KieBaseTemplate 注册成bean。
@Bean
@PostConstruct
public KieBaseTemplate kieBaseTemplate() {
kieHelper.addContent(ruleTemplate.getTemplate(), ResourceType.DRL);
KieBase kieBase = kieHelper.build();
return new KieBaseTemplate(kieBase);
}
每次新增规则后build的时候把生成的kieBase通过update的方式重新替换掉就好了。
查找的时候通过kieBaseTemplate的getKieBase方法获取KieBase。
(有时候换个思路去思考能写出更优雅的代码来,这一段就比直接操作Bean优雅多了)
以下是查找测试代码
KieSession kSession = kieBaseTemplate.getKieBase().newKieSession();
DataObject obj1 = new DataObject();
obj1.setLy("测试");
List lyList = new ArrayList();
kSession.setGlobal("lyList", lyList);
kSession.insert(obj1);
kSession.fireAllRules();
System.out.println(lyList);
总结
这段时间由于公司需求去研究一下这个工具,基础知识阅读官方文档即可,实现方式有多重,官网介绍有InternalKnowledgeBase来实现的,还有一种是KieBuilder来实现的。KieHelper是我在GitHub(他的地址)上借鉴某位大佬的实现方式所得到的启发。
欢迎评论和批评,菜鸟一枚,有错误及时修正,以免误导他人。