一、背景
最近在學習規則引擎drools
,此處簡單記錄一下drools
的入門案例。
二、為什麼要學習drools
假設我們存在如下場景:在我們到商店購買衣服的時候,經常會發生這樣的事情,購買1件不打折,購買2件打0.98折,購買3件級以上打0.85折。
那麼我們在程式碼中如果要實現上述功能,是不是就需要編寫if ... else
語句,假設後期規則變了,是不是就需要修改這些if ... else
語句,然後程式重新部署。這樣是可以實現,但是不夠優雅。那麼我們是否可以將這些業務規則寫入到規則檔案中,以後規則變更直接修改規則檔案即可?而drools
就可以實現這個功能。
三、實現上方這個簡單的打折案例
1、引入jar包
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-bom</artifactId>
<type>pom</type>
<version>7.69.0.Final</version>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-mvel</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
</dependencies>
2、編寫kmodule.xml配置檔案
此配置檔案需要放置在resources/META-INF
目錄下。
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<!--
kbase 可以存在多個
name: 指定kbase的名字,需要是唯一的
packages: 包名,可以理解為到src/main/resources目錄下查詢這個包名下的規則檔案,多個包使用逗號分割
default: 當前kbase是否是預設的kbase
-->
<kbase name="shop-kabse" packages="com.huan.shop" default="false">
<!--
ksession 可以存在多個
name: 指定ksession 的名字,需要唯一
defalut: 當前ksession在這個kbase下是否是預設的
type: 指定當前ksession是否是有狀態的 stateless表示是無狀態的
-->
<ksession name="shop-ksession" default="false" type="stateless"/>
<ksession name="shop-ksession-stateful" default="false" type="stateful"/>
</kbase>
</kmodule>
此處我們需要關注一下 kbase
下package
的值,這個值需要和規則檔案中的package
值一致,否則會找不到規則,具體看下方。
3、編寫規則檔案
1、規則檔案的語法
包名,必須放置在第一行
package
// 引入Java中的類,需要些全限定名
import
// 定義function ,可選
function // Optional
// 定義 query ,可選
query // Optional
declare // Optional
global // Optional
// rule 關鍵字 "rule name" 規則的名字
rule "rule name"
// Attributes 屬性可選
when // 關鍵字
// Conditions 條件,可為空
then
// Actions // 匹配後執行的結果
end // 關鍵字
2、編寫規則檔案
規則檔案的名字無所謂,比如: book-discount.drl
// 包名,必須防止到第一行,這個名字需要和 kbase中package屬性的值一致
package com.huan.shop
/**
* 倒入類
*/
import com.huan.drools.CustomerOrder
// 定義規則
rule "shop-rule-01"
when
// 模式匹配:到工作記憶體中查詢CustomerOrder,並且這個物件的purchaseQuantity值需要是1,
// 如果條件成立,$order是繫結變數名,一般以$開頭,和fact物件區分開
$order:CustomerOrder(purchaseQuantity == 1)
then
System.out.println("匹配規則 shop-rule-01");
// 賦值,此處賦值後,在Java程式碼中獲取獲取到賦值後的值
$order.setDiscount(1D);
end
rule "shop-rule-02"
when
$order:CustomerOrder(purchaseQuantity == 2)
then
System.out.println("匹配規則 shop-rule-02");
$order.setDiscount(0.98);
end
rule "shop-rule-03"
when
$order:CustomerOrder(purchaseQuantity >= 3)
then
System.out.println("匹配規則 shop-rule-03");
$order.setDiscount(0.85);
end
3、解釋一下包名
shop-discount.drl
從上圖中可知是規則檔案。
如果 shop-discount.drl
的包名修改為com.huan.shop1
則會提示如下警告:
`12:43:01.589 [main] WARN org.drools.compiler.kie.builder.impl.KieBuilderImpl - File 'com/huan/shop/shop-discount.drl' is in folder 'com/huan/shop' but declares package 'com.huan.shop1'. It is advised to have a correspondance between package and folder names.
`
四、編寫Java程式碼
1、編寫一個訂單物件
此物件儲存的是使用者購買了幾件衣服和對應的折扣。
/**
* 客戶購買衣服的訂單,省略 getter 和 setter 方法
*
* @author huan.fu
* @date 2022/5/12 - 11:27
*/
public class CustomerOrder {
/**
* 購買了幾件衣服
*/
private Integer purchaseQuantity;
/**
* 最終打多少折
*/
private Double discount;
public CustomerOrder(Integer purchaseQuantity) {
this.purchaseQuantity = purchaseQuantity;
}
}
2、編寫測試程式碼
1、無狀態測試方法statelessSessionTest
規則規則2,即最終打0.98
折。
2、有狀態測試方法statefulSessionTest
規則規則3,即最終打0.85
折。
package com.huan.drools;
import org.kie.api.KieServices;
import org.kie.api.event.rule.DebugRuleRuntimeEventListener;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.StatelessKieSession;
/**
* drools 測試類
*/
public class DroolsApplication {
public static void main(String[] args) throws InterruptedException {
// 無狀態session測試
statelessSessionTest();
// 有狀態session測試
statefulSessionTest();
}
private static void statelessSessionTest() {
// 獲取kie services
KieServices kieServices = KieServices.get();
// 獲取kie容器物件
KieContainer kieContainer = kieServices.getKieClasspathContainer();
// 獲取kie session , 此處獲取的是無狀態的session,因為 <ksession name="shop-ksession" default="false" type="stateless"/>
// 中type="stateless"就是無狀態的session
StatelessKieSession kieSession = kieContainer.newStatelessKieSession("shop-ksession");
// 建立一個物件,可以理解為 Fact物件,即事實物件
CustomerOrder customerOrder = new CustomerOrder(2);
// 新增監聽器,便於觀察日誌
kieSession.addEventListener(new DebugRuleRuntimeEventListener());
// 無狀態的session只需要執行 execute 方法即可。
kieSession.execute(customerOrder);
System.err.println("通過規則後,獲取到的折扣為:" + customerOrder.getDiscount());
}
private static void statefulSessionTest() {
// 獲取kie services
KieServices kieServices = KieServices.get();
// 獲取kie容器物件
KieContainer kieContainer = kieServices.getKieClasspathContainer();
// 獲取kie session , 此處獲取的是有狀態的session
KieSession kieSession = kieContainer.newKieSession("shop-ksession-stateful");
// 建立一個物件,可以理解為 Fact物件,即事實物件
CustomerOrder customerOrder = new CustomerOrder(3);
// 新增監聽器,便於觀察日誌
kieSession.addEventListener(new DebugRuleRuntimeEventListener());
// 將customerOrder物件加入到工作記憶體中
kieSession.insert(customerOrder);
// 觸發所有的規則,如果只想觸發指定的規則,則使用fireAllRules(AgendaFilter agendaFilter)方法
kieSession.fireAllRules();
// 有狀態的session一定需要呼叫dispose方法
kieSession.dispose();
System.err.println("通過規則後,獲取到的折扣為:" + customerOrder.getDiscount());
}
}
此處需要注意有狀態session
和無狀態session
寫法的區別。
五、測試結果
到此,我們使用drools
實現的一個簡單的案例就實現了。
六、drools引擎的基本元件
1、Rules
:我們自己定義的業務規則,比如我們自己寫的規則檔案。所有規則必須至少包含觸發規則的條件和規則規定的操作。
2、Production memory
:規則儲存在 Drools 引擎中的位置。
3、Facts
:輸入或更改到 Drools 引擎中的資料,Drools 引擎匹配規則條件以執行適用規則。在規則中修改了Fact物件的值,真實的JavaBean的資料也會發生改變。
比如:當我們呼叫ksession.insert(物件)
,那麼插入的這個物件就可以理解成Facts
物件。
4、Working memory
:facts 在 Drools 引擎中儲存的位置。
5、Pattern matcher
:匹配器,將Rule Base中所有的規則與Working memory
中的Fact
物件進行模式匹配,匹配成功的規則將被啟用並放入到Agenda
中。
6、Agenda
:議程,執行Agenda中被啟用的排好序的規則。
七、完整程式碼
https://gitee.com/huan1993/spring-cloud-parent/tree/master/drools/drools-quickstart