参考:规则引擎 Drools:决策表_jueyinga的博客-CSDN博客_drools 决策表
一、规则引擎 Drools:决策表
Drools除了支持drl形式的文件外还支持xls格式的文件(即Excel文件)。这种xls格式的文件通常称为决策表(decision table)。
决策表(decision table)是一个“精确而紧凑的”表示条件逻辑的方式,非常适合商业级别的规则。决策表与现有的drl文件可以无缝替换。Drools提供了相应的API可以将xls文件编译为drl格式的字符串。
1.1 决策表:
决策表语法:
关键字 | 说明 | 是否必须 |
---|---|---|
RuleSet | 相当于drl文件中的package | 必须,只能有一个。如果没有设置RuleSet对应的值则使用默认值rule_table |
Sequential | 取值为Boolean类型。true表示规则按照表格自上到下的顺序执行,false表示乱序 | 可选 |
Import | 相当于drl文件中的import,如果引入多个类则类之间用逗号分隔 | 可选 |
Variables | 相当于drl文件中的global,用于定义全局变量,如果有多个全局变量则中间用逗号分隔 | 可选 |
RuleTable | 它指示了后面将会有一批rule,RuleTable的名称将会作为以后生成rule的前缀 | 必须 |
CONDITION | 规则条件关键字,相当于drl文件中的when。下面两行则表示 LHS 部分,第三行则为注释行,不计为规则部分,从第四行开始,每一行表示一条规则 | 每个规则表至少有一个 |
ACTION | 规则结果关键字,相当于drl文件中的then | 每个规则表至少有一个 |
NO-LOOP | 相当于drl文件中的no-loop | 可选 |
AGENDA-GROUP | 相当于drl文件中的agenda-group | 可选 |
在决策表中还经常使用到占位符,语法为$后面加数字,用于替换每条规则中设置的具体值。
上面的决策表例子转换为drl格式的规则文件内容如下:
package rules.excels;
//generated from Decision Table
import com.ppl.demo.entity.Account;
import java.util.List;
import java.util.ArrayList;
global List<String> list;
// rule values at B11, header at B6
rule "ExcelTable_11"
salience 65535
agenda-group "rule-group-001"
when
$account: Account(sex != "女")
then
list.add("性别不对");
end
// rule values at B12, header at B6
rule "ExcelTable_12"
salience 65534
agenda-group "rule-group-001"
when
$account: Account(age < 22 || age> 28)
then
list.add("年龄不合适");
end
// rule values at B13, header at B6
rule "ExcelTable_13"
salience 65533
agenda-group "rule-group-002"
when
$account: Account(balance < 1000)
then
list.add("工资太低");
end
Drools提供的将xls文件编译为drl格式字符串的API如下:
@DisplayName("Excel To Rule")
@Test
public void testExceltoRule() {
String realPath = "D:\\IDEA_Work\\excelrule001.xls";//指定决策表xls文件的磁盘路径
File file = new File(realPath);
InputStream is = null;
try {
is = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
SpreadsheetCompiler compiler = new SpreadsheetCompiler();
String drl = compiler.compile(is, InputType.XLS);
System.out.println(drl);
}
Drools还提供了基于drl格式字符串创建KieSession的API:
KieHelper kieHelper = new KieHelper();
kieHelper.addContent(drl, ResourceType.DRL);
KieSession kieSession = kieHelper.build().newKieSession();
1.2 相关代码
package com.ppl.demo.entity;
public class Account {
private long accountno;
private double balance;
private String sex;
private int age;
public Account(long accountno, double balance) {
super();
this.accountno = accountno;
this.balance = balance;
}
public Account() {
super();
// TODO Auto-generated constructor stub
}
public long getAccountno() {
return accountno;
}
public void setAccountno(long accountno) {
this.accountno = accountno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Account{" +
"accountno=" + accountno +
", balance=" + balance +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
测试类:
package com.ppl.demo;
import com.ppl.demo.entity.Account;
import com.ppl.demo.utils.KnowledgeSessionHelper;
import org.drools.decisiontable.InputType;
import org.drools.decisiontable.SpreadsheetCompiler;
import org.junit.jupiter.api.*;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.StatelessKieSession;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
@DisplayName("junit5功能测试")
class RuleExcelsTests {
static KieContainer kieContainer;
StatelessKieSession sessionStateless = null;
KieSession sessionStatefull = null;
@BeforeEach
void testBeforeEach() {
kieContainer = KnowledgeSessionHelper.createRuleBase();
System.out.println("测试就要开始。。。");
}
@AfterEach
void testAfterEach() {
System.out.println("测试就要结束。。。");
}
@BeforeAll
static void testBeforeAll() {
System.out.println("所有测试就要开始。。。");
}
@AfterAll
static void testAfterAll() {
System.out.println("所有测试已经结束。。。");
}
@DisplayName("Excel Rule")
@Test
public void testExcelRule() {
sessionStatefull = KnowledgeSessionHelper
.getStatefulKnowledgeSession(kieContainer, "lesson5-session");
//设置全局变量,名称和类型必须和规则文件中定义的全局变量名称对应
List<String> list = new ArrayList();
sessionStatefull.setGlobal("list",list);
Account account = new Account();
account.setBalance(300D);
account.setAge(35);
account.setSex("男");
sessionStatefull.insert(account);
sessionStatefull.getAgenda().getAgendaGroup("rule-group-001").setFocus();
int count=sessionStatefull.fireAllRules();
//激活规则引擎,如果规则匹配成功则执行规则
System.out.println("用户balance:"+account.getBalance());
System.out.println("list:"+list);
//关闭会话
System.out.println("总共执行了: "+count+" 条规则");
sessionStatefull.dispose();
}
@DisplayName("Excel To Rule")
@Test
public void testExceltoRule() {
String realPath = "D:\\IDEA_Work\\excelrule001.xls";//指定决策表xls文件的磁盘路径
File file = new File(realPath);
InputStream is = null;
try {
is = new FileInputStream(file);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
SpreadsheetCompiler compiler = new SpreadsheetCompiler();
String drl = compiler.compile(is, InputType.XLS);
System.out.println(drl);
}
}
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.6.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ppl</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<drools.version>7.73.0.Final</drools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!--drools规则引擎-->
<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-templates</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
agenda-group关键字说明
没有被focus的规则分组会运行规则条件,不运行规则结果。
详细说明可能遇到情况,如果一个kieSession有多个规则,每个规则都包含agenda-group关键字,且值不完全一样会出现以下的情况,auto-focus没有配置的情况。
直接fireAllRules,所有规则会运行一遍条件,也就是when到then中间的部分,不会运行结果,then后的部分。
focus某一个agenda-group,所有规则会运行一遍条件,也就是when到then中间的部分,只运行focus规则的结果部分。
我的处理方案:将不同agenda-group的规则放在不同的KieBase中,保证每一个KieSession中只有一个agenda-group的数据。因为我的条件部分调用了很多工具类,如果都运行一遍会影响效率。
其他示例:
决策表转换成drl文件代码:
package rules.decision.tables;
//generated from Decision Table
import java.lang.StringBuilder;
import com.ppl.demo.entity.Student;
global java.lang.StringBuilder resultsInfo;
// rule values at B15, header at B10
rule "student-score-name-1"
/* 1、姓名为张三的特殊处理
2、自定义规则的名字 */
salience 65535
activation-group "score"
when
$stu: Student(name == "张三")
then
resultsInfo.append("张三特殊处理:");
System.out.println("规则:" + drools.getRule().getName() + " 执行了.");
resultsInfo.append("优");
System.out.println("规则:" + drools.getRule().getName() + " 执行了.");
end
// rule values at B16, header at B10
rule "student-score_16"
salience 65534
activation-group "score"
when
$stu: Student(name == "李四", score > 0 && score < 60)
then
resultsInfo.append("李四部分特殊处理:");
System.out.println("规则:" + drools.getRule().getName() + " 执行了.");
resultsInfo.append("一般");
System.out.println("规则:" + drools.getRule().getName() + " 执行了.");
end
// rule values at B17, header at B10
rule "student-score_17"
salience 65533
activation-group "score"
when
$stu: Student(score > 0 && score < 60)
then
resultsInfo.append("不及格");
System.out.println("规则:" + drools.getRule().getName() + " 执行了.");
end
// rule values at B18, header at B10
rule "student-score_18"
salience 65532
activation-group "score"
when
$stu: Student(score > 60 && score < 70)
then
resultsInfo.append("一般");
System.out.println("规则:" + drools.getRule().getName() + " 执行了.");
end
// rule values at B19, header at B10
rule "student-score_19"
salience 65531
activation-group "score"
when
$stu: Student(score > 70 && score < 90)
then
resultsInfo.append("良好");
System.out.println("规则:" + drools.getRule().getName() + " 执行了.");
end
// rule values at B20, header at B10
rule "student-score_20"
salience 65530
activation-group "score"
when
$stu: Student(score > 90 && score < 100)
then
resultsInfo.append("优");
System.out.println("规则:" + drools.getRule().getName() + " 执行了.");
end
测试代码
@DisplayName("Excel decision.tables")
@Test
public void testExcelDecisionTables() {
// 张三虽然只得20分,但是根据规则判断,结果应该是 优
invokedDecisionTable(new Student("张三", 20));
// 李四虽然只得20分,但是根据规则判断,结果应该是 一般
invokedDecisionTable( new Student("李四", 20));
// 李四得75分,但是根据规则判断,结果应该是 良好
invokedDecisionTable( new Student("李四", 75));
// 王五得59分,但是根据规则判断,结果应该是 不及格
invokedDecisionTable( new Student("王五", 59));
// 赵六得20分,但是根据规则判断,结果应该是 一般
invokedDecisionTable( new Student("赵六", 65));
// 钱七得20分,但是根据规则判断,结果应该是 良好
invokedDecisionTable( new Student("钱七", 75));
// 李八得20分,但是根据规则判断,结果应该是 优
invokedDecisionTable(new Student("李八", 95));
}
public void invokedDecisionTable(Student student) {
System.out.println("\r");
sessionStatefull = KnowledgeSessionHelper
.getStatefulKnowledgeSession(kieContainer, "lesson6-session");
StringBuilder result = new StringBuilder();
sessionStatefull.setGlobal("resultsInfo", result);
sessionStatefull.insert(student);
int count=sessionStatefull.fireAllRules();
//激活规则引擎,如果规则匹配成功则执行规则
System.out.println("总共执行了: "+count+" 条规则");
sessionStatefull.dispose();
System.out.println("规则执行结果:" + result);
}
打印日志如下:
规则:student-score-name-1 执行了.
规则:student-score-name-1 执行了.
总共执行了: 1 条规则
规则执行结果:张三特殊处理:优
规则:student-score_16 执行了.
规则:student-score_16 执行了.
总共执行了: 1 条规则
规则执行结果:李四部分特殊处理:一般
规则:student-score_19 执行了.
总共执行了: 1 条规则
规则执行结果:良好
规则:student-score_17 执行了.
总共执行了: 1 条规则
规则执行结果:不及格
规则:student-score_18 执行了.
总共执行了: 1 条规则
规则执行结果:一般
规则:student-score_19 执行了.
总共执行了: 1 条规则
规则执行结果:良好
规则:student-score_20 执行了.
总共执行了: 1 条规则
规则执行结果:优