参考:规则引擎 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 65535agenda-group "rule-group-001"when$account: Account(sex != "女")thenlist.add("性别不对");
end// rule values at B12, header at B6
rule "ExcelTable_12"salience 65534agenda-group "rule-group-001"when$account: Account(age < 22 || age> 28)thenlist.add("年龄不合适");
end// rule values at B13, header at B6
rule "ExcelTable_13"salience 65533agenda-group "rule-group-002"when$account: Account(balance < 1000)thenlist.add("工资太低");
end
Drools提供的将xls文件编译为drl格式字符串的API如下:
@DisplayName("Excel To Rule")@Testpublic 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;}@Overridepublic 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;@BeforeEachvoid testBeforeEach() {kieContainer = KnowledgeSessionHelper.createRuleBase();System.out.println("测试就要开始。。。");}@AfterEachvoid testAfterEach() {System.out.println("测试就要结束。。。");}@BeforeAllstatic void testBeforeAll() {System.out.println("所有测试就要开始。。。");}@AfterAllstatic void testAfterAll() {System.out.println("所有测试已经结束。。。");}@DisplayName("Excel Rule")@Testpublic 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")@Testpublic 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 65535activation-group "score"when$stu: Student(name == "张三")thenresultsInfo.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 65534activation-group "score"when$stu: Student(name == "李四", score > 0 && score < 60)thenresultsInfo.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 65533activation-group "score"when$stu: Student(score > 0 && score < 60)thenresultsInfo.append("不及格");
System.out.println("规则:" + drools.getRule().getName() + " 执行了.");
end// rule values at B18, header at B10
rule "student-score_18"salience 65532activation-group "score"when$stu: Student(score > 60 && score < 70)thenresultsInfo.append("一般");
System.out.println("规则:" + drools.getRule().getName() + " 执行了.");
end// rule values at B19, header at B10
rule "student-score_19"salience 65531activation-group "score"when$stu: Student(score > 70 && score < 90)thenresultsInfo.append("良好");
System.out.println("规则:" + drools.getRule().getName() + " 执行了.");
end// rule values at B20, header at B10
rule "student-score_20"salience 65530activation-group "score"when$stu: Student(score > 90 && score < 100)thenresultsInfo.append("优");
System.out.println("规则:" + drools.getRule().getName() + " 执行了.");
end
测试代码
@DisplayName("Excel decision.tables")@Testpublic 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 条规则
规则执行结果:优