drools中的條件 when

2022-05-24 12:00:20

1、介紹

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

2、語法結構

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")中的這個mapPerson的一個欄位是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、驗證一個ArrayCollection是否包含某個指定欄位的值(可以是常數也可以是變數)。
2、也可以是String型別的欄位是否包含某個值(可以是常數也可以是變數)。

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

hobbyList:List型別的欄位。
namecar.name:String型別的欄位。

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

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

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

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

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

如果PersonhobbyList是一個比較大的集合,那麼推薦將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、完整專案

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

9、參考地址

1、https://docs.drools.org/7.69.0.Final/drools-docs/html_single/index.html