drools官网地址:https://drools.org/
drools源码下载地址:https://github.com/kiegroup/drools
一 问题引入
在尚硅谷代驾项目里面,我们有一个接口叫预估订单金额。乘客选择了出发和结束的地点,计算预估金额是多少,根据约定规则进行金额计算。在实际中,规则可能进行随时调整,比如油价上涨收取燃油附加费,比如大雪天气,费用增加等等。对于不经常变化的业务,我们通常是硬编码到程序中。但是经常变化的业务,我们就得把业务流程从代码中剥离出来,我们怎么从程序中剥离出去?这里就需要用到规则引擎了。
他将我们的约定抽取了出来,放在代码的外面。可以是文件,可以是数据库。这样我们变更约定的时候就不用过多的修改我们的程序。
二 规则引擎介绍
1 是什么
规则引擎,全称为 业务规则管理系统,英文名为BRMS(即Business Rule Management System)。规则引擎的主要思想是将应用程序中的业务决策部分分离出来,并使用预定义的语义模块编写业务决策(业务规则),由用户或开发者在需要时进行配置、管理。
需要注意的是规则引擎并不是一个具体的技术框架,而是指的一类系统,即业务规则管理系统。目前市面上具体的规则引擎产品有:drools、VisualRules、iLog等。
规则引擎实现了将业务决策从应用程序代码中分离出来,接收数据输入,解释业务规则,并根据业务规则做出业务决策。规则引擎其实就是一个输入输出平台。
系统中引入规则引擎后,业务规则不再以程序代码的形式驻留在系统中,取而代之的是处理规则的规则引擎,业务规则存储在规则库中,完全独立于程序。业务人员可以像管理数据一样对业务规则进行管理,比如查询、添加、更新、统计、提交业务规则等。业务规则被加载到规则引擎中供应用系统调用。
2 规则引擎的优势
使用规则引擎的优势如下:
1、业务规则与系统代码分离,实现业务规则的集中管理
2、在不重启服务的情况下可随时对业务规则进行扩展和维护
3、可以动态修改业务规则,从而快速响应需求变更
4、规则引擎是相对独立的,只关心业务规则,使得业务分析人员也可以参与编辑、维护系统的业务规则
5、减少了硬编码业务规则的成本和风险
6、使用规则引擎提供的规则编辑工具,使复杂的业务规则实现变得的简单
3 规则引擎应用场景
对于一些存在比较复杂的业务规则并且业务规则会频繁变动的系统比较适合使用规则引擎,如下:
1、风险控制系统----风险贷款、风险评估
2、反欺诈项目----银行贷款、征信验证
3、决策平台系统----财务计算
4、促销平台系统----满减、打折、加价购
4 Drools的组成及其说明
drools规则引擎由以下三部分构成:
- Working Memory(工作内存)
- Rule Base(规则库)
- Inference Engine(推理引擎)
其中Inference Engine(推理引擎)又包括:
- Pattern Matcher(匹配器) 具体匹配哪一个规则,由这个完成
- Agenda(议程)
- Execution Engine(执行引擎)
如下图所示:
Working Memory:工作内存,drools规则引擎会从Working Memory中获取数据并和规则文件中定义的规则进行模式匹配,所以我们开发的应用程序只需要将我们的数据插入到Working Memory中即可。其实就是drools执行逻辑的地方
Fact:事实,是指在drools 规则应用当中,将一个普通的JavaBean插入到Working Memory后的对象就是Fact对象,Fact对象是我们的应用和规则引擎进行数据交互的桥梁或通道。
Rule Base:规则库,我们在规则文件中定义的规则都会被加载到规则库中。
Pattern Matcher:匹配器,将Rule Base中的所有规则与Working Memory中的Fact对象进行模式匹配,匹配成功的规则将被激活并放入Agenda(议程)中。
Agenda:议程,用于存放通过匹配器进行模式匹配后被激活的规则。
Execution Engine:执行引擎,执行Agenda中被激活的规则
5 执行流程
6 常见api
我们在操作Drools时经常使用的API以及它们之间的关系如下图:
通过上面的核心API可以发现,大部分类名都是以Kie开头。Kie全称为Knowledge Is Everything**,即"知识就是一切"的缩写,是Jboss一系列项目的总称。如下图所示,Kie的主要模块有OptaPlanner、Drools、UberFire、jBPM。
通过上图可以看到,Drools是整个KIE项目中的一个组件,Drools中还包括一个Drools-WB的模块,它是一个可视化的规则编辑器。
7 快速入门
7.1 引入依赖
里面的版本需要自己定义
<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-mvel</artifactId>
<version>${drools.version}</version>
</dependency>
7.2 配置类
package com.atguigu.daijia.rules.config;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieModule;
import org.kie.api.runtime.KieContainer;
import org.kie.internal.io.ResourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DroolsConfig {
// 制定规则文件的路径
private static final String RULES_CUSTOMER_RULES_DRL = "rules/FeeRule.drl";
@Bean
public KieContainer kieContainer() {
KieServices kieServices = KieServices.Factory.get();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL));
KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
kb.buildAll();
KieModule kieModule = kb.getKieModule();
KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
return kieContainer;
}
}
7.3 编写规则文件
按照规定创建文件
global com.atguigu.daijia.model.vo.rules.FeeRuleResponse feeRuleResponse;
/**
1.起步价
00:00:00-06:59:59 19元(含3公里)
07:00:00-23:59:59 19元(含5公里)
*/
rule "起步价 00:00:00-06:59:59 19元(含3公里)"
salience 10 //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
no-loop true //防止陷入死循环
when
/*规则条件,到工作内存中查找FeeRuleRequest对象
里面出来的结果只能是ture或者false
$rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
$rule:FeeRuleRequest(startTime >= "00:00:00" && startTime <= "06:59:59")
then
feeRuleResponse.setBaseDistance(new BigDecimal("3.0"));
feeRuleResponse.setBaseDistanceFee(new BigDecimal("19.0"));
//3公里内里程费为0
feeRuleResponse.setExceedDistance(new BigDecimal("0.0"));
feeRuleResponse.setExceedDistancePrice(new BigDecimal("4.0"));
System.out.println("00:00:00-06:59:59 " + feeRuleResponse.getBaseDistance() + "公里,起步价:" + feeRuleResponse.getBaseDistanceFee() + "元");
end
7.4 在代码内使用drools
里面的逻辑需要自己写,大概的模板就这样
@Autowired
private KieContainer kieContainer;
@Override
public FeeRuleResponseVo calculateOrderFee(FeeRuleRequestForm calculateOrderFeeForm) {
//封装输入对象
FeeRuleRequest feeRuleRequest = new FeeRuleRequest();
feeRuleRequest.setDistance(calculateOrderFeeForm.getDistance());
Date startTime = calculateOrderFeeForm.getStartTime();
feeRuleRequest.setStartTime(new DateTime(startTime).toString("HH:mm:ss"));
feeRuleRequest.setWaitMinute(calculateOrderFeeForm.getWaitMinute());
//Drools使用
KieSession kieSession = kieContainer.newKieSession();
//封装返回对象
FeeRuleResponse feeRuleResponse = new FeeRuleResponse();
kieSession.setGlobal("feeRuleResponse",feeRuleResponse);
kieSession.insert(feeRuleRequest);
kieSession.fireAllRules();
kieSession.dispose();
//封装数据到FeeRuleResponseVo返回
FeeRuleResponseVo feeRuleResponseVo = new FeeRuleResponseVo();
// feeRuleResponse -- feeRuleResponseVo
BeanUtils.copyProperties(feeRuleResponse,feeRuleResponseVo);
return feeRuleResponseVo;
}
三 Drools基础语法
1 规则文件构成
在使用Drools时非常重要的一个工作就是编写规则文件,通常规则文件的后缀为.drl。
drl是Drools Rule Language的缩写。在规则文件中编写具体的规则内容。
一套完整的规则文件内容构成如下:
package : 包名。只限于逻辑上的管理,他并不是真实存在的。可以根据业务逻辑自己来编辑名字。同一个包名下的查询或者函数可以直接调用
import : 用于导入类或者静态方法
global : 全局变量
function : 自定义函数
query : 查询
rule ... end : 规则体
Drools支持的规则文件,除了drl形式,还有Excel文件类型的。
2 规则体的语法
rule "ruleName"
attributes
when
LHS
then
RHS
end
rule:关键字,表示规则开始,参数为规则的唯一名称。
attributes:规则属性,是rule与when之间的参数,为可选项。
when:关键字,后面跟规则的条件部分。
LHS(Left Hand Side):是规则的条件部分的通用名称。它由零个或多个条件元素组成。如果LHS为空,则它将被视为始终为true的条件元素。 (左手边)
then:关键字,后面跟规则的结果部分。
RHS(Right Hand Side):是规则的后果或行动部分的通用名称。 (右手边)
end:关键字,表示一个规则结束。
3 注解
在drl形式的规则文件中使用注释和Java类中使用注释一致,分为单行注释和多行注释。
4 Pattern模式匹配
前面我们已经知道了Drools中的匹配器可以将Rule Base中的所有规则与Working Memory中的Fact对象进行模式匹配,那么我们就需要在规则体的LHS部分定义规则并进行模式匹配。LHS部分由一个或者多个条件组成,条件又称为pattern。
pattern的语法结构为:
绑定变量名:Object(Field约束)
其中绑定变量名可以省略,通常绑定变量名的命名一般建议以$开始。如果定义了绑定变量名,就可以在规则体的RHS部分使用此绑定变量名来操作相应的Fact对象。Field约束部分是需要返回true或者false的0个或多个表达式。
例如我们的入门案例中:
//规则二:100元 - 500元 加100分
rule "order_rule_2"
when
$order:Order(amout >= 100 && amout < 500)
then
$order.setScore(100);
System.out.println("成功匹配到规则二:100元 - 500元 加100分");
end
```
通过上面的例子我们可以知道,匹配的条件为:
1、工作内存中必须存在Order这种类型的Fact对象-----类型约束
2、Fact对象的amout属性值必须大于等于100------属性约束
3、Fact对象的amout属性值必须小于500------属性约束
以上条件必须同时满足当前规则才有可能被激活。
5 比较操作符
Drools提供的比较操作符,如下表:
| < | 小于 |
| > | 大于 |
| >= | 大于等于 |
| <= | 小于等于 |
| == | 等于 |
| != | 不等于 |
| contains | 检查一个Fact对象的某个属性值是否包含一个指定的对象值 |
| not contains | 检查一个Fact对象的某个属性值是否不包含一个指定的对象值 |
| memberOf | 判断一个Fact对象的某个属性是否在一个或多个集合中 |
| not memberOf | 判断一个Fact对象的某个属性是否不在一个或多个集合中 |
| matches | 判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配 |
| not matches | 判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配 |
前6个比较操作符和Java中的完全相同。
6 Drools内置方法
规则文件的`RHS`部分的主要作用是通过插入,删除或修改工作内存中的Fact数据,来达到控制规则引擎执行的目的。Drools提供了一些方法可以用来操作工作内存中的数据,操作完成后规则引擎会重新进行相关规则的匹配,原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功了。
6.1 update方法
update方法的作用是更新工作内存中的数据,并让相关的规则重新匹配。(要避免死循环)
参数:
//Fact对象,事实对象
Order order = new Order();
order.setAmout(30);
规则
//规则一:100元以下 不加分
rule "order_rule_1"
when
$order:Order(amout < 100)
then
$order.setAmout(150);
update($order) //update方法用于更新Fact对象,会导致相关规则重新匹配
System.out.println("成功匹配到规则一:100元以下 不加分");
end
规则二:
100元 - 500元 加100分
rule "order_rule_2"
when
$order:Order(amout >= 100 && amout < 500)
then
$order.setScore(100);
System.out.println("成功匹配到规则二:100元 - 500元 加100分");
end
在更新数据时需要注意防止发生死循环。
6.2 insert方法
insert方法的作用是向工作内存中插入数据,并让相关的规则重新匹配。
//规则一:100元以下 不加分
rule "order_rule_1"
when
$order:Order(amout < 100)
then
Order order = new Order();
order.setAmout(130);
insert(order); //insert方法的作用是向工作内存中插入Fact对象,会导致相关规则重新匹配
System.out.println("成功匹配到规则一:100元以下 不加分");
end
//规则二:100元 - 500元 加100分
rule "order_rule_2"
when
$order:Order(amout >= 100 && amout < 500)
then
$order.setScore(100);
System.out.println("成功匹配到规则二:100元 - 500元 加100分");
end
6.3 retract方法
retract方法的作用是删除工作内存中的数据,并让相关的规则重新匹配。
//规则一:100元以下 不加分
rule "order_rule_1"
when
$order:Order(amout < 100)
then
retract($order) //retract方法的作用是删除工作内存中的Fact对象,会导致相关规则重新匹配
System.out.println("成功匹配到规则一:100元以下 不加分");
end
7 规则属性 attributes
前面我们已经知道了规则体的构成如下:
rule "ruleName"
attributes
when
LHS
then
RHS
end
Drools中提供的属性如下表(部分属性):
| 属性名 | 说明 |
| :--------------- | :------------------------------------------------- |
| salience | 指定规则执行优先级 |
| dialect | 指定规则使用的语言类型,取值为java和mvel |
| enabled | 指定规则是否启用 |
| date-effective | 指定规则生效时间 |
| date-expires | 指定规则失效时间 |
| activation-group | 激活分组,具有相同分组名称的规则只能有一个规则触发 |
| agenda-group | 议程分组,只有获取焦点的组中的规则才有可能触发 |
| timer | 定时器,指定规则触发的时间 |
| auto-focus | 自动获取焦点,一般结合agenda-group一起使用 |
| no-loop | 防止死循环 |
7.1 salience属性
salience属性用于指定规则的执行优先级,取值类型为Integer。数值越大越优先执行。每个规则都有一个默认的执行顺序,如果不设置salience属性,规则体的执行顺序为由上到下。
7.2 no-loop属性
no-loop属性用于防止死循环,当规则通过update之类的函数修改了Fact对象时,可能使当前规则再次被激活从而导致死循环。取值类型为Boolean,默认值为false
8 全局变量
global全局变量
global关键字用于在规则文件中定义全局变量,它可以让应用程序的对象在规则文件中能够被访问。可以用来为规则文件提供数据或服务。
语法结构为:global 对象类型 对象名称
在使用global定义的全局变量时有两点需要注意:
1、如果对象类型为包装类型时,在一个规则中改变了global的值,那么只针对当前规则有效,对其他规则中的global不会有影响。可以理解为它是当前规则代码中的global副本,规则内部修改不会影响全局的使用。
2、如果对象类型为集合类型或JavaBean时,在一个规则中改变了global的值,对java代码和所有规则都有效。