Drools规则引擎

第01章 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)解决方案,涉及以下项目:

Drools Workbench:业务规则管理系统
Drools Expert:业务规则引擎
Drools Fusion:事件处理
jBPM:工作流引擎
OptaPlanner:规划引擎

1.4 Drools版本信息

目前Drools发布的最新版本为7.x.x.Final,本系列也是基于7.16.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的集成官方有详细的文档可参考,这里不再赘述。本系列后续项目及示例演示均采用JAVA8和Intellij IDEA。

1.6 官方资料

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

第02章 追溯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 代码实例

代码结构:

图片说明

首先创建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.wangchao</groupId>
    <artifactId>droolsdemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <drools-version>7.16.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.wangchao.drools.model;

import java.io.Serializable;

/**
 * 产品
 */
public class Product implements Serializable {
    /**
     * 钻石
     */
    public static final String DIAMOND = "0";
    /**
     * 黄金
     */
    public static final String GOLD = "1";
    /**
     * 类型
     */
    private String type;
    /**
     * 折扣
     */
    private int discount;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public int getDiscount() {
        return discount;
    }

    public void setDiscount(int discount) {
        this.discount = discount;
    }
}

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

package com.rules;
dialect  "mvel"

import com.wangchao.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:

public class Drools5Test {
    public static void main(String[] args) {
        Drools5Test test = new Drools5Test();
        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<KiePackage> pkgs = kbuilder.getKnowledgePackages();
        InternalKnowledgeBase internalKnowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();
        // 将KnowledgePackage集合添加到KnowledgeBase当中
        internalKnowledgeBase.addPackages(pkgs);

        KieSession kieSession = internalKnowledgeBase.newKieSession();
        Product product = new Product();
        product.setType(Product.GOLD);
        kieSession.insert(product);
        kieSession.fireAllRules();
        kieSession.dispose();

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

输出结果:

图片说明

The discount for the product 1 is 25%

2.2.3 实例详解

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

KnowledgeBuilder:

在业务代码中收集已编写的规则,并对规则文件进行编译,生成编译好的KnowledgePackage集合,提供给其他API使用。通过其提供的hasErrors()方法获得编译过程中是否有错,getErrors()方法打印错误信息。支持.drl文件、.dslr文件和xls文件等。

KiePackage:

存放编译之后规则的对象

InternalKnowledgeBase:

收集应用当中知识(knowledge)定义的知识库对象(KiePackage),在一个 InternalKnowledgeBase当中可以包含普通的规则(rule)、 规则流(rule flow)、函数定义(function)、用户自定义对象(type model)等,并创建session对象(KieSession和 StatelessKnowledgeSession)

KieSession:

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

StatelessKnowledgeSession:

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

第03章 Hello World

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

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.wangchao</groupId>
    <artifactId>droolsdemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <drools-version>7.16.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.12</version>
        </dependency>
    </dependencies>

</project>

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

package com.wangchao.drools.model;

import java.io.Serializable;

/**
 * 产品
 */
public class Product implements Serializable {
    /**
     * 钻石
     */
    public static final String DIAMOND = "0";
    /**
     * 黄金
     */
    public static final String GOLD = "1";
    /**
     * 类型
     */
    private String type;
    /**
     * 折扣
     */
    private int discount;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public int getDiscount() {
        return discount;
    }

    public void setDiscount(int discount) {
        this.discount = discount;
    }
}

规则文件依旧为Rules.drl:

package com.rules;
dialect  "mvel"

import com.wangchao.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目录下所有(包含子目录)规则文件;
  • kbase的default属性,标示当前KieBase是不是默认的,如果是默认的则不用名称就可以查找到该KieBase,但每个module最多只能有一个默认KieBase;
  • kbase下面可以有一个或多个ksession,ksession的name属性必须设置,且必须唯一;

以上为配置文件简单说明,后续会专门针对此配置文件进行详细介绍。

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

public class Drools7Test {

    @Test
    public void testRules(){
        // 构建KieServices
        KieServices kieServices = KieServices.Factory.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        // 获取kmodule.xml中配置中名称为ksession-rule的session,默认为有状态的。
        KieSession kieSession = kieContainer.newKieSession("ksession-rule");

        Product product = new Product();
        product.setType(Product.GOLD);

        kieSession.insert(product);
        int count = kieSession.fireAllRules();
        System.out.println("命中了" + count + "条规则!");
        System.out.println("商品" +product.getType() + "的商品折扣为" + product.getDiscount() + "%。");
    }
}

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

图片说明

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

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

第04章 KIE概念&FACT对象

4.1 什么是KIE?

KIE(Knowledge Is Everything),知识就是一切的简称。JBoss一系列项目的总称,在《Drools使用概述》章节已经介绍了KIE包含的大部分项目。它们之间有一定的关联,通用一些API。比如涉及到构建(building)、部署(deploying)和加载(loading)等方面都会以KIE作为前缀来表示这些是通用的API

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

图片说明

4.2 KIE生命周期

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

  • 编写:编写规则文件,比如:DRL,BPMN2、决策表、实体类等。
  • 构建:构建一个可以发布部署的组件,对于KIE来说是JAR文件。
  • 测试:部署之前对规则进行测试。
  • 部署:利用Maven仓库将jar部署到应用程序。
  • 使用:程序加载jar文件,通过KieContainer对其进行解析创建KieSession。
  • 执行:通过KieSession对象的API与Drools引擎进行交互,执行规则。
  • 交互:用户通过命令行或者UI与引擎进行交互。
  • 管理:管理KieSession或者KieContainer对象。

4.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对象即可。

第05章 KIE API解析

5.1 KieServices

该接口提供了很多方法,可以通过这些方法访问KIE关于构建和运行的相关对象,比如说可以获取KieContainer,利用KieContainer来访问KBase和KSession等信息;可以获取KieRepository对象,利用KieRepository来管理KieModule等。
KieServices就是一个中心,通过它来获取的各种对象来完成规则构建、管理和执行等操作。

图片说明

作用:KieServices是一个线程安全的单例类,是获取KIE的其他服务的枢纽。其中,getX()方法仅仅返回给其他单例的是对象的引用,newX()方法则会创建新的对象。

获取:通过KieServices的内部工厂获取

// 通过单例创建KieServices
KieServices kieServices = KieServices.Factory.get();

使用:

// 获取KieContainer 
KieContainer kieContainer = kieServices.getKieClasspathContainer();
// 获取KieRepository 
KieRepository kieRepository = kieServices.getRepository();

5.2 KieContainer

A container for all the KieBases of a given KieModule

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

1549710211454

KieServices中获取KieContainer的方法:

/**
 * 返回类路径的KieContainer,这是一个全局单例。
 */
KieContainer getKieClasspathContainer();

/**
 * 使用给定的类加载器返回类路径的KieContainer,这是一个全局单例
 */
KieContainer getKieClasspathContainer(ClassLoader classLoader);

/**
 * 返回给定containerId的类路径的KieContainer,这是一个全局单例
 * 如果执行containerId成功,则KieContainer及其containerId将在KieServices中注册。
 * 可以通过在生成的KieContainer上调用@link KieContainer Dispose()方法来执行注销。
 * 如果不想实施特定的containerid,请改用@link getkieClassPathContainer()方法。
 */
KieContainer getKieClasspathContainer(String containerId);

/**
 * 返回用于类路径的KieContainer,它强制使用给定的containerid,并使用给定的类加载器
 * 这是一个全局单例
 */
KieContainer getKieClasspathContainer(String containerId, ClassLoader classLoader);

/**
 * 新创建一个类路径的KieContainer,不管是否已有一个KieContainer
 */
KieContainer newKieClasspathContainer();

/**
 * 使用给定的类加载器新创建一个类路径的KieContainer
 * 不管是否已有一个KieContainer
 */
KieContainer newKieClasspathContainer(ClassLoader classLoader);

/**
 * 使用给定的containerId新创建一个类路径的KieContainer
 * 不管是否已有一个KieContainer
 */
KieContainer newKieClasspathContainer(String containerId);

/**
 * 使用给定的containerid,并使用给定的类加载器新创建一个类路径的KieContainer
 * 不管是否已有一个KieContainer
 */
KieContainer newKieClasspathContainer(String containerId, ClassLoader classLoader);

/**
 * 创建一个新的KieContainer,用给定的ReleaseId包装KieModule
 */
KieContainer newKieContainer(ReleaseId releaseId);

KieContainer newKieContainer(String containerId, ReleaseId releaseId);

KieContainer newKieContainer(ReleaseId releaseId, ClassLoader classLoader);

KieContainer newKieContainer(String containerId, ReleaseId releaseId, ClassLoader classLoader);

通常使用以下方式创建:

// 通过单例创建KieServices 
KieServices kieServices = KieServices.Factory.get(); 
// 获取KieContainer 
KieContainer kieContainer = kieServices.getKieClasspathContainer(); 
// 获取KieBase 
KieBase kieBase = kieContainer.getKieBase(); 
// 创建KieSession 
KieSession kieSession = kieContainer.newKieSession("session-name");

5.3 KieBase

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

1549711774168

KieContainer中获取KieBase的方法:

/**
 * 返回此KieContainer中的默认Kiebase.
 * 返回的Kiebase将由这个KieContainer管理,然后当KieContainer本身被更新为KieModule的新版本时,它将被更新
 */
KieBase getKieBase();

/**
 * 返回此KieContainer中具有给定名称的Kiebase
 * 返回的Kiebase将由这个KieContainer管理,然后当KieContainer本身被更新为KieModule的新版本时,它将被更新
 */
KieBase getKieBase(String kBaseName);

/**
 * 使用给定的配置创建新的默认Kiebase
 * 返回的Kiebase将从该KieContainer中分离出来,然后当KieContainer本身将更新为KieModule的新版本时,将不会更新
 */
KieBase newKieBase(KieBaseConfiguration conf);

/**
 * 使用给定的配置创建具有给定名称的新Kiebase
 * 返回的Kiebase将从该KieContainer中分离出来,然后当KieContainer本身将更新为KieModule的新版本时,将不会更新
 */
KieBase newKieBase(String kBaseName, KieBaseConfiguration conf);

通常使用以下方式创建:

// 通过单例创建KieServices 
KieServices kieServices = KieServices.Factory.get(); 
// 获取KieContainer 
KieContainer kieContainer = kieServices.getKieClasspathContainer(); 
// 获取KieBase 
KieBase kieBase = kieContainer.getKieBase(); 
// 创建KieSession 
KieSession kieSession = kieContainer.newKieSession("session-name");

5.4 KieSession

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

创建KieBase是一个成本非常高的事情,KieBase会建立知识(规则、流程)仓库,而创建KieSession则是一个成本非常低的事情,所以KieBase会建立缓存,而KieSession则不必。1549725916549

KieSession 为给定Java对象的集合执行规则的简单示例:

KieServices kieServices = KieServices.Factory.get();
KieContainer kContainer = kieServices.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession();

for(Object fact:facts){
    kSession.insert(fact);
}
kSession.fireAllRules();
kSession.dispose();

有状态会话执行进程的简单示例:

KieSession kSession = kbase.newKieSession();
kSession.startProcess("com.sample.processid");
kSession.signalEvent("SomeEvent",null);
kSession.startProcess("com.sample.processid");
kSession.dispose();

用于设置全局的代码段:

KieSession ksession = kbase.newKieSession();
// sets a global hibernate session, that can be used for DB interactions in the rules.
ksession.setGlobal( "hbnSession", hibernateSession ); 
for(Object fact:facts){
    kSession.insert(fact);
}
kSession.fireAllRules();
kSession.dispose();

5.5 KieRepository

1549726690517

public interface KieRepository {
    ReleaseId getDefaultReleaseId();
    void addKieModule(KieModule kModule);
    KieModule addKieModule(Resource resource, Resource... dependencies);
    KieModule getKieModule(ReleaseId releaseId);
    KieModule removeKieModule(ReleaseId releaseId);
}

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

// 通过单例创建KieServices
KieServices kieServices = KieServices.Factory.get();
// 获取KieRepository
KieRepository kieRepository = kieServices.getRepository();

5.6 KieProject

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

1549726976889

public interface KieProject {

    ReleaseId getGAV();

    InternalKieModule getKieModuleForKBase(String kBaseName);

    Collection<String> getKieBaseNames();

    KieBaseModel getKieBaseModel(String kBaseName);

    KieBaseModel getDefaultKieBaseModel();

    KieSessionModel getKieSessionModel(String kSessionName);

    KieSessionModel getDefaultKieSession();

    KieSessionModel getDefaultStatelessKieSession();

    void init();   

    ClassLoader getClassLoader();

    ResultsImpl verify();
    ResultsImpl verify( String... kModelNames );
    void verify(ResultsImpl messages);

    long getCreationTimestamp();

    Set<String> getTransitiveIncludes(String kBaseName);
    Set<String> getTransitiveIncludes(KieBaseModel kBaseModel);

    InputStream getPomAsStream();

    KnowledgeBuilder buildKnowledgePackages( KieBaseModelImpl kBaseModel, ResultsImpl messages );

    default void writeProjectOutput(MemoryFileSystem trgMfs, ResultsImpl messages) {}
}

5.7 ClasspathKieProject

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

public class KieModuleKieProject extends AbstractKieProject {

    private static final Logger            log               = LoggerFactory.getLogger( KieModuleKieProject.class );

    private List<InternalKieModule>        kieModules;

    private Map<String, InternalKieModule> kJarFromKBaseName = new HashMap<String, InternalKieModule>();

    private InternalKieModule              kieModule;

    private ProjectClassLoader             cl;

    public KieModuleKieProject( InternalKieModule kieModule ) {
        this( kieModule, null );
    }

    public KieModuleKieProject(InternalKieModule kieModule, ClassLoader parent) {
        this.kieModule = kieModule;
        this.cl = kieModule.createModuleClassLoader( parent );
    }

    public void init() {
        if ( kieModules == null ) {
            Collection<InternalKieModule> depKieModules = kieModule.getKieDependencies().values();
            indexParts( kieModule, depKieModules, kJarFromKBaseName );
            kieModules = new ArrayList<InternalKieModule>();
            kieModules.addAll( depKieModules );
            kieModules.add( kieModule );
            cl.storeClasses( getClassesMap() );
        }
    }

    private Map<String, byte[]> getClassesMap() {
        Map<String, byte[]> classes = new HashMap<String, byte[]>();
        for ( InternalKieModule kModule : kieModules ) {
            classes.putAll( kModule.getClassesMap() );
        }
        return classes;
    }

    public InputStream getPomAsStream() {
        return kieModule.getPomAsStream();
    }

    public ReleaseId getGAV() {
        return kieModule.getReleaseId();
    }

    public long getCreationTimestamp() {
        return kieModule.getCreationTimestamp();
    }

    public InternalKieModule getKieModuleForKBase(String kBaseName) {
        return this.kJarFromKBaseName.get( kBaseName );
    }

    public InternalKieModule getInternalKieModule() {
        return kieModule;
    }

    public ClassLoader getClassLoader() {
        return this.cl;
    }

    public Map<String, KieBaseModel> updateToModule(InternalKieModule updatedKieModule) {
        Map<String, KieBaseModel> oldKieBaseModels = new HashMap<String, KieBaseModel>();
        oldKieBaseModels.putAll( kBaseModels );

        this.kieModules = null;
        this.kJarFromKBaseName.clear();

        ReleaseId currentReleaseId = this.kieModule.getReleaseId();
        ReleaseId updatingReleaseId = updatedKieModule.getReleaseId();

        if (currentReleaseId.getGroupId().equals(updatingReleaseId.getGroupId()) &&
            currentReleaseId.getArtifactId().equals(updatingReleaseId.getArtifactId())) {
            this.kieModule = updatedKieModule;
        } else if (this.kieModule.getKieDependencies().keySet().contains(updatingReleaseId)) {
            this.kieModule.addKieDependency(updatedKieModule);
        }

        synchronized (this) {
            cleanIndex();
            init(); // this might override class definitions, not sure we can do it any other way though
            // reset resource provider so it will serve resources from updated kmodule
            this.cl.setResourceProvider(kieModule.createResourceProvider());
        }

        return oldKieBaseModels;
    }

    @Override
    public synchronized KieBaseModel getDefaultKieBaseModel() {
        return super.getDefaultKieBaseModel();
    }

    @Override
    public synchronized KieSessionModel getDefaultKieSession() {
        return super.getDefaultKieSession();
    }

    @Override
    public synchronized KieSessionModel getDefaultStatelessKieSession() {
        return super.getDefaultStatelessKieSession();
    }

    @Override
    public synchronized KieBaseModel getKieBaseModel(String kBaseName) {
        return super.getKieBaseModel(kBaseName);
    }

    @Override
    public synchronized KieSessionModel getKieSessionModel(String kSessionName) {
        return super.getKieSessionModel(kSessionName);
    }
}

5.8 kmodule.xml

kmodule的简单配置规则在上面的实例中已经简单介绍,示例如下:

<?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>

下面具体介绍具体的配置

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将事件视为正常事实,而在流模式下允许对其进行时间推理。
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的类型。

第06章 规则文件

6.1 规则文件简介

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

从架构角度来讲,一般将同一业务的规则放置在同一规则文件,也可以根据不同类型处理操作放置在不同规则文件当中。不建议将所有的规则放置与一个规则文件当中。分开放置,当规则变动时不至于影响到不相干的业务。读取构建规则的成本业务会相应减少。

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

package package-name

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。
  • 结果部分,简称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。

标准规则的结构示例:

rule "name"
    attributes
    when
        LHS
    then 
        RHS
    end

LHS为空示例:

rule "name"
when 
then 
end

6.2 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方法使用时一定要慎重,否则极可能会造成死循环。

可以通过设置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属性。

6.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对象的改变而被出发了。

通过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的规则不再被触发。

6.4 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

rule "test-ruleflow-group2"
    ruleflow-group "group1"
    when
    then
        System.out.println("test-ruleflow-group2 被触发");
    end

6.5 salience

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

示例代码:

package com.rules

rule salience1

    salience 3
    when
    then
        System.out.println("salience1 被执行");
    end

rule salience2

    salience 5
    when
    then
        System.out.println("salience2 被执行");
    end

执行结果:

salience2 被执行
salience1 被执行

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

Drools还支持动态salience,可以使用绑定绑定变量表达式来作为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的值了。

6.6 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" 
    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下的规则将不会被执行。

6.6 activation-group

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

package com.rules 

rule "test-activation-group1" 
    activation-group "foo" 
    when 
    then 
        System.out.println("test-activation-group1 被触发"); 
    end 

rule "test-activation-group2" 
    activation-group "foo" 
    salience 1 
    when 
    then 
        System.out.println("test-activation-group2 被触发"); 
    end

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

test-activation-group2 被触发

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

6.7 dialect

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

dialect "mvel"

6.8 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 
        System.out.println("规则被执行"); 
    end

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

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

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

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

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

6.9 date-expires

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

6.10 duration&enabled

duration:

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

enabled:

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

6.11 定时器

6.12 日历

6.13 LHS

6.14 Pattern&约束

6.15 RHS

6.16 结果条件

6.17 注释&错误信息

6.18 关键字

6.19 global全局变量

6.20 获取规则名称和包名

6.21 Query查询

6.22 Function函数

首先来看一下function函数的语法结构图:

1549885668920

函数是将语义代码放置在规则文件中的一种方式,就相当于java类中的方法一样。函数并不会比辅助类做更多的事情,实际上,编译器会在幕后生成助手类。使用函数的好处是可以将业务逻辑集中放置在一个地方,根据需要可以对函数进行修改。但它既有好处也有坏处。函数对于调用规则的后果部分操作是最有用处的,特别是只有参数变化但执行的操作完全相同时。这里的函数可以对照java中方法的抽取封装来理解。

典型的函数声明如下所示:

function String hello(String name) {
    return "Hello "+name+"!";
}

实例规则代码如下:

package com.rules

function String hello(String name){
    return "Hello " + name + "!";
}

rule helloSomeone

    agenda-group "function-group"
    when
        eval(true);
    then
        System.out.println(hello("Tom"));
    end

测试代码如下:

@Test
public void testFunction(){
    KieSession kieSession = this.getKieSession("function-group");
    int count = kieSession.fireAllRules();
    kieSession.dispose();
    System.out.println("Fire " + count + " rule(s)!");
}

执行结果:

Hello Tom!
Fire 1 rule(s)!

需要注意的是,function虽然不是java的一部分,但是依然可以在这里使用。函数的参数根据需要可以有一个,也可以有多个,也可以没有。返回结果的类型定义和正常的java语法没有区别。

前面我们已经讲过如何引入java中的静态方法,此处的function也可以用静态方法来代替

6.23 Map使用

6.24 FactHandler使用

6.25 session使用说明

6.26 相同对象和List的使用

第07章 Spring集成Drools

1549899235270

1549899261463

domain

1549899278182

service

1549899287104

1549899297275

规则文件

1549899314405

applicationContext.xml配置文件

1549899323019

applicationContex-drools.xml配置文件

1549899341435

第08章 SpringBoot集成Drools

1549898995335

1549899000617

1549899007773

1549899012604

1549899172691

8.1 依赖引入

引入了springboot和drools的依赖,同时引入了kie-spring的集成依赖

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.changan</groupId>
    <artifactId>springboot-drools</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-drools</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <drools-version>7.17.0.Final</drools-version>
    </properties>

    <dependencies>
        <!--spring boot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--drools-->
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>${drools-version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>mvel2</artifactId>
                    <groupId>org.mvel</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-spring</artifactId>
            <version>${drools-version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-tx</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-beans</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-context</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

8.2 配置类

基于springboot的初始化配置

package com.changan.springbootdrools.config;

import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.kie.spring.KModuleBeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;

@Configuration
public class DroolsAutoConfiguration {
    private static final String RULES_PATH = "rules/";

    /**
     * 获取KieFileSystem
     *
     * @return
     * @throws IOException
     */
    @Bean
    @ConditionalOnMissingBean(KieFileSystem.class)
    public KieFileSystem kieFileSystem() throws IOException {
        KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
        for (Resource file : getRuleFiles()) {
            kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
        }
        return kieFileSystem;
    }

    /**
     * 获取规则文件
     *
     * @return
     * @throws IOException
     */
    private Resource[] getRuleFiles() throws IOException {
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
    }

    /**
     * 获取KieContainer
     *
     * @return
     * @throws IOException
     */
    @Bean
    @ConditionalOnMissingBean(KieContainer.class)
    public KieContainer kieContainer() throws IOException {
        KieServices kieServices = getKieServices();
        final KieRepository kieRepository = kieServices.getRepository();

        kieRepository.addKieModule(new KieModule() {
            @Override
            public ReleaseId getReleaseId() {
                return kieRepository.getDefaultReleaseId();
            }
        });

        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem());
        kieBuilder.buildAll();
        return kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
    }

    /**
     * 获取KieService
     *
     * @return
     */
    private KieServices getKieServices() {
        return KieServices.Factory.get();
    }

    /**
     * 获取KieBase
     *
     * @return
     * @throws IOException
     */
    @Bean
    @ConditionalOnMissingBean(KieBase.class)
    public KieBase kieBase() throws IOException {
        return kieContainer().getKieBase();
    }

    /**
     * 获取KieSession
     * @return
     * @throws IOException
     */
    @Bean
    @ConditionalOnMissingBean(KieSession.class)
    public KieSession kieSession() throws IOException {
        return kieContainer().newKieSession();
    }

    @Bean
    @ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
    public KModuleBeanFactoryPostProcessor kiePostProcessor() {
        return new KModuleBeanFactoryPostProcessor();
    }
}

8.3 Springboot启动类

package com.changan.springbootdrools;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootDroolsApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootDroolsApplication.class, args);
    }

}

8.4 实体类

package com.changan.springbootdrools.model;

public class Address {
    private String postcode;

    private String street;

    private String state;

    public String getPostcode() {
        return postcode;
    }

    public void setPostcode(String postcode) {
        this.postcode = postcode;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}

8.5 规则返回结果类

package com.changan.springbootdrools.model;

public class AddressCheckResult {
    private boolean postCodeResult = false; // true:通过校验;false:未通过校验

    public boolean isPostCodeResult() {
        return postCodeResult;
    }

    public void setPostCodeResult(boolean postCodeResult) {
        this.postCodeResult = postCodeResult;
    }
}

8.6 规则文件

package plausibcheck.adress

import com.changan.springbootdrools.model.Address;
import com.changan.springbootdrools.model.AddressCheckResult;

rule "Postcode should be filled with exactly 5 numbers"
    no-loop true
    when
        address:Address(postcode != null,postcode matches "([0-9]{5})");
        checkResult:AddressCheckResult();
    then
        checkResult.setPostCodeResult(true);
        System.out.println("规则中打印日志:校验通过!");
end

8.7 测试Controller

package com.changan.springbootdrools.controller;

import com.changan.springbootdrools.model.Address;
import com.changan.springbootdrools.model.AddressCheckResult;
import org.kie.api.runtime.KieSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RequestMapping("/test")
@RestController
public class TestController {
    @Resource
    private KieSession kieSession;

    @RequestMapping("/address")
    public String test(String postCode) {
        Address address = new Address();
        address.setPostcode(postCode);

        AddressCheckResult result = new AddressCheckResult();
        kieSession.insert(address);
        kieSession.insert(result);
        int ruleFiredCount = kieSession.fireAllRules();
        System.out.println("触发了" + ruleFiredCount + "条规则");
        if(result.isPostCodeResult()){
            System.out.println("规则校验通过");
        }
        return "触发了" + ruleFiredCount + "条规则";
    }
}

启动后,浏览器输入地址:http://localhost:8080/test/address?postCode=66666

第09章 决策表(规则和数据分离)

决策表是一个“精确而紧凑的”表示条件逻辑的方式,非常适合商业级别的规则。
目前决策表支持xls格式和csv格式。决策表与现有的drools drl文件使用可以无缝替换。

9.1 何时使用决策表?

  • 规则能够被表达为模板+数据的格式,考虑使用决策表
  • 很少量的规则不建议使用决策表
  • 不是遵循一组规则模板的规则也不建议使用决策表

决策表中的每一行就是对应模板的一行数据,将产生一个规则。运行决策表——Drools引擎所部署的系统中执行决策表。首先,决策表转换成的Drools规则语言(DRL),然后执行规则引擎需求。这意味着它是可能的业务规则的变更,无需停止和启动,或重新部署任何软件。

决策表

  • 决策表是另一种规则的表现形式
  • 特别是当你的规则有遵循类似的模式或者模板
  • 希望有这样一个电子表格视图管理规则
  • 实现了规则与数据的分离

9.2 决策表格式

1549885500915

解释:

  • RuleSet 和 drl 文件中的 package 是一样

  • Sequential 与 drl 文件中的属性优先级是一样的,只是这边为 true

  • Functions 与 drl 文件中的 function 是一样的

  • RuleTable 表示 rule name,必填

  • CONDITION ACTION 表示 rule 中的 LHS RHS 部分 至少要有一个

  • 从CONDITION 下面两行则表示 LHS 部分 第三行则为注释行,不计为规则部分,从第四行开始,每一行表示一条规则。

  • $param 表示占位符会替换下面每一行的值,生成一条规则。

    1549886168054

    1549886173548

9.3 决策表使用

首先需要引入maven的pom依赖:

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-decisiontables</artifactId>
    <version>${drools-version}</version>
</dependency>

可以通过以下两个方式来对决策表进行解析生成drl文件:

@Test
public void checkDrl() throws FileNotFoundException {
    File file = new File("decision.xls");
    InputStream is = new FileInputStream(file);
    SpreadsheetCompiler compiler = new SpreadsheetCompiler();
    String drl = compiler.compile(is, InputType.XLS);
    System.out.println(drl);
}

@Test
public void checkDrl2() throws FileNotFoundException {
    SpreadsheetCompiler compiler = new SpreadsheetCompiler();
    String drl = compiler.compile(ResourceFactory.newClassPathResource("decision/decision.xls"), InputType.XLS);
    System.out.println(drl);
}

文件防止位置如下:

1549887527882

可以通过查看生成的drl内容确定决策表的语法及业务是预期的。

决策表内容如下:

1549887775709

可以看到满足3<x<8的为第9行,输出结果为执行成功

像配置drl文件格式的规则一样,我们要执行一个xls决策表操作非常简单,只用把drl文件替换为对应的xls文件即可。

具体配置如下:
kmodule.xml中配置xls所在的resource目录对应的session:

<?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>
    <kbase name="decision-kbase" packages="decision">
        <ksession name="decision-rules"/>
    </kbase>
</kmodule>

同时将决策表的xls文件放置在decision包中,为了减少不必要的麻烦,决策表中定义的RuleSet值也应该是decision

同样的,在代码中只需像正常调用代码一样获取KieSession进行后续业务处理即可:

@Test
public void testDecision(){
    KieSession kieSession = this.getKieSessionBySessionName("decision-rules");
    kieSession.fireAllRules();
    kieSession.dispose();
}

BaseTest.java

/**
 * 基础测试类 提供了获取KieSession KieContainer方法
 */
public class BaseTest {
    protected KieSession getKieSession() {
        KieServices kieServices = KieServices.Factory.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("all-rules");
        return kieSession;
    }

    protected KieSession getKieSessionBySessionName(String sessionName) {
        KieContainer kieContainer = getKieContainer();
        KieSession kieSession = kieContainer.newKieSession(sessionName);
        return kieSession;
    }

    protected KieContainer getKieContainer(){
        KieServices kieServices = KieServices.Factory.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        return kieContainer;
    }

    protected KieSession getKieSession(String agendaGroupName) {
        KieSession kieSession = getKieSession();
        kieSession.getAgenda().getAgendaGroup(agendaGroupName).setFocus();
        return kieSession;
    }

    protected StatelessKieSession getStatelessKieSession() {
        KieContainer kieContainer = getKieContainer();
        StatelessKieSession kieSession = kieContainer.newStatelessKieSession("stateless-rules");
        return kieSession;
    }
}

运行结果:

1549887862247

第10章 Excel规则模板

10.1 规则模板简介

  • 规则模板是使用模板文件和表格数据源即时生成DRL规则的方法;
  • 表格数据源是指可以用表格中展示的数据,典型的介绍是数据库和Excel;
  • 如果根据存储在应用程序之外的数据来生成规则?解决方案之一就是:规则模板

10.2 规则模板优势

  • 规则的数据和结构完全分离;
  • 相同的模板可用于不同的数据集;
  • 同一数据集可用于不同的模板;
  • 与决策表相比,提供了极大的灵活性;

10.3 规则模板语法结构

template header 开头
变量(比如,id)
空行(表示header结束)
template
package
import
rule规则名称
规则属性
when、then、end,使用方法@{id}
end template

示例:

template header 
id 
username 

package template; 

import com.secbro2.drools.demo.Person; 

template "template-rules" 

rule "Categorize Persons_@{row.rowNumber}" 

    no-loop true 
    when 
        $p: Person(id == @{id}) 
    then 
        modify ($p){ 
            setUsername("@{username}")
        }; 
    end 
    end template

10.4 实战

kmodule.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <kbase packages="template" name="test-template">
        <ruleTemplate dtable="template/template.xls"
                      template="template/template.drt"
                      row="2"
                      col="1"/>
    </kbase>
</kmodule>

xls文件内容:

编号 名称
1 Tom
2 Lucy

执行程序:

public class TemplateTest {
    public static void main(String[] args) {
        KieServices kieServices = KieServices.Factory.get();
        KieBase kieBase = kieServices.getKieClasspathContainer().getKieBase("test-template");
        KieSession kieSession = kieBase.newKieSession();
        Person p = new Person(2,"");
        kieSession.insert(p);
        kieSession.fireAllRules();
        System.out.println(p.getUsername());
    }
}

打印结果:Lucy

1549893528150

第11章 Drools Workbench

使用Docker部署Drools Workbench

docker pull jboss/drools-workbench

1549884699036

第12章 动态规则文件

全部评论

相关推荐

不愿透露姓名的神秘牛友
05-01 13:13
ecece:这么明目张胆虚报就业率啊
点赞 评论 收藏
分享
评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客企业服务