规则引擎Drools介绍使用及SpringBoot整合Drools
Posted 爱是与世界平行
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了规则引擎Drools介绍使用及SpringBoot整合Drools相关的知识,希望对你有一定的参考价值。
规则引擎Drools介绍、使用及SpringBoot整合Drools
一、Drools介绍
Drools 是一款由JBoss组织提供的基于Java语言开发的开源规则引擎,可以将复杂且多变的业务规则从硬编码中解放出来,以规则脚本的形式存放在文件或特定的存储介质中(例如存放在数据库中),使得业务规则的变更不需要修改项目代码、重启服务器就可以在线上环境立即生效。
Drools使用 Rete 算法对所编写的规则求值。Drools 允许使用声明方式表达业务逻辑。
可以使用非 XML 的本地语言编写规则,从而便于学习和理解。并且,还可以将 Java 代码直接嵌入到规则文件中,这令 Drools 的学习更加吸引人。
官网:http://www.drools.org/#
官方文档:http://www.drools.org/learn/documentation.html
1.1 优点
- 非常活跃的社区支持
- 易用
- 快速的执行速度
- 在 Java 开发人员中流行
- 与 Java Rule Engine API(JSR 94)兼容
1.2 架构
Drools 是业务逻辑集成平台,被分为4个项目:
- Drools Guvnor (BRMS/BPMS):业务规则管理系统
- Drools Expert (rule engine):规则引擎,drools的核心部分
- Drools Flow (process/workflow):工作流引擎
- Drools Fusion (cep/temporal reasoning):事件处理
1.2.1 Drools引擎的核心组件
▶Rules:所有的业务规则和决策表都叫做规则。所有的规则都由规则条件校验(LHS 或conditions)和逻辑处理(RHS 或 actions)两部分组成;
▶Facts:业务数据或者称之为“事实”。所有加入到规则引擎的数据或者规则产生的数据都叫“事实”,规则引擎会根据规则或决策表过滤和加工这些数据。
▶Production memory:生产内存。当程序运行时,用于存储rules。
▶Working memory:工作内存。当程序运行时,所有facts都存储在这里。
▶Agenda:在规则引擎中注册和排序所有匹配实例(符合规则条件的数据 和 这一规则(或 一组规则)被称之为匹配实例),并激活匹配实例。
1.2.2 规则引擎工作流程简述
1、当业务数据被加入到规则引擎中后,它们会以一个或多个“事实”的形式被存储在工作内存中;
2、这些“事实”会与存储在生产内存中规则集合依次匹配,以确定哪些数据符合规则条件;
3、符合条件的数据与相应的规则组成 匹配实例被注册到agenda,然后依据规则的优先级等规则冲突策略对 匹配实例进行排序,以确定这些匹配实例的执行顺序并激活它们。
1.3 Drools语法
1.3.1 规则文件
规则文件可以使用 .drl文件,也可以是xml文件,这里我们使用drl文件
规则文件
package:对一个规则文件而言,package是必须定义的,必须放在规则文件第一行,package的名字是随意的,不必必须对应物理路径,跟java的package的概念不同,这里只是逻辑上的一种区分
如:
package com.sankuai.meituan.waimai.drools.demo
import:导入规则文件需要使用到的外部规则文件或者变量,这里的使用方法跟java相同,但是不同于java的是,这里的import导入的不仅仅可以是一个类,也可以是这个类中的某一个可访问的静态方法
import com.drools.demo.point.PointDomain;
rule:定义一个具体规则。rule “ruleName”。一个规则可以包含三个部分:
属性部分:
定义当前规则执行的一些属性等,比如是否可被重复执行、过期时间、生效时间等。条件部分(LHS):
定义当前规则的条件,如 when Message(); 判断当前workingMemory中是否存在Message对象。结果部分(RHS):
即当前规则条件满足后执行的操作,可以直接调用Fact对象的方法来操作应用。这里可以写普通java代码
rule部分
rule "ruleName"
no-loop true
<span class="hljs-keyword">when</span>
$message<span class="hljs-symbol">:Message</span>(status == <span class="hljs-number">0</span>)
<span class="hljs-keyword">then</span>
System.out.println(<span class="hljs-string">"fit"</span>);
$message.setStatus(<span class="hljs-number">1</span>);
update($message);
1.3.1.1. 规则详情
1. 属性详情
- no-loop:
定义当前的规则是否不允许多次循环执行,默认是false
;当前的规则只要满足条件,可以无限次执行。什么情况下会出现一条规则执行过一次又被多次重复执行呢?drools提供了一些api,可以对当前传入workingMemory中的Fact对象进行修改或者个数的增减,比如上述的update方法,就是将当前的workingMemory中的Message类型的Fact对象进行属性更新,这种操作会触发规则的重新匹配执行,可以理解为Fact对象更新了,所以规则需要重新匹配一遍,那么疑问是之前规则执行过并且修改过的那些Fact对象的属性的数据会不会被重置?结果是不会,已经修改过了就不会被重置,update之后,之前的修改都会生效。当然对Fact对象数据的修改并不是一定需要调用update才可以生效,简单的使用set方法设置就可以完成,这里类似于java的引用调用,所以何时使用update是一个需要仔细考虑的问题,一旦不慎,极有可能会造成规则的死循环。上述的no-loop true,即设置当前的规则,只执行一次,如果本身的RHS部分有update等触发规则重新执行的操作,也不要再次执行当前规则。
但是其他的规则会被重新执行,岂不是也会有可能造成多次重复执行,数据紊乱甚至死循环?答案是使用其他的标签限制,也是可以控制的:lock-on-active true - lock-on-active:lock-on-active true 通过这个标签,可以控制当前的规则只会被执行一次,因为一个规则的重复执行不一定是本身触发的,也可能是其他规则触发的,所以这个是no-loop的加强版
- date-expires:设置规则的过期时间,默认的时间格式:“日-月-年”
- date-effective:设置规则的生效时间,时间格式同上。
- duration:规则定时,duration 3000,3秒后执行规则
- salience:优先级,数值越大越先执行,这个可以控制规则的执行顺序。
rule attributes
2. 条件部分- LHS
- when:规则条件开始。条件可以单个,也可以多个,多个条件一次排列
如:当前规则只有在这三个条件都匹配的时候才会执行RHS部分
when
eval(true)
$customer:Customer()
$message:Message(status==0)
- eval(true):是一个默认的api,true 无条件执行,类似于 while(true)
- 操作符:
>
、>=
、<
、<=
、==
、!=
、contains
、not contains
、memberOf
、not memberOf
、matches
、not matches
操作符
- contains: 对比是否包含操作,操作的被包含目标可以是一个复杂对象也可以是一个简单的值
Person( fullName not contains "Jr" )
- not contains:与contains相反。
- memberOf:判断某个Fact属性值是否在某个集合中,与contains不同的是他被比较的对象是一个集合,而contains被比较的对象是单个值或者对象
CheeseCounter( cheese memberOf $matureCheeses )
- not memberOf:与memberOf正好相反
- matches:正则表达式匹配
Cheese( type matches "(Buffalo)?\\\\S*Mozarella" )
注意:
就像在Java中,写为字符串的正则表达式需要转义“\\”
- not matches:与matches正好相反
3. 结果部分- RHS
当规则条件满足,则进入规则结果部分执行,结果部分可以是纯java代码
- then:
then
System.out.println("OK"); //会在控制台打印出ok
end
- insert:往当前workingMemory中插入一个新的Fact对象,会触发规则的再次执行,除非使用no-loop限定
- update:更新
- modify:修改,与update语法不同,结果都是更新操作
- retract:删除
rule "Rule 03"
when
$number : Number( )
not Number( intValue < $number.intValue )
then
System.out.println("Number found with value: " + $number.intValue() );
retract( $number );
end
1.3.1.2 Drools关键词
关键词 | 描述 | 详情 |
---|---|---|
lock-on-active | ||
date-effective | ||
date-expires | ||
no-loop | ||
auto-focus | ||
activation-group | ||
agenda-group | ||
ruleflow-group | ||
entry-point | ||
duration | ||
package | ||
import | ||
dialect | ||
salience | ||
enabled | ||
attributes | ||
rule | ||
extend | ||
when | ||
then | ||
template | ||
query | ||
declare | ||
function | ||
global | ||
eval | ||
not | ||
in | ||
or | ||
and | ||
exists | ||
forall | ||
accumulate | ||
collect | ||
from | ||
action | ||
reverse | ||
result | ||
end | ||
over | ||
init | - |
1.3.1.3 Drools方法定义
- function
function String hello(String name) {
return "Hello "+name+"!";
}
1.3.1.4 Drools声明类型
- declare:声明类型
- 声明Class、Enum etc类型
- 声明元数据
1. 声明类类型
declare Address
number : int
streetName : String
city : String
end
2. 声明枚举类型
declare enum DaysOfWeek
SUN("Sunday"),MON("Monday"),TUE("Tuesday"),WED("Wednesday"),THU("Thursday"),FRI("Friday"),SAT("Saturday");
fullName : String
end
3. 声明元数据类型
元数据可以被分配给在Drools中几个不同的结构:
- fact types
- fact attributes
- rules
定义格式:
@metadata_key(metadata_value)
例子:
@author( Bob )
import java.util.Date
declare Person
@author( Bob )
@dateOfCreation( 01-Feb-2009 )
name : String @key @maxLength( 30 )
dateOfBirth : Date address : Address
end
声明元数据类级别 关键词
@role( <fact | event> )
import some.package.StockTick
declare StockTick
@role ( event )
end
@typesafe( <boolean> )
@timestamp( <attribute name> )
declare VoiceCall
@role( event )
@timestamp( callDateTime )
end
@duration( <attribute name> )
@expires( <time interval> )
@propertyChangeSupport
@propertyReactive
声明元数据属性级别 关键词
@key
两个方面影响:
根据@key作为类标识符,类比较以 @key 的字段为准
根据@key字段生成构造函数
declare Person
firstName : String @key
lastName : String @key
age : int
end
@position
declare Cheese
name : String @position(1)
shop : String @position(2)
price : int @position(0)
end
1.4 设计
1.5 Drools vs ILog vs Jess vs Mandarax
优点 | ||
---|---|---|
Drools | 开源、社区非常活跃、易使用、免费、JSR94兼容(JSR94是Java Rule Engine API)、支持Java、强大的工具集 | 只支持一种推理方式、安全性不够 |
ILog | 性能高(电信领域使用)、易使用 | 商业产品、不开源 |
Jess | 支持2种推理方式(正向链和反向链)、很强的表示、推理能力、支持AOP | 不开源、无规则管理工具、不易使用 |
Mandarax | 开源、免费、支持Java | JSR94不兼容(JSR94是Java Rule Engine API)、已经不更新、社区不活跃、并且文档不全 |
1.5.1 推理方式
- 正向链推理:一条由问题开始搜索,并得到其解答的链称为正向链推理。
- 反向链推理:一条由假设回推到支持该假设的事实的链称为反向链推理。
二、Drools使用
2.1 Drools的基本使用
在项目中使用drools时,即可以单独使用也可以整合spring使用。如果单独使用只需要导入如下maven坐标即可:
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.6.0.Final</version>
</dependency>
如果我们使用IDEA开发drools应用,IDEA中已经集成了drools插件。如果使用eclipse开发drools应用还需要单独安装drools插件。
drools API开发步骤如下:
2.2 Drools基础语法
2.2.1 规则文件构成
在使用Drools时非常重要的一个工作就是编写规则文件,通常规则文件的后缀为.drl。
drl是Drools Rule Language的缩写。在规则文件中编写具体的规则内容。
一套完整的规则文件内容构成如下:
关键字 | 描述 |
---|---|
package | 包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用 |
import | 用于导入类或者静态方法 |
global | 全局变量 |
function | 自定义函数 |
query | 查询 |
rule end | 规则体 |
Drools支持的规则文件,除了drl形式,还有Excel文件类型的。
2.2.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:关键字,表示一个规则结束。
2.2.3 注释
在drl形式的规则文件中使用注释和Java类中使用注释一致,分为单行注释和多行注释。
单行注释用"//“进行标记,多行注释以”/“开始,以”/"结束。如下示例:
//规则rule1的注释,这是一个单行注释
rule "rule1"
when
then
System.out.println("rule1触发");
end
/*
规则rule2的注释,
这是一个多行注释
*/
rule "rule2"
when
then
System.out.println("rule2触发");
end
2.2.4 Pattern模式匹配
前面我们已经知道了Drools中的匹配器可以将Rule Base中的所有规则与Working Memory中的Fact对象进行模式匹配,那么我们就需要在规则体的LHS部分定义规则并进行模式匹配。LHS部分由一个或者多个条件组成,条件又称为pattern。
pattern的语法结构为:绑定变量名:Object(Field约束)
其中绑定变量名可以省略,通常绑定变量名的命名一般建议以$开始。如果定义了绑定变量名,就可以在规则体的RHS部分使用此绑定变量名来操作相应的Fact对象。Field约束部分是需要返回true或者false的0个或多个表达式。
例如我们的入门案例中:
//规则二:所购图书总价在100到200元的优惠20元
rule "book_discount_2"
when
//Order为类型约束,originalPrice为属性约束
$order:Order(originalPrice < 200 && originalPrice >= 100)
then
$order.setRealPrice($order.getOriginalPrice() - 20);
System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end
通过上面的例子我们可以知道,匹配的条件为:
1、工作内存中必须存在Order这种类型的Fact对象-----类型约束
2、Fact对象的originalPrice属性值必须小于200------属性约束
3、Fact对象的originalPrice属性值必须大于等于100------属性约束
以上条件必须同时满足当前规则才有可能被激活。
绑定变量既可以用在对象上,也可以用在对象的属性上。例如上面的例子可以改为:
//规则二:所购图书总价在100到200元的优惠20元
rule "book_discount_2"
when
$order:Order($op:originalPrice < 200 && originalPrice >= 100)
then
System.out.println("$op=" + $op);
$order.setRealPrice($order.getOriginalPrice() - 20);
System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end
LHS部分还可以定义多个pattern,多个pattern之间可以使用and或者or进行连接,也可以不写,默认连接为and。
//规则二:所购图书总价在100到200元的优惠20元
rule "book_discount_2"
when
$order:Order($op:originalPrice < 200 && originalPrice >= 100) and
$customer:Customer(age > 20 && gender=='male')
then
System.out.println("$op=" + $op);
$order.setRealPrice($order.getOriginalPrice() - 20);
System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end
2.2.5 比较操作符
Drools提供的比较操作符,如下表:
符号 | 说明 |
---|---|
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
== | 等于 |
!= | 不等于 |
contains | 检查一个Fact对象的某个属性值是否包含一个指定的对象值 |
not contains | 检查一个Fact对象的某个属性值是否不包含一个指定的对象值 |
memberOf | 判断一个Fact对象的某个属性是否在一个或多个集合中 |
not memberOf | 判断一个Fact对象的某个属性是否不在一个或多个集合中 |
matches | 判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配 |
not matches | 判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配 |
前6个比较操作符和Java中的完全相同,下面我们重点学习后6个比较操作符。
2.2.5.1 语法
-
contains | not contains语法结构
Object(Field[Collection/Array] contains value)
Object(Field[Collection/Array] not contains value)
-
memberOf | not memberOf语法结构
Object(field memberOf value[Collection/Array])
Object(field not memberOf value[Collection/Array])
-
matches | not matches语法结构
Object(field matches “正则表达式”)
Object(field not matches “正则表达式”)
contain是前面包含后面,memberOf是后面包含前面。
2.2.5.2 操作步骤
第一步:创建实体类,用于测试比较操作符
package com.itheima.drools.entity;
import java.util.List;
/**
* 实体类
* 用于测试比较操作符
*/
public class ComparisonOperatorEntity {
private String names;
private List<String> list;
public String getNames() {
return names;
}
public void setNames(String names) {
this.names = names;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
}
第二步:在/resources/rules下创建规则文件comparisonOperator.drl
package comparisonOperator
import com.itheima.drools.entity.ComparisonOperatorEntity
/*
当前规则文件用于测试Drools提供的比较操作符
*/
//测试比较操作符contains
rule "rule_comparison_contains"
when
ComparisonOperatorEntity(names contains "张三")
ComparisonOperatorEntity(list contains names)
then
System.out.println("规则rule_comparison_contains触发");
end
//测试比较操作符not contains
rule "rule_comparison_notContains"
when
ComparisonOperatorEntity(names not contains "张三")
ComparisonOperatorEntity(list not contains names)
then
System.out.println("规则rule_comparison_notContains触发");
end
//测试比较操作符memberOf
rule "rule_comparison_memberOf"
when
ComparisonOperatorEntity(names memberOf list)
then
System.out.println("规则rule_comparison_memberOf触发");
end
//测试比较操作符not memberOf
rule "rule_comparison_notMemberOf"
when
ComparisonOperatorEntity(names not memberOf list)
then
System.out.println("规则rule_comparison_notMemberOf触发");
end
//测试比较操作符matches
rule "rule_comparison_matches"
when
ComparisonOperatorEntity(names matches "张.*")
then
System.out.println("规则rule_comparison_matches触发");
end
//测试比较操作符not matches
rule "rule_comparison_notMatches"
when
ComparisonOperatorEntity(names not matches "张.*")
then
System.out.println("规则rule_comparison_notMatches触发");
end
第三步:编写单元测试
//测试比较操作符
@Test
public void test3(){
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
ComparisonOperatorEntity comparisonOperatorEntity = new ComparisonOperatorEntity();
comparisonOperatorEntity.setNames("张三");
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
comparisonOperatorEntity.setList(list);
//将数据提供给规则引擎,规则引擎会根据提供的数据进行规则匹配,如果规则匹配成功则执行规则
kieSession.insert(comparisonOperatorEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
2.2.6 执行指定规则
通过前面的案例可以看到,我们在调用规则代码时,满足条件的规则都会被执行。那么如果我们只想执行其中的某个规则如何实现呢?
Drools给我们提供的方式是通过规则过滤器来实现执行指定规则。对于规则文件不用做任何修改,只需要修改Java代码即可,如下:
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
ComparisonOperatorEntity comparisonOperatorEntity = new ComparisonOperatorEntity();
comparisonOperatorEntity.setNames("张三");
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
comparisonOperatorEntity.setList(list);
kieSession.insert(comparisonOperatorEntity);
//通过规则过滤器实现只执行指定规则
kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rule_comparison_memberOf"));
kieSession.dispose();
2.2.7 关键字
Drools的关键字分为:硬关键字(Hard keywords)和软关键字(Soft keywords)。
硬关键字是我们在规则文件中定义包名或者规则名时明确不能使用的,否则程序会报错。软关键字虽然可以使用,但是不建议使用。
硬关键字包括:true false null
软关键字包括:lock-on-active date-effective date-expires no-loop auto-focus activation-group agenda-group ruleflow-group entry-point duration package import dialect salience enabled attributes rule extend when then template query declare function global eval not in or and exists forall accumulate collect from action reverse result end over init
比如:
rule true //不可以
rule "true" //可以
2.2.8 Drools内置方法
规则文件的RHS
部分的主要作用是通过插入,删除或修改工作内存中的Fact数据,来达到控制规则引擎执行的目的。Drools提供了一些方法可以用来操作工作内存中的数据,**操作完成后规则引擎会重新进行相关规则的匹配,**原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功了。
创建如下实体类:
package com.itheima.drools.entity;
import java.util.List;
/**
* 学生
*/
SpringBoot2 整合 Drools规则引擎,实现高效的业务规则
SpringBoot2 整合 Drools规则引擎,实现高效的业务规则