2.1 经典Hello World
在讲Drools规则引擎的使用前,先要了解它的文件扩展名,因为这会关系到后面的知识点。Drools规则引擎文件扩展名有很多种,最基本的是*.drl文件,也可以是*.xml[1]和*.drls文件,甚至还可以是*.xls或*.xlsx[2]文件。为什么会有这样多的规则文件呢?因为每一种规则文件都代表一类规则的应用。本章将用最基本也是最常用的*.drl文件方式对Drools规则引擎进行详细说明。
创建一个空项目,这里除了JDK 1.8外,什么都不需要引用,创建资源目录后创建Java的目录。在资源文件夹中创建rules/ rulesHello文件夹,如图2-1所示。
图2-1 项目结构
在当前目录下创建hello.drl文件,其代码为:
package rules.rulesHello rule "test001" when eval(true); then System.out.println("hello world"); end
如图2-2所示,创建一个以.drl为扩展名的文件,在IntelliJ IDEA工具下图标都变了。当然,如果在Eclipse下是需要安装插件才可以使其变化的,读者不必担心为什么显现方式与本书中不同。
图2-2 规则文件内容
hello.drl代码就是一个标准的规则文件。其中,package表示规则逻辑路径,rule表示规则开始,end表示规则结束,test001表示规则名,when表示规则条件,then表示规则返回结果。在关键字then中的代码是Java的打印语句。在规则引擎脚本中可以写Java代码,这是因为Drools是基于Java语言开发的。
简单说明了规则文件中各行代码表示的含义后,就要执行规则了,看其能否在控制台输出hello world。
执行规则文件,就不得不引用Drools相关的jar包。这时就将项目变成一个Maven项目,并引用Drools的相关jar包。pom.xml文件内容为:
<properties> <drools.version>7.10.0.Final</drools.version> </properties> <dependencies> <dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>${drools.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> </dependency> </dependencies>
这里只引用Drools相关的jar包是不够的,接下来需要在资源目录中创建一个META-INF目录,并创建一个kmodule.xml配置文件,其内容为:
<?xml version="1.0" encoding="UTF-8"?> <kmodule xmlns="http://www.drools.org/xsd/kmodule"> <kbase name="rules" packages="rules.rulesHello"> <ksession name="testhelloworld"/> </kbase> </kmodule>
完成如上配置后,创建调用规则的Java文件。创建包名com.rulesHello,并添加Java文件RulesHello.java,其内容为:
package com.rulesHello; import org.kie.api.KieServices; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; public class RulesHello { public static void main(String[] args) { KieServices kss = KieServices.Factory.get(); KieContainer kc = kss.getKieClasspathContainer(); KieSession ks =kc.newKieSession("testhelloworld"); int count = ks.fireAllRules(); System.out.println("总执行了"+count+"条规则"); ks.dispose(); } }
执行RulesHello类的主函数main,可以看到控制台输出以下代码,如图2-3所示。
图2-3 规则执行结果
看到控制台成功输出了规则文件then中的内容,就证明规则测试成功。上述规则代码中只是一个非常简单的例子,只要触发了对应的ks. fireAllRules()方法就会在控制台输出“hello world”。虽然规则代码看似非常简单,但整个执行过程却是非常复杂的。下面对规则文件的内容进行扩展说明。
规则文件内容一般包含三大块,即包路径、引用、规则体。一个最简单的规则至少要包含包路径与规则体。规则文件还有一个重点,即规则文件名,接下来将对规则文件中的三大块及规则文件名进行阐述。
1. 规则文件名
规则文件名即hello.drl,其命名规则不像Java那样要求首字母大写。虽然要求并不严格,但对规则文件命名时也最好要规范,见名知意,既方便自己,又方便他人。
2. 规则文件的内容
(1)package。关键字package为三大块中的包路径,这里的包路径是逻辑路径,理论上是可以随便定义的,并且是必填内容。为了更方便地开发,建议读者在编辑包路径时最好与文件目录同名,类似Java一样以小数点(.)的方式隔开,且必须用小数点隔开。这里要提醒一下读者,在规则文件中关键字package永远在代码的第一行(规则模板除外)。
(2)rule。关键字rule为三大块中的规则体,是核心内容之一,以关键字rule开头,以end结尾,每个规则文件中可以包含多个rule规则体,但rule规则体之间不能交叉使用,即一个rule只能对应一个end。rule的参数是可以随意定义的,rule的参数指规则名,建议读者在编写规则名时以驼峰式命名,虽然有时可以不添加双引号("")[3],但还是建议每一个规则名都加上引号,以避免出现编译报错的问题。同一个规则库[4]中相同规则逻辑路径下的规则名不可以相同,可以理解为规则名即是一个ID,不可重复。
(3)import。关键字import为三大块中的引用,它与Java引用其他类是一样的,其目的是为了对象类引用。与Java不同的是,在引用静态方法时,需要添加function[5]关键字。
3. 规则体说明
规则体是规则文件内容中的核心,分为LHS、RHS两大功能模块。
(1)LHS。条件部分又被称为Left Hand Side(LHS),即规则体when与then中间的部分。在LHS中,可以包含0~n个条件(非常类似Java语法中的if判断语句),如果LHS部分为空,那么引擎会自动添加一个eval(true)条件,由于该条件总是返回true,因此LHS为空的规则体也总是返回true。其内容为:
package rules.rulesHello rule "test001" when //这里为空 则表示 eval(true) then System.out.println("hello world"); end
规则体LHS不会如此简单,业务规则是否正确关键在业务条件,业务条件是否可以满足决定返回结果是否正确。换句话说,只要有一个业务条件不满足,规则体就不会执行RHS模块,LHS部分为OR的关系除外。所以规则体LHS部分与Java中的逻辑运算符的功能是一样的。
(2)RHS。结果部分又被称为Right Hand Side,即一个规则体中then与end之前的部分,只有LHS部分的条件都满足时RHS部分才会被执行。这里要注意的是,在Rete[6]的算法中,规则在匹配时只会执行LHS为true的规则;加载规则时,会将所有规则体中的LHS部分先执行,即当前规则库中的LHS部分会被先一步加载。例如,当前规则体中的LHS条件为false时也是会被加载的,这可以理解为规则执行前的预加载功能,区别在于规则体的RHS部分不进行运算。只有Fact对象发生了改变,规则体才有可能重新被激活,之前为false的LHS就有可能变成true。
RHS才是规则体真正做事情的部分,即要处理和返回业务结果的部分。可以将条件满足而触发的动作写在该部分,在RHS中可以使用LHS定义绑定变量名、设置全局变量,或者直接编写Java代码(对于要用到的Java类或静态方法需要在规则文件中用import将该类引入后才能使用,这与Java文件的编写原则相同)。
规则体中的LHS部分是用来放置条件的,RHS部分是编写满足条件后处理结果的,虽然RHS部分可以直接编写Java脚本,但不建议在RHS中有条件判断。如果需要条件判断,那么要重新考虑将其放在LHS中,否则就违背使用规则的初衷了。
(3)Fact。上述说明中提到了一个关键字Fact,它在规则引擎中是非常重要的,在介绍对象引用章节之前,必须先将其概念性的知识点进行一个说明。它也是一个必须要理解的概念,希望读者可以认真多读几遍加深印象。
Drools规则引擎中传递的数据,术语称Fact对象[7]。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对象是插入Working Memory中Fact对象的引用句柄,通过FactHandler对象可以实现对相应的Fact对象通过API进行删除及修改等操作。
在RHS中提供了一些对当前Working Memory实现快速操作的宏函数或对象,如insert/insertLogical、update/modify与retract/delete。通过这些函数可以实现对当前Working Memory的Fact对象进行新增、修改或删除的操作。如果读者还要使用Drools中提供的其他方法,那么也可以使用其他的宏函数进行更多的操作。