drools中的條件 when
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")中的這個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 正則匹配
- 用來判斷
給定的欄位是否匹配執行的正則表達式。 正則表達式可以是一個給定的字元串,也可以是從變數中動態獲取。- 轉義需要使用
\\。
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驗證欄位是否以什麼開頭或結尾
- 驗證指定的字元串是以什麼開頭
str[startsWith]。 - 驗證指定的字元串是以什麼結尾
str[endsWith]。 - 驗證指定字元串的長度
str[length]。 - 查看這個類
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
- 使用
and可以將條件分組為邏輯組合。 and支援中綴和前綴方式。- 可以使用
()明確的進行分組。 - 默認情況下是
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:
- Avoid using the
fromelement when you can insert all facts into the working memory of the Drools engine or use nested object references in your constraint expressions. - Place the variable used in the
modify()block as the last sentence in your rule condition. - Avoid using the
lock-on-activerule 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

