Drools基础语法

规则文件的构成

drl是Drools Rule Language的缩写。在规则文件中编写具体的规则内容。

一套完整的规则文件内容构成由以下元素:

  • package:包名,package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问
  • import:用于导入类或静态方法
  • global:全局变量
  • function:自定义函数
  • query:查询
  • rule end:规则体

规则体语法结构

一个规则通常包括三个部分:属性部分(attribute)、条件部分(LHS)、结果部分(RHS)

1
2
3
4
5
6
7
rule "ruleName"    //rule关键字,表示规则开始,参数为规则的唯一名称
attributes //规则属性,是rule与when之间的参数,为可选项
when //关键字,后面是规则的条件部分
LHS //Left Hand Side,是规则的条件部分
then //后面跟规则的结果部分
RHS //是规则的结果或者行为
end //表示一个规则的结束

条件部分

LHS(Left Hand Side): 是规则的条件部分的通用名称。它由零个或者多个条件元素组成。如果LHS为空,则它将被视为始终为true的条件元素。

LHS部分的条件又称为pattern。

1
pattern的语法结构为:绑定变量名:Object(Field约束)

其中绑定变量名可以省略,通常绑定变量名的命名一般建议以$开始。如果定义了绑定了绑定变量名,就可以在规则体的RHS部分使用此绑定变量名来操作响应的Fact对象。Field约束部分是需要返回true或者false的0个或者多个表达式。

1
2
3
4
5
6
7
8
9
10
//规则1:100元以下,不加积分
rule "score_1"
when
//工作内存中必须存在Order这种类型的Fact对象——类型约束
//Fact对象的amout属性值必须小于等于100——属性约束
$order:Order(amout <= 100)
then
$order.setScore(0);
System.out.println("成功匹配到规则1:100元以下,不加积分");
end

如果LHS部分为空的话,那么引擎会自动添加一个eval(true)的条件,由于条件总是返回true,所以LHS为空的规则总是返回true

1、约束连接

在LHS当中,可以包含0~n个条件,多个pattern之间可以采用”&&”、”||”、”,”来实现,也可以不写,默认连接为”&&”

“&&”:表示and

“||”:表示or

“,”:表示and

1
2
3
4
5
6
7
8
//规则2:100元~500元,加100积分
rule "score_2"
when
$order:Order(amout > 100 && amout <= 500)
then
$order.setScore(100);
System.out.println("匹配到规则2:100元~500元,加100积分");
end

2、比较操作符

在Drools中一共提供了十二种类型的比较操作符,分别是:>、>=、<、<=、==、!=、contains、not contains、memberof、not memberof、matches、not matches;

符号 说明
> 大于
< 小于
>= 大于等于
<= 小于等于
== 等于
!= 不等于
contains 检查一个Fact对象的某个属性是否包含一个指定对象值
not contains 检查一个Fact对象的某个属性值是否不包含一个指定对象值
memberOf 判断一个Fact对象的某个属性是否在一个或多个集合中
not memberOf 判断一个Fact对象的某个属性是否不在一个或多个集合中
matches 判断一个Fact对象的属性是否与提供的标准的java正则表达式进行匹配
not matches 判断一个Fact对象的属性是否不与提供的标准的java正则表达式进行匹配

示例:

第一步:创建实体类,用于测试比较操作符

1
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
package com.lhb.entity;

import java.util.List;

public class Customer {
//客户姓名
private String name;
private List<Order> orderList;//订单集合

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public List<Order> getOrderList() {
return orderList;
}

public void setOrderList(List<Order> orderList) {
this.orderList = orderList;
}
}

第二步:在resources/rules下创建规则文件customer-rules.drl

1
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
34
35
36
37
38
package rules

import com.lhb.entity.*;

//测试contains规则
rule "rule1"
when
$order:Order();
$customer:Customer(orderList contains $order);
then
System.out.println("测试contains规则触发:"+$customer.getName());
end

//测试not contains规则
rule "rule2"
when
$order:Order();
$customer:Customer(orderList not contains $order);
then
System.out.println("测试not contains规则触发:"+$customer.getName());
end


//测试比较操作符matches
rule "rule3"
when
Customer(name matches "张.*")
then
System.out.println("测试比较操作符matches触发...");
end

//测试比较操作符not matches
rule "rule4"
when
Customer(name not matches "张.*")
then
System.out.println("测试比较操作符not matches触发...");
end

第三步:编写单元测试

1
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
@Test
public void test2(){
KieServices kieServices = KieServices.Factory.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
//会话对象,用于和规则引擎交互
KieSession kieSession = kieContainer.newKieSession();
//构造订单对象,设置订单金额,由规则引擎计算获得的积分
Order order = new Order();
//匹配规则:$order:Order();
kieSession.insert(order);




Customer customer = new Customer();
List<Order> orderList = new ArrayList<>();
//匹配规则: $customer:Customer(orderList contains $order);
//orderList.add(order);
customer.setOrderList(orderList);
customer.setName("Jack");


//将数据交给规则引擎,规则引擎会根据提供的数据进行规则匹配
kieSession.insert(customer);


//激活规则引擎,如果匹配成功则执行规则
kieSession.fireAllRules();
//关闭会话
kieSession.dispose();
}

注意:

在调用规则代码时,满足条件的规则都会被执行。那么如果我们只想执行其中的某个规则要怎么实现呢?Drools给我们提供的方式是通过规则过滤器来实现执行指定规则。对于规则文件不用做任何修改,只需要改Java代码即可

如下:

1
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
@Test
public void test2(){
KieServices kieServices = KieServices.Factory.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
//会话对象,用于和规则引擎交互
KieSession kieSession = kieContainer.newKieSession();
//构造订单对象,设置订单金额,由规则引擎计算获得的积分
Order order = new Order();
kieSession.insert(order);


Customer customer = new Customer();
List<Order> orderList = new ArrayList<>();
//orderList.add(order);
customer.setOrderList(orderList);
customer.setName("Jack");


//将数据交给规则引擎,规则引擎会根据提供的数据进行规则匹配
kieSession.insert(customer);


//通过规则过滤器实现只执行指定规则
kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rule4"));
//关闭会话
kieSession.dispose();
}

结果部分

在Drools中RHS里,提供了一些对当前Working Memory实现快速操作的宏函数或对象,比如insert、insertLogical、update和retract就可以实现对当前Working Memory中的Fact对象进行新增、删除或修改

1. insert

函数insert的作用与我们在Java类当中调用StatefulKnowledgeSession对象的insert方法作用相同,都是用来将一个Fact对象插入到当前的Working Memory中。

需要注意的是:一旦调用insert宏函数,那么Drools会重新与所有的规则再重新匹配一次,对于没有设置no-loop属性为true的规则,如果满足条件,不管其之前是否执行过都会再执行一次,这个特性不仅存在于insert宏函数上,后面介绍的update、retract宏函数同样也有这个特性所以在某些情况下因考虑不周调用insert、update或retract容易发送死循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//Drools提供的内置方法insert

rule "rule5"
when
eval(true); //默认成立
then
Customer cus=new Customer();
cus.setName("张三");
insert(cus);
System.out.println("测试Drools提供的内置方法insert 触发...");
end

rule "rule6"
when
$customer:Customer(name =="张三");
then
System.out.println("测试Drools提供的内置方法insert 触发..."+$customer.getName());
end

测试:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test3(){
KieServices kieServices = KieServices.Factory.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
//会话对象,用于和规则引擎交互
KieSession kieSession = kieContainer.newKieSession();
//激活规则引擎,如果匹配成功则执行规则
kieSession.fireAllRules();
//关闭会话
kieSession.dispose();
}

insertLogical

insertLogical 作用与 insert 类似,它的作用也是将一个 Fact 对象插入到当前的 Working Memroy 当中

2. update

update函数意义与其名称一样,用来实现对当前Working Memory当中的Fact进行更新,用来告诉当前Working Memory该Fact对象已经发生了变化

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
rule "rule7"
//no-loop true
when
$customer:Customer(name =="李四");
then
$customer.setName("张三");
update($customer);
System.out.println("测试Drools提供的内置方法update 触发...");
end

rule "rule8"
when
$customer:Customer(name =="张三");
then
System.out.println("测试Drools提供的内置方法update 触发..."+$customer.getName());
end

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void test3(){
KieServices kieServices = KieServices.Factory.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
//会话对象,用于和规则引擎交互
KieSession kieSession = kieContainer.newKieSession();
Customer customer = new Customer();
customer.setName("李四");
kieSession.insert(customer);

//激活规则引擎,如果匹配成功则执行规则
kieSession.fireAllRules();
//关闭会话
kieSession.dispose();
}

3. retract

retract用来将Working Memory当中某个Fact对象从Working Memory当中删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Drools提供的内置方法retract
rule "rule9"
when
$customer:Customer(name =="李四");
then
//retract($customer);
System.out.println("测试Drools提供的内置方法retract 触发...");
end

rule "rule10"
when
$customer:Customer();
then
System.out.println("测试Drools提供的内置方法retract 触发..."+$customer.getName());
end

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test3(){
KieServices kieServices = KieServices.Factory.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
//会话对象,用于和规则引擎交互
KieSession kieSession = kieContainer.newKieSession();
Customer customer = new Customer();
customer.setName("李四");
kieSession.insert(customer);

//激活规则引擎,如果匹配成功则执行规则
kieSession.fireAllRules();
//通过规则过滤器实现只执行指定规则
//kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rule5"));

//关闭会话
kieSession.dispose();
}

属性部分

Drools中提供的属性如下表:

属性名 说明
salience 指定规则执行优先级
dialect 指定规则使用的语言类型,取值为java和mvel
enabled 指定规则是否启用
date-effective 指定规则生效时间
date-expires 指定规则失效时间
activatioin-group 激活分组,具有相同分组名称的规则只能有一个规则触发
agenda-group 议程分组,只有获取焦点的组中的规则才有可能触发
timer 定时器,指定规则触发的时间
auto-focus 自动获取焦点,一般结合agenda-group一起使用
no-loop 防止死循环

1. salience

作用是用来设置规则执行的优先级,salience属性的值是一个数字,数字越大执行优先级越高。默认情况下,规则的salience默认是0,如果不设置salience属性,规则体的执行顺序为由上到下。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rule "attributes_rule1"
salience 1
when
eval(true)
then
System.out.println("rule1....");
end

rule "attributes_rule2"
salience 2
when
eval(true)
then
System.out.println("rule2....");
end

2. no-loop

作用是用来控制已经执行过的规则在条件再次满足时是否再次执行。no-loop属性的值是有一个布尔型,默认情况下规则的no-loop属性的值为false,如果no-loop属性值为true,那么就表示该规则只会被引擎检查一次,如果满足条件就执行规则的RHS部分,如果引擎内部因为对Fact更新引起引擎再次启动检查规则,那么它会忽略掉所有的no-loop属性设置为true的规则。

示例:

1
2
3
4
5
6
7
8
9
10
//测试no-loop
rule "attributes_rule3"
salience 1
no-loop true
when
$customer:Customer(name=="张三")
then
update($customer);
System.out.println("customer name:"+$customer.getName());
end

3. date-effective

作用是用来控制规则只有在达到设置时间后才会触发,在规则运行时,引擎会自动拿当前操作系统的时间与date-effective设置的时间值进行比较,只有当系统时间>=date-effective设置的时间时,规则才会触发执行,否则将不执行。在没有设置该属性的情况下,规则随时可以触发,没有限制。date-effective的值为一个日期型字符串,默认情况下,date-effective可接受的日期格式为”dd-MM-yyyy”,在实际使用的过程中,如果不想使用默认的时间格式,可以通过Java代码来设置时间格式

1
2
// System.setProperty(String key, String value)
System.setProperty("drools.dateformat","yyyy-MM-dd");

示例:

1
2
3
4
5
6
7
8
//测试date-effective
rule "attributes_rule4"
date-effective "2021-11-20" //当前日期不小于2021-11-25时可以执行
when
eval(true);
then
System.out.println("attributes_rule4 is execution!");
end

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//测试属性部分
@Test
public void test4(){
//设置修改默认的时间格式
System.setProperty("drools.dateformat","yyyy-MM-dd");
KieServices kieServices = KieServices.Factory.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
//会话对象,用于和规则引擎交互
KieSession kieSession = kieContainer.newKieSession();
Customer customer = new Customer();
customer.setName("张三");
kieSession.insert(customer);
//激活规则引擎,如果匹配成功则执行规则
kieSession.fireAllRules();
//通过规则过滤器实现只执行指定规则
//kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rule5"));

//关闭会话
kieSession.dispose();
}

4. date-expires

作用是与date-effective属性恰恰相反,date-expires的作用是用来设置规则的失效时间,引擎在执行规则的时候,会检查规则有没有date-expires属性,如果有设置的话,那么将这个属性的值与当前系统时间进行比较,如果大于系统时间,那么规则就执行,否则就不执行。

5. enabled

作用是用来定义一个规则是否可用的。该属性的值是一个布尔值,默认该属性的值为true,表示规则是可用的。设置其enabled属性值为false,那么引擎就不会执行该规则

6. dialect

作用是用来定义规则当中要使用的语言模型,目前支持两种类型的语言:mvel和Java,默认情况下,如果没有手工设置规则的dialect,那么使用的Java语言

7. activation-group

作用是将若干个规则划分成一个组,用一个字符串来给这个组命名,这样在执行的时候,具有相同activation-group属性的规则中只有一个会被执行,其他规则将不生效。也就是在相同activation-group属性规则中,只有一个规则会被执行,其他规则将不被执行,当然对于具有相同activation-group属性的规则当中究竟哪一个会先执行,可以用属性salience来实现

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//测试activation-group
rule "attributes_rule5"
activation-group "test"
when
eval(true)
then
System.out.println("attributes_rule5 execute...");
end

rule "attributes_rule6"
activation-group "test"
when
eval(true)
then
System.out.println("attributes_rule6 execute...");
end

8. agenda-group

作用是agenda-group属性的值也是一个字符串,通过这个字符串,可以将规则分为若干个Agenda Group,默认情况下,引擎在调用这些设置了agenda-group属性的规则的时候需要显式指定某个Agenda Group得到Focus(焦点),这样位于该Agenda Group当中的规则才会触发执行,否则将不执行

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//测试agenda-group
rule "attributes_rule7"
agenda-group "001"
when
eval(true)
then
System.out.println("attributes_rule7 execute");
end

rule "attributes_rule8"
agenda-group "002"
when
eval(true)
then
System.out.println("attributes_rule8 execute...");
end

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//测试属性部分
@Test
public void test4(){
//设置修改默认的时间格式
System.setProperty("drools.dateformat","yyyy-MM-dd");
KieServices kieServices = KieServices.Factory.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
//会话对象,用于和规则引擎交互
KieSession kieSession = kieContainer.newKieSession();

kieSession.getAgenda().getAgendaGroup("002").setFocus(); //获得执行焦点

//激活规则引擎,如果匹配成功则执行规则
kieSession.fireAllRules();
//通过规则过滤器实现只执行指定规则
//kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rule5"));

//关闭会话
kieSession.dispose();
}

9. auto-focus

作用是用来在已设置了agenda-group的规则上设置该规则是否可以自动获取Focus,如果该属性设置为true,那么在引擎执行时,就不需要显式的为某个Agenda Group设置Focus,否则需要显式设置。

10. timer

timer属性可以通过定时器的方式指定规则执行的时间,使用方式有两种:

方式一: timer(int: ?)

此方式遵循java.util.Timer对象的使用方式,第一个参数表示几秒后执行,第二个参数表示每隔几秒执行一次,第二个参数为可选

方式二: timer(cron: )

此种方式使用标准的unix cron表达式的使用方式来定义规则执行的时间

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.text.SimpleDateFormat
import java.util.Date
/*
此规则文件用于测试timer属性
*/

rule "rule_timer_1"
timer (5s 2s) //含义:5秒后触发,然后每隔2秒触发一次
when
then
System.out.println("规则rule_timer_1触发,触发时间为:" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
end

rule "rule_timer_2"
timer (cron:0/1 * * * * ?) //含义:每隔1秒触发一次
when
then
System.out.println("规则rule_timer_2触发,触发时间为:" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
end

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//测试属性部分
@Test
public void test5() throws InterruptedException {
//设置修改默认的时间格式
System.setProperty("drools.dateformat","yyyy-MM-dd");
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
final KieSession kieSession = kieClasspathContainer.newKieSession();

new Thread(new Runnable() {
public void run() {
//启动规则引擎进行规则匹配,直到调用halt方法才结束规则引擎
kieSession.fireUntilHalt();
}
}).start();

Thread.sleep(10000);
//结束规则引擎
kieSession.halt();
kieSession.dispose();
}

注意:

单元测试的代码和以前的有所不同,因为我们规则文件中使用到了timer进行定时执行,需要程序能够持续一段时间才能看到定时器触发效果

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~

支付宝
微信