Drools入门之drl规则结构讲解
上一节我们写了一个Drools的hello world小例子,里面涉及了.drl
文件。这一节我们来讲讲它。
drl规则文件
.drl
文件,这是其中一种规则文件,也可以是.xml
和.drls
文件,甚至还可以是.xls
或.xlsx
文件。其中最基本的就是.drl
文件,下面就对该文件的基本结构作讲解。
规则文件名
规则文件名即hello.drl,其命名规则不像Java那样要求首字母大写。虽然要求并不严格,但对规则文件命名时也最好要规范,见名知意,既方便自己,又方便他人。
规则文件的内容
- package:关键字package为三大块中的包路径,这里的包路径是逻辑路径,理论上是可以随便定义的,并且是必填内容。为了更方便地开发,建议在编辑包路径时最好与文件目录同名,类似Java一样以小数点(.)的方式隔开,且必须用小数点隔开。这里要提醒一下读者,在规则文件中关键字package永远在代码的第一行(规则模板除外)。
- rule:关键字rule为三大块中的规则体,是核心内容之一,以关键字rule开头,以end结尾,每个规则文件中可以包含多个rule规则体,但rule规则体之间不能交叉使用,即一个rule只能对应一个end。rule的参数是可以随意定义的,rule的参数指规则名,建议读者在编写规则名时以驼峰式命名,虽然有时可以不添加双引号(“”),但还是建议每一个规则名都加上引号,以避免出现编译报错的问题。同一个规则库[4]中相同规则逻辑路径下的规则名不可以相同,可以理解为规则名即是一个ID,不可重复。
- import:关键字import为三大块中的引用,它与Java引用其他类是一样的,其目的是为了对象类引用。与Java不同的是,在引用静态方法时,需要添加function关键字。
规则体说明
规则体是规则文件内容中的核心,分为LHS、RHS两大功能模块。
-
LHS:条件部分又被称为Left Hand Side(LHS),即规则体when与then中间的部分。在LHS中,可以包含0~n个条件(非常类似Java语法中的if判断语句),如果LHS部分为空,那么引擎会自动添加一个eval(true)条件,由于该条件总是返回true,因此LHS为空的规则体也总是返回true。其内容为:
rule "test001" when // 这里为空表示 eval(true); then System.out.println("hello world"); end
-
RHS:结果部分又被称为Right Hand Side,即一个规则体中then与end之前的部分,只有LHS部分的条件都满足时RHS部分才会被执行。这里要注意的是,在Rete的算法中,规则在匹配时只会执行LHS为true的规则;加载规则时,会将所有规则体中的LHS部分先执行,即当前规则库中的LHS部分会被先一步加载。例如,当前规则体中的LHS条件为false时也是会被加载的,这可以理解为规则执行前的预加载功能,区别在于规则体的RHS部分不进行运算。只有Fact对象发生了改变,规则体才有可能重新被激活,之前为false的LHS就有可能变成true。
RHS才是规则体真正做事情的部分,即要处理和返回业务结果的部分。可以将条件满足而触发的动作写在该部分,在RHS中可以使用LHS定义绑定变量名、设置全局变量,或者直接编写Java代码(对于要用到的Java类或静态方法需要在规则文件中用import将该类引入后才能使用,这与Java文件的编写原则相同)。规则体中的LHS部分是用来放置条件的,RHS部分是编写满足条件后处理结果的,虽然RHS部分可以直接编写Java脚本,但不建议在RHS中有条件判断。如果需要条件判断,那么要重新考虑将其放在LHS中,否则就违背使用规则的初衷了。
-
Fact。上述说明中提到了一个关键字Fact,它在规则引擎中是非常重要的,在介绍对象引用章节之前,必须先将其概念性的知识点进行一个说明。它也是一个必须要理解的概念。
Drools规则引擎中传递的数据,术语称Fact对象。Fact对象是一个普通的JavaBean(不只是JavaBean对象,也可以是任何Object对象),规则体中可以对当前对象进行任何的读/写操作,调用该对象提供的方法。当一个Fact(JavaBean)插入Working Memory(内存储存)中,规则体使用的是原有对象的引用(并不是克隆,与Java变量性质相似),规则体通过操作Fact对象来实现对应用数据的管理,对于其中的属性,需要提供getter setter或“Object”的可操作方法。执行规则时,可以动态地向当前Working Memory插入、删除或更新Fact对象。
规则进行计算时需要用到应用系统中的数据,先将这些数据设置到Fact对象中,然后将其插入规则的Working Memory中,一个Fact对象通常是一个具有getter方法和setter方法的POJO对象,由Java代码进行insert操作。通过getter方法和setter方法可以方便地对Fact对象进行操作,所以可以更通俗地把Fact对象理解为规则与应用系统数据交互的桥梁或通道。
当Fact对象插入Working Memory后,会与当前Working Memory中所有的规则进行匹配,同时返回一个FactHandler对象。FactHandler对象是插入WorkingMemory中Fact对象的引用句柄,通过FactHandler对象可以实现对相应的Fact对象通过API进行删除及修改等操作。
在RHS中提供了一些对当前Working Memory实现快速操作的宏函数或对象,如insert/insertLogical、update/modify与retract/delete。通过这些函数可以实现对当前Working Memory的Fact对象进行新增、修改或删除的操作。如果读者还要使用Drools中提供的其他方法,那么也可以使用其他的宏函数进行更多的操作。
对象引用
关键字import是用来导入规则文件需要使用的外部对象,这里的使用方法与Java相似,与Java不同的是,import不仅可以引入类,也可以导入类中的某一个可访问的静态方法。
创建一个Person类
package com.domain;
public class Person {
private Integer age;
private String name;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
然后我们新增一条规则test002
package rules
import com.domain.Person
rule "test001"
when
eval(true);
then
System.out.println("hello world");
end
rule "test002"
when
$p:Person()
then
System.out.println("输出引用对象"+$p);
end
在代码中注入fact对象
public class RulesHello {
public static void main(String[] args) {
KieServices kieService = KieServices.Factory.get();
KieContainer kieContainer = kieService.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("testhelloworld");
Person person = new Person();
person.setAge(20);
person.setName("张三");
kieSession.insert(person);
int count = kieSession.fireAllRules();
System.out.println("总共执行了"+count+"条规则");
kieSession.dispose();
}
}
其中kieSession.insert(person);
就是注入fact对象。
运行代码,观察输出日志,可以看到test002已被执行
hello world
输出引用对象Person{age=20, name='张三'}
总共执行了2条规则
条件
判断张三的年龄是否是20岁,新增一条规则
rule "test003"
when
$p:Person(age == "20",name == "张三")
then
System.out.println("张三的年龄为20");
end