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.5 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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM