drools指北——规则使用

2,395 阅读8分钟

drools指北——初识

drools指北——规则使用

KIE核心API

image.png

1.1 KieService

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

通过如下方式产生 KieServices:

KieServices ks = KieServices.Factory.get();

1.2 KieContainer

KieContainer 是所有给定 KieModule 的 KieBases 的集合。可以理解KieContainer就是一个KieBase的容器,KieContainer 承载了 KieModule 和其依赖,一个层级的 KieModules 结构可以被加载到一个 KieContainer 实例中。

KieContainer 可以拥有一个或者多个 KieBases.

image.png

KieContainer 可以通过 KieService 产生:

KieContainer kContainer = ks.newKieClasspathContainer();

1.3 KieRepository:

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

1.4 KieModule

每一个 KieModule 包含了 business assets(包括了流程,规则,决策表等等)。

一个 KieModule 是一个标准的 Java-Maven 项目,一般用kmodule.xml来表示。

KieModule 在 org.kie.api.builder 包下,KieModule 是一个存放所有定义好的 KieBases 资源的容器,和 pom.xml 文件相似,定义了其 ReleaseId, kmodule.xml 文件定义了 KieBase 的名字,配置,以及其他用来创建 KieSession 的资源,以及其他用来 build KIEBases 的资源。

指定的文件 kmodule.xml 定义在 META-TNF/ 目录下,一定了内部的资源如何分组如何配置等等信息。

KieModule 用来定义多个 KieBases 和 KieSessions。KieModule 可以包含其他 KieModules。

1.5 KieBase

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

1.6 KieProject:

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

1.7 KieSession

KieSession 是和工作流引擎交互的最常用的方式,KieSession 允许应用建立和引擎的交互,session 的状态会在每一次调用的时候保存下来。

而流程会在一组相同的变量上触发多次。当应用完成调用,必须调用 dispose() 来释放资源以及使用的内存。

KieServices kieServices = KieServices.Factory.get(); 
KieContainer kContainer = kieServices.getKieClasspathContainer(); 
KieSession kSession = kContainer.newKieSession(); 
for( Object fact : facts ) {
    kSession.insert( fact );
} 
kSession.fireAllRules(); 
kSession.dispose();

每一个 KieBase 都可以有一个或者多个 KieSessions。KieContainer 是线程安全的。KieContainer.newStatelessKieSession() 和KieContainer.newKieSession() 方法是线程不安全的。

1.8 有状态 Session 和无状态 Session 区别

Drools 的 Session 分为有状态和无状态。

stateful KieSession 可以在多次和 Rule Engine 交互的过程中保持状态。 而无状态的 KieSession 只允许我们交互一次,并直接获取结果。

StatefulKnowledgeSession

  • 与[[规则引擎]]持久交互
  • 推理过程多次触发同一个数据集
  • 使用完后,要调用 dispose() 方法
  • 有状态会话可以随时添加 Fact

Stateful 可以通过 insert 方法插入 Fact,并取得 FactHandle,通过这个 Handle 可以多次更新 Fact 从而触发规则

FactHandle handle = statefulKieSession.insert(factObject); 

factObject.setBalance(100.0); 

statefulKieSession.update(handle,factObject);

StatelessKnowledgeSession

  • 对 StatefulKnowledgeSession 做了包装
  • 不能重复插入 Fact
  • 执行规则使用 execute() 方法
  • insert, fireAllRules 和 dispose 方法

Stateless 类似一次函数调用,通过 execute 方法传入 Fact,匹配规则

有状态和无状态会话的一个重要区别是,无状态会话不使用推理,是一种最简单的使用方式,那么到底怎么理解这个“推理”,举个例子来说明:有两个规则,规则一里面可以修改age,规则二的条件是age>18,那么,插入一个age为10的对象,当你在规则一中用modify把age设置为24时,规则引擎会意识到这个值变了,会触发规则二的执行,这就是推理

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

DRL常用语法

DRL(Drools Rule Language) 文件是 Drools 规则引擎的规则文件,它使用规则的声明形式来描述应用程序行为。DRL 文件由许多规则组成,每个规则由三个部分构成:规则头、规则体和规则属性。以下是一个 DRL 文件的示例:

package org.example.rules

import org.example.objects.Person

rule "Age Rule"
when
    $person : Person(age < 30)
then
    System.out.println("Person " + $person.getName() + " is young.");
end

rule "Name Rule"
when
    $person : Person(name == "Smith")
then
    System.out.println("Person " + $person.getName() + " is named Smith.");
end

上面的 DRL 文件中,我们定义了两个规则:一个基于年龄比较,一个基于名称比较。在第一个规则中,当 Person 对象的年龄小于 30 岁时,规则会被触发,并输出该 Person 的名称和年龄。在第二个规则中,当 Person 对象的名称为 "Smith" 时,规则会被触发,并输出该 Person 的名称。其中,person是规则中用来标识Person对象的变量名,它可以在规则体中使用,通过person 是规则中用来标识 Person 对象的变量名,它可以在规则体中使用,通过 person.getName() 或 $person.getAge() 来获取对应的属性值。

如果我们想要编写适合我们业务场景的drl文件,需要先了解下他的常用语法

2.1 规则文件构成

关键字描述
package包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用
import用于导入类或者静态方法
global全局变量,用于在规则文件中定义全局变量,它可以让应用程序的对象在规则文件中能够被访问。可以用来为规则文件提供数据或服务。
functionfunction关键字用于在规则文件中定义函数,就相当于java类中的方法一样
queryquery查询提供了一种查询working memory中符合约束条件的Fact对象的简单方法。
rule end规则体

2.2 规则体语法结构

关键字描述
rule关键字,表示规则开始,参数为规则的唯一名称
attributes规则属性,是rule与when之间的参数,为可选项
when关键字,规则的条件部分(LHS)
then关键字,规则的结果部分(RHS)
end关键字,表示一个规则结束
  • When(LHS)中的一个条件或者多个条件,又称为pattern,多个pattern之间可以使用and和or进行连接,不写,默认连接为and.
  • 绑定变量名使用$符号作为前缀,可以作用于对象上也可以作用于对象的属性上。
package org.example.rules

import org.example.objects.Person

rule "Age and Gender Rule"
when
  $person: Person(age < 30, gender == "male")
then
  System.out.println("Person " + $person.getName() + " is a young male.");
end

2.3 规则语法

  • LHS(Left Hand Side):是规则的条件部分的通用名称。它由零个或多个条件元素组成。如果LHS为空,则它将被视为始终为true的条件元素。 (左手边)
  • RHS(Right Hand Side):是规则的后果或行动部分的通用名称。 (右手边)

这些都属于Drools的硬或者软关键字是我们在规则文件中定义包名或者规则名时明确不能使用的,否则程序会报错

本文篇幅有限,抛砖引玉,更多语法 可以在这里学习。

2.3.1 LHS(条件部分)语法

符号说明
>, <, >=, <=, ==, !=常规符号
contains, not contains检查一个Fact对象的某个属性值是否包含一个指定的对象值
memberOf, not memberOf判断一个Fact对象的某个属性是否在一个或多个集合中
matches, not matches判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配
in/not in类似于SQL语句中的in关键字
not, exists用于判断Working Memory中是否存在某个Fact对象
extends规则之间可以使用extends关键字进行规则条件部分的继承,类似于java类之间的继承

2.3.2 RHS(结果部分)语法

符号说明
update()更新工作内存中的数据,并让相关的规则重新匹配。 (要避免死循环)
insert()向工作内存中插入数据,并让相关的规则重新匹配
retract()删除工作内存中的数据,并让相关的规则重新匹配
rule "促销门槛30"
when 
$s:Order(shreshold == 30,amount > 30) 
then 
System.out.println("命中门槛30"); 
end 
rule “促销门槛50"
when 
$s:Order(shreshold == 50,amount > 50) 
then 
$s.setShreshold(30); 
update($s); 
System.out.println("将50门槛设置为30"); 
end 

这些操作完成后规则引擎会重新进行相关规则的匹配,原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功了。在drools中单纯的set值,是不会引起规则重新匹配。

2.3.3 其他属性

属性名说明
salience优先级,取值类型为Integer。数值越大越优先执行。每个规则都有一个默认的执行顺序,如果不设置salience,执行顺序为由上到下。
date-effective, date-expires指定规则生效,失效时间
activation-group激活分组,具有相同分组名称的规则只能有一个规则触发
agenda-group议程分组,只有获取焦点的组中的规则才有可能触发
timer定时器,指定规则触发的时间,类似cron表达式
auto-focus自动获取焦点,一般结合agenda-group一起使用
no-loop防止次规则死循环(update())

SpringBoot的整合

3.1 添加Drools的相关依赖

pom.xml文件中添加以下依赖:

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-core</artifactId>
    <version>7.57.0.Final</version>
</dependency>

<dependency>
    <groupId>org.kie</groupId>
    <artifactId>kie-api</artifactId>
    <version>7.57.0.Final</version>
</dependency>

3.2 配置Drools规则文件

将Drools规则文件(DRL文件)放到resources/rules目录下,例如discount.drl

package com.example.rules

import com.example.Order

rule "discount1"
    when
        $order: Order(totalAmount >= 500, totalAmount < 1000)
    then
        $order.setDiscount(0.9);
end

rule "discount2"
    when
        $order: Order(totalAmount >= 1000, totalAmount < 2000)
    then
        $order.setDiscount(0.8);
end

rule "discount3"
    when
        $order: Order(totalAmount >= 2000)
    then
        $order.setDiscount(0.7);
end

当订单金额大于等于500元时,打九折;当订单金额大于等于1000元时,打八折;当订单金额大于等于2000元时,打七折。

3.3 创建KieSession实例

在Spring Boot的配置类中创建KieSession实例,例如:

@Configuration
public class DroolsConfig {

    @Autowired
    private KieFileSystem kieFileSystem;

    @Bean
    public KieContainer kieContainer() {
        KieServices kieServices = KieServices.Factory.get();
        kieFileSystem.write(ResourceFactory.newClassPathResource("rules/discount.drl"));
        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem).buildAll();
        KieModule kieModule = kieBuilder.getKieModule();
        KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
        return kieContainer;
    }

    @Bean
    public KieSession kieSession() {
        KieSession kieSession = kieContainer().newKieSession();
        return kieSession;
    }
}


这里使用KieFileSystem对象将Drools规则文件加载到内存中,然后创建KieContainerKieSession对象,以便在应用程序中使用。

3.4 应用程序中使用Drools规则

在需要使用Drools规则的服务或控制器中注入KieSession实例,并在代码中执行规则匹配和规则操作,例如:

@Service
public class OrderService {

    @Autowired
    private KieSession kieSession;

    public Order applyDiscount(Order order) {
        kieSession.insert(order);
        kieSession.fireAllRules();
        return order;
    }
}

在上述代码中,OrderService服务中的applyDiscount方法将Order对象插入到KieSession中,并使用fireAllRules方法执行规则匹配和操作。匹配成功的规则将会触发规则中的动作,从而实现规则操作。

这样,就可以在Spring Boot中成功地整合Drools规则引擎了。

3.5 实战——代码生成drl文件

如果需要动态生成并加载Drools规则文件,可以使用KieServicesKieFileSystem对象来实现。以下是一个示例代码:

package com.example.droolsdemo;

import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieModule;
import org.kie.api.builder.ReleaseId;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class DroolsDemoApplication {

    public static void main(String[] args) throws IOException {
        // 创建KieServices对象
        KieServices kieServices = KieServices.Factory.get();

        // 创建KieFileSystem对象
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();

        // 生成Drools规则文件
        String drlContent = "package com.example.rules\n\n"
                + "import com.example.Order\n\n"
                + "rule \"discount\"\n"
                + "    when\n"
                + "        $order: Order(totalAmount >= 500)\n"
                + "    then\n"
                + "        $order.setDiscount(0.9);\n"
                + "end\n";

        // 将Drools规则文件写入KieFileSystem中
        Path drlFilePath = Paths.get("src/main/resources/rules/discount.drl");
        Files.writeString(drlFilePath, drlContent);
        kieFileSystem.write("src/main/resources/rules/discount.drl", kieServices.getResources().newFileSystemResource(drlFilePath.toFile()));

        // 创建KieBuilder对象
        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);

        // 编译Drools规则文件
        kieBuilder.buildAll();

        // 获取KieModule对象
        KieModule kieModule = kieBuilder.getKieModule();

        // 获取ReleaseId对象
        ReleaseId releaseId = kieModule.getReleaseId();

        // 创建KieContainer对象
        KieContainer kieContainer = kieServices.newKieContainer(releaseId);

        // 创建KieSession对象
        KieSession kieSession = kieContainer.newKieSession();

        // 插入订单数据
        Order order = new Order(1000.0);
        kieSession.insert(order);

        // 执行Drools规则
        kieSession.fireAllRules();

        // 输出订单折扣
        System.out.println("Order total amount: " + order.getTotalAmount());
        System.out.println("Order discount: " + order.getDiscount());

        // 删除生成的Drools规则文件
        Files.deleteIfExists(drlFilePath);
    }
}

在这个示例代码中,首先创建了KieServicesKieFileSystem对象,并使用write()方法将动态生成的Drools规则文件写入KieFileSystem中。接着创建KieBuilder对象,并调用buildAll()方法编译Drools规则文件。然后获取KieModule对象,并使用getReleaseId()方法获取ReleaseId对象。接着创建KieContainerKieSession对象,插入订单数据并执行Drools规则。最后输出订单的折扣