Drools 規則引擎應用

ityml發表於2022-03-11

規則引擎-drools

1 .場景

1.1需求

商城系統消費贈送積分

100元以下, 不加分 
100元-500元 加100分 
500元-1000元 加500分 
1000元 以上 加1000分
......

1.2傳統做法

1.2.1 if...else

if (order.getAmout() <= 100){
    order.setScore(0);
    addScore(order);
}else if(order.getAmout() > 100 && order.getAmout() <= 500){
    order.setScore(100);
    addScore(order);
}else if(order.getAmout() > 500 && order.getAmout() <= 1000){
    order.setScore(500);
    addScore(order);
}else{
    order.setScore(1000);
    addScore(order);
}

1.2.2 策略

interface Strategy {
    addScore(int num1,int num2);
}

class Strategy1 {
    addScore(int num1);
}
......................
interface StrategyN {
    addScore(int num1);
}

class Environment {
    private Strategy strategy;

    public Environment(Strategy strategy) {
        this.strategy = strategy;
    }

    public int addScore(int num1) {
        return strategy.addScore(num1);
    }
}

1.2.3 問題?

以上解決方法問題思考:
如果需求變更,積分層次結構增加,積分比例調整?
資料庫?

遇到的問題瓶頸:
第一,我們要簡化if else結構,讓業務邏輯和資料分離!
第二,分離出的業務邏輯必須要易於編寫,至少單獨編寫這些業務邏輯,要比寫程式碼快!
第三,分離出的業務邏輯必須要比原來的程式碼更容易讀懂!
第四,分離出的業務邏輯必須比原來的易於維護,至少改動這些邏輯,應用程式不用重啟!

2.是什麼

2.1概念

規則引擎由推理引擎發展而來,是一種嵌入在應用程式中的元件,實現了將業務決策從應用程式程式碼中分離出來,並使用預定義的語義模組編寫業務決策。接受資料輸入,解釋業務規則,並根據業務規則做出業務決策

需要注意的是規則引擎並不是一個具體的技術框架,而是指的一類系統,即業務規則管理系統。目前市面上具體的規則引擎產品有:drools、VisualRules、iLog等

在很多企業的 IT 業務系統中,經常會有大量的業務規則配置,而且隨著企業管理者的決策變化,這些業務規則也會隨之發生更改。為了適應這樣的需求,我們的 IT 業務系統應該能快速且低成本的更新。適應這樣的需求,一般的作法是將業務規則的配置單獨拿出來,使之與業務系統保持低耦合。目前,實現這樣的功能的程式,已經被開發成為規則引擎。

2.2 起源

2.3 原理--基於 rete 演算法的規則引擎

2.3.1 原理

在 AI 領域,產生式系統是一個很重要的理論,產生式推理分為正向推理和逆向推理產生式,其規則的一般形式是:IF 條件 THEN 操作。rete 演算法是實現產生式系統中正向推理的高效模式匹配演算法,通過形成一個 rete 網路進行模式匹配,利用基於規則的系統的時間冗餘性和結構相似性特徵 ,提高系統模式匹配效率

正向推理(Forward-Chaining)和反向推理(Backward-Chaining)

(1)正向推理也叫演繹法,由事實驅動,從一個初始的事實出發,不斷地從應用規則得出結論。首先在候選佇列中選擇一條規則作為啟用規則進行推理,記錄其結論作為下一步推理的證據。如此重複這個過程,直到再無可用規則可被選用或者求得了所要求的解為止。

(2)反向推理也叫歸納法,由目標驅動,首先提出某個假設,然後尋找支援該假設的證據,若所需的證據都能找到,說明原假設是正確的,若無論如何都找不到所需要的證據,則說明原假設不成立,此時需要另作新的假設。

2.3.2 rete演算法

Rete 演算法最初是由卡內基梅隆大學的 Charles L.Forgy 博士在 1974 年發表的論文中所闡述的演算法 , 該演算法提供了專家系統的一個高效實現。自 Rete 演算法提出以後 , 它就被用到一些大型的規則系統中 , 像 ILog、Jess、JBoss Rules 等都是基於 RETE 演算法的規則引擎 。

Rete 在拉丁語中譯為”net”,即網路。Rete 匹配演算法是一種進行大量模式集合和大量物件集合間比較的高效方法,通過網路篩選的方法找出所有匹配各個模式的物件和規則。

其核心思想是將分離的匹配項根據內容動態構造匹配樹,以達到顯著降低計算量的效果。Rete 演算法可以被分為兩個部分:規則編譯和規則執行 。當 Rete 演算法進行事實的斷言時,包含三個階段:匹配、選擇和執行,稱做 match-select-act cycle。

2.4 規則引擎應用場景

業務領域 示例
財務決策 貸款發放,徵信系統
庫存管理 及時的供應鏈路
票價計算 航空,傳播,火車及其他公共汽車運輸
生產採購系統 產品原材料採購管理
風控系統 風控規則計算
促銷平臺系統 滿減,打折,加價購

2.5 Drools 介紹

Drools 具有一個易於訪問企業策略、易於調整以及易於管理的開源業務 規則引擎,符合業內標準,速度快、效率高。業務分析師或稽核人員可以利用它輕鬆檢視業務規則,從而檢驗已編碼的規則是否執行了所需的業務規則。其前身是 Codehaus 的一個開源專案叫 Drools,後被納入 JBoss 門下,更名為 JBoss Rules,成為了 JBoss 應用伺服器的規則引擎。

Drools 被分為兩個主要的部分:編譯和執行時。編譯是將規則描述檔案按 ANTLR 3 語法進行解析,對語法進行正確性的檢查,然後產生一種中間結構“descr”,descr 用 AST 來描述規則。目前,Drools 支援四種規則描述檔案,分別是:drl 檔案、 xls 檔案、brl 檔案和 dsl 檔案,其中,常用的描述檔案是 drl 檔案和 xls 檔案,而 xls 檔案更易於維護,更直觀,更為被業務人員所理解。執行時是將 AST傳到 PackageBuilder,由 PackagBuilder來產生 RuleBase,它包含了一個或多個 Package 物件。

3 .消費贈送積分案例

上圖為實際用法:

3.1 第一步: 建立工程,引入jar

由於當前java開發,普通使用springboot ,本課程以springboot為基本框架演示

jar 依賴,注意,排除spring相關依賴

<!-- 規則引擎 -->
        <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>

3.2 建立 drools 自動配置類

drools 在spring 或者springboot中用法一樣,其實就是建立好一些bean

package com.ityml.drools.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;


/**
 * <p> 規則引擎自動配置類 </p>
 * @author ityml
 * @date 2019/9/10 11:20
 */
@Configuration
public class DroolsAutoConfiguration {

    private static final String RULES_PATH = "rules/";

    private KieServices getKieServices() {
        return KieServices.Factory.get();
    }

    @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;
    }

    private Resource[] getRuleFiles() throws IOException {
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
    }

    @Bean
    @ConditionalOnMissingBean(KieContainer.class)
    public KieContainer kieContainer() throws IOException {
        final KieRepository kieRepository = getKieServices().getRepository();

        kieRepository.addKieModule(() -> kieRepository.getDefaultReleaseId());

        KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
        kieBuilder.buildAll();

        KieContainer kieContainer = getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());

        return kieContainer;
    }

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

}

3.2訂單實體類

@Data
@Accessors(chain = true)
public class Order {

    /**
     * 訂單原價金額
     */
    private int price;

    /**
     *下單人
     */
    private User user;

    /**
     *積分
     */
    private int score;

    /**
     * 下單日期
     */
    private Date bookingDate;
} 

3.3規則引擎檔案

package rules

import com.ityml.drools.entity.Order

rule "zero"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout <= 100)
    then
        $s.setScore(0);
        update($s);
end

rule "add100"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout > 100 && amout <= 500)
    then
        $s.setScore(100);
        update($s);
end

rule "add500"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout > 500 && amout <= 1000)
    then
        $s.setScore(500);
        update($s);
end

rule "add1000"
    no-loop true
    lock-on-active true
    salience 1
    when
        $s : Order(amout > 1000)
    then
        $s.setScore(1000);
        update($s);
end

3.4客戶端

/**
 * 需求
 * 計算額外積分金額 規則如下: 訂單原價金額
 * 100以下, 不加分
 * 100-500 加100分
 * 500-1000 加500分
 * 1000 以上 加1000分
 */
public class DroolsOrderTests extends DroolsApplicationTests {
    @Resource
    private KieContainer kieContainer;

    @Test
    public void Test() throws Exception {
        List<Order> orderList = getInitData();
        for (Order order : orderList) {
            if (order.getAmout() <= 100) {
                order.setScore(0);
                addScore(order);
            } else if (order.getAmout() > 100 && order.getAmout() <= 500) {
                order.setScore(100);
                addScore(order);
            } else if (order.getAmout() > 500 && order.getAmout() <= 1000) {
                order.setScore(500);
                addScore(order);
            } else {
                order.setScore(1000);
                addScore(order);
            }
        }
    }

    @Test
    public void droolsOrderTest() throws Exception {
        KieSession kieSession = kieContainer.newKieSession();
        List<Order> orderList = getInitData();
        for (Order order: orderList) {
            // 1-規則引擎處理邏輯
            kieSession.insert(order);
            kieSession.fireAllRules();
            // 2-執行完規則後, 執行相關的邏輯
            addScore(order);
        }
        kieSession.dispose();
    }



    private static void addScore(Order o){
        System.out.println("使用者" + o.getUser().getName() + "享受額外增加積分: " + o.getScore());
    }

    private static List<Order> getInitData() throws Exception {
        List<Order> orderList = new ArrayList<>();
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        {
            Order order = new Order();
            order.setAmout(80);
            order.setBookingDate(df.parse("2015-07-01"));
            User user = new User();
            user.setLevel(1);
            user.setName("Name1");
            order.setUser(user);
            order.setScore(111);
            orderList.add(order);
        }
        {
            Order order = new Order();
            order.setAmout(200);
            order.setBookingDate(df.parse("2015-07-02"));
            User user = new User();
            user.setLevel(2);
            user.setName("Name2");
            order.setUser(user);
            orderList.add(order);
        }
        {
            Order order = new Order();
            order.setAmout(800);
            order.setBookingDate(df.parse("2015-07-03"));
            User user = new User();
            user.setLevel(3);
            user.setName("Name3");
            order.setUser(user);
            orderList.add(order);
        }
        {
            Order order = new Order();
            order.setAmout(1500);
            order.setBookingDate(df.parse("2015-07-04"));
            User user = new User();
            user.setLevel(4);
            user.setName("Name4");
            order.setUser(user);
            orderList.add(order);
        }
        return orderList;
    }
}

3.5 drools 開發小結

3.5.1 drools 組成

drools規則引擎由以下幾部分構成:

  • Working Memory(工作記憶體)
  • Rules(規則庫)
  • Facts
  • Production memory
  • Working memory:
  • Agenda

如下圖所示:

3.5.2 相關概念說明

Working Memory:工作記憶體,drools規則引擎會從Working Memory中獲取資料並和規則檔案中定義的規則進行模式匹配,所以我們開發的應用程式只需要將我們的資料插入到Working Memory中即可,例如本案例中我們呼叫kieSession.insert(order)就是將order物件插入到了工作記憶體中。

Fact:事實,是指在drools 規則應用當中,將一個普通的JavaBean插入到Working Memory後的物件就是Fact物件,例如本案例中的Order物件就屬於Fact物件。Fact物件是我們的應用和規則引擎進行資料互動的橋樑或通道。

Rules:規則庫,我們在規則檔案中定義的規則都會被載入到規則庫中。

Pattern Matcher:匹配器,將Rule Base中的所有規則與Working Memory中的Fact物件進行模式匹配,匹配成功的規則將被啟用並放入Agenda中。

Agenda:議程,用於存放通過匹配器進行模式匹配後被啟用的規則。

3.5.3 規則引擎執行過程

3.5.4 KIE介紹

在上述分析積分兌換的過程中,簡單地使用了 "kie "開頭的一些類名,Kie全稱為Knowledge Is Everything,即"知識就是一切"的縮寫,是Jboss一系列專案的總稱。官網描述:這個名字滲透在GitHub賬戶和Maven POMs中。隨著範圍的擴大和新專案的展開,KIE(Knowledge Is Everything的縮寫)被選為新的組名。KIE的名字也被用於系統的共享方面;如統一的構建、部署和使用。

4.規則檔案開發

4.1 規則檔案構成

在使用Drools時非常重要的一個工作就是編寫規則檔案,通常規則檔案的字尾為.drl。

drl是Drools Rule Language的縮寫。在規則檔案中編寫具體的規則內容。

一套完整的規則檔案內容構成如下:

關鍵字 描述
package 包名,只限於邏輯上的管理,同一個包名下的查詢或者函式可以直接呼叫
import 用於匯入類或者靜態方法
global 全域性變數
function 自定義函式
query 查詢
rule end 規則體

Drools支援的規則檔案,除了drl形式,還有Excel檔案型別的。

4.2 規則體語法結構

規則體是規則檔案內容中的重要組成部分,是進行業務規則判斷、處理業務結果的部分。

規則體語法結構如下:

rule "ruleName"
    attributes
    when
        LHS 
    then
        RHS
end

rule:關鍵字,表示規則開始,引數為規則的唯一名稱。

attributes:規則屬性,是rule與when之間的引數,為可選項。

when:關鍵字,後面跟規則的條件部分。

LHS(Left Hand Side):是規則的條件部分的通用名稱。它由零個或多個條件元素組成。如果LHS為空,則它將被視為始終為true的條件元素。 (左手邊)

then:關鍵字,後面跟規則的結果部分。

RHS(Right Hand Side):是規則的後果或行動部分的通用名稱。 (右手邊)

end:關鍵字,表示一個規則結束。

4.3 註釋

在drl形式的規則檔案中使用註釋和Java類中使用註釋一致,分為單行註釋和多行註釋。

單行註釋用"//"進行標記,多行註釋以"/"開始,以"/"結束。如下示例:

//規則rule1的註釋,這是一個單行註釋
rule "rule1"
    when
    then
        System.out.println("rule1觸發");
end

/*
規則rule2的註釋,
這是一個多行註釋
*/
rule "rule2"
    when
    then
        System.out.println("rule2觸發");
end

4.4 Pattern模式匹配

前面我們已經知道了Drools中的匹配器可以將Rule Base中的所有規則與Working Memory中的Fact物件進行模式匹配,那麼我們就需要在規則體的LHS部分定義規則並進行模式匹配。LHS部分由一個或者多個條件組成,條件又稱為pattern。

pattern的語法結構為:繫結變數名:Object(Field約束)

其中繫結變數名可以省略,通常繫結變數名的命名一般建議以$開始。如果定義了繫結變數名,就可以在規則體的RHS部分使用此繫結變數名來操作相應的Fact物件。Field約束部分是需要返回true或者false的0個或多個表示式。

例如我們的入門案例中:

rule "add100"
    no-loop true
    lock-on-active true
    salience 1
    when
        $order : Order(price > 100 && price <= 500)
    then
        $order.setScore(100);
        update($s);
end

通過上面的例子我們可以知道,匹配的條件為:

1、工作記憶體中必須存在Order這種型別的Fact物件-----型別約束

2、Fact物件的price屬性值必須大於100------屬性約束

3、Fact物件的price屬性值必須小於等於500------屬性約束

以上條件必須同時滿足當前規則才有可能被啟用。

繫結變數既可以用在物件上,也可以用在物件的屬性上。例如上面的例子可以改為:

rule "add100"
    no-loop true
    lock-on-active true
    salience 1
    when
        $order : Order($price:price > 100 && amopriceut <= 500)
    then
        System.out.println("$price=" + $price);
        $s.setScore(100);
        update($s);
end

LHS部分還可以定義多個pattern,多個pattern之間可以使用and或者or進行連線,也可以不寫,預設連線為and。

rule "add100"
    no-loop true
    lock-on-active true
    salience 1
    when
        $order : Order(price > 100 && price <= 500) and
        $user : User(level>3)
    then
        System.out.println($order.getUser());
        $order.setScore(100);
        update($order);
end

4.5 比較操作符

Drools提供的比較操作符,如下表:

符號 說明
> 大於
< 小於
>= 大於等於
<= 小於等於
== 等於
!= 不等於
contains 檢查一個Fact物件的某個屬性值是否包含一個指定的物件值
not contains 檢查一個Fact物件的某個屬性值是否不包含一個指定的物件值
memberOf 判斷一個Fact物件的某個屬性是否在一個或多個集合中
not memberOf 判斷一個Fact物件的某個屬性是否不在一個或多個集合中
matches 判斷一個Fact物件的屬性是否與提供的標準的Java正規表示式進行匹配
not matches 判斷一個Fact物件的屬性是否不與提供的標準的Java正規表示式進行匹配

前6個比較操作符和Java中的完全相同,下面我們重點學習後6個比較操作符。

4.5.1 語法

  • contains | not contains語法結構

    Object(Field[Collection/Array] contains value)

    Object(Field[Collection/Array] not contains value)

  • memberOf | not memberOf語法結構

    Object(field memberOf value[Collection/Array])

    Object(field not memberOf value[Collection/Array])

  • matches | not matches語法結構

    Object(field matches "正規表示式")

    Object(field not matches "正規表示式")

contain是前面包含後面,memberOf是後面包含前面。

4.5.2 操作步驟

第一步:建立實體類,用於測試比較操作符

package com.ityml.drools.entity;

import lombok.Data;
import lombok.experimental.Accessors;

import java.util.List;

/**
 * @author ityml
 * @date 2021-06-16 21:11
 */

@Data
@Accessors(chain = true)
public class ComparisonEntity {

    /**
     *名字集合
     */
    private String names;

    /**
     * 字串集合
     */
    private List<String> list;

}

第二步:在/resources/rules下建立規則檔案comparison.drl

package rules
import com.ityml.drools.entity.ComparisonEntity

/*
 用於測試Drools提供的比較操作符
*/

//測試比較操作符contains
rule "rule_comparison_contains"
    when
        ComparisonEntity(names contains "張三")
        ComparisonEntity(list contains names)
    then
        System.out.println("規則rule_comparison_contains觸發");
end

//測試比較操作符not contains
rule "rule_comparison_notContains"
    when
        ComparisonEntity(names not contains "張三")
        ComparisonEntity(list not contains names)
    then
        System.out.println("規則rule_comparison_notContains觸發");
end

//測試比較操作符memberOf
rule "rule_comparison_memberOf"
    when
        ComparisonEntity(names memberOf list)
    then
        System.out.println("規則rule_comparison_memberOf觸發");
end

//測試比較操作符not memberOf
rule "rule_comparison_notMemberOf"
    when
        ComparisonEntity(names not memberOf list)
    then
        System.out.println("規則rule_comparison_notMemberOf觸發");
end

//測試比較操作符matches
rule "rule_comparison_matches"
    when
        ComparisonEntity(names matches "張.*")
    then
        System.out.println("規則rule_comparison_matches觸發");
end

//測試比較操作符not matches
rule "rule_comparison_notMatches"
    when
        ComparisonEntity(names not matches "張.*")
    then
        System.out.println("規則rule_comparison_notMatches觸發");
end

第三步:編寫單元測試

package com.ityml.drools.client;

import com.ityml.drools.DroolsApplicationTests;
import com.ityml.drools.entity.ComparisonEntity;
import org.junit.jupiter.api.Test;
import org.kie.api.KieBase;
import org.kie.api.runtime.KieSession;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * @author ityml
 * @date 2021-06-17 23:46
 */
public class ComparisonTest extends DroolsApplicationTests {

    @Resource
    public KieBase kieBase;

    @Test
    public void testComparison(){
        KieSession kieSession = kieBase.newKieSession();
        ComparisonEntity comparisonEntity = new ComparisonEntity();
        comparisonEntity.setNames("張三");
        List<String> list = new ArrayList<>();
        list.add("張三");
        list.add("李四");
        comparisonEntity.setList(list);

        kieSession.insert(comparisonEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

4.6 執行指定規則

通過前面的案例可以看到,我們在呼叫規則程式碼時,滿足條件的規則都會被執行。那麼如果我們只想執行其中的某個規則如何實現呢?

Drools給我們提供的方式是通過規則過濾器來實現執行指定規則。對於規則檔案不用做任何修改,只需要修改Java程式碼即可,如下:


//通過規則過濾器實現只執行指定規則
kieSession.fireAllRules(new kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rule_filter_1"));

4.7 關鍵字

Drools的關鍵字分為:硬關鍵字(Hard keywords)和軟關鍵字(Soft keywords)。

硬關鍵字是我們在規則檔案中定義包名或者規則名時明確不能使用的,否則程式會報錯。軟關鍵字雖然可以使用,但是不建議使用。

硬關鍵字包括:true false null

軟關鍵字包括:lock-on-active date-effective date-expires no-loop auto-focus activation-group agenda-group ruleflow-group entry-point duration package import dialect salience enabled attributes rule extend when then template query declare function global eval not in or and exists forall accumulate collect from action reverse result end over init

比如:
rule true  //不可以
rule "true" 可以

5. 規則屬性 attributes

前面我們已經知道了規則體的構成如下:

rule "ruleName"
    attributes
    when
        LHS
    then
        RHS
end

本章節就是針對規則體的attributes屬性部分進行講解。Drools中提供的屬性如下表(部分屬性):

屬性名 說明
salience 指定規則執行優先順序
dialect 指定規則使用的語言型別,取值為java和mvel
enabled 指定規則是否啟用
date-effective 指定規則生效時間
date-expires 指定規則失效時間
activation-group 啟用分組,具有相同分組名稱的規則只能有一個規則觸發
agenda-group 議程分組,只有獲取焦點的組中的規則才有可能觸發
timer 定時器,指定規則觸發的時間
auto-focus 自動獲取焦點,一般結合agenda-group一起使用
no-loop 防止死迴圈,防止自己更新規則再次觸發
lock-on-active no-loop增強版本。可防止別人更新規則再次出發

5.1 enabled屬性

enabled屬性對應的取值為true和false,預設值為true。

用於指定當前規則是否啟用,如果設定的值為false則當前規則無論是否匹配成功都不會觸發

package rules
import com.ityml.drools.entity.AttributesEnabledEntity

/*
 用於測試Drools 屬性:enabled
*/

//測試enabled
rule "rule_attributes_enabled"
    enabled false
    when
        AttributesEnabledEntity(num > 10)
    then
        System.out.println("規則rule_attributes_enabled觸發");
end

5.2 dialect屬性

dialect屬性用於指定當前規則使用的語言型別,取值為java和mvel,預設值為java。

注:mvel是一種基於java語法的表示式語言。

雖然mvel吸收了大量的java語法,但作為一個表示式語言,還是有著很多重要的不同之處,以達到更高的效率,比如:mvel像正規表示式一樣,有直接支援集合、陣列和字串匹配的操作符。

除了表示式語言外,mvel還提供了用來配置和構造字串的模板語言。

mvel2.x表示式包含以下部分的內容:屬性表示式,布林表示式,方法呼叫,變數賦值,函式定義

5.3 salience屬性

salience屬性用於指定規則的執行優先順序,取值型別為Integer數值越大越優先執行。每個規則都有一個預設的執行順序,如果不設定salience屬性,規則體的執行順序為由上到下。

drl檔案內容如下:

package rules
import com.ityml.drools.entity.AttributesSalienceEntity

/*
 用於測試Drools 屬性:salience
*/

rule "rule_attributes_salience_1"
    when
        AttributesSalienceEntity(flag == true)
    then
        System.out.println("規則 rule_attributes_salience_1 觸發");
end

rule "rule_attributes_salience_2"
    when
        AttributesSalienceEntity(flag == true)
    then
        System.out.println("規則 rule_attributes_salience_2 觸發");
end

rule "rule_attributes_salience_3"
    when
        AttributesSalienceEntity(flag == true)
    then
        System.out.println("規則 rule_attributes_salience_3 觸發");
end

通過控制檯可以看到,由於以上三個規則沒有設定salience屬性,所以執行的順序是按照規則檔案中規則的順序由上到下執行的。接下來我們修改一下檔案內容:

package rules
import com.ityml.drools.entity.AttributesSalienceEntity

/*
 用於測試Drools 屬性:salience
*/

rule "rule_attributes_salience_1"
    salience 10
    when
        AttributesSalienceEntity(flag == true)
    then
        System.out.println("規則 rule_attributes_salience_1 觸發");
end

rule "rule_attributes_salience_2"
    salience 20
    when
        AttributesSalienceEntity(flag == true)
    then
        System.out.println("規則 rule_attributes_salience_2 觸發");
end

rule "rule_attributes_salience_3"
    salience 1
    when
        AttributesSalienceEntity(flag == true)
    then
        System.out.println("規則 rule_attributes_salience_3 觸發");
end

通過控制檯可以看到,規則檔案執行的順序是按照我們設定的salience值由大到小順序執行的。

建議在編寫規則時使用salience屬性明確指定執行優先順序。

5.4 no-loop屬性

no-loop屬性用於防止死迴圈,當規則通過update之類的函式修改了Fact物件時,可能使當前規則再次被啟用從而導致死迴圈。取值型別為Boolean,預設值為false。測試步驟如下:

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.AttributesNoLoopEntity

/*
 用於測試Drools 屬性:no-loop
*/

rule "rule_attributes_noloop"
    //no-loop true
    when
        $attributesNoLoopEntity:AttributesNoLoopEntity(num > 1)
    then
        update($attributesNoLoopEntity)
        System.out.println("規則 rule_attributes_noloop 觸發");
end

第二步:編寫單元測試

    @Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        AttributesNoLoopEntity attributesNoLoopEntity = new AttributesNoLoopEntity();
        attributesNoLoopEntity.setNum(20);

        kieSession.insert(attributesNoLoopEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

通過控制檯可以看到,由於我們沒有設定no-loop屬性的值,所以發生了死迴圈。接下來設定no-loop的值為true再次測試則不會發生死迴圈。

5.5 lock-on-active屬性

lock-on-active這個屬性,可以限制當前規則只會被執行一次,包括當前規則的重複執行不是本身觸發的。取值型別為Boolean,預設值為false。測試步驟如下:

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.AttributesLockOnActiveEntity

/*
 用於測試Drools 屬性:lock-on-active
*/

rule "rule_attributes_lock_on_active_1"
    no-loop true
    when
        $attributesLockOnActiveEntity:AttributesLockOnActiveEntity(num > 1)
    then
        update($attributesLockOnActiveEntity)
        System.out.println("規則 rule_attributes_lock_on_active_1 觸發");
end

rule "rule_attributes_lock_on_active_2"
    no-loop true
    lock-on-active true
    when
        $attributesLockOnActiveEntity:AttributesLockOnActiveEntity(num > 1)
    then
        update($attributesLockOnActiveEntity)
        System.out.println("規則 rule_attributes_lock_on_active_2 觸發");
end

第二步:編寫單元測試

    @Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        AttributesLockOnActiveEntity attributesLockOnActiveEntity = new AttributesLockOnActiveEntity();

        attributesLockOnActiveEntity.setNum(20);

        kieSession.insert(attributesLockOnActiveEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

no-loop的作用是限制因為modify等更新操作導致規則重複執行,但是有一個限定條件,是當前規則中進行更新導致當前規則重複執行。而不是防止其他規則更新相同的fact物件,導致當前規則更新,lock-on-active可以看作是no-loop的加強版,不僅能限制自己的更新,還能限制別人的更新造成的死迴圈。

5.6 activation-group屬性。

activation-group屬性是指啟用分組,取值為String型別。具有相同分組名稱的規則只能有一個規則被觸發。

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.AttributesActivationGroupEntity

/*
 用於測試Drools 屬性: activation-group
*/

rule "rule_attributes_activation_group_1"
    activation-group "customGroup"
    when
        $attributesActivationGroupEntity:AttributesActivationGroupEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_activation_group_1 觸發");
end

rule "rule_attributes_activation_group_2"
    activation-group "customGroup"
    when
        $attributesActivationGroupEntity:AttributesActivationGroupEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_activation_group_2 觸發");
end

第二步:編寫單元測試

	@Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        AttributesActivationGroupEntity attributesActivationGroupEntity = new AttributesActivationGroupEntity();
        attributesActivationGroupEntity.setNum(20);

        kieSession.insert(attributesActivationGroupEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

通過控制檯可以發現,上面的兩個規則因為屬於同一個分組,所以只有一個觸發了。同一個分組中的多個規則如果都能夠匹配成功,具體哪一個最終能夠被觸發可以通過salience屬性確定。

5.7 agenda-group屬性

agenda-group屬性為議程分組,屬於另一種可控的規則執行方式。使用者可以通過設定agenda-group來控制規則的執行,只有獲取焦點的組中的規則才會被觸發。

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.AttributesAgendaGroupEntity

/*
 用於測試Drools 屬性: agenda-group
*/

rule "rule_attributes_agenda_group_1"
    agenda-group "customAgendaGroup1"
    when
        $attributesAgendaGroupEntity:AttributesAgendaGroupEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_agenda_group_1 觸發");
end

rule "rule_attributes_agenda_group_2"
    agenda-group "customAgendaGroup1"
    when
        $attributesAgendaGroupEntity:AttributesAgendaGroupEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_agenda_group_2 觸發");
end


rule "rule_attributes_activation_group_3"
    agenda-group "customAgendaGroup2"
    when
        $attributesAgendaGroupEntity:AttributesAgendaGroupEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_activation_group_3 觸發");
end

rule "rule_attributes_agenda_group_4"
    agenda-group "customAgendaGroup2"
    when
        $attributesAgendaGroupEntity:AttributesAgendaGroupEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_agenda_group_4 觸發");
end

第二步:編寫單元測試

    @Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        AttributesAgendaGroupEntity attributesAgendaGroupEntity = new AttributesAgendaGroupEntity();
        attributesAgendaGroupEntity.setNum(20);

        kieSession.insert(attributesAgendaGroupEntity);
        kieSession.getAgenda().getAgendaGroup("customAgendaGroup2").setFocus();

        kieSession.fireAllRules();
        kieSession.dispose();
    }

通過控制檯可以看到,只有獲取焦點的分組中的規則才會觸發。與activation-group不同的是,activation-group定義的分組中只能夠有一個規則可以被觸發,而agenda-group分組中的多個規則都可以被觸發。

5.8 auto-focus屬性

auto-focus屬性為自動獲取焦點,取值型別為Boolean,預設值為false。一般結合agenda-group屬性使用,當一個議程分組未獲取焦點時,可以設定auto-focus屬性來控制。

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.AttributesAutoFocusEntity

/*
 用於測試Drools 屬性: auto-focus
*/

rule "rule_attributes_auto_focus_1"
    agenda-group "customAgendaGroup1"
    when
        $attributesAutoFocusEntity:AttributesAutoFocusEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_auto_focus_1 觸發");
end

rule "rule_attributes_auto_focus_2"
    agenda-group "customAgendaGroup1"
    when
        $attributesAutoFocusEntity:AttributesAutoFocusEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_auto_focus_2 觸發");
end

rule "rule_attributes_auto_focus_3"
    agenda-group "customAgendaGroup2"
//    auto-focus true
    when
        $attributesAutoFocusEntity:AttributesAutoFocusEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_auto_focus_3 觸發");
end

rule "rule_attributes_auto_focus_4"
    agenda-group "customAgendaGroup2"
    when
        $attributesAutoFocusEntity:AttributesAutoFocusEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_auto_focus_4 觸發");
end

第二步:編寫單元測試

    @Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        AttributesAutoFocusEntity attributesAutoFocusEntity = new AttributesAutoFocusEntity();
        attributesAutoFocusEntity.setNum(20);

        kieSession.insert(attributesAutoFocusEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

通過控制檯可以看到,設定auto-focus屬性為true的規則都觸發了。

注意:同一個組,只要有個設定auto-focus true 其他的設定不設定都無所謂啦。都會起作用的。

5.9 timer屬性

timer屬性可以通過定時器的方式指定規則執行的時間,使用方式有兩種:

方式一:timer (int: ?)

此種方式遵循java.util.Timer物件的使用方式,第一個參數列示幾秒後執行,第二個參數列示每隔幾秒執行一次,第二個引數為可選。

方式二:timer(cron: )

此種方式使用標準的unix cron表示式的使用方式來定義規則執行的時間。

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.AttributesTimerEntity

/*
 用於測試Drools 屬性: timer
*/

rule "rule_attributes_timer_1"
    timer(5s 2s)
    when
        $attributesTimerEntity:AttributesTimerEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_timer_1 觸發");
end

rule "rule_attributes_timer_2"
    timer(cron:0/1 * * * * ?)
    when
        $attributesTimerEntity:AttributesTimerEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_timer_2 觸發");
end

第二步:編寫單元測試

@Test
    public void test() throws InterruptedException {

        KieSession kieSession = kieBase.newKieSession();
        AttributesTimerEntity attributesTimerEntity = new AttributesTimerEntity();
        attributesTimerEntity.setNum(20);

        kieSession.insert(attributesTimerEntity);
        kieSession.fireUntilHalt();

        Thread.sleep(10000);
        kieSession.halt();

        kieSession.dispose();
    }

注意:如果規則中有用到了timer屬性,匹配規則需要呼叫kieSession.fireUntilHalt();這裡涉及一個規則引擎的執行模式和執行緒問題,關於具體細節,我們後續討論。

5.10 date-effective屬性

date-effective屬性用於指定規則的生效時間,即只有當前系統時間大於等於設定的時間或者日期規則才有可能觸發。預設日期格式為:dd-MMM-yyyy。使用者也可以自定義日期格式。

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.AttributesDateEffectiveEntity

/*
 用於測試Drools 屬性: date-effective
*/

rule "rule_attributes_date_effective"
//    date-effective "20-七月-2021"
    date-effective "2021-02-20"
    when
        $attributesDateEffectiveEntity:AttributesDateEffectiveEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_date_effective 觸發");
end


第二步:編寫單元測試

    @Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        AttributesDateEffectiveEntity attributesDateEffectiveEntity = new AttributesDateEffectiveEntity();
        attributesDateEffectiveEntity.setNum(20);

        kieSession.insert(attributesDateEffectiveEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

注意:需要在VM引數上加上日期格式:-Ddrools.dateformat=yyyy-MM-dd,在生產環境所在規則引擎的JVM設定中,也需要設定此引數,以保證開發和生產的一致性。

5.11 date-expires屬性

date-expires屬性用於指定規則的失效時間,即只有當前系統時間小於設定的時間或者日期規則才有可能觸發。預設日期格式為:dd-MMM-yyyy。使用者也可以自定義日期格式。

第一步:編寫規則檔案/resource/rules/dateexpires.drl

package rules
import com.ityml.drools.entity.AttributesDateExpiresEntity

/*
 用於測試Drools 屬性: date-expires
*/

rule "rule_attributes_date_expires"
    date-expires "2021-06-20"
    when
        $attributesDateExpiresEntity:AttributesDateExpiresEntity(num > 1)
    then
        System.out.println("規則 rule_attributes_date_expires 觸發");
end

第二步:編寫單元測試

@Test
public void test(){
    KieSession kieSession = kieBase.newKieSession();
    AttributesDateExpiresEntity attributesDateExpiresEntity = new AttributesDateExpiresEntity();
    attributesDateExpiresEntity.setNum(20);

    kieSession.insert(attributesDateExpiresEntity);

    kieSession.fireAllRules();
    kieSession.dispose();
}

注意:需要在VM引數上加上日期格式:-Ddrools.dateformat=yyyy-MM-dd,在生產環境所在規則引擎的JVM設定中,也需要設定此引數,以保證開發和生產的一致性。

6. Drools高階語法

前面章節我們已經知道了一套完整的規則檔案內容構成如下:

關鍵字 描述
package 包名,只限於邏輯上的管理,同一個包名下的查詢或者函式可以直接呼叫
import 用於匯入類或者靜態方法
global 全域性變數
function 自定義函式
query 查詢
rule end 規則體

本章節我們就來學習其中的幾個關鍵字。

6.1 global全域性變數

global關鍵字用於在規則檔案中定義全域性變數,它可以讓應用程式的物件在規則檔案中能夠被訪問。可以用來為規則檔案提供資料或服務。

語法結構為:global 物件型別 物件名稱

在使用global定義的全域性變數時有兩點需要注意:

1、如果物件型別為包裝型別時,在一個規則中改變了global的值,那麼只針對當前規則有效,對其他規則中的global不會有影響。可以理解為它是當前規則程式碼中的global副本,規則內部修改不會影響全域性的使用。

2、如果物件型別為集合型別或JavaBean時,在一個規則中改變了global的值,對java程式碼和所有規則都有效。

下面我們通過程式碼進行驗證:

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.GlobalEntity

/*
 用於測試Drools 全域性變數 : global
*/

global java.lang.Integer globalCount
global java.util.List globalList

rule "rule_global_1"
    when
        $globalEntity:GlobalEntity(num > 1)
    then
        System.out.println("規則 rule_global_1 開始...");
        globalCount++ ;
        globalList.add("張三");
        globalList.add("李四");

        System.out.println(globalCount);
        System.out.println(globalList);
        System.out.println("規則 rule_global_1 結束...");
end

rule "rule_global_2"
    when
        $globalEntity:GlobalEntity(num > 1)
    then
        System.out.println("規則 rule_global_2 開始...");
        System.out.println(globalCount);
        System.out.println(globalList);
        System.out.println("規則 rule_global_2 結束...");
end

第二步:編寫單元測試

	@Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        GlobalEntity globalEntity = new GlobalEntity();
        globalEntity.setNum(20);

        ArrayList<Object> globalList = new ArrayList<>();

        Integer globalCount = 10;
        kieSession.setGlobal("globalCount", 10);
        kieSession.setGlobal("globalList", globalList);

        kieSession.insert(globalEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
        System.out.println("globalCount=" + globalCount);
        System.out.println("globalList=" + globalList);
    }

注意:

1-後面的程式碼中定義了全域性變數以後,前面的test都需要加,不然會出錯。

2-屬性當中的 關於時間的屬性,如果涉及格式問題,也不要忘記,jvm啟動引數新增相關配置

6.2 query查詢

query查詢提供了一種查詢working memory中符合約束條件的Fact物件的簡單方法。它僅包含規則檔案中的LHS部分,不用指定“when”和“then”部分並且以end結束。具體語法結構如下:

query 查詢的名稱(可選引數)
    LHS
end

具體操作步驟:

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.QueryEntity

/*
 用於測試Drools 方法: query
*/

//無參查詢
query "query_1"
    $queryEntity:QueryEntity(age>20)
end

//有參查詢
query "query_2"(Integer qAge,String qName)
    $queryEntity:QueryEntity(age > qAge && name == qName)
end

第二步:編寫單元測試

    @Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();

        QueryEntity queryEntity1= new QueryEntity();
        QueryEntity queryEntity2= new QueryEntity();
        QueryEntity queryEntity3= new QueryEntity();

        queryEntity1.setName("張三").setAge(10);
        queryEntity2.setName("李四").setAge(20);
        queryEntity3.setName("王五").setAge(30);


        kieSession.insert(queryEntity1);
        kieSession.insert(queryEntity2);
        kieSession.insert(queryEntity3);

        QueryResults results1 = kieSession.getQueryResults("query_1");
        QueryResults results2 = kieSession.getQueryResults("query_2", 1, "張三");


        for (QueryResultsRow queryResultsRow : results1) {
            QueryEntity queryEntity = (QueryEntity) (queryResultsRow.get("$queryEntity"));
            System.out.println(queryEntity);
        }

        for (QueryResultsRow queryResultsRow : results2) {
            QueryEntity queryEntity = (QueryEntity) (queryResultsRow.get("$queryEntity"));
            System.out.println(queryEntity);
        }


        kieSession.fireAllRules();
        kieSession.dispose();
    }

6.3 function函式

function關鍵字用於在規則檔案中定義函式,就相當於java類中的方法一樣。可以在規則體中呼叫定義的函式。使用函式的好處是可以將業務邏輯集中放置在一個地方,根據需要可以對函式進行修改。

函式定義的語法結構如下:

function 返回值型別 函式名(可選引數){    //邏輯程式碼}

具體操作步驟:

第一步:編寫規則檔案/resources/rules/function.drl

package rules
import com.ityml.drools.entity.FunctionEntity

/*
 用於測試Drools 方法: function
*/

//定義一個 假髮 方法
function Integer add(Integer num){
    return num+10;
}

rule  "function"
    when
        $functionEntity:FunctionEntity(num>20)
    then
        Integer result = add($functionEntity.getNum());
        System.out.println(result);
end

第二步:編寫單元測試

    @Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();

        FunctionEntity functionEntity = new FunctionEntity();
        functionEntity.setNum(30);

        kieSession.insert(functionEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

6.4 條件-LHS加強

前面我們已經知道了在規則體中的LHS部分是介於when和then之間的部分,主要用於模式匹配,只有匹配結果為true時,才會觸發RHS部分的執行。本章節我們會針對LHS部分學習幾個新的用法。

6.4.1 複合值限制in/not in

複合值限制是指超過一種匹配值的限制條件,類似於SQL語句中的in關鍵字。Drools規則體中的LHS部分可以使用in或者not in進行復合值的匹配。具體語法結構如下:

Object(field in (比較值1,比較值2...))

舉例:

package rules
import com.ityml.drools.entity.LhsInEntity

/*
 用於測試Drools LHS: in not in
*/


rule "lhs_in"
    when
        $lhsInEntity:LhsInEntity(name in ("張三","李四","王五"))
    then
        System.out.println("規則 lhs_in 觸發");
end

rule "lhs_not_in"
    when
        $lhsInEntity:LhsInEntity(name not in ("張三","李四","王五"))
    then
        System.out.println("規則 lhs_not_in 觸發");
end

6.4.2 條件元素eval

eval用於規則體的LHS部分,並返回一個Boolean型別的值。語法結構如下:

eval(表示式)

舉例:

package rules
import com.ityml.drools.entity.LhsEvalEntity

/*
 用於測試Drools LHS: in not in
*/


rule "lhs_eval"
    when
        $lhsInEntity:LhsEvalEntity(age > 10) and eval(2>1)
    then
        System.out.println("規則 lhs_eval 觸發");
end

6.4.3 條件元素not

not用於判斷Working Memory中是否存在某個Fact物件,如果不存在則返回true,如果存在則返回false。語法結構如下:

not Object(可選屬性約束)

舉例:

package rules
import com.ityml.drools.entity.LhsNotEntity

/*
 用於測試Drools LHS: not
*/


rule "lhs_not"
    when
       not $lhsNotEntity:LhsNotEntity(age > 10)
    then
       System.out.println("規則 lhs_not 觸發");
end


6.4.4 條件元素exists

exists的作用與not相反,用於判斷Working Memory中是否存在某個Fact物件,如果存在則返回true,不存在則返回false。語法結構如下:

exists Object(可選屬性約束)

舉例:

package rules
import com.ityml.drools.entity.LhsEvalEntity

/*
 用於測試Drools LHS: exists
*/


rule "lhs_exists"
    when
       exists $lhsInEntity:LhsEvalEntity(age > 10)
    then
        System.out.println("規則 lhs_eval 觸發");
end

Java程式碼:

package com.ityml.drools.client;

import com.ityml.drools.DroolsApplicationTests;
import com.ityml.drools.entity.LhsExistsEntity;
import com.ityml.drools.entity.LhsNotEntity;
import org.junit.jupiter.api.Test;
import org.kie.api.KieBase;
import org.kie.api.runtime.KieSession;

import javax.annotation.Resource;

/**
 * @author ityml
 * @date 2021-06-17 23:46
 */
public class LhsNotTest extends DroolsApplicationTests {

    @Resource
    public KieBase kieBase;

    @Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        LhsNotEntity lhsNotEntity = new LhsNotEntity();
        lhsNotEntity.setAge(1);

        kieSession.insert(lhsNotEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

}

可能有人會有疑問,我們前面在LHS部分進行條件編寫時並沒有使用exists也可以達到判斷Working Memory中是否存在某個符合條件的Fact元素的目的,那麼我們使用exists還有什麼意義?

兩者的區別:當向Working Memory中加入多個滿足條件的Fact物件時,使用了exists的規則執行一次,不使用exists的規則會執行多次。

例如:

規則檔案(只有規則體):

package rules
import com.ityml.drools.entity.LhsExistsEntity

/*
 用於測試Drools LHS: exists
*/


rule "lhs_exists_1"
    when
       exists $lhsExistsEntity:LhsExistsEntity(age > 10)
    then
        System.out.println("規則 lhs_exists_1 觸發");
end


rule "lhs_exists_2"
    when
       $lhsExistsEntity:LhsExistsEntity(age > 10)
    then
       System.out.println("規則 lhs_exists_2 觸發");
end


Java程式碼:

    @Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        LhsExistsEntity lhsExistsEntity = new LhsExistsEntity();
        lhsExistsEntity.setAge(30);

        LhsExistsEntity lhsExistsEntity2 = new LhsExistsEntity();
        lhsExistsEntity2.setAge(30);

        kieSession.insert(lhsExistsEntity);
        kieSession.insert(lhsExistsEntity2);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

上面第一個規則只會執行一次,因為Working Memory中存在兩個滿足條件的Fact物件,第二個規則會執行兩次。

6.4.5 規則繼承

規則之間可以使用extends關鍵字進行規則條件部分的繼承,類似於java類之間的繼承。

例如:

    @Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        LhsExistsEntity lhsExistsEntity = new LhsExistsEntity();
        lhsExistsEntity.setAge(30);

        LhsExistsEntity lhsExistsEntity2 = new LhsExistsEntity();
        lhsExistsEntity2.setAge(30);

        kieSession.insert(lhsExistsEntity);
        kieSession.insert(lhsExistsEntity2);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

6.5 結果-RHS

規則檔案的RHS部分的主要作用是通過插入,刪除或修改工作記憶體中的Fact資料,來達到控制規則引擎執行的目的。Drools提供了一些方法可以用來操作工作記憶體中的資料,操作完成後規則引擎會重新進行相關規則的匹配,原來沒有匹配成功的規則在我們修改資料完成後有可能就會匹配成功了。

6.5.1 insert方法

insert方法的作用是向工作記憶體中插入資料,並讓相關的規則重新匹配。

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.RhsInsertEntity

/*
 用於測試Drools RHS: insert
*/


rule "rhs_insert_1"
    when
        $rhsInsertEntity:RhsInsertEntity(age <= 10)
    then
        RhsInsertEntity rhsInsertEntity = new RhsInsertEntity();
        rhsInsertEntity.setAge(15);
        insert(rhsInsertEntity);
        System.out.println("規則 rhs_insert_1 觸發");
end

rule "rhs_insert_2"
    when
        $rhsInsertEntity:RhsInsertEntity(age <=20 && age>10)
    then
        RhsInsertEntity rhsInsertEntity = new RhsInsertEntity();
        rhsInsertEntity.setAge(25);
        insert(rhsInsertEntity);
        System.out.println("規則 rhs_insert_2 觸發");
end

rule "rhs_insert_3"
    when
        $rhsInsertEntity:RhsInsertEntity(age > 20 )
    then
        System.out.println("規則 rhs_insert_3 觸發");
end

第二步:編寫單元測試

    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        RhsInsertEntity rhsInsertEntity = new RhsInsertEntity();
        rhsInsertEntity.setAge(5);

        kieSession.insert(rhsInsertEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

通過控制檯輸出可以發現,3個規則都觸發了,這是因為首先進行規則匹配時只有第一個規則可以匹配成功,但是在第一個規則中向工作記憶體中插入了一個資料導致重新進行規則匹配,此時第二個規則可以匹配成功。在第二個規則中同樣向工作記憶體中插入了一個資料導致重新進行規則匹配,那麼第三個規則就出發了。

6.5.2 update方法

update方法的作用是更新工作記憶體中的資料,並讓相關的規則重新匹配。 (要避免死迴圈)

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.RhsUpdateEntity

/*
 用於測試Drools RHS: update
*/


rule "rhs_update_1"
    when
        $rhsUpdateEntity:RhsUpdateEntity(age <= 10)
    then
        $rhsUpdateEntity.setAge(15);
        update($rhsUpdateEntity);
        System.out.println("規則 rhs_update_1 觸發");
end

rule "rhs_update_2"
    when
        $rhsUpdateEntity:RhsUpdateEntity(age <=20 && age>10)
    then
        $rhsUpdateEntity.setAge(25);
        update($rhsUpdateEntity);
        System.out.println("規則 rhs_update_2 觸發");
end

rule "rhs_update_3"
    when
        $rhsUpdateEntity:RhsUpdateEntity(age > 20 )
    then
        System.out.println("規則 rhs_update_3 觸發");
end



第二步:編寫單元測試

    @Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        RhsUpdateEntity rhsUpdateEntity = new RhsUpdateEntity();
        rhsUpdateEntity.setAge(5);

        kieSession.insert(rhsUpdateEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

通過控制檯的輸出可以看到規則檔案中定義的三個規則都觸發了。

在更新資料時需要注意防止發生死迴圈。

6.5.3 modify方法

modify方法的作用跟update一樣,是更新工作記憶體中的資料,並讓相關的規則重新匹配。只不過語法略有區別 (要避免死迴圈)

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.RhsModifyEntity

/*
 用於測試Drools RHS: modify
*/


rule "rhs_modify_1"
    when
        $rhsModifyEntity:RhsModifyEntity(age <= 10)
    then
        modify($rhsModifyEntity){
            setAge(15)
        }
        System.out.println("規則 rhs_modify_1 觸發");
end

rule "rhs_modify_2"
    when
        $rhsModifyEntity:RhsModifyEntity(age <=20 && age>10)
    then
        modify($rhsModifyEntity){
            setAge(25)
        }
        System.out.println("規則 rhs_modify_2 觸發");
end

rule "rhs_modify_3"
    when
        $rhsModifyEntity:RhsModifyEntity(age > 20 )
    then
        System.out.println("規則 rhs_modify_3 觸發");
end

第二步:編寫單元測試

    @Test
    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        RhsModifyEntity rhsModifyEntity = new RhsModifyEntity();
        rhsModifyEntity.setAge(5);

        kieSession.insert(rhsModifyEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

通過控制檯的輸出可以看到規則檔案中定義的三個規則都觸發了。

在更新資料時需要注意防止發生死迴圈。

6.5.4 retract/delete方法

retract方法的作用是刪除工作記憶體中的資料,並讓相關的規則重新匹配。

第一步:編寫規則檔案

package rules
import com.ityml.drools.entity.RhsRetractEntity

/*
 用於測試Drools RHS: retract
*/


rule "rhs_retract_1"
    when
        $rhsRetractEntity:RhsRetractEntity(age <= 10)
    then
//        retract($rhsRetractEntity);
        System.out.println("規則 rhs_retract_1 觸發");
end

rule "rhs_retract_2"
    when
        $rhsRetractEntity:RhsRetractEntity(age <= 10)
    then
        System.out.println("規則 rhs_retract_2 觸發");
end

第二步:編寫單元測試

    public void test(){
        KieSession kieSession = kieBase.newKieSession();
        RhsRetractEntity rhsRetractEntity = new RhsRetractEntity();
        rhsRetractEntity.setAge(5);

        kieSession.insert(rhsRetractEntity);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

通過控制檯輸出可以發現,只有第一個規則觸發了,因為在第一個規則中將工作記憶體中的資料刪除了導致第二個規則並沒有匹配成功。

6.6 RHS加強

RHS部分是規則體的重要組成部分,當LHS部分的條件匹配成功後,對應的RHS部分就會觸發執行。一般在RHS部分中需要進行業務處理。

在RHS部分Drools為我們提供了一個內建物件,名稱就是drools。本小節我們來介紹幾個drools物件提供的方法。

6.5.1 halt

halt方法的作用是立即終止後面所有規則的執行

package rules
import com.ityml.drools.entity.RhsHaftEntity

/*
 用於測試Drools RHS: haft
*/


rule "rhs_haft_1"
    when
        $rhsHaftEntity:RhsHaftEntity(age <= 10)
    then
        drools.halt();
        System.out.println("規則 rhs_haft_1 觸發");
end

rule "rhs_haft_2"
    when
        $rhsHaftEntity:RhsHaftEntity(age <= 20)
    then
        System.out.println("規則 rhs_haft_2 觸發");
end

6.5.2 getWorkingMemory

getWorkingMemory方法的作用是返回工作記憶體物件。

rule "rhs_get_working_memory_1"
    when
        $rhsDroolsMethodsEntity:RhsDroolsMethodsEntity(age <= 10)
    then
        System.out.println(drools.getWorkingMemory());
        System.out.println("規則 rhs_get_working_memory_1 觸發");
end

6.5.3 getRule

getRule方法的作用是返回規則物件。

rule "rhs_rule_2"
    when
        $rhsDroolsMethodsEntity:RhsDroolsMethodsEntity(age <=20)
    then
        System.out.println(drools.getRule());
        System.out.println("規則 rhs_rule_2 觸發");
end

6.6 規則檔案編碼規範(重要)

我們在進行drl型別的規則檔案編寫時儘量遵循如下規範:

  • 所有的規則檔案(.drl)應統一放在一個規定的資料夾中,如:/rules資料夾
  • 書寫的每個規則應儘量加上註釋。註釋要清晰明瞭,言簡意賅
  • 同一型別的物件儘量放在一個規則檔案中,如所有Student型別的物件儘量放在一個規則檔案中
  • 規則結果部分(RHS)儘量不要有條件語句,如if(...),儘量不要有複雜的邏輯和深層次的巢狀語句
  • 每個規則最好都加上salience屬性,明確執行順序
  • Drools預設dialect為"Java",儘量避免使用dialect "mvel"

7. WorkBench

7.1 WorkBench簡介

WorkBench是KIE元件中的元素,也稱為KIE-WB,是Drools-WB與JBPM-WB的結合體。它是一個視覺化的規則編輯器。WorkBench其實就是一個war包。

WorkBench經過幾次版本迭代,已經不提供tomcat啟動的war包,綜合考慮,本課程仍然採用 tomcat版本作為演示。

環境:

  • apache-tomcat-9.0.29
  • kie-drools-wb-7.6.0.Final-tomcat8 下載地址:Drools - Download

說明:

準備jar包:需要放到tomcat lib中,否則啟動失敗

具體安裝步驟:

7.1.1 配置 Tomcat

1.修改tomcat-user.xml,新增使用者

<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">

  <!--定義admin角色-->
  <role rolename="admin"/>
  <!--定義一個使用者,使用者名稱為kie,密碼為kie,對應的角色為admin角色-->
  <user username="kie-web" password="kie-web123" roles="admin"/>
  <user username="admin" password="admin" roles="manager-gui,manager-script,manager-jmx,manager-status"/>
</tomcat-users>

此賬號密碼用於登入WorkBench管理控制檯

2.修改server.xml

      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">

        <!-- SingleSignOn valve, share authentication between web applications
             Documentation at: /docs/config/valve.html -->
        <!--
        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
        -->

        <!-- Access log processes all example.
             Documentation at: /docs/config/valve.html
             Note: The pattern used is equivalent to using pattern="common" -->
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
		<Valve className="org.kie.integration.tomcat.JACCValve"/>
      </Host>

host節點下新增

3.複製jar到tomcat根目錄的lib下面:

kie-tomcat-integration-7.10.0.Final.jar
javax.security.jacc-api-1.5.jar
slf4j-api-1.7.25.jar

4.複製 kie-drools-wb-7.6.0.Final-tomcat8.war 到tomcat webapp下面並修改成kie-web.war

7.1.2啟動伺服器

啟動tomcat

訪問http://localhost:8080/kie-web,可以看到WorkBench的登入頁面。使用前面建立的kie-web/kie-web123登入

登入成功後進入系統首頁:

7.2WorkBench使用

7.2.1建立空間、專案

首頁中點選 project,建立空間

我們建立一個 ityml 的工作空間。點選 Save,儲存。

點選工作空間當中的 ityml,進入空間

點選Add Project新增專案

成功後,我們可以看見下圖

左上角的導航條,可以在空間和project之間切換

7.2.2建立資料物件和drl檔案

切換到pro1專案內,點選 Create New Assert

選中資料物件:

輸入Order,點選確定,成功後跳轉如下頁面

Order相當於我們程式碼中的實體類,在左側 Project Explorer檢視中,可以看見專案結構

接下來新增欄位,點選新增欄位按鈕:

ID 位置,輸入java bean的欄位,標籤是備註資訊,型別選擇對應的欄位型別,儲存,點選建立,關閉彈窗,點選建立並繼續,可以繼續建立。

點選右上角的儲存,至此,一個資料物件我們就建立完成,可以在原始碼中檢視程式碼內容。

接下來我們建立一個drl檔案,建立過程跟建立bean類似,drl檔案內容如下

package com.ityml.pro1;

rule "rule_1"
    when
        $order:Order(age > 10)
    then
        System.out.print("rule run...");
end

儲存之後,點選導航條回到專案主頁

7.2.3 設定KieBase+KieSession

專案首頁點選Settings

選擇知識庫跟會話

彈出視窗,輸入Kiebase名稱即可,我們以kb1為例

同理,我們補充完軟體包資訊,新增只是會話,即kiesession

操作完成後,不要忘記儲存,此時,我們可在Project Explorer檢視中,resource/META-INF/kmodule.xml中看見如下資訊

<kmodule xmlns="http://www.drools.org/xsd/kmodule" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <kbase name="kb1" default="false" eventProcessingMode="stream" equalsBehavior="identity" packages="com.ityml.pro1">
    <ksession name="ks1" type="stateful" default="true" clockType="realtime"/>
  </kbase>
</kmodule>

導航回到專案首頁,進行編譯釋出

釋出成功後,我們可以在maven倉庫中看到對應的jar

也可以訪問:http://localhost:8080/kie-web/maven2/com/ityml/pro1/1.0.0/pro1-1.0.0.jar 驗證是否釋出成功

7.2.4 程式碼使用

@Test
    public void test() throws Exception{
        //通過此URL可以訪問到maven倉庫中的jar包
        //URL地址構成:http://ip地址:Tomcat埠號/WorkBench工程名/maven2/座標/版本號/xxx.jar
        String url = "http://localhost:8080/kie-web/maven2/com/ityml/pro1/1.0.0/pro1-1.0.0.jar";

        KieServices kieServices = KieServices.Factory.get();
        UrlResource resource = (UrlResource) kieServices.getResources().newUrlResource(url);
        //認證
        resource.setUsername("kie-web");
        resource.setPassword("kie-web123");
        resource.setBasicAuthentication("enabled");

        KieRepository repository = kieServices.getRepository();

        //通過輸入流讀取maven倉庫中的jar包資料,包裝成KieModule模組新增到倉庫中
        KieModule kieModule = repository.addKieModule(kieServices.getResources().newInputStreamResource(resource.getInputStream()));

        KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
        KieSession session = kieContainer.newKieSession();

        Order order = new Order();
        order.setName("張三");
        order.setAge(30);
        session.insert(order);

        session.fireAllRules();
        session.dispose();
    }

我們用URL流的方式,獲取jar資源,並構造kiesession物件,即可動態訪問workbench中的規則

8 其他

8.1 有狀態session和無狀態session

無狀態session

無狀態的KIE會話是一個不使用推理來對事實進行反覆修改的會話。在無狀態的KIE會話中,來自KIE會話先前呼叫的資料(先前的會話狀態)在會話呼叫之間被丟棄,而在有狀態的KIE會話中,這些資料被保留。一個無狀態的KIE會話的行為類似於一個函式,因為它產生的結果是由KIE基礎的內容和被傳入KIE會話以在特定時間點執行的資料決定的。KIE會話對以前傳入KIE會話的任何資料都沒有記憶。

使用方法類似如下程式碼:

    @Test
    public void testStatelessSession() {
        StatelessKieSession statelessKieSession = kieBase.newStatelessKieSession();
        List<Command> cmds = new ArrayList<>();
        KieSessionEntity kieSessionEntity = new KieSessionEntity();
        kieSessionEntity.setNum(10);
        kieSessionEntity.setValid(false);
        cmds.add(CommandFactory.newInsert(kieSessionEntity, "kieSessionEntity"));
        statelessKieSession.execute(CommandFactory.newBatchExecution(cmds));

        System.out.println(kieSessionEntity);
    }

簡單說來,無狀態session執行的時候,不需要呼叫 fireAllRules(),也不需要執行dispose(),程式碼執行完execute之後,即銷燬所有的資料。

使用場景:比如上述的校驗num
驗證資料: 比如計算積分,按揭房貸等

路有訊息:比如對郵件排序,傳送郵件等,行為類的場景

有狀態session

有狀態的KIE會話是一個使用推理來對事實進行反覆修改的會話。在有狀態的KIE會話中,來自KIE會話先前呼叫的資料(先前的會話狀態)在會話呼叫之間被保留,而在無狀態的KIE會話中,這些資料被丟棄了。

對比無狀態session,有狀態session呼叫fireAllRules()的時候採取匹配規則,就會執行規則匹配,除非遇見dispose()

示例:

資料模型

public class Room {
  private String name;
  // Getter and setter methods
}

public class Sprinkler {
  private Room room;
  private boolean on;
  // Getter and setter methods
}

public class Fire {
  private Room room;
  // Getter and setter methods
}

public class Alarm { }

規則檔案

rule "When there is a fire turn on the sprinkler"
when
  Fire($room : room)
  $sprinkler : Sprinkler(room == $room, on == false)
then
  modify($sprinkler) { setOn(true) };
  System.out.println("Turn on the sprinkler for room "+$room.getName());
end

rule "Raise the alarm when we have one or more fires"
when
    exists Fire()
then
    insert( new Alarm() );
    System.out.println( "Raise the alarm" );
end

rule "Cancel the alarm when all the fires have gone"
when
    not Fire()
    $alarm : Alarm()
then
    delete( $alarm );
    System.out.println( "Cancel the alarm" );
end


rule "Status output when things are ok"
when
    not Alarm()
    not Sprinkler( on == true )
then
    System.out.println( "Everything is ok" );
end

程式碼

KieSession ksession = kContainer.newKieSession();

String[] names = new String[]{"kitchen", "bedroom", "office", "livingroom"};
Map<String,Room> name2room = new HashMap<String,Room>();
for( String name: names ){
    Room room = new Room( name );
    name2room.put( name, room );
    ksession.insert( room );
    Sprinkler sprinkler = new Sprinkler( room );
    ksession.insert( sprinkler );
}

ksession.fireAllRules();

輸出

Console output
> Everything is ok

此時還可以繼續輸入

Fire kitchenFire = new Fire( name2room.get( "kitchen" ) );
Fire officeFire = new Fire( name2room.get( "office" ) );

FactHandle kitchenFireHandle = ksession.insert( kitchenFire );
FactHandle officeFireHandle = ksession.insert( officeFire );

ksession.fireAllRules();
Console output
> Raise the alarm
> Turn on the sprinkler for room kitchen
> Turn on the sprinkler for room office

繼續輸入

ksession.delete( kitchenFireHandle );
ksession.delete( officeFireHandle );

ksession.fireAllRules();

輸出

Console output

> Cancel the alarm
> Turn off the sprinkler for room office
> Turn off the sprinkler for room kitchen
> Everything is ok

使用場景:

  • 監測,如監測股票市場並使購買過程自動化

  • 診斷,如執行故障查詢過程或醫療診斷過程

  • 物流,如包裹跟蹤和配送供應

  • 確保合規性,如驗證市場交易的合法性

參考文件:

【1】 百度百科:規則引擎 :https://baike.baidu.com/item/規則引擎/3076955?fr=aladdin

【2】 開源規則引擎 drools:https://blog.csdn.net/sdmxdzb/article/details/81461744

【3】 drools官網:Drools - Business Rules Management System (Java™, Open Source)

相關文章