1、背景
此處介紹一下drools
中then
部分的寫法,以及一些內建的方法,比如insert/delete/modify
等等。同時也介紹一下rule
的繼承,和在when
中實現if else if
等操作。
2、支援的方法
drools提供了一些內建的方法,這些方法會修改drools的工作記憶體
中Fact
物件的值。從而會導致重新進行模式匹配。
2.1 insert 插入物件到工作記憶體中
insert
是向工作記憶體中插入物件,同時會導致重新進行規則的模式匹配。同時當規則不滿足時,不會自動刪除。
2.1.1 需求
當發生火災Fire
時,向工作記憶體中插入一個Alarm
物件,告警發生後,刪除工作記憶體中的Fire
物件,然後檢測Alarm
物件是否還存在。
2.1.2 drl檔案編寫
package rules
import com.huan.drools.insertmethod.Fire
import com.huan.drools.insertmethod.Alarm
rule "insert_發生火災時,往工作記憶體中插入告警物件"
when
$fire: Fire()
then
System.out.println("1、發生火災時,往工作記憶體中插入告警物件");
insert(new Alarm($fire));
end
rule "insert_當規則記憶體中存在告警物件,進行告警,然後刪除火災物件"
when
$fire: Fire()
$alarm: Alarm( fire == $fire )
then
System.out.println("2、進行告警,然後刪除對應的火災物件");
end
rule "insert_檢測告警物件是否還是存在-01"
when
Alarm()
then
System.out.println("3、insert 插入的告警物件還存在");
// 刪除工作記憶體中的Fire物件
delete($fire);
end
rule "insert_檢測告警物件不存在"
when
not Alarm()
then
System.out.println("3、insert 插入的告警物件不存在");
end
此處使用的是 insert
進行插入
2.1.3 部分java程式碼編寫
// 將火災物件插入到工作記憶體中
kieSession.insert(new Fire());
// 只觸發規則名稱以 insert_ 開頭的規則
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("insert_"));
2.1.4 執行結果
1、發生火災時,往工作記憶體中插入告警物件
2、進行告警,然後刪除對應的火災物件
3、insert 插入的告警物件還存在
2.1.5 結論
insert
可以向工作記憶體中插入Fact
物件。insert
方法呼叫後,會導致模式的重新匹配,導致之前不會執行的規則,重新執行。insert
方法插入到工作記憶體的物件,在規則不成立時,不會自動刪除,需要手動刪除,注意和insertLogical
的區別
2.2 insertLogical 插入物件到工作記憶體中
insert
是向工作記憶體中插入物件,同時會導致重新進行規則的模式匹配。同時當規則不滿足時,會自動刪除。
2.2.1 需求
當發生火災Fire
時,向工作記憶體中插入一個Alarm
物件,告警發生後,刪除工作記憶體中的Fire
物件,然後檢測Alarm
物件是否還存在。
2.2.2 drl檔案編寫
package rules
import com.huan.drools.Fire
import com.huan.drools.Alarm
rule "insertLogical_發生火災時,往工作記憶體中插入告警物件"
when
$fire: Fire()
then
System.out.println("1、發生火災時,往工作記憶體中插入告警物件");
insertLogical(new Alarm($fire));
end
rule "insertLogical_當規則記憶體中存在告警物件,進行告警,然後刪除火災物件"
when
$fire: Fire()
$alarm: Alarm( fire == $fire )
then
System.out.println("2、進行告警,然後刪除對應的火災物件");
delete($fire);
end
rule "insertLogical_檢測告警物件是否還是存在-01"
when
Alarm()
then
System.out.println("3、insertLogical 插入的告警物件還存在");
end
rule "insertLogical_檢測告警物件不存在"
when
not Alarm()
then
System.out.println("3、insertLogical 插入的告警物件不存在");
end
此處使用的是insertLogical
插入
2.2.3 部分java程式碼編寫
kieSession.insert(new Fire());
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("insertLogical_"));
2.2.4 執行結果
1、發生火災時,往工作記憶體中插入告警物件
2、進行告警,然後刪除對應的火災物件
3、insertLogical 插入的告警物件不存在
2.2.5 結論
insertLogical
可以向工作記憶體中插入Fact
物件。insertLogical
方法呼叫後,會導致模式的重新匹配,導致之前不會執行的規則,重新執行。insertLogical
方法插入到工作記憶體的物件,在規則不成立時,會自動刪除注意和insert
的區別
2.3、update 更新工作記憶體中的物件
update:
使用它來指定要更新的欄位和整個相關Fact,並將更改通知 Drools 引擎。 Fact發生更改後,您必須在更改可能受更新值影響的另一個事實之前呼叫 update。 為避免此新增步驟,請改用 modify 方法。
2.3.1 需求
規則一: 當工作記憶體中存在火災物件Fire
,並且名字name為空時觸發規則,同時在設定火災的名字為大火災
。
規則二: 當火災存在名字時,輸出火災名字
2.3.2 drl 檔案編寫
package rules
import com.huan.drools.Fire
import com.huan.drools.Alarm
rule "update_當存在火災物件時,設定一個火災的名字"
when
$fire: Fire(name == null)
then
System.out.println("1、設定火災名字");
$fire.setName("大火災");
update($fire)
end
rule "update_當火災物件存在名字時觸發"
when
$fire: Fire(name != null)
then
System.out.println("2、火災物件的名字為: " + $fire.getName());
end
2.3.3 部分java檔案編寫
kieSession.insert(new Fire());
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("update_"));
2.3.4 執行結果
1、設定火災名字
2、火災物件的名字為: 大火災
2.3.4 結論
update
會導致模式的重新匹配。update
會修改工作物件記憶體中的值。
2.4、modify 更新工作記憶體中的物件
modify:
使用它來指定要為Fact物件修改的欄位並將更改通知 Drools 引擎。 此方法提供了一種結構化的事實更新方法。 它將更新操作與 setter 呼叫相結合以更改物件欄位。
2.4.1 需求
規則一: 當工作記憶體中存在火災物件Fire
,並且名字name為空時觸發規則,同時在設定火災的名字為大火災
。
規則二: 當火災存在名字時,輸出火災名字
2.4.2 drl 檔案編寫
package rules
import com.huan.drools.Fire
import com.huan.drools.Alarm
rule "modify_當存在火災物件時,設定一個火災的名字"
when
$fire: Fire(name == null)
then
System.out.println("1、設定火災名字");
modify($fire){
setName("大火災")
}
end
rule "modify_當火災物件存在名字時觸發"
when
$fire: Fire(name != null)
then
System.out.println("2、火災物件的名字為: " + $fire.getName());
end
2.4.3 部分java檔案編寫
kieSession.insert(new Fire());
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("modify_"));
2.4.4 執行結果
1、設定火災名字
2、火災物件的名字為: 大火災
2.4.5 結論
modify
會導致模式的重新匹配。modify
會修改工作物件記憶體中的值。- 一般情況下使用
modify
,不要使用update
。
2.5 delete 刪除工作記憶體中的物件
用法:delete(<object>)
retract
也是和delete
一樣的效果,但是推薦使用delete
。
3、drools變數的簡單使用
package rules
rule "drools_變數的使用"
when
eval(true)
then
System.out.println("Match啟用的當前觸發規則: " + drools.getMatch());
System.out.println("當前觸發規則的名字: " + drools.getRule().getName());
// System.out.println("終止規則執行fireUntilHalt(): " + drools.getKieRuntime().halt());
// System.out.println("啟用AgendaGroup組: " + drools.getKieRuntime().getAgenda().getAgendaGroup( "CleanUp" ).setFocus());
System.out.println("獲取所有全域性變數: " + drools.getKieRuntime().getGlobals());
// System.out.println("設定全域性變數:" + drools.getKieRuntime().setGlobal("username","huan"); );
// System.out.println("獲取查詢結果:" + drools.getKieRuntime().getQueryResults());
end
4、規則的繼承
4.1 需求
規則一: 如果使用者(customer
)的年齡(age
)大於60歲,則打0.9
折。
規則二: 在規則一的基礎上,如果使用者有車(car
),則可以免費停車(freeParking
)。
4.2 drl檔案編寫
package rules
import com.huan.drools.Customer
import com.huan.drools.Car
rule "rule_extends_規則一"
when
$c: Customer(age > 60)
then
modify($c){
setDiscount(0.9)
}
System.out.println("觸發規則一:使用者年齡>60歲,打0.9折");
end
// 規則二繼承規則一的條件
rule "rule_extends_規則二" extends "rule_extends_規則一"
when
$car: Car()
then
modify($car){
setFreeParking(true)
}
System.out.println("觸發規則二:使用者有車,免費停車");
end
此處rule_extends_規則二
繼承了rule_extends_規則一
,所以規則一的條件也繼承了。
4.3 部分java程式碼
Car car = new Car();
Customer customer = new Customer();
customer.setAge(65);
kieSession.insert(customer);
kieSession.insert(car);
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("rule_extends_"));
客戶有車,並且年齡是65歲,滿足上方的規則一和規則二
4.4 執行結果
觸發規則一:使用者年齡>60歲,打0.9折
觸發規則二:使用者有車,免費停車
4.5 結論
可以看到在rule
上使用extends
關鍵字,可以實現規則的繼承。
5、使用do[...]語法重寫上方繼承的例子
5.1 需求
只要使用者大於60歲,直接打0.9折,如果還有車,則可以免費停車。
5.2 drl檔案編寫
package rules
import com.huan.drools.Customer
import com.huan.drools.Car
rule "命名結果_rule"
when
$c: Customer(age > 60)
do[giveDiscount] // 當上方的條件成立時執行 then [giveDiscount]
$car: Car() // 此條件成立時,執行預設的 then
then
modify($car){
setFreeParking(true)
};
System.out.println("使用者有車,免費停車");
then [giveDiscount]
modify($c){
setDiscount(0.9)
};
System.out.println("使用者年齡>60歲,打0.9折");
end
解釋:
見上方的規則檔案裡的注視
5.3 部分java程式碼編寫
Car car = new Car();
Customer customer = new Customer();
customer.setAge(65);
kieSession.insert(customer);
kieSession.insert(car);
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("命名結果_"));
5.4 執行結果
使用者年齡>60歲,打0.9折
使用者有車,免費停車
也實現了需求
5.5 結論
通過when
中使用 do[名字]
然後 then
then 名字
也可以實現。
6、實現 if else if 的效果
6.1 需求
完成類似 if else if else
效果。見下方的各種執行結果。
6.2 規則檔案
rule "if else-if"
when
$customer: Customer(age > 60) // 規則記憶體中存在Customer物件,並且age>60
if($customer.getLevel() == 1) do[level1] // 使用者的級別是1,執行then[level1],然後繼續執行下方的條件
else if ($customer.getLevel() == 2) break[level2] // 使用者的級別是2,執行then[level2],不在執行下方的條件
else do[levelOther] // 其他的level級別,執行then[levelOther],然後在執行下方的條件
Car()
then
System.out.println("我執行了");
then[level1]
System.out.println("level1");
then[level2]
System.out.println("level2");
then[levelOther]
System.out.println("levelOther");
end
6.3 執行結果圖
6.4 各種執行結果-也就是上圖的解釋
1、Customer
的age
小於60
。 輸出:
沒有輸出。
2、Customer
的age
大於60
並且level=1
,沒有Car
。輸出:
level1
3、Customer
的age
大於60
並且level=1
,有Car
。 輸出:
level1 我執行了
4、Customer
的age
大於60
並且level=2
,沒有Car
。輸出:
level2
5、Customer
的age
大於60
並且level=2
,有Car
。輸出:
level2
6、Customer
的age
大於60
並且level=3
,沒有Car
。 輸出:
levelOther
7、Customer
的age
大於60
並且level=3
,有Car
。 輸出:
levelOther 我執行了
6.5 do和 break 有什麼區別
do
:執行完之後,還會繼續判斷後面的執行條件。 (即還會執行後面的Car判斷,根據是否有Car獲取不同的結果
)break
:執行完之後,不會在判斷後面的執行條件。(即忽略了後面的Car判斷,rule執行完了
)
7、完整程式碼
https://gitee.com/huan1993/spring-cloud-parent/tree/master/drools/drools-drl-then
8、參考文件
1、https://docs.drools.org/7.69.0.Final/drools-docs/html_single/index.html#drl-rules-THEN-con_drl-rules