此篇文章主要記錄一下 drools
中的模式(patterns
)和約束(constraints
)以及when
中條件的寫法。
rule "工作記憶體中只要有Person物件就執行,存在多個執行多次"
when Person()
then
System.out.println("工作記憶體中存在Person物件");
end
rule "只要工作記憶體中有物件,都會匹配到"
when Object()
then
System.out.println("只要工作記憶體中有物件,都會匹配到");
end
rule "匹配年齡小於20歲的"
when
Person(age < 20) // 等駕與getAge() < 20,推薦使用屬性的寫法
then
System.out.println("匹配年齡小於20歲的");
end
1、匹配的條件結果需要是 true
或者false
。
2、Person(age < 20)
和 Person(getAge() < 20)
是等價的,但是推薦第一種寫法
。
3、Person(age < 20)
預設會呼叫getAge()
方法,如果該方法不存在則會呼叫age()
方法,如果還不存在,則丟擲異常。
4、Drools engine
會快取呼叫期間的匹配結果以提高效率,因此我們的getter
方法,不要有狀態。
rule "巢狀屬性的存取"
when
Person(car.name == "寶馬")
then
System.out.println("巢狀屬性的存取");
end
rule "巢狀屬性的存取-02"
when
Person( age < 20 && car.name == "寶馬" && car.color == null)
then
System.out.println("巢狀屬性的存取-02");
end
.( <constraints> )
將這些屬性存取器分組到巢狀物件,以獲得更易讀的規則
rule "巢狀屬性的存取-03"
when
Person(age < 20 , car.(name == "寶馬" || color != null)) // 屬性分組存取
then
System.out.println("巢狀屬性的存取-03");
end
在巢狀模式中,我們可以使用 <type>#<subtype>
語法強制轉換為子型別並使父類別型的 getter 用於子型別。
rule "巢狀屬性的存取-強制型別轉換"
when
Person(age < 20 , car#BMWCar.name == "寶馬") // 強制型別轉換
then
System.out.println("巢狀屬性的存取-強制型別轉換");
end
注意看上方的car#BMWCar
,這個是將car
轉換成BMWCar
型別來使用。
在有狀態的kie session
中,需要謹慎的使用巢狀屬性
。因為 Drools engine
的工作記憶體不知道任何巢狀值,也不會檢測它們何時更改。
rule "呼叫java方法約束"
when
Person(!isChild())
then
System.out.println("呼叫java方法約束");
end
isChild()
方法不應該修改fact
的狀態,因為drools引擎
為了提高工作效率,會將呼叫期間的結果進行快取,如果修改了狀態,可能將會導致匹配的結果不準。
rule "多個欄位約束"
when
Person((name != null && age < 20) && car != null) // isChild 方法中需要有狀態的更改
then
System.out.println("多個欄位約束");
end
Person(name != null , age < 20 , car != null)
Person((name != null && age < 20) && car != null)
上面2種寫法是一樣的。
1、在頂級欄位約束中,
的效能是要高於&&
的。
2、&&
優先於||
,&&
和||
兩者都優先,
。
3、不可在複合表示式中嵌入,
,比如:Person((name != null , age < 20) , car != null)
這是錯誤的寫法,需要將,
換成&&
符號。
在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
rule "使用繫結變數"
when
$p: Person($age: age)
then
System.err.println("使用繫結變數 " + $p.getName() + ": " + $age);
end
後期我們可以使用$p
和$age
,$p
表示當前規則執行時,工作記憶體中匹配到的Person
物件,$age
表示匹配到這個物件的age
屬性,一般繫結變數以$
開頭,和fact
的屬性區分開。
rule "使用繫結變數-不好的寫法"
when
Person($age: age * 2 < 100)
then
System.err.println("使用繫結變數-不好的寫法 " + ": " + $age);
end
這樣寫不清晰,而且執行效率不高。
rule "使用繫結變數-推薦的寫法"
when
Person( age * 2 < 100, $age: age) // 這樣寫更清晰,執行效率更高
then
System.err.println("使用繫結變數-推薦的寫法 " + ": " + $age);
end
Person( $age1: (age * 2))
和
Person( $age2: age * 2)
的結果是不一樣的,$age1
的結果是$age2
的結果的2倍。
使用 .()
運運算元將屬性存取器分組到巢狀物件
Person(age < 20 , car.(name == "寶馬" , color == null ))
Person(age < 20 , car.name == "寶馬" , car.color == null )
以上2種寫法是同一個意思
在巢狀模式中,我們可以使用 <type>#<subtype>
語法強制轉換為子型別並使父類別型的 getter 用於子型別。
Person(car#BMWCar.brand == "BMW")
car#BMWCar
指的是將car
轉換成BMWCar
型別。
Person(car!.name == "寶馬") // 如果此時 car 為null,則使用 car!.name 是不會報錯的
當我們的屬性存在巢狀的時候,使用!.
可以避免空指標異常。
1、List操作-按照索引存取
Person(hobbyList[0] == "打籃球")
2、map操作-按照鍵操作
Person(map["key1"] == "value1")
Person(map["key1"] == "value1")
中的這個map
是Person
的一個欄位是map
這些操作符和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))
是相等的。
給定的欄位
是否匹配執行的正規表示式。正規表示式
可以是一個給定的字串,也可以是從變數中動態獲取。\\
。 Person(name matches hobbyList[2] && car.name not matches "^奧迪") // 正規表示式可以是動態來的
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的作用一致。
驗證某個欄位是否是Array
或Collection
的一員。Array
或Collection
必須是可變的。
Person("打籃球" memberOf hobbyList && "籃球" not memberOf hobbyList
&& name not memberOf hobbyList)
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] "張"
)
判斷某個值是否在某一組值中
Person(
$name: name &&
name in ($name, "李四") &&
"打籃球" in ("打籃球", "踢足球") &&
car.name not in ("打籃球", $name)
)
下表列出了 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 |
drl
中支援的規則條件元素比較多,此處講解部分
關鍵字字的用法。
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
or
也支援好幾種寫法,此處列出一種寫法。和java
中的or
用法一致
rule "or-01"
when
$p: Person() or Order() // 規則記憶體中只要存在Pereson或Order物件就會執行,如果都存在,那麼可能會執行多次。如果只想執行一次,可以看下exists的用法
then
System.out.println("or-01");
end
與工作記憶體中的Fact
進行匹配,只會在第一次匹配時觸發,不會觸發多次,如果和多個模式一起使用,則需要使用()
。
簡單理解:
假設我工作記憶體中一次插入了5個Person
物件,如果exists
匹配到了,那麼只會執行一次,不會執行5次。
rule "exists"
when
exists (Person() or Order()) // 單個: exists Person() 多個:需要()分割
then
System.out.println("exists 工作記憶體中同時存在多個Person()物件和Order()物件,該規則也只執行一次");
end
規則記憶體中不存在這個物件時,觸發規則。
比如: 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
使用它來指定模式的資料來源。 這使 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
中,來提高效能。
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:
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.modify()
block as the last sentence in your rule condition.lock-on-active
rule attribute when you can explicitly manage how rules within the same ruleflow group place activations on one another.包含 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
使用它來定義與模式的資料來源相對應的入口點或事件流
。 此元素通常與 from
條件元素一起使用。 您可以為事件宣告一個入口點,以便 Drools 引擎僅使用來自該入口點的資料來評估規則。 您可以通過在 DRL 規則中參照它來隱式宣告一個入口點,或者在您的 Java 應用程式中顯式宣告它。
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
這個地方來的。而不是別的地方來的。
// order-entry-point 這個是 drl 檔案中定義的
EntryPoint entryPoint = kieSession.getEntryPoint("order-entry-point");
entryPoint.insert(new Order(2001L, 10000L));
https://gitee.com/huan1993/spring-cloud-parent/tree/master/drools/drools-drl-when
1、https://docs.drools.org/7.69.0.Final/drools-docs/html_single/index.html