• 沒有找到結果。

从 Drools 规则引擎到风控反洗钱

N/A
N/A
Protected

Academic year: 2021

Share "从 Drools 规则引擎到风控反洗钱"

Copied!
72
0
0

加載中.... (立即查看全文)

全文

(1)

从 Drools 规则引擎到风控反洗钱

——《Drools7.0.0.Final 规则引擎教程》

修订日期 变更版本 变更描述 修订方法 修订人

2017/7/05 V0.1 创建、新增《Drools 简介》 创建 朱智胜

2017/7/06 V0.1 追溯 Drools5 的使用 新增 朱智胜

2017/7/07 V0.1 Hello World 实例 新增 朱智胜

2017/7/11 V0.1 KIE&FACT 新增 朱智胜

2017/7/12 V0.1 KIE API 解析 新增 朱智胜

2017/7/15 V0.2.1 规则文件 新增 朱智胜

2017/7/15 V0.2.1 no-loop&lock-on-active 新增 朱智胜 2017/7/18 V0.2.1 ruleflow-group& salience 新增 朱智胜 2017/7/19 V0.2.1 agenda-group& auto-focus 新增 朱智胜 2017/7/20 V0.2.1 activation-group& dialect& date-

effective& date-expires&duration&

enabled

新增 朱智胜

2017/7/21 V0.2.2 timer 新增 朱智胜

2017/7/27 V0.2.2 calendar 新增 朱智胜

2017/7/28 V0.2.3 LHS 新增 朱智胜

2017/7/29 V0.2.3 Pattern&约束 新增 朱智胜

2017/8/1 V0.2.3 与 Springboot 集成 新增 朱智胜

2017/8/2 V0.2.3 动态加载规则实例&约束 新增 朱智胜

2017/8/3 V0.2.4 RHS 语法 新增 朱智胜

2017/8/4 V0.2.4 结果条件 新增 朱智胜

2017/8/5 V0.2.4 注释 新增 朱智胜

2017/8/5 V0.2.4 异常&关键字 新增 朱智胜

2017/8/11 V0.3.0 1、相同对象 and List 使用 demo 2、获取规则名称和包名 demo

新增 朱智胜

2017/8/11 V0.3.1 global 全局变量 新增 朱智胜

2017/8/20 V0.3.2 Query 基本语法&实例 新增 朱智胜

(2)

目录

从 Drools 规则引擎到风控反洗钱 ...1

1 Drools 简介 ...3

1.1 什么是规则引擎...3

1.2 Drools 规则引擎 ...3

1.3 Drools 使用概览 ...3

1.4 Drools 版本信息 ...4

1.5 JDK 版本及 IDE ...4

1.6 官方资料 ...4

2 追溯 Drools5 的使用 ...5

2.1 Drools5 简述 ...5

2.2 Drools5 之 HelloWorld ...5

3 Drools7 之 HelloWorld ...9

3.1 Hello World 实例 ...9

3.2 API 解析 ... 12

4 规则 ... 20

4.1 规则文件 ... 20

4.2 包(package) ... 22

4.3 规则属性 ... 24

4.4 定时器和日历 ... 34

4.5 LHS 语法 ... 40

4.6 RHS 语法 ... 46

4.7 Query 查询 ... 49

4.8 结果条件 ... 52

4.9 注释 ... 55

4.10 错误信息 ... 55

4.11 关键字 ... 57

5 与 Springboot 集成 ... 58

6 基于 Springboot 动态加载规则实例 ... 63

7 应用实例集合 ... 68

7.1 相同对象 and List 使用 ... 68

7.2 获取规则名称和包名 ... 71

8 异常问题汇总 ... 71

编者寄语: ... 72

(3)

1 Drools 简介

1.1 什么是规则引擎

规则引擎是由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策 从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释 业务规则,并根据业务规则做出业务决策。

大多数规则引擎都支持规则的次序和规则冲突检验,支持简单脚本语言的规则实现,支 持通用开发语言的嵌入开发。目前业内有多个规则引擎可供使用,其中包括商业和开放源码 选择。开源的代表是 Drools,商业的代表是 Visual Rules ,I Log。

1.2 Drools 规则引擎

Drools(JBoss Rules )具有一个易于访问企业策略、易于调整以及易于管理的开源业 务规则引擎,符合业内标准,速度快、效率高。业务分析师或审核人员可以利用它轻松查看 业务规则,从而检验是否已编码的规则执行了所需的业务规则。

JBoss Rules 的前身是 Codehaus 的一个开源项目叫 Drools。现在被纳入 JBoss 门下,更 名为 JBoss Rules,成为了 JBoss 应用服务器的规则引擎。

Drools 是为 Java 量身定制的基于 Charles Forgy 的 RETE 算法的规则引擎的实现。具有 了 OO 接口的 RETE,使得商业规则有了更自然的表达。

1.3 Drools 使用概览

Drools 是 Java 编写的一款开源规则引擎,实现了 Rete 算法对所编写的规则求值,支持 声明方式表达业务逻辑。使用 DSL(Domain Specific Language)语言来编写业务规则,使得规 则通俗易懂,便于学习理解。支持 Java 代码直接嵌入到规则文件中。

Drools 主要分为两个部分:一是 Drools 规则,二是 Drools 规则的解释执行。规则的编 译与运行要通过 Drools 提供的相关 API 来实现。而这些 API 总体上游可分为三类:规则编 译、规则收集和规则的执行。

Drools 是业务规则管理系统(BRMS)解决方案,涉及以下项目:

(4)

Drools Workbench:业务规则管理系统

Drools Expert:业务规则引擎

Drools Fusion:事件处理

jBPM:工作流引擎

OptaPlanner:规划引擎

1.4 Drools 版本信息

目前 Drools 发布的最新版本为 7.0.0.Final,其他版本正在研发过程中。官方表示后 续版本会加快迭代速度。本系列也是基于此版本进行讲解。

从 Drools6.x 到 7 版本发生重大的变化项:

 @PropertyReactive 不需要再配置,在 Drools7 中作为默认配置项。同时向下 兼容。

 Drools6 版本中执行 sum 方法计算结果数据类型问题修正。

 重命名 TimedRuleExecutionOption。

 重命名和统一配置文件。

Drools7 新功能:

 支持多线程执行规则引擎,默认为开启,处于试验阶段。

 OOPath 改进,处于试验阶段。

 OOPath Maven 插件支持。

 事件的软过期。

 规则单元 RuleUnit。

1.5 JDK 版本及 IDE

从 Drools6.4.0 开始已经支持 JAVA8,最低版本 JDK1.5。可通过 Eclipse 插件进行集 成,也可通过 Intellij IDEA 中插件进行集成开发。Drools 提供了一个 Eclipse 的集成版 本,不过它核心依赖于 JDK1.5。

关键 Eclipse 的集成官方有详细的文档可参考,这里不再赘述。Drools7.0.0.Final 要 求的最低 JDK 版本为 JDK 1.8(核心包采用此版本编译,低于此版本的 JDK 运行时会抛 出异常)。本系列后续项目及示例演示均采用 JAVA8 和 Intellij IDEA。

1.6 官方资料

官网地址:http://www.drools.org/

官方最新文档:

https://docs.jboss.org/drools/release/7.0.0.Final/drools-docs/html_single/index.html

(5)

2 追溯 Drools5 的使用

2.1 Drools5 简述

上面已经提到 Drools 是通过规则编译、规则收集和规则的执行来实现具体功能的。

Drools5 提供了以下主要实现 API:

 KnowledgeBuilder

 KnowledgeBase

 KnowledgePackage

 StatefulKnowledgeSession

 StatelessKnowledgeSession

它们起到了对规则文件进行收集、编译、查错、插入 fact、设置 global、执行规则或规 则流等作用。

2.2 Drools5 之 HelloWorld

下面结合实例,使用上面的 API 来实现一个简单规则使用实例。随后简单介绍每个 API 的主要作用。Drools7 目前依旧包含上面提的 Drools5 的 API,因此本实例直接使用 Drools7 的 jar 包。

2.2.1 业务场景

目前有两种商品钻石(diamond)和黄金(Gold),需要对这两种商品分别制定销售折 扣(discount)。如果使用 Drools 规则引擎就是为了适用两种商品折扣的各种变化,不用修 改代码就可以实现复杂业务组合的变更。当然简单的情况,使用普通的 if else 或配置项也可 以达到变更的目的,那就不需要 Drools,也就不是本节讨论的范畴了。

2.2.2 代码实例

整体目录结构如下图:

(6)

首先创建 JAVA 项目,使用 maven 进行管理。创建之后 maven 的 pom.xml 文件内容如 下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.secbro</groupId>

<artifactId>drools-demo</artifactId>

<version>1.0-SNAPSHOT</version>

<properties>

<drools-version>7.0.0.Final</drools-version>

</properties>

<dependencies>

<dependency>

<groupId>org.drools</groupId>

<artifactId>drools-compiler</artifactId>

<version>${drools-version}</version>

</dependency>

</dependencies>

</project>

创建产品类 Product,如下:

package com.secbro.drools.model;

(7)

/**

* 产品类

* Created by zhuzs on 2017/7/4.

*/

public class Product {

public static final String DIAMOND = "DIAMOND"; // 钻石 public static final String GOLD = "GOLD"; // 黄金

private String type;

private int discount;

// 省略 getter/setter 方法 }

在项目的 resources 目录下创建 com/rules 目录,并在创建 Rules.drl,内容如下:

package com.rules

import com.secbro.drools.model.Product

rule Offer4Diamond when

productObject : Product(type == Product.DIAMOND) then

productObject.setDiscount(15);

end rule Offer4Gold

when

productObject: Product(type == Product.GOLD) then

productObject.setDiscount(25);

end

创建执行规则的测试类 Drools5Test:

package com.secbro.drools;

import com.secbro.drools.model.Product;

import org.kie.api.io.ResourceType;

import org.kie.internal.KnowledgeBase;

import org.kie.internal.KnowledgeBaseFactory;

import org.kie.internal.builder.KnowledgeBuilder;

import org.kie.internal.builder.KnowledgeBuilderFactory;

import org.kie.internal.definition.KnowledgePackage;

(8)

import org.kie.internal.io.ResourceFactory;

import org.kie.internal.runtime.StatefulKnowledgeSession;

import java.util.Collection;

/**

* Created by zhuzs on 2017/7/4.

*/

public class Drools5xTest {

public static void main(String[] args) { Drools5xTest test = new Drools5xTest();

test.oldExecuteDrools();

}

private void oldExecuteDrools() {

KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();

kbuilder.add(ResourceFactory.newClassPathResource("com/rules/Rules.drl", this.getClass()), ResourceType.DRL);

if (kbuilder.hasErrors()) {

System.out.println(kbuilder.getErrors().toString());

}

Collection<KnowledgePackage> pkgs = kbuilder.getKnowledgePackages();

// add the package to a rulebase

KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();

// 将 KnowledgePackage 集合添加到 KnowledgeBase 当中 kbase.addKnowledgePackages(pkgs);

StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();

Product product = new Product();

product.setType(Product.GOLD);

ksession.insert(product);

ksession.fireAllRules();

ksession.dispose();

System.out.println("The discount for the product " + product.getType() + " is " + product.getDiscount()+”%”);

} }

现在执行,main 方法,打印出来的结果为:

The discount for the product 1 is 25%

(9)

2.2.3 实例详解

通过上面的实例我们已经完成了 Drools 规则引擎 API 的使用。下面,针对实例逐步讲 解每个 API 的使用方法及 drl 文件的语法。

类名 使用说明

KnowledgeBuilder 在业务代码中收集已编写的规则,并对规则文件进行编译,生成编译

好的 KnowledgePackage 集合,提供给其他 API 使用。通过其提供的 hasErrors()方法获得编译过程中是否有错,getErrors()方法打印错误 信息。支持.drl 文件、.dslr 文件和 xls 文件等。

KnowledgePackage 存放编译之后规则的对象

KnowledgeBase 收 集 应 用 当 中 知 识 ( knowledge ) 定 义 的 知 识 库 对 象

(KnowledgePackage),在一个 KnowledgeBase 当中可以包含普通 的规则(rule)、 规则流(rule flow)、函数定义(function)、用户自定义 对象(type model)等,并创建 session 对象(StatefulKnowledgeSession 和 StatelessKnowledgeSession)

StatefulKnowledgeSession 接收外部插入的数据 fact 对象(POJO),将编译好的规则包和业务 数据通过 fireAllRules()方法触发所有的规则执行。使用完成需调用 dispose()方法以释放相关内存资源。

StatelessKnowledgeSession 对 StatefulKnowledgeSession 的封装实现,与其对比不需要调用 dispose()方法释放内存,只能插入一次 fact 对象。

以上是针对 Drools5x 版本 API 相关使用简介,Drools7 版本已经不再使用此系列的 API,

此处章节就不展开描述。规则的语法也放在 Drools7 对应章节中进行详细介绍。

3 Drools7 之 HelloWorld

3.1 Hello World 实例

在上一章中介绍了 Drools5x 版本中规则引擎使用的实例,很明显在 Drools7 中 KnowledgeBase 类已被标注为“@Deprecated”——废弃。在本章节中介绍 Drools7 版本中的 使用方法。后续实例都将默认使用此版本。

先看一下 Drools 项目的目录结构:

(10)

Maven pom.xml 文件中依赖配置:

<properties>

<drools-version>7.0.0.Final</drools-version>

</properties>

<dependencies>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.12</version>

</dependency>

<dependency>

<groupId>org.drools</groupId>

<artifactId>drools-compiler</artifactId>

<version>${drools-version}</version>

</dependency>

</dependencies>

Fact 对象对应的实体类依旧为 Product:

package com.secbro.drools.model;

/**

* 产品类

* Created by zhuzs on 2017/7/4.

*/

(11)

public class Product {

public static final String DIAMOND = "DIAMOND"; // 钻石 public static final String GOLD = "GOLD"; // 黄金

private String type;

private int discount;

// 省略 getter/setter 方法 }

规则文件依旧为 Rules.drl:

package com.rules

import com.secbro.drools.model.Product

rule Offer4Diamond when

productObject : Product(type == Product.DIAMOND) then

productObject.setDiscount(15);

end rule Offer4Gold

when

productObject: Product(type == Product.GOLD) then

productObject.setDiscount(25);

end

与 Drools5 不同的是 Drools 中引入了 kmodule.xml 文件。配置内容如下:

<?xml version="1.0" encoding="UTF-8"?>

<kmodule xmlns="http://www.drools.org/xsd/kmodule">

<kbase name="rules" packages="com.rules">

<ksession name="ksession-rule"/>

</kbase>

</kmodule>

以下对配置说明进行简单说明,后续会专门针对此配置文件进行详细解释:

 Kmodule 中可以包含一个到多个 kbase,分别对应 drl 的规则文件。

 Kbase 需要一个唯一的 name,可以取任意字符串。

 packages 为 drl 文件所在 resource 目录下的路径。注意区分 drl 文件中的 package 与此处的 package 不一定相同。多个包用逗号分隔。默认情况下会扫描 resources

(12)

目录下所有(包含子目录)规则文件。

 kbase 的 default 属性,标示当前 KieBase 是不是默认的,如果是默认的则不用名称 就可以查找到该 KieBase,但每个 module 最多只能有一个默认 KieBase。

 kbase 下面可以有一个或多个 ksession,ksession 的 name 属性必须设置,且必须 唯一。

实例代码,本实例采用单元测试的方法进行编写:

@Test

public void testRules() { // 构建 KieServices

KieServices ks = KieServices.Factory.get();

KieContainer kieContainer = ks.getKieClasspathContainer();

// 获取 kmodule.xml 中配置中名称为 ksession-rule 的 session,默认为有状态 的。

KieSession kSession = kieContainer.newKieSession("ksession-rule");

Product product = new Product();

product.setType(Product.GOLD);

kSession.insert(product);

int count = kSession.fireAllRules();

System.out.println("命中了" + count + "条规则!");

System.out.println(" 商 品 " +product.getType() + " 的 商 品 折 扣 为 " + product.getDiscount() + "%。");

}

运行单元测试打印结果为:

命中了 1 条规则!

商品 GOLD 的商品折扣为 25%。

以上实例首先定义了一个商品,支持 DIAMOND 和 GOLD,并在规则文件中配置了这两 种商品的折扣信息。然后传入商品类型为 GLOD 的 FACT 对象,并调用规则引擎,规则引擎 执行了 1 条规则,并返回了此商品的折扣。

至此,我们已经完成了一个规则引擎的使用。通过上面的实例我们可以清楚的看到 Drools7 版本与 Drools5 版本之间所使用的 API 是完全两套 API。

3.2 API 解析

针对上面的实例,我们逐步了解一下使用到的 API 的使用说明及相关概念性知识。

(13)

3.2.1 什么是 KIE

KIE(Knowledge Is Everything),知识就是一切的简称。JBoss 一系列项目的总称,在《Drools 使用概述》章节已经介绍了 KIE 包含的大部分项目。它们之间有一定的关联,通用一些 API。

比如涉及到构建(building)、部署(deploying)和加载(loading)等方面都会以 KIE 作为前 缀来表示这些是通用的 API。

下图为 KIE 所包含的子项目结构图:

3.2.2 KIE 生命周期

无论是 Drools 还是 JBPM,生命周期都包含以下部分:

 编写:编写规则文件,比如:DRL,BPMN2、决策表、实体类等。

 构建:构建一个可以发布部署的组件,对于 KIE 来说是 JAR 文件。

 测试:部署之前对规则进行测试。

 部署:利用 Maven 仓库将 jar 部署到应用程序。

 使用:程序加载 jar 文件,通过 KieContainer 对其进行解析创建 KieSession。

 执行:通过 KieSession 对象的 API 与 Drools 引擎进行交互,执行规则。

 交互:用户通过命令行或者 UI 与引擎进行交互。

 管理:管理 KieSession 或者 KieContainer 对象。

(14)

3.2.3 FACT 对象

Fact 对象是指在使用 Drools 规则时,将一个普通的 JavaBean 对象插入到规则引擎的 WorkingMemory 当中的对象。规则可以对 Fact 对象进行任意的读写操作。Fact 对象不是对 原来的 JavaBean 对象进行 Clone,而是使用传入的 JavaBean 对象的引用。规则在进行计算 时需要的应用系统数据设置在 Fact 对象当中,这样规则就可以通过对 Fact 对象数据的读写 实现对应用数据的读写操作。

Fact 对象通常是一个具有 getter 和 setter 方法的 POJO 对象,通过 getter 和 setter 方法 可以方便的实现对 Fact 对象的读写操作,所以我们可以简单的把 Fact 对象理解为规则与 应用系统数据交互的桥梁或通道。

当 Fact 对象插入到 WorkingMemory 当中后,会与当前 WorkingMemory 当中所有的规 则进行匹配,同时返回一个 FactHandler 对象。FactHandler 对象是插入到 WorkingMemory 当中 Fact 对象的引用句柄,通过 FactHandler 对象可以实现对 Fact 对象的删除及修改等操 作。

前面的实例中通过调用 insert 方法将 Product 对象插入到 WorkingMemory 当中,Product 对象插入到规则中之后就是说为的 FACT 对象。如果需要插入多个 FACT 对象,多次调用 insert 方法,并传入对应 FACT 对象即可。

3.2.4 KieServices

该接口提供了很多方法,可以通过这些方法访问 KIE 关于构建和运行的相关对象,比如 说可以获取 KieContainer,利用 KieContainer 来访问 KBase 和 KSession 等信息;可以获取 KieRepository 对象,利用 KieRepository 来管理 KieModule 等。

KieServices 就是一个中心,通过它来获取的各种对象来完成规则构建、管理和执行等操 作。

示例 demo:

// 通过单例创建 KieServices

KieServices kieServices = KieServices.Factory.get();

// 获取 KieContainer

KieContainer kieContainer = kieServices.getKieClasspathContainer();

// 获取 KieRepository

KieRepository kieRepository = kieServices.getRepository();

(15)
(16)

3.2.5 KieContainer

可以理解 KieContainer 就是一个 KieBase 的容器。提供了获取 KieBase 的方法和创建 KieSession 的方法。其中获取 KieSession 的方法内部依旧通过 KieBase 来创建 KieSession。

// 通过单例创建 KieServices

KieServices kieServices = KieServices.Factory.get();

// 获取 KieContainer

KieContainer kieContainer = kieServices.getKieClasspathContainer();

// 获取 KieBase

KieBase kieBase = kieContainer.getKieBase();

// 创建 KieSession

KieSession kieSession = kieContainer.newKieSession("session-name");

3.2.6 KieBase

KieBase 就是一个知识仓库,包含了若干的规则、流程、方法等,在 Drools 中主要就是规 则和方法,KieBase 本身并不包含运行时的数据之类的,如果需要执行规则 KieBase 中的规 则的话,就需要根据 KieBase 创建 KieSession。

// 获取 KieBase

(17)

KieBase kieBase = kieContainer.getKieBase();

KieSession kieSession = kieBase.newKieSession();

StatelessKieSession statelessKieSession = kieBase.newStatelessKieSession();

3.2.7 KieSession

KieSession 就是一个跟 Drools 引擎打交道的会话,其基于 KieBase 创建,它会包含运行 时数据,包含“事实 Fact”,并对运行时数据实时进行规则运算。通过 KieContainer 创建 KieSession 是一种较为方便的做法,其本质上是从 KieBase 中创建出来的。KieSession 就是 应用程序跟规则引擎进行交互的会话通道。

创建 KieBase 是一个成本非常高的事情,KieBase 会建立知识(规则、流程)仓库,而创

(18)

建 KieSession 则是一个成本非常低的事情,所以 KieBase 会建立缓存,而 KieSession 则不必。

3.2.8 KieRepository

KieRepository 是一个单例对象,它是存放 KieModule 的仓库,KieModule 由 kmodule.xml 文件定义(当然不仅仅只是用它来定义)。

// 通过单例创建 KieServices

KieServices kieServices = KieServices.Factory.get();

// 获取 KieRepository

KieRepository kieRepository = kieServices.getRepository();

3.2.9 KieProject

KieContainer 通过 KieProject 来初始化、构造 KieModule,并将 KieModule 存放到 KieRepository 中,然后 KieContainer 可以通过 KieProject 来查找 KieModule 定义的信息,并 根据这些信息构造 KieBase 和 KieSession。

(19)

3.2.10 ClasspathKieProject

ClasspathKieProject 实 现 了 KieProject 接 口 , 它 提 供 了 根 据 类 路 径 中 的 META- INF/kmodule.xml 文件构造 KieModule 的能力,是基于 Maven 构造 Drools 组件的基本保障 之一。意味着只要按照前面提到过的 Maven 工程结构组织我们的规则文件或流程文件,只 用很少的代码完成模型的加载和构建。

3.2.11 kmodule.xml

kmodule 的简单配置规则在上面的实例中已经简单介绍,下面具体介绍具体的配置。

kbase 的属性:

属性名 默认值 合法的值 描述

name none any KieBase 的名称,这个属性是强制的,

必须设置。

includes none 逗 号 分 隔 的

KieBase 名 称 列

意 味 着 本 KieBase 将 会 包 含 所 有 include 的 KieBase 的 rule、process 定 义制品文件。非强制属性。

packages all 逗号分隔的字符

串列表

默认情况下将包含 resources 目录下 面(子目录)的所有规则文件。也可 以指定具体目录下面的规则文件,通 过逗号可以包含多个目录下的文件。

default false true, false 表示当前 KieBase 是不是默认的,如 果是默认的话,不用名称就可以查找 到该 KieBase,但是每一个 module 最 多只能有一个 KieBase。

equalsBehavior identity identity,equality 顾名思义就是定义“equals”(等于)的 行为,这个 equals 是针对 Fact(事实)

的 , 当 插 入 一 个 Fact 到 Working Memory 中的时候,Drools 引擎会检 查该 Fact 是否已经存在,如果存在的 话就使用已有的 FactHandle,否则就 创建新的。而判断 Fact 是否存在的依 据通过该属性定义的方式来进行的:

设置成 identity,就是判断对象是否 存在,可以理解为用==判断,看是否 是同一个对象; 如果该属性设置成 equality 的话,就是通过 Fact 对象的 equals 方法来判断。

eventProcessingMode cloud cloud, stream 当以云模式编译时,KieBase 将事件视 为正常事实,而在流模式下允许对其 进行时间推理。

(20)

declarativeAgenda disabled disabled,enabled 这是一个高级功能开关,打开后规则 将可以控制一些规则的执行与否。

ksession 的属性:

属性名 默认值 合法的值 描述

name none any KieSession 的名称,该值必须唯一,也是强

制的,必须设置。

type stateful stateful, stateless 定义该 session 到底是有状态(stateful)的还 是无状态(stateless)的,有状态的 session 可 以利用 Working Memory 执行多次,而无状 态的则只能执行一次。

default false true, false 定义该 session 是否是默认的,如果是默认的 话则可以不用通过 session 的 name 来创建 session,在同一个 module 中最多只能有一个 默认的 session。

clockType realtime realtime,pseudo 定义时钟类型,用在事件处理上面,在复合事 件处理上会用到,其中 realtime 表示用的是 系统时钟,而 pseudo 则是用在单元测试时模 拟用的。

beliefSystem simple simple,defeasible, jtms

定义 KieSession 使用的 belief System 的类型。

4 规则

在前面的章节中我们已经实现了一个简单规则引擎的使用。留心的朋友可能已经发现规 则引擎的优势所在,那就是将可变的部分剥离出来放置在 DRL 文件中,规则的变化只用修 改 DRL 文件中的逻辑即可,而其他相关的业务代码则几乎不用改动。

本章节的重点介绍规则文件的构成、语法函数、执行及各种属性等。

4.1 规则文件

一个标准的规则文件的格式为已“.drl”结尾的文本文件,因此可以通过记事本工具 进行编辑。规则放置于规则文件当中,一个规则文件可以放置多条规则。在规则文件当 中也可以存放用户自定义的函数、数据对象及自定义查询等相关在规则当中可能会用到 的一些对象。

从架构角度来讲,一般将同一业务的规则放置在同一规则文件,也可以根据不同类 型处理操作放置在不同规则文件当中。不建议将所有的规则放置与一个规则文件当中。

分开放置,当规则变动时不至于影响到不相干的业务。读取构建规则的成本业务会相应 减少。

标准规则文件的结构如下:

package package-name

(21)

imports globals functions queries

rules

package:在一个规则文件当中 package 是必须的,而且必须放置在文件的第一行。

package 的名字是随意的,不必必须对应物理路径,这里跟 java 的 package 的概念不同,

只是逻辑上的区分,但建议与文件路径一致。同一的 package 下定义的 function 和 query 等 可以直接使用。

比如,上面实例中 package 的定义:

package com.rules

import:导入规则文件需要的外部变量,使用方法跟 java 相同。像 java 的是 import 一

样,还可以导入类中的某一个可访问的静态方法。(特别注意的是,某些教程中提示 import

引入静态方法是不同于 java 的一方面,可能是作者没有用过 java 的静态方法引入。)另外,

目前针对 Drools7 版本,static 和 function 关键字的效果是一样的。

import static com.secbro.drools.utils.DroolsStringUtils.isEmpty;

import function com.secbro.drools.utils.DroolsStringUtils.isEmpty;

rules:定义一个条规则。rule “ruleName”。一条规则包含三部分:属性部分、条件部分 和结果部分。rule 规则以 rule 开头,以 end 结尾。

属性部分:定义当前规则执行的一些属性等,比如是否可被重复执行、过期时间、生效 时间等。

条件部分,简称 LHS,即 Left Hand Side。定义当前规则的条件,处于 when 和 then 之 间。如 when Message();判断当前 workingMemory 中是否存在 Message 对象。LHS 中,可 包含 0~n 个条件,如果没有条件,默认为 eval(true),也就是始终返回 true。条件又称之为 pattern(匹配模式),多个 pattern 之间用可以使用 and 或 or 来进行连接,同时还可以使用 小括号来确定 pattern 的优先级。

结果部分,简称 RHS,即 Right Hand Side,处于 then 和 end 之间,用于处理满足条件 之后的业务逻辑。可以使用 LHS 部分定义的变量名、设置的全局变量、或者是直接编写 Java 代码。

RHS 部分可以直接编写 Java 代码,但不建议在代码当中有条件判断,如果需要条件判 断,那么需要重新考虑将其放在 LHS 部分,否则就违背了使用规则的初衷。

RHS 部分,提供了一些对当前 Working Memory 实现快速操作的宏函数或对象,比如 insert/insertLogical、update/modify 和 retract 等。利用这些函数可以实现对当前 Working Memory 中的 Fact 对象进行新增、修改或删除操作;如果还要使用 Drools 提供的其它方法,

可以使用另一个宏对象 drools,通过该对象可以使用更多的方法;同时 Drools 还提供了一 个 名 为 kcontext 的 宏 对 象 , 可 以 通 过 该 对 象 直 接 访 问 当 前 Working Memory 的 KnowledgeRuntime。

(22)

标准规则的结构示例:

rule "name"

attributes when LHS then RHS end LHS 为空示例:

rule "name"

when then end

4.2 包(package)

在 Drools 中,包的概念与 java 基本相同,区别的地方是 Drools 的中包不一定与物理路 径相对应。包一组规则和相关语法结构,比如引入和全局变量。在同一包下的规则相互之间 有一定的关联。

一个包代表一个命名空间,一般来讲针对同一组规则要确保包的唯一性。包名本身是命 名空间,并且不以任何方式关联到文件或文件夹。

4.2.1 import

Drools 的导入语句的作用和在 java 一样。需要为在规则中使用的对象指定完整路径和类 型名字。 Drools 会自动从同名的 java 包中导入类,默认会引入 java.lang 下面的类。

(23)

4.2.2 global 全局变量

global 用来定义全局变量,它可以让应用程序的对象在规则文件中能够被访问。通常,

可以用来为规则文件提供数据或服务。特别是用来操作规则执行结果的处理和从规则返回数 据,比如执行结果的日志或值,或者与应用程序进行交互的规则的回调处理。

全局变量并不会被插入到 Working Memory 中,因此,除非作为常量值,否则不应该将 全局变量用于规则约束的判断中。对规则引擎中的 fact 修改,规则引擎根据算法会动态更新 决策树,重新激活某些规则的执行,而全局变量不会对规则引擎的决策树有任何影响。在约 束条件中错误的使用全局变量会导致意想不到的错误。

如果多个包中声明具有相同标识符的全局变量,则必须是相同的类型,并且它们都将引 用相同的全局值。

实例代码如下:

规则文件内容:

package com.rules

import com.secbro.drools.model.Risk import com.secbro.drools.model.Message global com.secbro.drools.EmailService emailService

rule "test-global"

agenda-group "test-global-group"

when then

Message message = new Message();

message.setRule(drools.getRule().getName());

message.setDesc("to send email!");

emailService.sendEmail(message);

end

测试代码:

@Test

public void testGlobal(){

KieSession kieSession =

this.getKieSession("test-global-group");

int count = kieSession.fireAllRules();

kieSession.dispose();

System.out.println("Fire " + count + "

(24)

rule(s)!");

}

实体类:

public class Message { private String rule;

private String desc;

// getter/setter }

操作类:

public class EmailService {

public static void sendEmail(Message message){

System.out.println("Send message to

email,the fired rule is '" + message.getRule() + "', and description is '" + message.getDesc() + "'");

} }

执行之后,打印结果:

Send message to email,the fired rule is 'test-global', and description is 'to send email!' Fire 1 rule(s)!

上面的实例完成了一个规则从触发到通过 global 调用 emailService 方法的实现。

4.3 规则属性

规则属性提供了一种声明性的方式来影响规则的行为。有些很简单,而其他的则是复杂 子系统的一部分,比如规则流。如果想充分使用 Drools 提供的便利,应该对每个属性都有 正确的理解。

(25)

4.3.1 no-loop

定义当前的规则是否不允许多次循环执行,默认是 false,也就是当前的规则只要满足条 件,可以无限次执行。什么情况下会出现规则被多次重复执行呢?下面看一个实例:

package com.rules

import com.secbro.drools.model.Product;

rule updateDistcount no-loop false when

productObj:Product(discount > 0);

then

productObj.setDiscount(productObj.getDiscount() + 1);

System.out.println(productObj.getDiscount());

update(productObj);

end

其中 Product 对象的 discount 属性值默认为 1。执行此条规则时就会发现程序进入了死 循环。也就是说对传入当前 workingMemory 中的 FACT 对象的属性进行修改,并调用 update 方法就会重新触发规则。从打印的结果来看,update 之后被修改的数据已经生效,在重新 执行规则时并未被重置。当然对 Fact 对象数据的修改并不是一定需要调用 update 才可以生 效,简单的使用 set 方法设置就可以完成,但仅仅调用 set 方法时并不会重新触发规则。所 以,对 insert、retract、update 之类的方法使用时一定要慎重,否则极可能会造成死循环。

(26)

可以通过设置 no-loop 为 true 来避免规则的重新触发,同时,如果本身的 RHS 部分有 insert、retract、update 等触发规则重新执行的操作,也不会再次执行当前规则。

上面的设置虽然解决了当前规则的不会被重复执行,但其他规则还是会收到影响,比如 下面的例子:

package com.rules

import com.secbro.drools.model.Product;

rule updateDistcount no-loop true when

productObj:Product(discount > 0);

then

productObj.setDiscount(productObj.getDiscount() + 1);

System.out.println(productObj.getDiscount());

update(productObj);

end

rule otherRule when

productObj : Product(discount > 1);

then

System.out.println("被触发了" + productObj.getDiscount());

end

此时执行会发现,当第一个规则执行 update 方法之后,规则 otherRule 也会被触发执 行。如果注释掉 update 方法,规则 otherRule 则不会被触发。那么,这个问题是不是就没 办法解决了?当然可以,那就是引入 lock-on-active true 属性。

4.3.2 ruleflow-group

在使用规则流的时候要用到 ruleflow-group 属性,该属性的值为一个字符串,作用是将 规则划分为一个个的组,然后在规则流当中通过使用 ruleflow-group 属性的值,从而使用对 应的规则。该属性会通过流程的走向确定要执行哪一条规则。在规则流中有具体的说明。

代码实例:

package com.rules

rule "test-ruleflow-group1"

ruleflow-group "group1"

when then

System.out.println("test-ruleflow-group1 被触发");

end

(27)

rule "test-ruleflow-group2"

ruleflow-group "group1"

when then

System.out.println("test-ruleflow-group2 被触发");

end

4.3.3 lock-on-active

当在规则上使用 ruleflow-group 属性或 agenda-group 属性的时候,将 lock-on-active 属性的值设置为 true,可避免因某些 Fact 对象被修改而使已经执行过的规则再次被激活执 行。可以看出该属性与 no-loop 属性有相似之处,no-loop 属性是为了避免 Fact 被修改或 调用了 insert、retract、update 之类的方法而导致规则再次激活执行,这里的 lock-on-active 属性起同样的作用,lock-on-active 是 no-loop 的增强版属性,它主要作用在使用 ruleflow- group 属性或 agenda-group 属性的时候。lock-on-active 属性默认值为 false。与 no-loop 不同的是 lock-on-active 可以避免其他规则修改 FACT 对象导致规则的重新执行。

因 FACT 对象修改导致其他规则被重复执行示例:

package com.rules

import com.secbro.drools.model.Product;

rule rule1

no-loop true when

obj : Product(discount > 0);

then

obj.setDiscount(obj.getDiscount() + 1);

System.out.println("新折扣为:" + obj.getDiscount());

update(obj);

end

rule rule2 when

productObj : Product(discount > 1);

then

System.out.println("其他规则被触发了" + productObj.getDiscount());

end

执行之后打印结果为:

新折扣为:2

其他规则被触发了 2

第一次执行命中了 2 条规则!

其他规则(rule2)因 FACT 对象的改变而被出发了。

(28)

通过 lock-on-active 属性来避免被其他规则更新导致自身规则被重复执行示例:

package com.rules

import com.secbro.drools.model.Product;

rule rule1

no-loop true when

obj : Product(discount > 0);

then

obj.setDiscount(obj.getDiscount() + 1);

System.out.println("新折扣为:" + obj.getDiscount());

update(obj);

end

rule rule2

lock-on-active true when

productObj : Product(discount > 1);

then

System.out.println("其他规则被触发了" + productObj.getDiscount());

end

很明显在 rule2 的属性部分新增了 lock-on-active true。执行结果为:

新折扣为:2

第一次执行命中了 1 条规则!

标注了 lock-on-active true 的规则不再被触发。

4.3.4 salience

用来设置规则执行的优先级,salience 属性的值是一个数字,数字越大执行优先级越高,

同时它的值可以是一个负数。默认情况下,规则的 salience 默认值为 0。如果不设置规则的 salience 属性,那么执行顺序是随机的。

示例代码:

package com.rules

rule salience1 salience 3 when then

System.out.println("salience1 被执行");

end rule salience2

(29)

salience 5 when then

System.out.println("salience2 被执行");

end 执行结果:

salience2 被执行 salience1 被执行

显然,salience2 的优先级高于 salience1 的优先级,因此被先执行。

Drools 还支持动态 saline,可以使用绑定绑定变量表达式来作为 salience 的值。比如:

package com.rules

import com.secbro.drools.model.Product

rule salience1 salience sal when

Product(sal:discount);

then

System.out.println("salience1 被执行");

end

这样,salience 的值就是传入的 FACT 对象 Product 的 discount 的值了。

4.3.5 agenda-group

规则的调用与执行是通过 StatelessKieSession 或 KieSession 来实现的,一般的顺序是创 建一个 StatelessKieSession 或 KieSession,将各种经过编译的规则添加到 session 当中,然后 将规则当中可能用到的 Global 对象和 Fact 对象插入到 Session 当中,最后调用 fireAllRules 方法来触发、执行规则。

在没有调用 fireAllRules 方法之前,所有的规则及插入的 Fact 对象都存放在一个 Agenda 表的对象当中,这个 Agenda 表中每一个规则及与其匹配相关业务数据叫做 Activation,在 调用 fireAllRules 方法后,这些 Activation 会依次执行,执行顺序在没有设置相关控制顺序属 性时(比如 salience 属性),它的执行顺序是随机的。

Agenda Group 是用来在 Agenda 基础上对规则进行再次分组,可通过为规则添加 agenda-group 属性来实现。agenda-group 属性的值是一个字符串,通过这个字符串,可以 将规则分为若干个 Agenda Group。引擎在调用设置了 agenda-group 属性的规则时需要显 示的指定某个 Agenda Group 得到 Focus(焦点),否则将不执行该 Agenda Group 当中的规 则。

规则代码:

package com.rules

rule "test agenda-group"

(30)

agenda-group "abc"

when then

System.out.println("规则 test agenda-group 被触发");

end rule otherRule

when then

System.out.println("其他规则被触发");

end 调用代码:

KieServices kieServices = KieServices.Factory.get();

KieContainer kieContainer = kieServices.getKieClasspathContainer();

KieSession kSession = kieContainer.newKieSession("ksession-rule");

kSession.getAgenda().getAgendaGroup("abc").setFocus();

kSession.fireAllRules();

kSession.dispose();

执行以上代码,打印结果为:

规则 test agenda-group 被触发 其他规则被触发

如果将代码 kSession.getAgenda().getAgendaGroup("abc").setFocus()注释掉,则只会打 印出:

其他规则被触发

很显然,如果不设置指定 AgendaGroup 获得焦点,则该 AgendaGroup 下的规则将不会 被执行。

4.3.6 auto-focus

在 agenda-group 章节,我们知道想要让 AgendaGroup 下的规则被执行,需要在代码中 显式的设置 group 获得焦点。而此属性可配合 agenda-group 使用,代替代码中的显式调 用。默认值为 false,即不会自动获取焦点。设置为 true,则可自动获取焦点。

对于规则的执行的控制,还可以使用 org.kie.api.runtime.rule. AgendaFilter 来实现。用户 可以实现该接口的 accept 方法,通过规则当中的属性值来控制是否执行规则。

方法体如下:

boolean accept(Match match);

在该方法当中提供了一个 Match 参数,通过该参数可以获得当前正在执行的规则对象和 属性。该方法要返回一个布尔值,返回 true 就执行规则,否则不执行。

auto-focus 使用示例代码:

规则代码:

package com.rules

(31)

rule "test agenda-group"

agenda-group "abc"

auto-focus true

when then

System.out.println("规则 test agenda-group 被触发");

end

执行规则代码:

KieServices kieServices = KieServices.Factory.get();

KieContainer kieContainer = kieServices.getKieClasspathContainer();

KieSession kSession = kieContainer.newKieSession("ksession-rule");

kSession.fireAllRules();

kSession.dispose();

执行结果:

规则 test agenda-group 被触发

这里,我们没有在代码中显式的让 test agenda-group 获取焦点,但规则同样被执行了,

说明属性配置已生效。

AgendaFilter 代码实例 规则文件代码:

package com.rules

rule "test-agenda-group"

when then

System.out.println("规则 test-agenda-group 被触发");

end rule other

when then

System.out.println("规则 other 被触发");

end

实现的 MyAgendaFilter 代码:

package com.secbro.drools.filter;

import org.kie.api.runtime.rule.AgendaFilter;

import org.kie.api.runtime.rule.Match;

/**

(32)

* Created by zhuzs on 2017/7/19.

*/

public class MyAgendaFilter implements AgendaFilter{

private String ruleName;

public MyAgendaFilter(String ruleName) { this.ruleName = ruleName;

}

@Override

public boolean accept(Match match) {

return match.getRule().getName().equals(ruleName) ? true : false;

}

// 省略 getter/setter 方法 }

测试方法:

KieServices kieServices = KieServices.Factory.get();

KieContainer kieContainer = kieServices.getKieClasspathContainer();

KieSession kSession = kieContainer.newKieSession("ksession-rule");

AgendaFilter filter = new MyAgendaFilter("test-agenda-group");

kSession.fireAllRules(filter);

kSession.dispose();

执行结果:

规则 test-agenda-group 被触发

在执行规则的 Filter 中传入的规则名称为 test-agenda-group,此规则被执行。而对照组 的规则 other,却未被执行。

4.3.7 activation-group

该属性将若干个规则划分成一个组,统一命名。在执行的时候,具有相同 activation-group 属性的规则中只要有一个被执行,其它的规则都不再执行。可以用类似 salience 之类属性来 实现规则的执行优先级。该属性以前也被称为异或(Xor)组,但技术上并不是这样实现的,

当提到此概念,知道是该属性即可。

实例代码:

package com.rules

rule "test-activation-group1"

activation-group "foo"

when then

System.out.println("test-activation-group1 被触发");

(33)

end

rule "test-activation-group2"

activation-group "foo"

salience 1 when then

System.out.println("test-activation-group2 被触发");

end

执行规则之后,打印结果:

test-activation-group2 被触发

以上实例证明,同一 activation-group 优先级高的被执行,其他规则不会再被执行。

4.3.8 dialect

该属性用来定义规则(LHS、RHS)当中要使用的语言类型,可选值为“java”或“mvel”。默 认情况下使用 java 语言。当在包级别指定方言时,这个属性可以在具体的规则中覆盖掉包 级别的指定。

dialect "mvel"

4.3.9 date-effective

该属性是用来控制规则只有在到达指定时间后才会触发。在规则运行时,引擎会拿当前 操作系统的时间与 date-effective 设置的时间值进行比对,只有当系统时间大于等于 date- effective 设置的时间值时,规则才会触发执行,否则执行将不执行。在没有设置该属性的情 况下,规则随时可以触发。

date-effective 的值为一个日期型的字符串,默认情况下,date-effective 可接受的日期格 式为“dd-MMM-yyyy”。例如 2017 年 7 月 20 日,在设置为 date-effective 值时,如果操作 系统为英文的,那么应该写成“20-Jul-2017”;如果是中文操作系统则为“20-七月-2017”。

目前在 win10 操作系统下验证,中文和英文格式均支持。而且在上面日期格式后面添加 空格,添加其他字符并不影响前面日期的效果。

示例代码:

package com.rules

rule "test-date"

// date-effective "20-七月-2017 aa"

// date-effective "20-七月-2017"

// date-effective "20-Jul-2017aaa"

date-effective "20-Jul-2017"

when then

(34)

System.out.println("规则被执行");

end

值得注意的是以上注释掉的格式均能成功命中规则与后面的字符无关,因为默认时间格 式只取字符串的指定位数进行格式化。

晋级用法:上面已经提到了,其实针对日期之后的时间是无效的。那么如果需要精确到 时分秒改如何使用呢?可以通过设置 drools 的日期格式化来完成任意格式的时间设定,而 不是使用默认的格式。在调用代码之前设置日期格式化格式:

System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm");

在规则文件中就可以按照上面设定的格式来传入日期:

date-effective "2017-07-20 16:31"

4.3.10 date-expires

此属性与date-effective 的作用相反,用来设置规则的过期时间。时间格式可完全参考 date-effective 的时间格式。引擎在执行规则时会检查属性是否设置,如果设置则比较当前 系统时间与设置时间,如果设置时间大于系统时间,则执行规则,否则不执行。实例代码同 样参考 date-effective。

4.3.11 duration

已废弃。设置该属性,规则将指定的时间之后在另外一个线程里触发。属性值为一个 长整型,单位是毫秒。如果属性值设置为 0,则标示立即执行,与未设置相同。

4.3.12 enabled

设置规则是否可用。true:表示该规则可用;false:表示该规则不可用。

4.4 定时器和日历

4.4.1 定时器

规则用基于 interval(间隔)和 cron 的定时器(timer),替代了被标注过时的 duration 属性。timer 属性的使用示例:

timer ( int: <initial delay> <repeat interval>? ) timer ( int: 30s )

timer ( int: 30s 5m )

timer ( cron: <cron expression> ) timer ( cron:* 0/15 * * * ? )

(35)

间隔定时器用 int 来定义,它遵循 java.util.Timer 对象的使用方法。具有延迟和重复执行 的选择。其中第一个参数表示启动之后延迟多长时间执行,第二个参数表示每隔多久执行一 次。

Cron 定时器用 cron 来定义,使用标准的 Unix cron 表达式。示例代码如下:

rule "Send SMS every 15 minutes"

timer (cron:* 0/15 * * * ?) when

$a : Alarm( on == true ) then

channels[ "sms" ].insert( new Sms( $a.mobileNumber, "The alarm is still on" );

end

上面代码实现了每隔 15 分钟发送一封邮件的部分规则代码。

下面以一个模拟的系统报警器来示例一下 Timer 的使用。规则 timer 每隔一秒执行一次,

当满足触发规则返回结果至 ResultEvent 对象中,业务系统拿到报警信息,并打印。为了达 到模拟的效果,使用了 KieSession 的 fireUntilHalt 方法和 halt 方法。示例代码如下。

规则文件:

package com.rules import java.util.Date import java.util.List

import com.secbro.drools.testTimer.Server

global com.secbro.drools.testTimer.ResultEvent event

rule "timerTest"

timer (cron:0/1 * * * * ?) when

server : Server(times > 10) then

System.out.println("已经尝试"+server.getTimes()+"次,超过预警次数!");

event.getEvents().add(new java.util.Date() + " - 服务器已经尝试" + server.getTimes() + "次,依旧失败,特发次报警信息!");

end

Server 类:

package com.secbro.drools.testTimer;

/**

* Created by zhuzs on 2017/7/21.

*/

public class Server { // 尝试次数 private int times;

Server(int times) { this.times = times;

參考文獻

相關文件

法治 (Rule of Law) 没有統一定義。當代最廣為人知及引用的有關著作是英國兵咸勳爵 (Lord Tom Bingham) 在2010年所寫的 “The Rule Of Law” (中譯本: “ 法治 : 英國首席大法官

z按下確定即可產生 DataSet (資料集),再 利用 DataAdapter 中 Fill 方法即可將所設 定的查詢內容填入 DataSet 當中.. DataGrid

 可利用 HTML 控制項 中的 Table 控制項進 行排版動作.  (最好將 Table

把作法用乘法算式記下來,並把算式中

例如學生在必修部分已嘗試寫作不同類型的實用文

一說到網路搜尋,我們就會想到 G oogle ,但其 實搜尋引擎不是 G oogle 發明的,早在 G oogle 出現 之前就已經有搜尋引擎的應用。那麼, G oogle

本研究採用的方法是將階層式與非階層式集群法結合。第一步先運用

在教书育人第一线工作的广大中小学教师,对社会主义教育科学的