drools规则引擎
# HelloWord
Drools Jar 包介绍:
knowledge-api.jar - 提供接口和工厂。它清楚地描述用户 API 的职责,还有什么引擎 API。 knowledge-internal-api.jar - 提供内部接口和工厂。 drools-core.jar - 核心引擎,运行时组件。包含 RETE 引擎和 LEAPS 引擎。 drools-compiler.jar - 包含编译器/构建器组件,以获取规则源,并构建可执行规则库。 drools-decisiontables.jar - 决策表编译器组件,在 drools-compiler 组件中使用。支持 Excel 和 CSV 输入格式。
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-bom</artifactId>
<version>${drools.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 1、编写/META-INF/kmodule.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<!--
name:指定kbase的名称,可以任意,但是需要唯一
packages:指定drl规则文件内的包名,需要根据实际情况填写,否则无法加载到规则文件
default:指定当前kbase是否为默认
-->
<kbase name="myKbase1" packages="com.zxp.rules" default="true">
<!--
name:指定ksession名称,可以任意,但是需要唯一
default:指定当前session是否为默认
-->
<ksession name="ksession-rule" default="true"/>
</kbase>
</kmodule>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2、resources/rules 新建XXX.drl规则文件
package com.zxp.rules // 包名,与kmodule.xml保持一致
import com.zxp.drools.bean.Order
//规则一:所购图书总价在100元以下的没有优惠
rule "book_discount_1"
when
$order:Order(originalPrice < 100)
then
$order.setRealPrice($order.getOriginalPrice());
System.out.println("成功匹配到规则一:所购图书总价在100元以下的没有优惠");
end
2
3
4
5
6
7
8
9
10
11
# 3、组装
@Test
public void test1() {
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
//会话对象,用于和规则引擎交互
KieSession kieSession = kieClasspathContainer.newKieSession();
//构造订单对象,设置原始价格,由规则引擎根据优惠规则计算优惠后的价格
Order order = new Order();
order.setOriginalPrice(210d);
//将数据提供给规则引擎,规则引擎会根据提供的数据进行规则匹配
kieSession.insert(order);
//激活规则引擎,可以指定过滤器,如果规则匹配成功则执行规则
kieSession.fireAllRules();
// kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rule-1"));
//关闭会话
kieSession.dispose();
System.out.println("优惠前原始价格:" + order.getOriginalPrice() +
",优惠后价格:" + order.getRealPrice());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
开发步骤
- 获取KieService
- 获取kieContainer
- 获取KieSession
- Insert Fact
- 触发规则
- 关闭kieSession
# 原理
# 规则引擎构成
drools规则引擎由以下三部分构成:
Working Memory(工作内存),drools规则引擎会从Working Memory中获取数据并和规则文件中定义的规则进行模式匹配。
- Fact : 事实,我们把一个javaBean插入到Working Memory后的对象就是Fact对象。
Rule Base(规则库),我们在规则文件中定义的规则都会被加载到规则库中。
Inference Engine(推理引擎)
- Pattern Matcher,(匹配器)将Rule Base中的所有规则与Working Memory中的Fact对象进行模式匹配,匹配成功的规则将被激活并放入Agenda中。
- Agenda(议程),用于存放通过匹配器进行模式匹配后被激活的规则。
- Execution Engine(执行引擎),执行Agenda中被激活的规则。
如图:
# 规则引擎执行过程
# drools语法
# 1、规则文件构成
在使用Drools时非常重要的一个工作就是编写规则文件,通常规则文件的后缀为.drl
,drl是Drools Rule Language的缩写。在规则文件中编写具体的规则内容。
一套完整的规则文件内容构成如下:
Drools支持的规则文件,除了drl形式,还有Excel文件类型的。
package package-name
imports
globals
functions
queries
rules
2
3
4
5
6
7
8
9
10
11
# 2、规则体语法结构
规则体是规则文件内容中的重要组成部分,是进行业务规则判断、处理业务结果的部分。
规则体语法结构如下:
rule "ruleName"
attributes
when
LHS
then
RHS
end
2
3
4
5
6
7
rule:关键字,表示规则开始,参数为规则的唯一名称。
attributes:规则属性,是rule与when之间的参数,为可选项。
when:关键字,后面跟规则的条件部分。条件可以不写,默认就是eval(true)
LHS(Left Hand Side):是规则的条件部分的通用名称。它由零个或多个条件元素组成。如果LHS为空,则它将被视为始终为true的条件元素。
then:关键字,后面跟规则的结果部分。
RHS(Right Hand Side):是规则的后果或行动部分的通用名称。
end:关键字,表示一个规则结束。
# 3、注释
在drl形式的规则文件中使用注释和Java类中使用注释一致,分为单行注释和多行注释。
单行注释用"//"进行标记,多行注释以"/"开始,以"/"结束。如下示例:
//规则rule1的注释,这是一个单行注释
rule "rule1"
when
then
System.out.println("rule1触发");
end
/*
规则rule2的注释,
这是一个多行注释
*/
rule "rule2"
when
then
System.out.println("rule2触发");
end
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4、条件部分模式匹配
Drools中的匹配器可以将Rule Base中的所有规则与Working Memory中的Fact对象进行模式匹配,那么我们就需要在规则体的LHS部分定义规则并进行模式匹配。LHS部分由一个或者多个条件组成,条件又称为pattern。
pattern的语法结构为:绑定变量名:Object(Field约束)
其中绑定变量名可以省略,通常绑定变量名的命名一般建议以$
开始。如果定义了绑定变量名,就可以在规则体的RHS部分使用此绑定变量名来操作相应的Fact对象。Field约束部分是需要返回true或者false的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正好相反
# 5、结果部分RHS
- **insert:**往当前workingMemory中插入一个新的Fact对象,会触发规则的再次执行,除非使用
no-loop true
限定(只会匹配一次)。 要防止发生死循环 - **update:**更新
- **modify:**修改,与update语法不同,结果都是更新操作
- **retract:**删除
动作 | 描述 |
---|---|
set | 给属性赋值 |
modify | 将改变通知drools引擎 |
update | 将改变通知drools引擎 |
insert | 将新事实插入到drools引擎的工作 |
insertLogical | insert增强版,需声明撤回事件,或待不在匹配条件后自动撤回 |
delete | 删除事实 |
# 6、属性部分
属性名 | 说明 |
---|---|
salience | 指定规则执行优先级,数值越大,优先级别越高 |
dialect | 指定规则使用的语言类型,取值为java和mvel |
enabled | 指定规则是否启用 |
date-effective | 指定规则生效时间,大于时执行,默认日期格式为dd-MMM-yyyy 用户也可以自定义日期格式,System.setProperty("drools.dateformat","yyyy-MM-dd"); |
date-expires | 指定规则失效时间,即只有当前系统时间小于设置的时间或者日期规则才有可能触发。默认日期格式为:dd-MMM-yyyy 用户也可以自定义日期格式 |
activation-group | 激活分组,具有相同分组名称的规则只能有一个规则触发,可以与salience 搭配 |
agenda-group | 议程分组,只有获取焦点的组中的规则才有可能触发,kieSession.getAgenda().getAgendaGroup("rule-AG1").setFocus(); |
timer | 定时器,指定规则触发的时间,timer(5s 2s) 第一个参数延时时间,第二个参数为间隔时间(可选); cron表达式timer (cron:0/1 * * * * ?) |
auto-focus | 自动获取焦点,一般结合agenda-group一起使用 |
no-loop | 防止死循环,为true,只执行一次,当规则通过update之类的函数修改了Fact对象时,可能使当前规则再次被激活从而导致死循环 |
# Drools高级语法
# global全局变量
global关键字用于在规则文件中定义全局变量,它可以让应用程序的对象在规则文件中能够被访问。可以用来为规则文件提供数据或服务。
语法结构为:global 对象类型 对象名称
在使用global定义的全局变量时有两点需要注意:
1、如果对象类型为包装类型时,在一个规则中改变了global的值,那么只针对当前规则有效,对其他规则中的global不会有影响。可以理解为它是当前规则代码中的global副本,规则内部修改不会影响全局的使用。
2、如果对象类型为集合类型或JavaBean时,在一个规则中改变了global的值,对java代码和所有规则都有效
// .drl文件中
global java.lang.Integer count //定义一个包装类型的全局变量
global java.util.List gList //定义一个集合类型的全局变量
global UserService userService //定义一个JavaBean类型的全局变量
//设置全局变量,名称和类型必须和规则文件中定义的全局变量名称对应
kieSession.setGlobal("count", 5);
List<String> list = new ArrayList<String>();
list.add("itheima");
kieSession.setGlobal("gList", list);
kieSession.setGlobal("userService", new UserService());
2
3
4
5
6
7
8
9
10
11
12
13
14
# query查询
query查询提供了一种查询working memory中符合约束条件的Fact对象的简单方法。它仅包含规则文件中的LHS部分,不用指定“when”和“then”部分并且以end结束
query 'query-1'
$person:Person(age>30)
end
query 'query-2'(String nameParam)
$person:Person(age>30 , name==nameParam)
end
2
3
4
5
6
7
QueryResults queryResults = kieSession.getQueryResults("query-1");
for (QueryResultsRow row:queryResults) {
Person p = (Person) row.get("$person");
System.out.println(p);
}
2
3
4
5
# function函数
function关键字用于在规则文件中定义函数,就相当于java类中的方法一样。可以在规则体中调用定义的函数。使用函数的好处是可以将业务逻辑集中放置在一个地方,根据需要可以对函数进行修改。
# LHS加强
在规则体中的LHS部分是介于when和then之间的部分,主要用于模式匹配,只有匹配结果为true时,才会触发RHS部分的执行。
5.4.1、复合值限制in/not in
复合值限制是指超过一种匹配值的限制条件,类似于SQL语句中的in关键字。Drools规则体中的LHS部分可以使用in或者not in进行复合值的匹配。具体语法结构如下:
Object(field in (比较值1,比较值2...))
举例:
$s:Student(name in ("张三","李四","王五"))
$s:Student(name not in ("张三","李四","王五"))
2
5.4.2、条件元素eval
eval用于规则体的LHS部分,并返回一个Boolean类型的值。语法结构如下:
eval(表达式)
举例:
eval(true)
eval(false)
eval(1 == 1)
2
3
5.4.3、条件元素not
not用于判断Working Memory中是否存在某个Fact对象,如果不存在则返回true,如果存在则返回false。语法结构如下:
not Object(可选属性约束)
举例:
not Student()
not Student(age < 10)
2
5.4.4、条件元素exists
exists的作用与not相反,用于判断Working Memory中是否存在某个Fact对象,如果存在则返回true,不存在则返回false。语法结构如下:
exists Object(可选属性约束)
举例:
exists Student()
exists Student(age < 10 && name != null)
2
在LHS部分进行条件编写时并没有使用exists也可以达到判断Working Memory中是否存在某个符合条件的Fact元素的目的,那么我们使用exists还有什么意义?
两者的区别:当向Working Memory中加入多个满足条件的Fact对象时,使用了exists的规则执行一次,不使用exists的规则会执行多次。
例如:
规则文件(只有规则体):
rule "使用exists的规则"
when
exists Student()
then
System.out.println("规则:使用exists的规则触发");
end
rule "没有使用exists的规则"
when
Student()
then
System.out.println("规则:没有使用exists的规则触发");
end
2
3
4
5
6
7
8
9
10
11
12
13
Java代码:
kieSession.insert(new Student());
kieSession.insert(new Student());
kieSession.fireAllRules();
2
3
上面第一个规则只会执行一次,因为Working Memory中存在两个满足条件的Fact对象,第二个规则会执行两次。
5.4.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)和Student(age > 10)
*/
Student(age < 20)
then
System.out.println("规则:rule_2触发");
end
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
需要注意的是,只继承条件
# RHS加强
RHS部分是规则体的重要组成部分,当LHS部分的条件匹配成功后,对应的RHS部分就会触发执行。一般在RHS部分中需要进行业务处理。
在RHS部分Drools为我们提供了一个内置对象,名称就是drools。
5.5.1、halt
halt方法的作用是立即终止后面所有规则的执行。
package testhalt
rule "rule_halt_1"
when
then
System.out.println("规则:rule_halt_1触发");
drools.halt();//立即终止后面所有规则执行
end
//当前规则并不会触发,因为上面的规则调用了halt方法导致后面所有规则都不会执行
rule "rule_halt_2"
when
then
System.out.println("规则:rule_halt_2触发");
end
2
3
4
5
6
7
8
9
10
11
12
13
14
5.5.2、getWorkingMemory
getWorkingMemory方法的作用是返回工作内存对象。
package testgetWorkingMemory
rule "rule_getWorkingMemory"
when
then
System.out.println(drools.getWorkingMemory());
end
2
3
4
5
6
5.5.3、getRule
getRule方法的作用是返回规则对象。
package testgetRule
rule "rule_getRule"
when
then
System.out.println(drools.getRule());
end
2
3
4
5
6
5.6、规则文件编写规范
我们在进行drl类型的规则文件编写时尽量遵循如下规范:
所有的规则文件(.drl)应统一放在一个规定的文件夹中,如:/rules文件夹
书写的每个规则应尽量加上注释。注释要清晰明了,言简意赅
同一类型的对象尽量放在一个规则文件中,如所有Student类型的对象尽量放在一个规则文件中
规则结果部分(RHS)尽量不要有条件语句,如if(...),尽量不要有复杂的逻辑和深层次的嵌套语句
每个规则最好都加上salience属性,明确执行顺序
Drools默认dialect为"Java",尽量避免使用dialect "mvel"
# 整合springboot
# workbench
参考视频:
Drools规则引擎讲解教程,从入门到整合实战_哔哩哔哩_bilibili (opens new window)
参考文档: