场景描述:
需要判断的规则有上千上万个或者更多。
举个例子:法律案件或者交通法规,像这类场景,每一个法条都相当于是一个条件。(如:是什么原因导致违章的?是闯了红灯啊,还是超速了呀还是超载了呀等等等)各种if-else或者switch-case判断。
这些是已有的规则判断,好说,弄个Excel表格或者插入到库中,一次性读出来,用kie提供的方式直接用就行了。但是如果后期有新增怎么办?
本文只分享新增的场景解决的方案。更新和删除部分目前还没找解决方法。
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.***.drools</groupId>
<artifactId>rule-engine</artifactId>
<version>1.0.0</version>
<name>rule-engine</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<drools.version>7.33.0.Final</drools.version>
</properties>
<dependencies>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--kie-->
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-templates</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-internal</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.mvel</groupId>
<artifactId>mvel2</artifactId>
<version>2.4.4.Final</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.11.1</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
实现思路:
- 使用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;
}
- 容器初始化的时候去读取已有的规则数据(数据库或者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;
}
到这块是项目启动阶段完成的,预加载已知的规则。
以下部分是项目运行阶段来动态的去添加规则后编译并查找。
- 新规则字符串。
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";
- 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(他的连接)上借鉴某位大佬的实现方式所得到的启发。
欢迎评论和批评,菜鸟一枚,有错误及时修正,以免误导他人。