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
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. - Place the variable used in the
modify()
block as the last sentence in your rule condition. - 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#drl-rules-WHEN-con_drl-rules