1、DRools介绍
官网:https://www.drools.org/
规则引擎主要完成的就是将业务规则从代码中分离出来。
DRools一款由JBoss组织提供的基于Java语言开发的开源规则引擎,目前由 Redhat 开源的规则引擎,它是 Redhat 的 KIE Group 中的组件之一,可以比较方便的跟另一个组件 JBPM 工作流配合用于管理复杂的规则流;同时 Drools 的推理策略算法在经典 Rete 算法以及其它算法的基础上做了多个版本的增强。DRools是一个具有基于前向链接和后向链接推理的规则引擎,允许快速和可靠地评估业务规则和复杂事件处理。
关于规则引擎的说明和对比可以参考另一篇文章:规则引擎
2、DRools API开发流程
Drools API开发步骤:
- 获取KieServices
- 获取KieContainer
- 获取KieSession
- 插入Fact对象
- 触发规则(规则需事先编写,如DRL文件或规则表等)
- 关闭KieSession
//1.获取KieServices
KieServices kieServices = KieServices.get();
//2.获取Kie容器对象
KieContainer kieContainer = kieServices.getKieClasspathContainer();
//3.从kie容器对象中获取会话对象
KieSession session = kieContainer.newKieSession();
//4.创建Fact对象(事实对象)
FactExample fact = new FactExample();
fact .setTest("test");
//5.将Fact对象插入到工作内存中去
session.insert(fact);
//6.激活规则,由drools框架自动进行规则匹配,如果匹配成功,则执行规则
session.fireAllRules();
//7.关闭session
session.dispose();
2.1、规则文件编码规范(建议)
- 所有的规则文件(.drl)应统一放在一个规定的文件夹汇总,如:/rules文件夹
- 书写的每一个规则应该尽量加上注释
- 同一类型的对象尽量放在一个规则文件中,如所有的Student类型的对象尽量放在一个规则文件中
- 每个规则最好都加上salience属性,明确执行顺序
- Drools默认dialect为"java",尽量避免使用dialect "mvel"
3、DRools主要概念
3.1、规则引擎的构成
- working Memory(工作内存)
- Rule Base(规则库)
- Inference Engine(推理引擎):Pattern Matcher(匹配器)、Agenda(议程)、Execution Engine(执行引擎)
3.2、规则引擎执行过程
- 将初始数据(fact)输入至工作内存(working Memory)
- 使用匹配器(Pattern Matcher)将规则库中的规则(rule)和数据(fact)匹配,匹配成功的放入到议程(Agenda)中
- 如果执行规则存在冲突,即同时激活了多个规则,将冲突的规则放入冲突集合(冲突是同时匹配了多条规则,不需要自己处理,drools会自动处理,也可以通过主动声明的方式限制只命中一条规则)
- 解决冲突,将激活的规则按顺序放入议程(Agenda)
- 执行议程(Agenda)中的规则,重复2-4,直到执行完毕议程中的所有规则
3.3、规则文件
3.3.1、规则文件的构成
- package:包名,package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问
- import:用于导入类或者静态方法
- global:全局变量
- function:自定义函数
- query:查询
- rule end:规则体
3.3.2、规则体(rule)的基本语法
rule "ruleName" //rule关键字,表示规则开始,参数为规则的唯一名称
attributes //规则属性,是rule与when之间的参数,为可选项
when //关键字,后面是规则的条件部分
LHS //Left Hand Side,是规则的条件部分
then //后面跟规则的结果部分
RHS //是规则的结果或行为
end //表示一个规则的结束
3.3.3、规则体的属性
- salience:指定规则执行的优先级,用数字表示,数字越大优先级越高。如果不设置,默认从上到下执行
- dialect:指定规则使用的语言类型,java或mvel
- enabled:指定规则是否启用,取值为ture或false,默认为true
- date-effective:指定规则生效的时间
- date-expires:指定规则失效的时间
- timer:定时器,指定规则触发的时间
- activation-group:激活分组,同一个组内只能有一个规则触发
- agenda-group:议程分组,只有获取焦点的组中的规则才有可能触发。在java代码中获得焦点。
session.getAgenda().getAgendaGroup("agenda_group_name").setFocus;
- auto-focus:自动获取焦点,一般结合agenda-group使用,取值为ture或false
- no-loop:防止死循环,当规则使用update之类的函数修改了Fact对象时,使当前规则再次被激活从而导致死循环。
3.4、DRools的基本语法
- 运算符:> < >= <= == !=
- contains:检查一个Fact对象的某个属性值是否包含一个指定的对象值
- not contains:检查一个Fact对象的某个属性值是否不包含一个指定的对象值
- memberOf:判断一个Fact对象的某个属性是否在一个或多个集合中
- not memberOf:判断一个Fact对象的某个属性是否不在一个或多个集合中
- matches:判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配
- not matcher:判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配
3.5、DRools内置方法
- update方法:更新工作内存中的数据,并让相关的规则重新匹配。更新数据时,要注意防止陷入死循环。
- insert方法:向工作内存中插入数据,并让相关的规则重新匹配。
- retract方法:删除工作内存中的数据,并让相关的规则重新匹配。
3.6、DRools高级语法
3.6.1、global全局变量
global关键字用于在规则文件中定义全局变量,它可以让应用程序的对象在规则文件中能够被访问。可以用来为规则文件提供数据或服务。语法结构:global 对象类型 对象名称。
注意事项:如果对象类型为包装类时,在一个规则中改变了global的值,那么只针对当前规则有效,对其他规则中的global不会有影响。如果对象类型为集合类型或者javaBean时,在一个规则中改变了global的值,对java代码和所有规则都有效。
3.6.2、query查询
query查询提供了一种查询working memory中符合约束条件的Fact对象的简单方法。它仅包含规则文件中的LHS部分,不用指定"when"和"then"部分并且以end结束。具体语法结构如下:
query 查询的名称(可选参数)
LHS
end
3.6.3、function方法
function关键字用于在规则文件中定义函数,规则体中可以调用定义的function函数。使用函数的好处是可以将业务逻辑集中放置在一个地方,根据需要可以对函数进行修改。
function 返回值类型 函数名(可选参数){
//逻辑代码
}
3.6.4、LHS语法
1. 复合值限制in/not in
复合值限制是指超过一种匹配值的限制条件,类似于SQL语句中的in关键字。
$s:Student(name in ("张三","李四","王五"))
$s:Student(name not in ("张三","李四","王五"))
2. 条件元素eval
eval用于规则体的LHS部分,并返回一个Boolean类型的值。语法结构:eval(表达式)
eval(1==1)
3. 条件元素not
not用于判断working memory中是否存在某个Fact对象,如果不存在则返回true,存在返回true。
not Student(age<10)
4. 条件元素exists
exists与not相反,用于判断working memory中是否存在某个Fact对象,如果存在则返回true,不存在返回true。
exists Student(age<10)
5. 规则继承
规则之间使用extends关键字进行规则部分的继承,类似于java类之间的继承。
rule "rule_1"
when
Student(age>10)
then
System.out.println("rule_1触发");
end
rule "rule_2" extends "rule_1" //继承上面的规则
when
Student(age<20) //此处虽然只有一个条件,但继承上面的条件,所以此处的条件是10<age<20
then
System.out.println("rule_2触发");
end
3.6.5、RHS语法
RHS部分需要进行业务处理。在RHS部分Drools提供了一个内置对象,名称是drools,介绍几个drools对象的方法。
1. halt
halt方法的作用是立即终止后面所有规则的执行。
rule "rule_1"
when
Student(age>10)
then
System.out.println("rule_1触发");
drools.halt(); //后面的规则不再执行
end
rule "rule_2"
when
Student(age<20)
then
System.out.println("rule_2触发");
end
2. getWorkingMemory
getRule方法的作用是返回工作内存中的对象。
rule "rule_getWorkingMemory"
when
Student(age>10)
then
System.out.println(drools.getWorkingMemory());
end
3.getRule
getRule方法的作用是返回规则对象。
rule "rule_getRule"
when
Student(age>10)
then
System.out.println(drools.getRule());
end
4、Spring Boot整合DRools
实现一个产品佣金规则计算逻辑,满足项目的规则触发条件则按照相应公式计算佣金,并返回结果。
例子业务逻辑简介:
- 创建maven工程并配置pom.xml
- 创建resources/rules/settlement.drl文件
- 创建domainn/ProjectRule***.java实体类
- 编写配置类config/DroolsAutoConfiguration.java(例子做了简化)
- 创建ProjectService.java
具体代码如下:
- 构建Spring Boot基础项目,可通过:https://start.spring.io/
- Maven引入依赖
<?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.5.3</version>
</parent>
<groupId>com.xxx</groupId>
<artifactId>drools-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>drools-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<drools.version>7.57.0.Final</drools.version>
</properties>
<dependencies>
//... Spring Boot 依赖
<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-api</artifactId>
<version>${drools.version}</version>
</dependency>
</dependencies>
</project>
- 编写规则文件DRL
//package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问
package com.xxx;
import com.xxx.droolsdemo.domain.ProjectRuleWhenCondition
import com.xxx.droolsdemo.domain.ProjectRuleWhenValue
import com.xxx.droolsdemo.domain.ProjectRuleThenCondition
import com.xxx.droolsdemo.domain.ProjectRuleThenResult
import java.math.BigDecimal
import com.sun.javafx.binding.StringFormatter
import java.util.List
import java.util.ArrayList
// TODO:function定义说明
//function 返回值类型 函数名(可选参数){
// //逻辑代码
//}
function Boolean inList(List targetList, Object value){
Boolean flag = false;
for(Object obj : targetList){
System.out.println("inList" + obj);
if(obj.equals(value)){
flag = true;
break;
}
}
return flag;
}
rule "product rule"
salience 1 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
$thenCondition:ProjectRuleThenCondition();
$thenResult:ProjectRuleThenResult();
$whenCondition:ProjectRuleWhenCondition();
$whenValue : ProjectRuleWhenValue(
//inList($whenCondition.getPropertyTypes(),propertyType) && //户型
propertyType memberOf $whenCondition.getPropertyTypes() &&
area > 0 && area<$whenCondition.getArea() && //面积区间
dealCount>=$whenCondition.getDealCount() ); //成交总套数
then
if($thenCondition.getIsServiceFee()){
$thenResult.setServiceFeeAmount($thenCondition.getReturnAmount().multiply($thenCondition.getReturnRatio()));
}
else{
$thenResult.setServiceFeeAmount(new BigDecimal(10086));
}
System.out.println(drools.getRule().getName() +"被执行");
end;
- 构建实体
- ProjectRuleWhenCondition:规则When条件类
- ProjectRuleWhenValue:规则When Fact值类
- ProjectRuleThenCondition:规则Then条件类
- ProjectRuleThenResult:规则Then结果类
/**
* 项目规则-When条件
*/
@Data
public class ProjectRuleWhenCondition {
/**
* 物业类型
*/
private List<String> propertyTypes;
/**
* 面积
*/
private BigDecimal area;
/**
* 成交总套数
*/
private Integer dealCount;
}
/**
* 项目规则-When值
*/
@Data
public class ProjectRuleWhenValue {
/**
* 物业类型
*/
private String propertyType;
/**
* 面积
*/
private BigDecimal area;
/**
* 成交总套数
*/
private Integer dealCount;
}
/**
* 项目规则-Then条件
*/
@Data
public class ProjectRuleThenCondition {
/**
* 是否服务费
*/
private Boolean isServiceFee;
/**
* 回款金额
*/
private BigDecimal returnAmount;
/**
* 回款比率
*/
private BigDecimal returnRatio;
}
/**
* 项目规则-When结果
*/
@Data
public class ProjectRuleThenResult {
/**
* 服务费(计算得出)
*/
private BigDecimal serviceFeeAmount;
}
- 构建ProjectService,实现触发逻辑
public class ProjectService {
private KieContainer kieContainer;
public ProjectService() {
init();
}
public void reload() {
init();
}
public void run() {
//1. 构建when参数
ProjectRuleWhenCondition whenCondition = new ProjectRuleWhenCondition();
whenCondition.setPropertyTypes(Arrays.asList("住宅", "公寓", "别墅"));
whenCondition.setArea(new BigDecimal(200));
whenCondition.setDealCount(1);
ProjectRuleWhenValue
whenValue = new ProjectRuleWhenValue();
whenValue.setPropertyType("住宅");
whenValue.setArea(new BigDecimal(80));
whenValue.setUnitType("三房");
whenValue.setDealCount(2);
whenValue.setDealTotalAmount(new BigDecimal(5000));
//2. 构建then参数
ProjectRuleThenCondition thenCondition = new ProjectRuleThenCondition();
thenCondition.setIsServiceFee(true);
thenCondition.setReturnAmount(new BigDecimal(100000));
thenCondition.setReturnRatio(new BigDecimal(0.2));
ProjectRuleThenResult thenResult = new ProjectRuleThenResult();
//3. 初始化KIE Session
//KIE是JBoss提供的一套集中式管理API,@link:https://docs.jboss.org/drools/release/7.58.0.Final/kie-api-javadoc/index.html
KieSession kieSession = kieContainer.newKieSession();
kieSession.insert(whenCondition);
kieSession.insert(whenValue);
kieSession.insert(thenCondition);
kieSession.insert(thenResult);
//只执行一个符合条件的rule,需要配合salience优先级使用
//kieSession.fireAllRules(1);
int i = kieSession.fireAllRules();
System.out.println("规则执行了" + i + "次,返回计算结果为" + thenResult.getServiceFeeAmount());
kieSession.dispose();
}
@SneakyThrows
private void init() {
KieServices kieServices = KieServices.get();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resourceArray = resourcePatternResolver.getResources("rules/**/*.drl");
for (Resource file : resourceArray) {
kieFileSystem.write(ResourceFactory.newClassPathResource("rules/" + file.getFilename(), "UTF-8"));
}
KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
kb.buildAll();
KieModule kieModule = kb.getKieModule();
kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
}
}
- 测试验证,输出结果如下
product rule被执行
规则执行了1次,返回计算结果为20000.000000000001110223024625156540423631668090820312500000
修改ProjectRuleWhenValue的值,将输出不同的结果。