规则引擎Drools语法要点
1、规则文件构成
在使用Drools时非常重要的一个工作就是编写规则文件,通常规则文件的后缀为.drl。
**drl是Drools Rule Language的缩写**。在规则文件中编写具体的规则内容。
一套完整的规则文件内容构成如下:
| 关键字 | 描述 |
| :------- | :----------------------------------------------------------- |
| package | 包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用 |
| import | 用于导入类或者静态方法 |
| global | 全局变量 |
| function | 自定义函数 |
| query | 查询 |
| rule end | 规则体 |
Drools支持的规则文件,除了drl形式,还有Excel文件类型的。
2、规则体语法结构
规则体是规则文件内容中的重要组成部分,是进行业务规则判断、处理业务结果的部分。
规则体语法结构如下:
rule "ruleName"attributeswhenLHS thenRHS
endrule:关键字,表示规则开始,参数为规则的唯一名称。
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约束)
例如:$order:Order
其中绑定变量名可以省略,通常绑定变量名的命名一般建议以$开始。如果定义了绑定变量名,就可以在规则体的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属性值必须小于100------属性约束
以上条件必须同时满足当前规则才有可能被激活。
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方法的作用是更新工作内存中的数据,并让相关的规则重新匹配。(要避免死循环)
参数:
```java
//Fact对象,事实对象
Order order = new Order();
order.setAmout(30);
```
规则:
```java
//规则一: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方法的作用是向工作内存中插入数据,并让相关的规则重新匹配。
```java
//规则一: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方法的作用是删除工作内存中的数据,并让相关的规则重新匹配。
```java
//规则一:100元以下 不加分
rule "order_rule_1"
when
$order:Order(amout < 100)
then
retract($order) //retract方法的作用是删除工作内存中的Fact对象,会导致相关规则重新匹配
System.out.println("成功匹配到规则一:100元以下 不加分");
end
```
7、规则属性attributes
前面我们已经知道了规则体的构成如下:
```java
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属性,规则体的执行顺序为由上到下。
可以通过创建规则文件salience.drl来测试salience属性,内容如下:
```java
package com.order
rule "rule_1"
when
eval(true)
then
System.out.println("规则rule_1触发");
end
rule "rule_2"
when
eval(true)
then
System.out.println("规则rule_2触发");
end
rule "rule_3"
when
eval(true)
then
System.out.println("规则rule_3触发");
end
```
通过控制台可以看到,由于以上三个规则没有设置salience属性,所以执行的顺序是按照规则文件中规则的顺序由上到下执行的。接下来我们修改一下文件内容:
```java
package com.order
rule "rule_1"
salience 9
when
eval(true)
then
System.out.println("规则rule_1触发");
end
rule "rule_2"
salience 10
when
eval(true)
then
System.out.println("规则rule_2触发");
end
rule "rule_3"
salience 8
when
eval(true)
then
System.out.println("规则rule_3触发");
end
```
通过控制台可以看到,规则文件执行的顺序是按照我们设置的salience值由大到小顺序执行的。
建议在编写规则时使用salience属性明确指定执行优先级。
7.2、no-loop属性
no-loop属性用于防止死循环,当规则通过update之类的函数修改了Fact对象时,可能使当前规则再次被激活从而导致死循环。取值类型为Boolean,默认值为false,测试步骤如下:
编写规则文件/resources/rules/activationgroup.drl
```java
//订单积分规则
package com.order
import com.atguigu.drools.model.Order
//规则一:100元以下 不加分
rule "order_rule_1"
no-loop true //防止陷入死循环
when
$order:Order(amout < 100)
then
$order.setScore(0);
update($order)
System.out.println("成功匹配到规则一:100元以下 不加分");
end
```
通过控制台可以看到,由于我们没有设置no-loop属性的值,所以发生了死循环。接下来设置no-loop的值为true再次测试则不会发生死循环。
8、Drools高级语法
前面已经介绍了一套完整的规则文件内容构成如下:
| 关键字 | 描述 |
| :------- | :----------------------------------------------------------- |
| package | 包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用 |
| import | 用于导入类或者静态方法 |
| global | 全局变量 |
| function | 自定义函数 |
| query | 查询 |
| rule end | 规则体 |
重点说一下global属性
global全局变量
global关键字用于在规则文件中**定义全局变量**,它可以让应用程序的对象在规则文件中能够被访问。可以用来为规则文件提供数据或服务。
语法结构为:**global 对象类型 对象名称**
在使用global定义的全局变量时有两点需要注意:
1、如果对象类型为**包装类型**时,在一个规则中改变了global的值,那么**只针对当前规则有效**,对其他规则中的global不会有影响。可以理解为它是当前规则代码中的global副本,规则内部修改不会影响全局的使用。
2、如果对象类型为**集合类型或JavaBean**时,在一个规则中改变了global的值,对java代码和所有规则都有效。
订单Order:
```java
package com.atguigu.drools.model;
public class Order {
private double amout;
public double getAmout() {
return amout;
}
public void setAmout(double amout) {
this.amout = amout;
}
}
```
积分Integral:
```java
package com.atguigu.drools.model;
public class Integral {
private double score;
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}
```
规则文件:
```java
//订单积分规则
package com.order
import com.atguigu.drools.model.Order
global com.atguigu.drools.model.Integral integral;
//规则一:100元以下 不加分
rule "order_rule_1"
no-loop true //防止陷入死循环
when
$order:Order(amout < 100)
then
integral.setScore(10);
update($order)
System.out.println("成功匹配到规则一:100元以下 不加分");
end
```
测试:
```java
@Test
public void test1(){
//从Kie容器对象中获取会话对象
KieSession session = kieContainer.newKieSession();
//Fact对象,事实对象
Order order = new Order();
order.setAmout(30);
//全局变量
Integral integral = new Integral();
session.setGlobal("integral", integral);
//将Order对象插入到工作内存中
session.insert(order);
//激活规则,由Drools框架自动进行规则匹配,如果规则匹配成功,则执行当前规则
session.fireAllRules();
//关闭会话
session.dispose();
System.out.println("订单金额:" + order.getAmout());
System.out.println("添加积分:" + integral.getScore());
}
```
