文章目錄
- 規則引擎 Drools
- 1 問題
- 2 規則引擎概述
- 2.1 規則引擎
- 2.2 使用規則引擎的優勢
- 2.3 規則引擎應用場景
- 2.4 Drools介紹
- 3 Drools入門案例
- 3.1 創建springboot項目 引入依賴
- 3.2 添加Drools配置類
- 3.4 創建實體類Order
- 3.5 orderScore.drl
- 3.6 編寫測試類
- 4 Drools基礎語法
- 4.1 規則文件構成
- 4.2 規則體語法結構
- 4.3 注釋
- 4.4 Pattern模式匹配
- 4.5 比較操作符
- 4.6 Drools內置方法
- 4.6.1 update方法
- 4.6.2 insert方法
- 4.6.3 retract方法
- 5 規則屬性 attributes
- 5.1 salience屬性
- 5.2 no-loop屬性
- 6 Drools高級語法
- 6.1 global全局變量
規則引擎 Drools
1 問題
對于不經常變化的業務,我們通常是硬編碼到程序中。但是經常變化的業務,我們就得把業務流程從代碼中剝離出來,我們怎么從程序中剝離出去?這里就需要用到規則引擎了。
規則引擎可以做到把算法剝離出程序,你可以保存到TXT文件或者數據庫表里面,用的時候再加載回程序。雖然加載回來的算法是字符串,但是規則引擎有辦法運行這些字符串。例如商業中心人流量大的地方,共享充電寶收費就得上調一些。人流量小的地方可以下調一點。既然費用的算法經常要變動,我們肯定不能把算法寫死到程序里面。我們要把算法從程序中抽離,保存到MySQL里面。將來我們要改動計費算法,直接添加一個新紀錄就行了,原有記錄不需要刪改,程序默認使用最新的計費方式。
2 規則引擎概述
2.1 規則引擎
-
規則引擎,全稱為業務規則管理系統,英文名為BRMS(即Business Rule Management System)。規則引擎的主要思想是將應用程序中的業務決策部分分離出來,并使用預定義的語義模塊編寫業務決策(業務規則),由用戶或開發者在需要時進行配置、管理。
-
注意規則引擎并不是一個具體的技術框架,而是指的一類系統,即業務規則管理系統。目前市面上具體的規則引擎產品有:drools、VisualRules、iLog等。
-
規則引擎實現了將業務決策從應用程序代碼中分離出來,接收數據輸入,解釋業務規則,并根據業務規則做出業務決策。規則引擎其實就是一個輸入輸出平臺。
-
系統中引入規則引擎后,業務規則不再以程序代碼的形式駐留在系統中,取而代之的是處理規則的規則引擎,業務規則存儲在規則庫中,完全獨立于程序。業務人員可以像管理數據一樣對業務規則進行管理,比如查詢、添加、更新、統計、提交業務規則等。業務規則被加載到規則引擎中供應用系統調用。
2.2 使用規則引擎的優勢
使用規則引擎的優勢如下:
- 業務規則與系統代碼分離,實現業務規則的集中管理
- 在不重啟服務的情況下可隨時對業務規則進行擴展和維護
- 可以動態修改業務規則,從而快速響應需求變更
- 規則引擎是相對獨立的,只關心業務規則,使得業務分析人員也可以參與編輯、維護系統的業務規則
- 減少了硬編碼業務規則的成本和風險
- 使用規則引擎提供的規則編輯工具,使復雜的業務規則實現變得的簡單
2.3 規則引擎應用場景
對于一些存在比較復雜的業務規則并且業務規則會頻繁變動的系統比較適合使用規則引擎,如下:
- 風險控制系統----風險貸款、風險評估
- 反欺詐項目----銀行貸款、征信驗證
- 決策平臺系統----財務計算
- 促銷平臺系統----滿減、打折、加價購
2.4 Drools介紹
- drools是一款由JBoss組織提供的基于Java語言開發的開源規則引擎,可以將復雜且多變的業務規則從硬編碼中解放出來,以規則腳本的形式存放在文件或特定的存儲介質中(例如存放在數據庫中),使得業務規則的變更不需要修改項目代碼、重啟服務器就可以在線上環境立即生效。
- drools官網地址:https://drools.org/
- drools源碼下載地址:https://github.com/kiegroup/drools
3 Drools入門案例
3.1 創建springboot項目 引入依賴
<properties><drools.version>8.41.0.Final</drools.version>
</properties>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.drools</groupId><artifactId>drools-core</artifactId><version>${drools.version}</version></dependency><dependency><groupId>org.drools</groupId><artifactId>drools-compiler</artifactId><version>${drools.version}</version></dependency><dependency><groupId>org.drools</groupId><artifactId>drools-decisiontables</artifactId><version>${drools.version}</version></dependency><dependency><groupId>org.drools</groupId><artifactId>drools-mvel</artifactId><version>${drools.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>
3.2 添加Drools配置類
import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 規則引擎配置類*/
@Configuration
public class DroolsConfig {private static final KieServices kieServices = KieServices.Factory.get();//制定規則文件的路徑private static final String RULES_CUSTOMER_RULES_DRL = "rules/order.drl";@Beanpublic KieContainer kieContainer() {//獲得Kie容器對象KieFileSystem kieFileSystem = kieServices.newKieFileSystem();kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL));KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);kieBuilder.buildAll();KieModule kieModule = kieBuilder.getKieModule();KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());return kieContainer;}}
說明:
- 定義了一個
KieContainer
的Spring Bean
,KieContainer
用于通過加載應用程序的/resources
文件夾下的規則文件來構建規則引擎。 - 創建
KieFileSystem
實例并配置規則引擎并從應用程序的資源目錄加載規則的DRL
文件。 - 使用
KieBuilder
實例來構建drools
模塊。我們可以使用KieSerive單例實例來創建KieBuilder
實例。 - 最后,使用
KieService
創建一個KieContainer
并將其配置為spring bean
3.4 創建實體類Order
@Data
public class Order {private double amout;private double score;}
3.5 orderScore.drl
- 創建規則文件resources/rules/orderScore.drl
//訂單積分規則
package com.order
import com.atguigu.drools.model.Order//規則一:100元以下 不加分
rule "order_rule_1"when$order:Order(amout < 100)then$order.setScore(0);System.out.println("成功匹配到規則一:100元以下 不加分");
end//規則二:100元 - 500元 加100分
rule "order_rule_2"when$order:Order(amout >= 100 && amout < 500)then$order.setScore(100);System.out.println("成功匹配到規則二:100元 - 500元 加100分");
end//規則三:500元 - 1000元 加500分
rule "order_rule_3"when$order:Order(amout >= 500 && amout < 1000)then$order.setScore(500);System.out.println("成功匹配到規則三:500元 - 1000元 加500分");
end//規則四:1000元以上 加1000分
rule "order_rule_4"when$order:Order(amout >= 1000)then$order.setScore(1000);System.out.println("成功匹配到規則四:1000元以上 加1000分");
end
3.6 編寫測試類
import org.junit.jupiter.api.Test;
import com.atguigu.drools.model.Order;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class DroolsDemosApplicationTests {@Autowiredprivate KieContainer kieContainer;@Testpublic void test(){//從Kie容器對象中獲取會話對象KieSession session = kieContainer.newKieSession();//Fact對象,事實對象Order order = new Order();order.setAmout(1300);//將Order對象插入到工作內存中session.insert(order);//激活規則,由Drools框架自動進行規則匹配,如果規則匹配成功,則執行當前規則session.fireAllRules();//關閉會話session.dispose();System.out.println("訂單金額:" + order.getAmout() +",添加積分:" + order.getScore());}}
-
使用drools規則引擎主要工作就是編寫規則文件,在規則文件中定義跟業務相關的業務規則。規則定義好后就需要調用drools提供的API將數據提供給規則引擎進行規則模式匹配,規則引擎會執行匹配成功的規則并將計算的結果返回給我們。
-
使用規則引擎時業務規則可以做到動態管理。業務人員可以像管理數據一樣對業務規則進行管理,比如查詢、添加、更新、統計、提交業務規則等。這樣就可以做到在不重啟服務的情況下調整業務規則。
4 Drools基礎語法
4.1 規則文件構成
- 在使用Drools時非常重要的一個工作就是編寫規則文件,通常規則文件的后綴為.drl。
drl是Drools Rule Language的縮寫。在規則文件中編寫具體的規則內容。
一套完整的規則文件內容構成如下:
關鍵字 | 描述 |
---|---|
package | 包名,只限于邏輯上的管理,同一個包名下的查詢或者函數可以直接調用 |
import | 用于導入類或者靜態方法 |
global | 全局變量 |
function | 自定義函數 |
query | 查詢 |
rule end | 規則體 |
Drools支持的規則文件,除了drl形式,還有Excel文件類型的。
4.2 規則體語法結構
規則體是規則文件內容中的重要組成部分,是進行業務規則判斷、處理業務結果的部分。
規則體語法結構如下:
rule "ruleName"attributeswhenLHS thenRHS
end
rule:關鍵字,表示規則開始,參數為規則的唯一名稱。
attributes:規則屬性,是rule與when之間的參數,為可選項。
when:關鍵字,后面跟規則的條件部分。
LHS(Left Hand Side):是規則的條件部分的通用名稱。它由零個或多個條件元素組成。如果LHS為空,則它將被視為始終為true的條件元素。 (左手邊)
then:關鍵字,后面跟規則的結果部分。
RHS(Right Hand Side):是規則的后果或行動部分的通用名稱。 (右手邊)
end:關鍵字,表示一個規則結束。
4.3 注釋
- 在drl形式的規則文件中使用注釋和Java類中使用注釋一致,分為單行注釋和多行注釋。
單行注釋用"//“進行標記,多行注釋以”/“開始,以”/"結束。
//規則rule1的注釋,這是一個單行注釋
rule "rule1"whenthenSystem.out.println("rule1觸發");
end/*
規則rule2的注釋,
這是一個多行注釋
*/
rule "rule2"whenthenSystem.out.println("rule2觸發");
end
4.4 Pattern模式匹配
前面我們已經知道了Drools中的匹配器可以將Rule Base中的所有規則與Working Memory中的Fact對象進行模式匹配,那么我們就需要在規則體的LHS部分定義規則并進行模式匹配。LHS部分由一個或者多個條件組成,條件又稱為pattern。
pattern的語法結構為:綁定變量名:Object(Field約束)
其中綁定變量名可以省略,通常綁定變量名的命名一般建議以$開始。如果定義了綁定變量名,就可以在規則體的RHS部分使用此綁定變量名來操作相應的Fact對象。Field約束部分是需要返回true或者false的0個或多個表達式。例如入門案例中:
//規則二:100元 - 500元 加100分
rule "order_rule_2"when$order:Order(amout >= 100 && amout < 500)then$order.setScore(100);System.out.println("成功匹配到規則二:100元 - 500元 加100分");
end
通過上面的例子我們可以知道,匹配的條件為:
1、工作內存中必須存在Order這種類型的Fact對象-----類型約束
2、Fact對象的amout屬性值必須大于等于100------屬性約束
3、Fact對象的amout屬性值必須小于100------屬性約束
以上條件必須同時滿足當前規則才有可能被激活。
4.5 比較操作符
Drools提供的比較操作符,如下表:
符號 | 說明 |
---|---|
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
== | 等于 |
!= | 不等于 |
contains | 檢查一個Fact對象的某個屬性值是否包含一個指定的對象值 |
not contains | 檢查一個Fact對象的某個屬性值是否不包含一個指定的對象值 |
memberOf | 判斷一個Fact對象的某個屬性是否在一個或多個集合中 |
not memberOf | 判斷一個Fact對象的某個屬性是否不在一個或多個集合中 |
matches | 判斷一個Fact對象的屬性是否與提供的標準的Java正則表達式進行匹配 |
not matches | 判斷一個Fact對象的屬性是否不與提供的標準的Java正則表達式進行匹配 |
前6個比較操作符和Java中的完全相同。
4.6 Drools內置方法
規則文件的RHS
部分的主要作用是通過插入,刪除或修改工作內存中的Fact數據,來達到控制規則引擎執行的目的。Drools提供了一些方法可以用來操作工作內存中的數據,**操作完成后規則引擎會重新進行相關規則的匹配,**原來沒有匹配成功的規則在我們修改數據完成后有可能就會匹配成功了。
4.6.1 update方法
update方法的作用是更新工作內存中的數據,并讓相關的規則重新匹配。 (要避免死循環)
參數:
//Fact對象,事實對象
Order order = new Order();
order.setAmout(30);
規則:
//規則一:100元以下 不加分
rule "order_rule_1"when$order:Order(amout < 100)then$order.setAmout(150);update($order) //update方法用于更新Fact對象,會導致相關規則重新匹配System.out.println("成功匹配到規則一:100元以下 不加分");
end//規則二:100元 - 500元 加100分
rule "order_rule_2"when$order:Order(amout >= 100 && amout < 500)then$order.setScore(100);System.out.println("成功匹配到規則二:100元 - 500元 加100分");
end
在更新數據時需要注意防止發生死循環。
4.6.2 insert方法
- insert方法的作用是向工作內存中插入數據,并讓相關的規則重新匹配。
//規則一:100元以下 不加分
rule "order_rule_1"when$order:Order(amout < 100)thenOrder order = new Order();order.setAmout(130);insert(order); //insert方法的作用是向工作內存中插入Fact對象,會導致相關規則重新匹配System.out.println("成功匹配到規則一:100元以下 不加分");
end//規則二:100元 - 500元 加100分
rule "order_rule_2"when$order:Order(amout >= 100 && amout < 500)then$order.setScore(100);System.out.println("成功匹配到規則二:100元 - 500元 加100分");
end
4.6.3 retract方法
retract方法的作用是刪除工作內存中的數據,并讓相關的規則重新匹配。
//規則一:100元以下 不加分
rule "order_rule_1"when$order:Order(amout < 100)thenretract($order) //retract方法的作用是刪除工作內存中的Fact對象,會導致相關規則重新匹配System.out.println("成功匹配到規則一:100元以下 不加分");
end
5 規則屬性 attributes
Drools中提供的屬性如下表(部分屬性):
屬性名 | 說明 |
---|---|
salience | 指定規則執行優先級 |
dialect | 指定規則使用的語言類型,取值為java和mvel |
enabled | 指定規則是否啟用 |
date-effective | 指定規則生效時間 |
date-expires | 指定規則失效時間 |
activation-group | 激活分組,具有相同分組名稱的規則只能有一個規則觸發 |
agenda-group | 議程分組,只有獲取焦點的組中的規則才有可能觸發 |
timer | 定時器,指定規則觸發的時間 |
auto-focus | 自動獲取焦點,一般結合agenda-group一起使用 |
no-loop | 防止死循環 |
重點說一下我們項目需要使用的屬性
5.1 salience屬性
- salience屬性用于指定規則的執行優先級,取值類型為Integer。數值越大越優先執行。每個規則都有一個默認的執行順序,如果不設置salience屬性,規則體的執行順序為由上到下。可以通過創建規則文件salience.drl來測試salience屬性,內容如下:
package com.orderrule "rule_1"wheneval(true)thenSystem.out.println("規則rule_1觸發");
endrule "rule_2"wheneval(true)thenSystem.out.println("規則rule_2觸發");
endrule "rule_3"wheneval(true)thenSystem.out.println("規則rule_3觸發");
end
- 由于以上三個規則沒有設置salience屬性,所以執行的順序是按照規則文件中規則的順序由上到下執行的。接下來修改一下文件內容:
package com.orderrule "rule_1"salience 9wheneval(true)thenSystem.out.println("規則rule_1觸發");
endrule "rule_2"salience 10wheneval(true)thenSystem.out.println("規則rule_2觸發");
endrule "rule_3"salience 8wheneval(true)thenSystem.out.println("規則rule_3觸發");
end
- 規則文件執行的順序是按照我們設置的salience值由大到小順序執行的。
- 建議在編寫規則時使用salience屬性明確指定執行優先級。
5.2 no-loop屬性
- no-loop屬性用于防止死循環,當規則通過update之類的函數修改了Fact對象時,可能使當前規則再次被激活從而導致死循環。取值類型為Boolean,默認值為false,測試步驟如下:
//訂單積分規則
package com.order
import com.atguigu.drools.model.Order//規則一:100元以下 不加分
rule "order_rule_1"no-loop true //防止陷入死循環when$order:Order(amout < 100)then$order.setScore(0);update($order)System.out.println("成功匹配到規則一:100元以下 不加分");
end
- 通過控制臺可以看到,由于沒有設置no-loop屬性的值,所以發生了死循環。接下來設置no-loop的值為true再次測試則不會發生死循環。
6 Drools高級語法
關鍵字 | 描述 |
---|---|
package | 包名,只限于邏輯上的管理,同一個包名下的查詢或者函數可以直接調用 |
import | 用于導入類或者靜態方法 |
global | 全局變量 |
function | 自定義函數 |
query | 查詢 |
rule end | 規則體 |
6.1 global全局變量
- global關鍵字用于在規則文件中定義全局變量,它可以讓應用程序的對象在規則文件中能夠被訪問。可以用來為規則文件提供數據或服務。
- 語法結構為:global 對象類型 對象名稱
在使用global定義的全局變量時有兩點需要注意:
- 如果對象類型為包裝類型時,在一個規則中改變了global的值,那么只針對當前規則有效,對其他規則中的global不會有影響。可以理解為它是當前規則代碼中的global副本,規則內部修改不會影響全局的使用。
- 如果對象類型為集合類型或JavaBean時,在一個規則中改變了global的值,對java代碼和所有規則都有效。
- 訂單Order:
package com.atguigu.drools.model;public class Order {private double amout;public double getAmout() {return amout;}public void setAmout(double amout) {this.amout = amout;}}
- 積分Integral:
package com.atguigu.drools.model;public class Integral {private double score;public double getScore() {return score;}public void setScore(double score) {this.score = score;}
}
- 規則文件:
//訂單積分規則
package com.order
import com.atguigu.drools.model.Orderglobal com.atguigu.drools.model.Integral integral;//規則一:100元以下 不加分
rule "order_rule_1"no-loop true //防止陷入死循環when$order:Order(amout < 100)thenintegral.setScore(10);update($order)System.out.println("成功匹配到規則一:100元以下 不加分");
end
- 測試:
@Test
public void test1(){//從Kie容器對象中獲取會話對象KieSession session = kieContainer.newKieSession();//Fact對象,事實對象Order order = new Order();order.setAmout(30);//全局變量Integral integral = new Integral();session.setGlobal("integral", integral);//將Order對象插入到工作內存中session.insert(order);//激活規則,由Drools框架自動進行規則匹配,如果規則匹配成功,則執行當前規則session.fireAllRules();//關閉會話session.dispose();System.out.println("訂單金額:" + order.getAmout());System.out.println("添加積分:" + integral.getScore());
}