Drools规则引擎技术指南
上QQ阅读APP看书,第一时间看更新

2.1 经典Hello World

在讲Drools规则引擎的使用前,先要了解它的文件扩展名,因为这会关系到后面的知识点。Drools规则引擎文件扩展名有很多种,最基本的是*.drl文件,也可以是*.xml[1]和*.drls文件,甚至还可以是*.xls或*.xlsx[2]文件。为什么会有这样多的规则文件呢?因为每一种规则文件都代表一类规则的应用。本章将用最基本也是最常用的*.drl文件方式对Drools规则引擎进行详细说明。

创建一个空项目,这里除了JDK 1.8外,什么都不需要引用,创建资源目录后创建Java的目录。在资源文件夹中创建rules/ rulesHello文件夹,如图2-1所示。

019-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下是需要安装插件才可以使其变化的,读者不必担心为什么显现方式与本书中不同。

019-2

图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所示。

021-1

图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中提供的其他方法,那么也可以使用其他的宏函数进行更多的操作。