drools規則屬性(rule attributes)的使用

huan1993 發表於 2022-05-18

一、介紹

規則屬性是您可以新增到業務規則以修改規則行為的附加規範。 在 DRL 檔案中,您通常在規則條件和操作的上方定義規則屬性,多個屬性位於單獨的行中,格式如下:

rule "rule_name"
    // Attribute
    // Attribute
    when
        // Conditions
    then
        // Actions
end

二、常見的規則屬性

規則屬性 解釋 舉例
salience 定義規則優先順序,是一個整數。當在啟用佇列中排序時,salience的值越大,優先順序越高 salience 99
enabled 定義規則是否啟用. true 啟用,false 禁用,預設值是true enabled true
date-effective 包含時間和日期的字串,噹噹前時間大於date-effective時,該規則才會被啟用。這個時間格式可以修改,見下方具體的用法 date-effective "4-5月-2022"
date-expires 設定規則的過期時間,時間格式和上方一樣。 date-expires "4-5月-2022"
no-loop 布林值,預設值為false, 定義當當前規則規則的結果修改了fact物件時,是否可以再次執行該規則。true:不可以, false:可以,可能會導致死迴圈。指的是當前規則的修改,如果別的規則修改了,還會導致該規則的觸發 no-loop true
agenda-group Agenda groups允許您對agenda進行分割槽,以提供對規則組的更多執行控制。 只有獲得焦點的議程組中的規則才能被啟用。 ,但是這個裡面有個特例,如果某個規則沒有配置 agenda-group,但是它模式匹配成功了,那麼會被分到預設的組(main),這個main組的規則也會執行。 agenda-group "GroupName"
auto-focus 布林值,僅適用於Agenda-Group內的規則。當值為true時,下次啟用該規則時,會將焦點自動給這個Agenda group auto-focus true
activation-group 表示該組下的規則只有一個規則會被執行,該組下其餘啟用的規則會被取消執行。 但是別的組啟用的規則可能會被執行。 activation-group "GroupName"
duration long型別的值,如果在這個時間之後規則還成立,那麼執行該規則 duration 1000
timer 一個字串,標識用於排程規則的 int(間隔)或 cron 計時器定義。 Example: timer ( cron:* 0/15 * * * ? ) (every 15 minutes)
calendar 定義Quartz calendar用於排程規則。
lock-on-active 一個布林值,僅適用於規則流組或議程組中的規則。 選擇該選項後,下次規則的規則流組變為活動狀態或規則的議程組獲得焦點時,規則無法再次啟用,直到規則流組不再處於活動狀態或議程組失去焦點。 這是 no-loop 屬性的更強版本,因為匹配規則的啟用被丟棄,無論更新的來源如何(不僅是規則本身)。 此屬性非常適合計算規則,其中您有許多修改事實的規則並且您不希望任何規則重新匹配和再次觸發。 lock-on-active true
dialect 將 JAVA 或 MVEL 標識為用於規則中的程式碼表示式的語言的字串。 預設情況下,該規則使用在包級別指定的方言。 此處指定的任何方言都會覆蓋該規則的包方言設定。 dialect "JAVA"

三、部分規則屬性案例

此處編寫出規則檔案和部分核心Java程式碼
image

1、salience

定義規則執行的優先順序,salience的值越大,優先順序越高

1、規則檔案的編寫

rule "salience_rule_1"
    salience 4
    when
    then
        System.out.println("rule 1");
end

rule "salience_rule_2"
    salience 3
    when
    then
        System.out.println("rule 2");
end

// 此處優先順序的值是動態獲取來的
rule "salience_rule_3"
    salience $dynamicSalience
    when
        $dynamicSalience: Integer()
    then
        System.out.println("rule 3");
end

注意:
我們的salience_rule_3的優先順序的值是動態來的,即是從工作記憶體中獲取的。

2、java程式碼編寫

public class DroolsSalienceApplication {
    public static void main(String[] args) {
        KieServices kieServices = KieServices.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");

        // 向工作記憶體中插入一個Integer值,salience_rule_3 需要用到這個優先順序
        kieSession.insert(10);

        // 只匹配規則名稱是已 salience_ 開頭的規則,忽略其餘的規則
        kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("salience_"));

        kieSession.dispose();
    }
}

kieSession.insert(10);此處向工作記憶體中插入一個值,將會匹配到salience_rule_3,然後動態修改它的優先順序。

3、執行結果

rule 3
rule 1
rule 2

因為 salience 的值越大優先順序越高,所以是這個順序。

2、enabled

定義規則是否啟用,true啟用 false禁用

1、規則檔案編寫

package rules

rule "enabled_rule_1"
    // 禁用此規則
    enabled false
    when
    then
        System.out.println("enabled_rule_1");
end

rule "enabled_rule_2"
    // 啟用此規則,預設就是啟用
    enabled true
    when
    then
        System.out.println("enabled_rule_2");
end

enabled_rule_2這個規則需要執行,enabled_rule_1這個規則不能執行。

2、java程式碼編寫

/**
 * 測試規則的啟用和禁用
 */
public class DroolsEnabledApplication {
    public static void main(String[] args) {
        KieServices kieServices = KieServices.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
        // 只匹配規則名稱是已 enabled_ 開頭的規則,忽略其餘的規則
        kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("enabled_"));
        kieSession.dispose();
    }
}

沒有需要注意的地方

3、執行結果

enabled_rule_2

可以看到只有規則enabled_rule_2輸出了結果,而enabled_rule_1被禁用了。

3、date-effective

定義規則什麼時候啟用,只有當前時間>規則時間才會啟用。需要注意預設的時間格式,可以通過java程式碼進行修改。

1、規則檔案編寫

package rules
import java.text.SimpleDateFormat
import java.util.Date

// 規則一:輸出當前時間
rule "date_effective_rule_1"
    when
    then
        System.out.println("當前時間:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
end

// 規則二: 該規則會在2022-05-18 10:54:26之後被啟用
rule "date_effective_rule_2"
    date-effective "2022-05-18 10:54:26"
    when
    then
        System.out.println("date_effective_rule_2執行了,規則允許被執行的時間應該在2022-05-18 10:54:26之後");
end

// 規則三: 該規則會在2023-05-18 10:54:26之後被啟用
rule "date_effective_rule_3"
    date-effective "2023-05-18 10:54:26"
    when
    then
        System.out.println("date_effective_rule_3會在時間到了2023-05-18 10:54:26才啟用");
end

規則一:輸出當前時間
規則二: 該規則會在2022-05-18 10:54:26之後被啟用
規則三: 該規則會在2023-05-18 10:54:26之後被啟用

2、java程式碼編寫

/**
 * 測試規則在執行的時間之後才能執行
 */
public class DroolsDateEffectiveApplication {
    public static void main(String[] args) {
        // 設定日期格式,否則可能會報錯(Wrong date-effective value: Invalid date input format: [2022-05-18 10:54:26] it should follow: [d-MMM-yyyy]]])
        System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
        KieServices kieServices = KieServices.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
        // 只匹配規則名稱是已 date_effective_ 開頭的規則,忽略其餘的規則
        kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("date_effective_"));
        kieSession.dispose();
    }
}

需要注意System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");這句,這個修改drools中的日期格式,因為規則中寫的日期格式為date-effective "2023-05-18 10:54:26"而預設的格式為d-MMM-yyyy,不修會報錯。

3、執行結果

當前時間:2022-05-18 10:59:38
date_effective_rule_2執行了,規則允許被執行的時間應該在2022-05-18 10:54:26之後

可以看到規則二執行了,規則三沒有執行,因為規則三需要時間到達了2023-05-18 10:54:26才執行,而當前時間不符合。

4、注意事項

如果出現了Wrong date-effective value: Invalid date input format: [2022-05-18 10:54:26] it should follow: [d-MMM-yyyy]]]這個錯誤該怎麼解決了,這是因為日期格式不正確。需要在java程式碼中進行如下設定System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss")

4、date-expires

定義規則的過期時間,即規則到了該時間之後就不可使用了。和date-effective的用法類似,此處就不演示了。

5、no-loop

定義當當前規則的結果修改了fact物件時,是否可以再次執行該規則。可以防止死迴圈

1、規則檔案編寫

package rules
import java.util.concurrent.TimeUnit
import java.text.SimpleDateFormat
import java.util.Date

rule "no_loop_rule_1"
    no-loop true
    when
        $i: Integer(intValue() < 20)
    then
        modify($i){
        }
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " no_loop_rule_1 i=" + $i);
end

rule "no_loop_rule_2"
    no-loop false

    when
        $i: Integer(intValue() < 20)
    then
        modify($i){
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " no_loop_rule_2 i=" + $i);
end

解釋:
no_loop_rule_1no-loop true表示如果當前規則的RHS部分,對Fact物件進行了修改,則不會再次觸發該規則。那如果是no_loop_rule_2修改了,會導致該規則的觸發嗎?答案是會觸發,如果我不想被觸發呢?那麼使用lock-on-active可以實現。
no_loop_rule_2no-loop false表示如果當前規則的RHS部分,對Fact物件進行了修改,那麼還會再次匹配這個規則。

2、java程式碼編寫

/**
 * 測試規則是否可以再次被執行
 */
public class DroolsNoLoopApplication {
    public static void main(String[] args) throws InterruptedException {
        System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
        KieServices kieServices = KieServices.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
        kieSession.insert(10);
        // 只匹配規則名稱是已 no_loop_ 開頭的規則,忽略其餘的規則
        kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("no_loop_"));
        // 睡眠5s,使規則檔案中的規則執行完
        TimeUnit.SECONDS.sleep(5);
        kieSession.dispose();
    }
}

此處 java 程式碼,睡眠了5s,是為了讓規則執行。

3、執行結果

2022-05-18 11:42:29 no_loop_rule_1 i=10
2022-05-18 11:42:31 no_loop_rule_2 i=10
2022-05-18 11:42:31 no_loop_rule_1 i=10
2022-05-18 11:42:32 no_loop_rule_2 i=10

解釋:
2022-05-18 11:42:29 no_loop_rule_1 i=10: no_loop_rule_1被觸發,由於RHS部分使用了modify修改了規則記憶體中的物件,但是該規則存在 no-loop true 的屬性,所以該規則沒有再次被觸發,即只輸出了一次。

2022-05-18 11:42:30 no_loop_rule_2 i=10 2022-05-18 11:42:30 no_loop_rule_1 i=10 此時規則 no_loop_rule_2 執行了,由於該規則的 no-loop 為 false 並且使用了 modify 方法,所以該規則多次被觸發了,從結果上看,貌似規則 no_loop_rule_1 又再次被觸發了,不是應該不被觸發嗎,因為設定了no-loop true?因為這是no_loop_rule_2導致no_loop_rule_1觸發的,而no_loop只對自身的RHS修改有效。

疑問:
那如果將 no-loop換成lock-on-active結果會一樣嗎?可以自己嘗試一下看看結果。

6、agenda-group

將被模式匹配成功後的規則,進行分組,只有獲得焦點的組,才可以執行規則。但是這個裡面有個特列,如果某個規則在模式匹配,匹配成功了,但是沒有配置agenda-group,那麼它會被分配到main組,這個main組的規則總是執行的。

agenda-group的資料結構就類似stack,啟用的組是在棧頂。參考如下圖:
image

參考連結: https://stackoverflow.com/questions/6870192/understanding-agenda-group-in-drools

1、規則檔案編寫

package rules

/**
    agenda-group 的資料結構類似與棧,啟用的組會被放置在棧頂,
    `main`是預設組,總是存在的,即沒有配置agenda-group的就是`main`,
    `main`總是會執行的。
*/

rule "agenda_group_001_rule_1"
    agenda-group "group-001"
    when
    then
        System.out.println("agenda_group_001_rule_1");
end

rule "agenda_group_001_rule_2"
    agenda-group "group-001"
    when
    then
        System.out.println("agenda_group_001_rule_2");
end

rule "agenda_group_002_rule_3"
    agenda-group "group-002"
    when
    then
        System.out.println("agenda_group_002_rule_3");
end

rule "agenda_group_no_group_rule_4"
    when
    then
        System.out.println("agenda_group_no_group_rule_4");
end

注意: 此處其實是 存在 3個組的,agenda_group_no_group_rule_4如果模式匹配成功後會被分配到main組,main總是會被執行的。

2、java程式碼編寫

/**
 * 測試規則分組
 */
public class DroolsAgendaGroupApplication {
    public static void main(String[] args) {
        // 設定日期格式,否則可能會報錯(Wrong date-effective value: Invalid date input format: [2022-05-18 10:54:26] it should follow: [d-MMM-yyyy]]])
        System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
        KieServices kieServices = KieServices.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
    
        // 啟用組
        kieSession.getAgenda().getAgendaGroup("group-001").setFocus();

        // 只匹配規則名稱是已 agenda_group_ 開頭的規則,忽略其餘的規則
        kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("agenda_group_"));
        kieSession.dispose();
    }
}

啟用group-001分組。

3、執行結果

agenda_group_001_rule_1
agenda_group_001_rule_2
agenda_group_no_group_rule_4

解釋:
agenda_group_no_group_rule_4為什麼會被輸出呢?它沒有定義agenda-group啊,而且我們啟用的也是group-001分組,它不應該輸出啊。這是應為這個規則模式匹配成功後被分配到了預設的main組,而main組一定會被執行的。

7、auto-focus

設定某個agenda-group預設獲取到焦點,和在java程式碼中使用kieSession.getAgenda().getAgendaGroup("group-001").setFocus();或在drl檔案中使用drools.setFocus(..)一樣。

8、activation-group

處於該分組中啟用的規則,同一個組下,只有一個規則可以執行,其餘的會被取消執行。但是別的組中啟用的規則還是可以執行的。

1、規則檔案編寫

package rules

rule "activation_group_001_rule_1"
    activation-group "group-001"
    salience 1
    when
    then
        System.out.println("activation_group_001_rule_1");
end

rule "activation_group_001_rule_2"
    activation-group "group-001"
    salience 2
    when
    then
        System.out.println("activation_group_001_rule_2");
end

rule "activation_group_002_rule_3"
    activation-group "group-002"
    when
    then
        System.out.println("activation_group_002_rule_3");
end

rule "activation_group_no_group_rule_4"
    when
    then
        System.out.println("activation_group_no_group_rule_4");
end

activation-group "group-001"此處對這個組的規則指定了優先順序,優先順序高的先執行,執行完之後,該組別的規則不執行。

2、java程式碼編寫

public class DroolsActivationGroupApplication {
    public static void main(String[] args) {
        System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
        KieServices kieServices = KieServices.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
        // 只匹配規則名稱是已 activation_group_ 開頭的規則,忽略其餘的規則
        kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("activation_group_"));
        kieSession.dispose();
    }
}

3、執行結果

activation_group_001_rule_2
activation_group_002_rule_3
activation_group_no_group_rule_4

可以看到分組group-001中有2個規則,但是隻執行了一個規則。

9、duration

long型別的值,單位毫秒,如果在這個時間之後規則還成立,那麼執行該規則。

1、規則檔案編寫

package rules
import java.text.SimpleDateFormat
import java.util.Date

rule "duration_rule_1"
    // 延遲1s後執行規則
    duration 1000
    when
        $i: Integer(intValue() < 10)
    then
        System.out.println(Thread.currentThread().getName() + ": " + 
        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+ 
        " duration_rule_1 $i:"+$i);
end

定義規則延遲1s後進行執行。

2、java程式碼編寫

/**
 * 在多少毫秒後,如果條件還成立,則觸發該規則
 */
public class DroolsDurationApplication {
    public static void main(String[] args) throws InterruptedException {
        System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
        KieServices kieServices = KieServices.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");

        FactHandle factHandle = kieSession.insert(3);
        // 只匹配規則名稱是已 duration_ 開頭的規則,忽略其餘的規則
        new Thread(() -> {
            // 呼叫此方法會阻塞呼叫執行緒,直到 `kieSession.halt();`的呼叫
            System.out.println("當前時間:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            kieSession.fireUntilHalt(new RuleNameStartsWithAgendaFilter("duration_"));
        }, "fire-thread").start();

        // 如果修改這個值,使得規則的條件不成立,看規則是否還執行
        kieSession.update(factHandle, 4);

        TimeUnit.SECONDS.sleep(2);
        kieSession.halt();

        kieSession.dispose();
    }
}

注意:
1、我們呼叫出發所有規則執行的方法不在是fireAllRules而是fireUntilHalt
2、fireUntilHalt的呼叫會阻塞執行緒,直到呼叫halt方法,因此fireUntilHalt需要放置到另外的執行緒中呼叫。而且我們觀察規則的執行,也是在這個執行緒中呼叫的。

3、執行結果

當前時間:2022-05-18 14:13:36
fire-thread: 2022-05-18 14:13:37 duration_rule_1 $i:4

可以看到,延遲1s後規則執行了。

4、疑問

如果我們在1s鍾之內,將規則的條件修改成不成立,那麼規則還執行嗎?答案:不執行。

10、lock-on-active

rule flow groups or agenda groups配合使用。

需求:
我們有2個規則,並且同屬於一個組,規則二執行完之後,工作記憶體中的Fact物件的值發生了變化,導致規則一滿足執行的條件,而規則一已經執行一遍了,此處需要阻止規則二的觸發導致規則一的出觸發。使用lock-on-active 即可實現。

1、規則檔案編寫

package rules

import com.huan.drools.lockonactive.Person

rule "lock_on_active_rule_01"
    agenda-group "group-001"
    lock-on-active true
    when
        $p: Person(age < 18)
    then
        System.out.println("lock_on_active_rule_01: 使用者:[" + $p.getName() + "]當前的年齡是:[" + $p.getAge() + "]");
 end

rule "lock_on_active_rule_02"
    agenda-group "group-001"
    when
        $p: Person(name == "張三")
    then
        modify($p){
            setAge(15)
        }
        System.out.println("lock_on_active_rule_02: 使用者:[" + $p.getName() + "]當前的年齡是:[" + $p.getAge() + "]");
end

規則lock_on_active_rule_01加了lock-on-active true屬性後,規則lock_on_active_rule_02修改Fact導致規則lock_on_active_rule_01的條件成立,此時規則也是不會執行的。

2、java程式碼編寫

/**
 * 一個簡單的實體類
 *
 * @author huan.fu
 * @date 2022/5/18 - 14:34
 */
@Getter
@Setter
@AllArgsConstructor
public class Person {
    private String name;
    private Integer age;
}

public class DroolsLockOnActiveApplication {
    public static void main(String[] args) {
        System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
        KieServices kieServices = KieServices.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
        // 啟用組
        kieSession.getAgenda().getAgendaGroup("group-001").setFocus();

        Person person = new Person("張三", 20);
        kieSession.insert(person);

        // 只匹配規則名稱是已 lock_on_active_ 開頭的規則,忽略其餘的規則
        kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("lock_on_active_"));
        kieSession.dispose();
    }
}

3、執行結果

lock_on_active_rule_02: 使用者:[張三]當前的年齡是:[15]

可以看到只有規則二執行了,說明阻止了規則一的執行。

四、完整程式碼

https://gitee.com/huan1993/spring-cloud-parent/tree/master/drools/drools-drl-rule-attributes

五、參考連結

1、https://docs.drools.org/7.69.0.Final/drools-docs/html_single/index.html#rules-attributes-ref_drl-rules
2、 https://stackoverflow.com/questions/6870192/understanding-agenda-group-in-drools