drools中的條件 when

1、介紹

此篇文章主要記錄一下 drools 中的模式(patterns)和約束(constraints)以及when中條件的寫法。

2、語法結構

image

3、模式例子

3.1 單個對象匹配

rule "工作記憶體中只要有Person對象就執行,存在多個執行多次"
    when Person()
    then
        System.out.println("工作記憶體中存在Person對象");
end

3.2 匹配任何對象

rule "只要工作記憶體中有對象,都會匹配到"
    when Object()
    then
        System.out.println("只要工作記憶體中有對象,都會匹配到");
end

3.3 帶條件匹配

rule "匹配年齡小於20歲的"
    when
        Person(age < 20) // 等駕與getAge() < 20,推薦使用屬性的寫法
    then
        System.out.println("匹配年齡小於20歲的");
end

3.3.1 注意事項

1、匹配的條件結果需要是 true或者false。
2、Person(age < 20) 和 Person(getAge() < 20) 是等價的,但是推薦第一種寫法。
3、Person(age < 20)默認會調用getAge()方法,如果該方法不存在則會調用age()方法,如果還不存在,則拋出異常。
4、Drools engine 會快取調用期間的匹配結果以提高效率,因此我們的getter方法,不要有狀態。

3.4 嵌套屬性的匹配

3.4.1 訪問單個嵌套屬性

rule "嵌套屬性的訪問"
    when
        Person(car.name == "寶馬")
    then
        System.out.println("嵌套屬性的訪問");
end

3.4.2 訪問多個嵌套屬性

rule "嵌套屬性的訪問-02"
    when
        Person( age < 20 && car.name == "寶馬" && car.color == null)
    then
        System.out.println("嵌套屬性的訪問-02");
end

3.4.3 屬性分組

.( <constraints> ) 將這些屬性訪問器分組到嵌套對象,以獲得更易讀的規則

rule "嵌套屬性的訪問-03"
    when
        Person(age < 20 , car.(name == "寶馬" || color != null)) // 屬性分組訪問
    then
        System.out.println("嵌套屬性的訪問-03");
end

3.4.4 強制類型轉換

在嵌套模式中,我們可以使用 <type>#<subtype>語法強制轉換為子類型並使父類型的 getter 用於子類型。

rule "嵌套屬性的訪問-強制類型轉換"
    when
        Person(age < 20 , car#BMWCar.name == "寶馬") // 強制類型轉換
    then
        System.out.println("嵌套屬性的訪問-強制類型轉換");
end

注意看上方的car#BMWCar,這個是將car轉換成BMWCar類型來使用。

3.4.5 注意事項

在有狀態的kie session中,需要謹慎的使用嵌套屬性。因為 Drools engine 的工作記憶體不知道任何嵌套值,也不會檢測它們何時更改。

3.5 調用java方法約束

rule "調用java方法約束"
    when
        Person(!isChild())
    then
        System.out.println("調用java方法約束");
end

3.5.1 注意實現

isChild()方法不應該修改fact的狀態,因為drools引擎為了提高工作效率,會將調用期間的結果進行快取,如果修改了狀態,可能將會導致匹配的結果不準。

3.6 多個欄位約束

rule "多個欄位約束"
    when
        Person((name != null && age < 20) && car != null) // isChild 方法中需要有狀態的更改
    then
        System.out.println("多個欄位約束");
end

3.7 頂級欄位約束

Person(name != null , age < 20 , car != null)
Person((name != null && age < 20) && car != null)

上面2種寫法是一樣的。

3.7.1 注意事項

1、在頂級欄位約束中,的性能是要高於&&的。
2、&&優先於||,&&和||兩者都優先,。
3、不可在複合表達式中嵌入,,比如:Person((name != null , age < 20) , car != null)這是錯誤的寫法,需要將,換成&&符號。

3.8 日期類型的使用

在drools中默認的日期格式為dd-mmm-yyyy,此處我們通過設置系統變數drools.dateformat修改成yyyy-MM-dd HH:mm:ss格式。

rule "日期類型的使用"
    when
        $p: Person(registerDate < '2022-05-24 12:12:12' ) // 日期格式比較,System.setProperty("drools.dateformat","yyyy-MM-dd HH:mm:ss");
    then
        System.err.println("日期類型的使用 註冊時間:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format($p.getRegisterDate()) );
end

4、在模式和約束中使用綁定變數

rule "使用綁定變數"
    when
        $p: Person($age: age)
    then
        System.err.println("使用綁定變數 " + $p.getName() + ": " + $age);
end

後期我們可以使用$p和$age,$p表示當前規則運行時,工作記憶體中匹配到的Person對象,$age表示匹配到這個對象的age屬性,一般綁定變數以$開頭,和fact的屬性區分開。

4.1 欄位約束中綁定變數不好的寫法

rule "使用綁定變數-不好的寫法"
    when
        Person($age: age * 2 < 100)
    then
        System.err.println("使用綁定變數-不好的寫法 " + ": " + $age);
end

這樣寫不清晰,而且執行效率不高。

4.2 欄位約束中綁定變數好的寫法

rule "使用綁定變數-推薦的寫法"
    when
        Person( age * 2 < 100, $age: age) // 這樣寫更清晰,運行效率更高
    then
        System.err.println("使用綁定變數-推薦的寫法 " + ": " + $age);
end

4.3 約束綁定只考慮後面的第一個原子表達式

Person( $age1: (age * 2)) 

和

Person( $age2: age * 2)

的結果是不一樣的,$age1的結果是$age2的結果的2倍。

5、支援的操作符

5.1 .() 分組屬性

使用 .() 運算符將屬性訪問器分組到嵌套對象

Person(age < 20 , car.(name == "寶馬" , color == null ))
Person(age < 20 , car.name == "寶馬" , car.color == null )

以上2種寫法是同一個意思

5.2 # 類型轉換

在嵌套模式中,我們可以使用 <type>#<subtype>語法強制轉換為子類型並使父類型的 getter 用於子類型。

Person(car#BMWCar.brand == "BMW")

car#BMWCar指的是將car轉換成BMWCar類型。

5.3 !. 嵌套屬性null安全

Person(car!.name == "寶馬") // 如果此時 car 為null,則使用 car!.name 是不會報錯的

當我們的屬性存在嵌套的時候,使用!.可以避免空指針異常。

5.4 [] 操作List或Map

1、List操作-按照索引訪問

Person(hobbyList[0] == "打籃球")

2、map操作-按照鍵操作

Person(map["key1"] == "value1")

Person(map["key1"] == "value1")中的這個map是Person的一個欄位是map

5.5 <, <=, >, >=,==, !=,&&,||

這些操作符和Java中的用法一致。
<, <=, >, >= 這些操作符,如果是用在Date類型的欄位,則<表示before,對於String類型的欄位,則按照自然順序排序比較

Person(age ((> 30 && < 40) || (>= 18 && <= 25)) 
&& car != null && registerDate < '2022-12-12 12:12:12')

Person(age >= 18 && age <= 25)和Person(age (>= 18 && <= 25))是相等的。

5.6 matches, not matches 正則匹配

  1. 用來判斷給定的欄位是否匹配執行的正則表達式。
  2. 正則表達式可以是一個給定的字元串,也可以是從變數中動態獲取。
  3. 轉義需要使用\\。
 Person(name matches hobbyList[2] && car.name not matches "^奧迪") // 正則表達式可以是動態來的

5.7 contains, not contains集合或字元串是否包含什麼

contains:包含;not contains:不包含。

1、驗證一個Array或Collection是否包含某個指定欄位的值(可以是常量也可以是變數)。
2、也可以是String類型的欄位是否包含某個值(可以是常量也可以是變數)。

Person(
    hobbyList contains "打籃球" && hobbyList not contains "打橄欖球" 
    &&。hobbyList not contains name &&
    name contains "張" && name not contains car.name
)

hobbyList:List類型的欄位。
name或car.name:String類型的欄位。

從上方的例子中可以看到:
hobbyList contains "打籃球":"打籃球"是一個常量字元串。
hobbyList not contains nam:"name"是一個動態變數,從Person中獲取。

為了向後兼容,excludes運算符和not contains的作用一致。

5.8 memberOf, not memberOf欄位是否是某個集合的一員

驗證某個欄位是否是Array或Collection的一員。Array或Collection必須是可變的。

Person("打籃球" memberOf hobbyList && "籃球" not memberOf hobbyList 
&& name not memberOf hobbyList)

5.9 str驗證欄位是否以什麼開頭或結尾

  1. 驗證指定的字元串是以什麼開頭str[startsWith]。
  2. 驗證指定的字元串是以什麼結尾str[endsWith]。
  3. 驗證指定字元串的長度str[length]。
  4. 查看這個類org.drools.core.base.evaluators.StrEvaluatorDefinition
Person(
   name str[startsWith] "張" && name str[endsWith] "三" &&
   name str[length] 2 && "張三" str[startsWith] "張"
)

5.10 in not in

判斷某個值是否在某一組值中

Person(
    $name: name &&
    name in ($name, "李四") &&
    "打籃球"  in ("打籃球", "踢足球") &&
    car.name not in ("打籃球", $name)
)

6、運算符的優先順序

下表列出了 DRL 運算符從高到低的優先順序。

Operator type Operators Notes
Nested or null-safe property access ., .(), !. Not standard Java semantics
List or Map access [] Not standard Java semantics
Constraint binding : Not standard Java semantics
Multiplicative *, /%
Additive +, -
Shift >>, >>>, <<
Relational <, <=, >, >=, instanceof
Equality == != Uses equals() and !equals() semantics, not standard Java same and not same semantics
Non-short-circuiting AND &
Non-short-circuiting exclusive OR ^
Non-short-circuiting inclusive OR |
Logical AND &&
Logical OR |
Ternary ? :
Comma-separated AND , Not standard Java semantics

7、DRL支援的規則條件元素(關鍵字)

drl中支援的規則條件元素比較多,此處講解部分關鍵字字的用法。

7.1 and

  1. 使用and可以將條件分組為邏輯組合。
  2. and支援中綴和前綴方式。
  3. 可以使用()明確的進行分組。
  4. 默認情況下是and
// 規則 and-01 and-02 and-03 是同一個意思,工作記憶體中需要同時存在Person和Order對象
rule "and-01"
    when
        Person() and Order()
    then
        System.out.println("and-01");
end

rule "and-02"
    when
        (and Person() Order())
    then
        System.out.println("and-02");
end

rule "and-03"
    when
        Person()
        Order()
    then
        System.out.println("and-03");
end

7.2 or

or也支援好幾種寫法,此處列出一種寫法。和java中的or用法一致

rule "or-01"
    when
        $p: Person() or Order() // 規則記憶體中只要存在Pereson或Order對象就會執行,如果都存在,那麼可能會執行多次。如果只想執行一次,可以看下exists的用法
    then
        System.out.println("or-01");
end

7.3 exists

與工作記憶體中的Fact進行匹配,只會在第一次匹配時觸發,不會觸發多次,如果和多個模式一起使用,則需要使用()。

簡單理解: 假設我工作記憶體中一次插入了5個Person對象,如果exists匹配到了,那麼只會執行一次,不會執行5次。

rule "exists"
    when
        exists (Person() or Order()) // 單個: exists Person() 多個:需要()分割
    then
        System.out.println("exists 工作記憶體中同時存在多個Person()對象和Order()對象,該規則也只執行一次");
end

7.4 not

規則記憶體中不存在這個對象時,觸發規則。

比如: not Person() 表示規則記憶體中沒有Person這個Fact對象時觸發。

rule "not-02"
    when
        not (Person(name == "李四") or Order(orderId == 1000))
    then
        System.out.println("not-02,規則記憶體中不存在Person#name==李四或Order#orderId=1000 時觸發");
end

7.5 from

使用它來指定模式的數據源。 這使 Drools 引擎能夠對不在工作記憶體中的數據進行推理。 數據源可以是綁定變數的子欄位,也可以是方法調用的結果。 用於定義對象源的表達式是任何遵循常規 MVEL 語法的表達式。 因此,from 元素使您能夠輕鬆地使用對象屬性導航、執行方法調用以及訪問映射和集合元素。

基本用法:

rule "from"
    when
        $p: Person($hobbyList: hobbyList)
        $hobby: String() from $hobbyList
    then
        System.out.println("如果$hobby有多個,那麼此處可能執行多次");
        System.out.println("from: person: " + $p.getName() + " 的 hobby is: " +$hobby);
end

如果Person的hobbyList是一個比較大的集合,那麼推薦將hobbyList這個插入到kie session中,來提高性能。

和lock-on-active一起使用的解決辦法

Using from with lock-on-active rule attribute can result in rules not being executed. You can address this issue in one of the following ways:

  1. Avoid using the from element when you can insert all facts into the working memory of the Drools engine or use nested object references in your constraint expressions.
  2. Place the variable used in the modify() block as the last sentence in your rule condition.
  3. Avoid using the lock-on-active rule attribute when you can explicitly manage how rules within the same ruleflow group place activations on one another.

form子句後在跟一個模式的解決辦法

包含 from 子句的模式後面不能跟以括弧開頭的另一個模式。 此限制的原因是 DRL 解析器將 from 表達式讀取為「來自 $l (String() or Number())」,它無法將此表達式與函數調用區分開來。 最簡單的解決方法是將 from 子句括在括弧中,如以下示例所示:

// Do not use `from` in this way:
rule R
  when
    $l : List()
    String() from $l
    (String() or Number())
  then
    // Actions
end

// Use `from` in this way instead:
rule R
  when
    $l : List()
    (String() from $l)
    (String() or Number())
  then
    // Actions
end

7.6 entry-point

使用它來定義與模式的數據源相對應的入口點或事件流。 此元素通常與 from 條件元素一起使用。 您可以為事件聲明一個入口點,以便 Drools 引擎僅使用來自該入口點的數據來評估規則。 您可以通過在 DRL 規則中引用它來隱式聲明一個入口點,或者在您的 Java 應用程式中顯式聲明它。

drl文件

rule "entry-point"
    when
        $o: Order() from entry-point "order-entry-point" // 這個地方的數據是從 order-entry-point 中來的,kieSession.getEntryPoint("order-entry-point");
        $p: Person() // 這個地方的數據是通過kieSession.insert 來的
    then
        System.err.println("entry-point" + $p.getName() + ": " + $o.getOrderId());
end

Order()從上方的規則文件中可以,這個Order()對象是從order-entry-point這個地方來的。而不是別的地方來的。

Java文件

// order-entry-point 這個是 drl 文件中定義的
EntryPoint entryPoint = kieSession.getEntryPoint("order-entry-point");
entryPoint.insert(new Order(2001L, 10000L));

8、完整項目

//gitee.com/huan1993/spring-cloud-parent/tree/master/drools/drools-drl-when

9、參考地址

1、//docs.drools.org/7.69.0.Final/drools-docs/html_single/index.html#drl-rules-WHEN-con_drl-rules

Tags: