1 .場景
1.1需求
商城系統消費贈送積分
100元以下, 不加分
100元-500元 加100分
500元-1000元 加500分
1000元 以上 加1000分
......
1.2傳統做法
1.2.1 if...else
if (order.getAmout() <= 100){
order.setScore(0);
addScore(order);
}else if(order.getAmout() > 100 && order.getAmout() <= 500){
order.setScore(100);
addScore(order);
}else if(order.getAmout() > 500 && order.getAmout() <= 1000){
order.setScore(500);
addScore(order);
}else{
order.setScore(1000);
addScore(order);
}
1.2.2 策略
interface Strategy {
addScore(int num1,int num2);
}
class Strategy1 {
addScore(int num1);
}
......................
interface StrategyN {
addScore(int num1);
}
class Environment {
private Strategy strategy;
public Environment(Strategy strategy) {
this.strategy = strategy;
}
public int addScore(int num1) {
return strategy.addScore(num1);
}
}
1.2.3 問題?
以上解決方法問題思考:
如果需求變更,積分層次結構增加,積分比例調整?
資料庫?
遇到的問題瓶頸:
第一,我們要簡化if else結構,讓業務邏輯和資料分離!
第二,分離出的業務邏輯必須要易于撰寫,至少單獨撰寫這些業務邏輯,要比寫代碼快!
第三,分離出的業務邏輯必須要比原來的代碼更容易讀懂!
第四,分離出的業務邏輯必須比原來的易于維護,至少改動這些邏輯,應用程式不用重啟!
2.是什么
2.1概念
規則引擎由推理引擎發展而來,是一種嵌入在應用程式中的組件,實作了將業務決策從應用程式代碼中分離出來,并使用預定義的語意模塊撰寫業務決策,接受資料輸入,解釋業務規則,并根據業務規則做出業務決策
需要注意的是規則引擎并不是一個具體的技術框架,而是指的一類系統,即業務規則管理系統,目前市面上具體的規則引擎產品有:drools、VisualRules、iLog等
在很多企業的 IT 業務系統中,經常會有大量的業務規則配置,而且隨著企業管理者的決策變化,這些業務規則也會隨之發生更改,為了適應這樣的需求,我們的 IT 業務系統應該能快速且低成本的更新,適應這樣的需求,一般的作法是將業務規則的配置單獨拿出來,使之與業務系統保持低耦合,目前,實作這樣的功能的程式,已經被開發成為規則引擎,
2.2 起源

2.3 原理--基于 rete 演算法的規則引擎
2.3.1 原理
在 AI 領域,產生式系統是一個很重要的理論,產生式推理分為正向推理和逆向推理產生式,其規則的一般形式是:IF 條件 THEN 操作,rete 演算法是實作產生式系統中正向推理的高效模式匹配演算法,通過形成一個 rete 網路進行模式匹配,利用基于規則的系統的時間冗余性和結構相似性特征 ,提高系統模式匹配效率
正向推理(Forward-Chaining)和反向推理(Backward-Chaining)
(1)正向推理也叫演繹法,由事實驅動,從一個初始的事實出發,不斷地從應用規則得出結論,首先在候選佇列中選擇一條規則作為啟用規則進行推理,記錄其結論作為下一步推理的證據,如此重復這個程序,直到再無可用規則可被選用或者求得了所要求的解為止,
(2)反向推理也叫歸納法,由目標驅動,首先提出某個假設,然后尋找支持該假設的證據,若所需的證據都能找到,說明原假設是正確的,若無論如何都找不到所需要的證據,則說明原假設不成立,此時需要另作新的假設,
2.3.2 rete演算法
Rete 演算法最初是由卡內基梅隆大學的 Charles L.Forgy 博士在 1974 年發表的論文中所闡述的演算法 , 該演算法提供了專家系統的一個高效實作,自 Rete 演算法提出以后 , 它就被用到一些大型的規則系統中 , 像 ILog、Jess、JBoss Rules 等都是基于 RETE 演算法的規則引擎 ,
Rete 在拉丁語中譯為”net”,即網路,Rete 匹配演算法是一種進行大量模式集合和大量物件集合間比較的高效方法,通過網路篩選的方法找出所有匹配各個模式的物件和規則,
其核心思想是將分離的匹配項根據內容動態構造匹配樹,以達到顯著降低計算量的效果,Rete 演算法可以被分為兩個部分:規則編譯和規則執行 ,當 Rete 演算法進行事實的斷言時,包含三個階段:匹配、選擇和執行,稱做 match-select-act cycle,
2.4 規則引擎應用場景
| 業務領域 | 示例 |
|---|---|
| 財務決策 | 貸款發放,征信系統 |
| 庫存管理 | 及時的供應鏈路 |
| 票價計算 | 航空,傳播,火車及其他公共汽車運輸 |
| 生產采購系統 | 產品原材料采購管理 |
| 風控系統 | 風控規則計算 |
| 促銷平臺系統 | 滿減,打折,加價購 |
2.5 Drools 介紹
Drools 具有一個易于訪問企業策略、易于調整以及易于管理的開源業務 規則引擎,符合業內標準,速度快、效率高,業務分析師或審核人員可以利用它輕松查看業務規則,從而檢驗已編碼的規則是否執行了所需的業務規則,其前身是 Codehaus 的一個開源專案叫 Drools,后被納入 JBoss 門下,更名為 JBoss Rules,成為了 JBoss 應用服務器的規則引擎,
Drools 被分為兩個主要的部分:編譯和運行時,編譯是將規則描述檔案按 ANTLR 3 語法進行決議,對語法進行正確性的檢查,然后產生一種中間結構“descr”,descr 用 AST 來描述規則,目前,Drools 支持四種規則描述檔案,分別是:drl 檔案、 xls 檔案、brl 檔案和 dsl 檔案,其中,常用的描述檔案是 drl 檔案和 xls 檔案,而 xls 檔案更易于維護,更直觀,更為被業務人員所理解,運行時是將 AST傳到 PackageBuilder,由 PackagBuilder來產生 RuleBase,它包含了一個或多個 Package 物件,
3 .消費贈送積分案例

上圖為實際用法:
3.1 第一步: 創建工程,引入jar
由于當前java開發,普通使用springboot ,本課程以springboot為基本框架演示
jar 依賴,注意,排除spring相關依賴
<!-- 規則引擎 -->
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>${drools.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
</exclusions>
</dependency>
3.2 創建 drools 自動配置類
drools 在spring 或者springboot中用法一樣,其實就是創建好一些bean
package com.ityml.drools.config;
import org.kie.api.KieBase;
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.kie.spring.KModuleBeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import java.io.IOException;
/**
* <p> 規則引擎自動配置類 </p>
* @author ityml
* @date 2019/9/10 11:20
*/
@Configuration
public class DroolsAutoConfiguration {
private static final String RULES_PATH = "rules/";
private KieServices getKieServices() {
return KieServices.Factory.get();
}
@Bean
@ConditionalOnMissingBean(KieFileSystem.class)
public KieFileSystem kieFileSystem() throws IOException {
KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
for (Resource file : getRuleFiles()) {
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
}
return kieFileSystem;
}
private Resource[] getRuleFiles() throws IOException {
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
}
@Bean
@ConditionalOnMissingBean(KieContainer.class)
public KieContainer kieContainer() throws IOException {
final KieRepository kieRepository = getKieServices().getRepository();
kieRepository.addKieModule(() -> kieRepository.getDefaultReleaseId());
KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
kieBuilder.buildAll();
KieContainer kieContainer = getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
return kieContainer;
}
@Bean
@ConditionalOnMissingBean(KieBase.class)
public KieBase kieBase() throws IOException {
return kieContainer().getKieBase();
}
}
3.2訂單物體類
@Data
@Accessors(chain = true)
public class Order {
/**
* 訂單原價金額
*/
private int price;
/**
*下單人
*/
private User user;
/**
*積分
*/
private int score;
/**
* 下單日期
*/
private Date bookingDate;
}
3.3規則引擎檔案
package rules
import com.ityml.drools.entity.Order
rule "zero"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout <= 100)
then
$s.setScore(0);
update($s);
end
rule "add100"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout > 100 && amout <= 500)
then
$s.setScore(100);
update($s);
end
rule "add500"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout > 500 && amout <= 1000)
then
$s.setScore(500);
update($s);
end
rule "add1000"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout > 1000)
then
$s.setScore(1000);
update($s);
end
3.4客戶端
/**
* 需求
* 計算額外積分金額 規則如下: 訂單原價金額
* 100以下, 不加分
* 100-500 加100分
* 500-1000 加500分
* 1000 以上 加1000分
*/
public class DroolsOrderTests extends DroolsApplicationTests {
@Resource
private KieContainer kieContainer;
@Test
public void Test() throws Exception {
List<Order> orderList = getInitData();
for (Order order : orderList) {
if (order.getAmout() <= 100) {
order.setScore(0);
addScore(order);
} else if (order.getAmout() > 100 && order.getAmout() <= 500) {
order.setScore(100);
addScore(order);
} else if (order.getAmout() > 500 && order.getAmout() <= 1000) {
order.setScore(500);
addScore(order);
} else {
order.setScore(1000);
addScore(order);
}
}
}
@Test
public void droolsOrderTest() throws Exception {
KieSession kieSession = kieContainer.newKieSession();
List<Order> orderList = getInitData();
for (Order order: orderList) {
// 1-規則引擎處理邏輯
kieSession.insert(order);
kieSession.fireAllRules();
// 2-執行完規則后, 執行相關的邏輯
addScore(order);
}
kieSession.dispose();
}
private static void addScore(Order o){
System.out.println("用戶" + o.getUser().getName() + "享受額外增加積分: " + o.getScore());
}
private static List<Order> getInitData() throws Exception {
List<Order> orderList = new ArrayList<>();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
{
Order order = new Order();
order.setAmout(80);
order.setBookingDate(df.parse("2015-07-01"));
User user = new User();
user.setLevel(1);
user.setName("Name1");
order.setUser(user);
order.setScore(111);
orderList.add(order);
}
{
Order order = new Order();
order.setAmout(200);
order.setBookingDate(df.parse("2015-07-02"));
User user = new User();
user.setLevel(2);
user.setName("Name2");
order.setUser(user);
orderList.add(order);
}
{
Order order = new Order();
order.setAmout(800);
order.setBookingDate(df.parse("2015-07-03"));
User user = new User();
user.setLevel(3);
user.setName("Name3");
order.setUser(user);
orderList.add(order);
}
{
Order order = new Order();
order.setAmout(1500);
order.setBookingDate(df.parse("2015-07-04"));
User user = new User();
user.setLevel(4);
user.setName("Name4");
order.setUser(user);
orderList.add(order);
}
return orderList;
}
}
3.5 drools 開發小結
3.5.1 drools 組成
drools規則引擎由以下幾部分構成:
- Working Memory(作業記憶體)
- Rules(規則庫)
- Facts
- Production memory
- Working memory:
- Agenda
如下圖所示:

3.5.2 相關概念說明
Working Memory:作業記憶體,drools規則引擎會從Working Memory中獲取資料并和規則檔案中定義的規則進行模式匹配,所以我們開發的應用程式只需要將我們的資料插入到Working Memory中即可,例如本案例中我們呼叫kieSession.insert(order)就是將order物件插入到了作業記憶體中,
Fact:事實,是指在drools 規則應用當中,將一個普通的JavaBean插入到Working Memory后的物件就是Fact物件,例如本案例中的Order物件就屬于Fact物件,Fact物件是我們的應用和規則引擎進行資料互動的橋梁或通道,
Rules:規則庫,我們在規則檔案中定義的規則都會被加載到規則庫中,
Pattern Matcher:匹配器,將Rule Base中的所有規則與Working Memory中的Fact物件進行模式匹配,匹配成功的規則將被激活并放入Agenda中,
Agenda:議程,用于存放通過匹配器進行模式匹配后被激活的規則,
3.5.3 規則引擎執行程序
3.5.4 KIE介紹
在上述分析積分兌換的程序中,簡單地使用了 "kie "開頭的一些類名,Kie全稱為Knowledge Is Everything,即"知識就是一切"的縮寫,是Jboss一系列專案的總稱,官網描述:這個名字滲透在GitHub賬戶和Maven POMs中,隨著范圍的擴大和新專案的展開,KIE(Knowledge Is Everything的縮寫)被選為新的組名,KIE的名字也被用于系統的共享方面;如統一的構建、部署和使用,
4.規則檔案開發
4.1 規則檔案構成
在使用Drools時非常重要的一個作業就是撰寫規則檔案,通常規則檔案的后綴為.drl,
drl是Drools Rule Language的縮寫,在規則檔案中撰寫具體的規則內容,
一套完整的規則檔案內容構成如下:
| 關鍵字 | 描述 |
|---|---|
| package | 包名,只限于邏輯上的管理,同一個包名下的查詢或者函式可以直接呼叫 |
| import | 用于匯入類或者靜態方法 |
| global | 全域變數 |
| function | 自定義函式 |
| query | 查詢 |
| rule end | 規則體 |
Drools支持的規則檔案,除了drl形式,還有Excel檔案型別的,
4.2 規則體語法結構
規則體是規則檔案內容中的重要組成部分,是進行業務規則判斷、處理業務結果的部分,
規則體語法結構如下:
rule "ruleName"
attributes
when
LHS
then
RHS
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"
when
then
System.out.println("rule1觸發");
end
/*
規則rule2的注釋,
這是一個多行注釋
*/
rule "rule2"
when
then
System.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個或多個運算式,
例如我們的入門案例中:
rule "add100"
no-loop true
lock-on-active true
salience 1
when
$order : Order(price > 100 && price <= 500)
then
$order.setScore(100);
update($s);
end
通過上面的例子我們可以知道,匹配的條件為:
1、作業記憶體中必須存在Order這種型別的Fact物件-----型別約束
2、Fact物件的price屬性值必須大于100------屬性約束
3、Fact物件的price屬性值必須小于等于500------屬性約束
以上條件必須同時滿足當前規則才有可能被激活,
系結變數既可以用在物件上,也可以用在物件的屬性上,例如上面的例子可以改為:
rule "add100"
no-loop true
lock-on-active true
salience 1
when
$order : Order($price:price > 100 && amopriceut <= 500)
then
System.out.println("$price=" + $price);
$s.setScore(100);
update($s);
end
LHS部分還可以定義多個pattern,多個pattern之間可以使用and或者or進行連接,也可以不寫,默認連接為and,
rule "add100"
no-loop true
lock-on-active true
salience 1
when
$order : Order(price > 100 && price <= 500) and
$user : User(level>3)
then
System.out.println($order.getUser());
$order.setScore(100);
update($order);
end
4.5 比較運算子
Drools提供的比較運算子,如下表:
| 符號 | 說明 |
|---|---|
| > | 大于 |
| < | 小于 |
| >= | 大于等于 |
| <= | 小于等于 |
| == | 等于 |
| != | 不等于 |
| contains | 檢查一個Fact物件的某個屬性值是否包含一個指定的物件值 |
| not contains | 檢查一個Fact物件的某個屬性值是否不包含一個指定的物件值 |
| memberOf | 判斷一個Fact物件的某個屬性是否在一個或多個集合中 |
| not memberOf | 判斷一個Fact物件的某個屬性是否不在一個或多個集合中 |
| matches | 判斷一個Fact物件的屬性是否與提供的標準的Java正則運算式進行匹配 |
| not matches | 判斷一個Fact物件的屬性是否不與提供的標準的Java正則運算式進行匹配 |
前6個比較運算子和Java中的完全相同,下面我們重點學習后6個比較運算子,
4.5.1 語法
-
contains | not contains語法結構
Object(Field[Collection/Array] contains value)
Object(Field[Collection/Array] not contains value)
-
memberOf | not memberOf語法結構
Object(field memberOf value[Collection/Array])
Object(field not memberOf value[Collection/Array])
-
matches | not matches語法結構
Object(field matches "正則運算式")
Object(field not matches "正則運算式")
contain是前面包含后面,memberOf是后面包含前面,
4.5.2 操作步驟
第一步:創建物體類,用于測驗比較運算子
package com.ityml.drools.entity;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* @author ityml
* @date 2021-06-16 21:11
*/
@Data
@Accessors(chain = true)
public class ComparisonEntity {
/**
*名字集合
*/
private String names;
/**
* 字串集合
*/
private List<String> list;
}
第二步:在/resources/rules下創建規則檔案comparison.drl
package rules
import com.ityml.drools.entity.ComparisonEntity
/*
用于測驗Drools提供的比較運算子
*/
//測驗比較運算子contains
rule "rule_comparison_contains"
when
ComparisonEntity(names contains "張三")
ComparisonEntity(list contains names)
then
System.out.println("規則rule_comparison_contains觸發");
end
//測驗比較運算子not contains
rule "rule_comparison_notContains"
when
ComparisonEntity(names not contains "張三")
ComparisonEntity(list not contains names)
then
System.out.println("規則rule_comparison_notContains觸發");
end
//測驗比較運算子memberOf
rule "rule_comparison_memberOf"
when
ComparisonEntity(names memberOf list)
then
System.out.println("規則rule_comparison_memberOf觸發");
end
//測驗比較運算子not memberOf
rule "rule_comparison_notMemberOf"
when
ComparisonEntity(names not memberOf list)
then
System.out.println("規則rule_comparison_notMemberOf觸發");
end
//測驗比較運算子matches
rule "rule_comparison_matches"
when
ComparisonEntity(names matches "張.*")
then
System.out.println("規則rule_comparison_matches觸發");
end
//測驗比較運算子not matches
rule "rule_comparison_notMatches"
when
ComparisonEntity(names not matches "張.*")
then
System.out.println("規則rule_comparison_notMatches觸發");
end
第三步:撰寫單元測驗
package com.ityml.drools.client;
import com.ityml.drools.DroolsApplicationTests;
import com.ityml.drools.entity.ComparisonEntity;
import org.junit.jupiter.api.Test;
import org.kie.api.KieBase;
import org.kie.api.runtime.KieSession;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @author ityml
* @date 2021-06-17 23:46
*/
public class ComparisonTest extends DroolsApplicationTests {
@Resource
public KieBase kieBase;
@Test
public void testComparison(){
KieSession kieSession = kieBase.newKieSession();
ComparisonEntity comparisonEntity = new ComparisonEntity();
comparisonEntity.setNames("張三");
List<String> list = new ArrayList<>();
list.add("張三");
list.add("李四");
comparisonEntity.setList(list);
kieSession.insert(comparisonEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
}
4.6 執行指定規則
通過前面的案例可以看到,我們在呼叫規則代碼時,滿足條件的規則都會被執行,那么如果我們只想執行其中的某個規則如何實作呢?
Drools給我們提供的方式是通過規則過濾器來實作執行指定規則,對于規則檔案不用做任何修改,只需要修改Java代碼即可,如下:
//通過規則過濾器實作只執行指定規則
kieSession.fireAllRules(new kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rule_filter_1"));
4.7 關鍵字
Drools的關鍵字分為:硬關鍵字(Hard keywords)和軟關鍵字(Soft keywords),
硬關鍵字是我們在規則檔案中定義包名或者規則名時明確不能使用的,否則程式會報錯,軟關鍵字雖然可以使用,但是不建議使用,
硬關鍵字包括:true false null
軟關鍵字包括:lock-on-active date-effective date-expires no-loop auto-focus activation-group agenda-group ruleflow-group entry-point duration package import dialect salience enabled attributes rule extend when then template query declare function global eval not in or and exists forall accumulate collect from action reverse result end over init
比如:
rule true //不可以
rule "true" 可以
5. 規則屬性 attributes
前面我們已經知道了規則體的構成如下:
rule "ruleName"
attributes
when
LHS
then
RHS
end
本章節就是針對規則體的attributes屬性部分進行講解,Drools中提供的屬性如下表(部分屬性):
| 屬性名 | 說明 |
|---|---|
| salience | 指定規則執行優先級 |
| dialect | 指定規則使用的語言型別,取值為java和mvel |
| enabled | 指定規則是否啟用 |
| date-effective | 指定規則生效時間 |
| date-expires | 指定規則失效時間 |
| activation-group | 激活分組,具有相同分組名稱的規則只能有一個規則觸發 |
| agenda-group | 議程分組,只有獲取焦點的組中的規則才有可能觸發 |
| timer | 定時器,指定規則觸發的時間 |
| auto-focus | 自動獲取焦點,一般結合agenda-group一起使用 |
| no-loop | 防止死回圈,防止自己更新規則再次觸發 |
| lock-on-active | no-loop增強版本,可防止別人更新規則再次出發 |
5.1 enabled屬性
enabled屬性對應的取值為true和false,默認值為true,
用于指定當前規則是否啟用,如果設定的值為false則當前規則無論是否匹配成功都不會觸發
package rules
import com.ityml.drools.entity.AttributesEnabledEntity
/*
用于測驗Drools 屬性:enabled
*/
//測驗enabled
rule "rule_attributes_enabled"
enabled false
when
AttributesEnabledEntity(num > 10)
then
System.out.println("規則rule_attributes_enabled觸發");
end
5.2 dialect屬性
dialect屬性用于指定當前規則使用的語言型別,取值為java和mvel,默認值為java,
注:mvel是一種基于java語法的運算式語言,
雖然mvel吸收了大量的java語法,但作為一個運算式語言,還是有著很多重要的不同之處,以達到更高的效率,比如:mvel像正則運算式一樣,有直接支持集合、陣列和字串匹配的運算子,
除了運算式語言外,mvel還提供了用來配置和構造字串的模板語言,
mvel2.x運算式包含以下部分的內容:屬性運算式,布爾運算式,方法呼叫,變數賦值,函式定義
5.3 salience屬性
salience屬性用于指定規則的執行優先級,取值型別為Integer,數值越大越優先執行,每個規則都有一個默認的執行順序,如果不設定salience屬性,規則體的執行順序為由上到下,
drl檔案內容如下:
package rules
import com.ityml.drools.entity.AttributesSalienceEntity
/*
用于測驗Drools 屬性:salience
*/
rule "rule_attributes_salience_1"
when
AttributesSalienceEntity(flag == true)
then
System.out.println("規則 rule_attributes_salience_1 觸發");
end
rule "rule_attributes_salience_2"
when
AttributesSalienceEntity(flag == true)
then
System.out.println("規則 rule_attributes_salience_2 觸發");
end
rule "rule_attributes_salience_3"
when
AttributesSalienceEntity(flag == true)
then
System.out.println("規則 rule_attributes_salience_3 觸發");
end
通過控制臺可以看到,由于以上三個規則沒有設定salience屬性,所以執行的順序是按照規則檔案中規則的順序由上到下執行的,接下來我們修改一下檔案內容:
package rules
import com.ityml.drools.entity.AttributesSalienceEntity
/*
用于測驗Drools 屬性:salience
*/
rule "rule_attributes_salience_1"
salience 10
when
AttributesSalienceEntity(flag == true)
then
System.out.println("規則 rule_attributes_salience_1 觸發");
end
rule "rule_attributes_salience_2"
salience 20
when
AttributesSalienceEntity(flag == true)
then
System.out.println("規則 rule_attributes_salience_2 觸發");
end
rule "rule_attributes_salience_3"
salience 1
when
AttributesSalienceEntity(flag == true)
then
System.out.println("規則 rule_attributes_salience_3 觸發");
end
通過控制臺可以看到,規則檔案執行的順序是按照我們設定的salience值由大到小順序執行的,
建議在撰寫規則時使用salience屬性明確指定執行優先級,
5.4 no-loop屬性
no-loop屬性用于防止死回圈,當規則通過update之類的函式修改了Fact物件時,可能使當前規則再次被激活從而導致死回圈,取值型別為Boolean,默認值為false,測驗步驟如下:
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.AttributesNoLoopEntity
/*
用于測驗Drools 屬性:no-loop
*/
rule "rule_attributes_noloop"
//no-loop true
when
$attributesNoLoopEntity:AttributesNoLoopEntity(num > 1)
then
update($attributesNoLoopEntity)
System.out.println("規則 rule_attributes_noloop 觸發");
end
第二步:撰寫單元測驗
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
AttributesNoLoopEntity attributesNoLoopEntity = new AttributesNoLoopEntity();
attributesNoLoopEntity.setNum(20);
kieSession.insert(attributesNoLoopEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
通過控制臺可以看到,由于我們沒有設定no-loop屬性的值,所以發生了死回圈,接下來設定no-loop的值為true再次測驗則不會發生死回圈,
5.5 lock-on-active屬性
lock-on-active這個屬性,可以限制當前規則只會被執行一次,包括當前規則的重復執行不是本身觸發的,取值型別為Boolean,默認值為false,測驗步驟如下:
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.AttributesLockOnActiveEntity
/*
用于測驗Drools 屬性:lock-on-active
*/
rule "rule_attributes_lock_on_active_1"
no-loop true
when
$attributesLockOnActiveEntity:AttributesLockOnActiveEntity(num > 1)
then
update($attributesLockOnActiveEntity)
System.out.println("規則 rule_attributes_lock_on_active_1 觸發");
end
rule "rule_attributes_lock_on_active_2"
no-loop true
lock-on-active true
when
$attributesLockOnActiveEntity:AttributesLockOnActiveEntity(num > 1)
then
update($attributesLockOnActiveEntity)
System.out.println("規則 rule_attributes_lock_on_active_2 觸發");
end
第二步:撰寫單元測驗
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
AttributesLockOnActiveEntity attributesLockOnActiveEntity = new AttributesLockOnActiveEntity();
attributesLockOnActiveEntity.setNum(20);
kieSession.insert(attributesLockOnActiveEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
no-loop的作用是限制因為modify等更新操作導致規則重復執行,但是有一個限定條件,是當前規則中進行更新導致當前規則重復執行,而不是防止其他規則更新相同的fact物件,導致當前規則更新,lock-on-active可以看作是no-loop的加強版,不僅能限制自己的更新,還能限制別人的更新造成的死回圈,
5.6 activation-group屬性,
activation-group屬性是指激活分組,取值為String型別,具有相同分組名稱的規則只能有一個規則被觸發,
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.AttributesActivationGroupEntity
/*
用于測驗Drools 屬性: activation-group
*/
rule "rule_attributes_activation_group_1"
activation-group "customGroup"
when
$attributesActivationGroupEntity:AttributesActivationGroupEntity(num > 1)
then
System.out.println("規則 rule_attributes_activation_group_1 觸發");
end
rule "rule_attributes_activation_group_2"
activation-group "customGroup"
when
$attributesActivationGroupEntity:AttributesActivationGroupEntity(num > 1)
then
System.out.println("規則 rule_attributes_activation_group_2 觸發");
end
第二步:撰寫單元測驗
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
AttributesActivationGroupEntity attributesActivationGroupEntity = new AttributesActivationGroupEntity();
attributesActivationGroupEntity.setNum(20);
kieSession.insert(attributesActivationGroupEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
通過控制臺可以發現,上面的兩個規則因為屬于同一個分組,所以只有一個觸發了,同一個分組中的多個規則如果都能夠匹配成功,具體哪一個最終能夠被觸發可以通過salience屬性確定,
5.7 agenda-group屬性
agenda-group屬性為議程分組,屬于另一種可控的規則執行方式,用戶可以通過設定agenda-group來控制規則的執行,只有獲取焦點的組中的規則才會被觸發,
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.AttributesAgendaGroupEntity
/*
用于測驗Drools 屬性: agenda-group
*/
rule "rule_attributes_agenda_group_1"
agenda-group "customAgendaGroup1"
when
$attributesAgendaGroupEntity:AttributesAgendaGroupEntity(num > 1)
then
System.out.println("規則 rule_attributes_agenda_group_1 觸發");
end
rule "rule_attributes_agenda_group_2"
agenda-group "customAgendaGroup1"
when
$attributesAgendaGroupEntity:AttributesAgendaGroupEntity(num > 1)
then
System.out.println("規則 rule_attributes_agenda_group_2 觸發");
end
rule "rule_attributes_activation_group_3"
agenda-group "customAgendaGroup2"
when
$attributesAgendaGroupEntity:AttributesAgendaGroupEntity(num > 1)
then
System.out.println("規則 rule_attributes_activation_group_3 觸發");
end
rule "rule_attributes_agenda_group_4"
agenda-group "customAgendaGroup2"
when
$attributesAgendaGroupEntity:AttributesAgendaGroupEntity(num > 1)
then
System.out.println("規則 rule_attributes_agenda_group_4 觸發");
end
第二步:撰寫單元測驗
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
AttributesAgendaGroupEntity attributesAgendaGroupEntity = new AttributesAgendaGroupEntity();
attributesAgendaGroupEntity.setNum(20);
kieSession.insert(attributesAgendaGroupEntity);
kieSession.getAgenda().getAgendaGroup("customAgendaGroup2").setFocus();
kieSession.fireAllRules();
kieSession.dispose();
}
通過控制臺可以看到,只有獲取焦點的分組中的規則才會觸發,與activation-group不同的是,activation-group定義的分組中只能夠有一個規則可以被觸發,而agenda-group分組中的多個規則都可以被觸發,
5.8 auto-focus屬性
auto-focus屬性為自動獲取焦點,取值型別為Boolean,默認值為false,一般結合agenda-group屬性使用,當一個議程分組未獲取焦點時,可以設定auto-focus屬性來控制,
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.AttributesAutoFocusEntity
/*
用于測驗Drools 屬性: auto-focus
*/
rule "rule_attributes_auto_focus_1"
agenda-group "customAgendaGroup1"
when
$attributesAutoFocusEntity:AttributesAutoFocusEntity(num > 1)
then
System.out.println("規則 rule_attributes_auto_focus_1 觸發");
end
rule "rule_attributes_auto_focus_2"
agenda-group "customAgendaGroup1"
when
$attributesAutoFocusEntity:AttributesAutoFocusEntity(num > 1)
then
System.out.println("規則 rule_attributes_auto_focus_2 觸發");
end
rule "rule_attributes_auto_focus_3"
agenda-group "customAgendaGroup2"
// auto-focus true
when
$attributesAutoFocusEntity:AttributesAutoFocusEntity(num > 1)
then
System.out.println("規則 rule_attributes_auto_focus_3 觸發");
end
rule "rule_attributes_auto_focus_4"
agenda-group "customAgendaGroup2"
when
$attributesAutoFocusEntity:AttributesAutoFocusEntity(num > 1)
then
System.out.println("規則 rule_attributes_auto_focus_4 觸發");
end
第二步:撰寫單元測驗
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
AttributesAutoFocusEntity attributesAutoFocusEntity = new AttributesAutoFocusEntity();
attributesAutoFocusEntity.setNum(20);
kieSession.insert(attributesAutoFocusEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
通過控制臺可以看到,設定auto-focus屬性為true的規則都觸發了,
注意:同一個組,只要有個設定auto-focus true 其他的設定不設定都無所謂啦,都會起作用的,
5.9 timer屬性
timer屬性可以通過定時器的方式指定規則執行的時間,使用方式有兩種:
方式一:timer (int:
此種方式遵循java.util.Timer物件的使用方式,第一個引數表示幾秒后執行,第二個引數表示每隔幾秒執行一次,第二個引數為可選,
方式二:timer(cron:
此種方式使用標準的unix cron運算式的使用方式來定義規則執行的時間,
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.AttributesTimerEntity
/*
用于測驗Drools 屬性: timer
*/
rule "rule_attributes_timer_1"
timer(5s 2s)
when
$attributesTimerEntity:AttributesTimerEntity(num > 1)
then
System.out.println("規則 rule_attributes_timer_1 觸發");
end
rule "rule_attributes_timer_2"
timer(cron:0/1 * * * * ?)
when
$attributesTimerEntity:AttributesTimerEntity(num > 1)
then
System.out.println("規則 rule_attributes_timer_2 觸發");
end
第二步:撰寫單元測驗
@Test
public void test() throws InterruptedException {
KieSession kieSession = kieBase.newKieSession();
AttributesTimerEntity attributesTimerEntity = new AttributesTimerEntity();
attributesTimerEntity.setNum(20);
kieSession.insert(attributesTimerEntity);
kieSession.fireUntilHalt();
Thread.sleep(10000);
kieSession.halt();
kieSession.dispose();
}
注意:如果規則中有用到了timer屬性,匹配規則需要呼叫kieSession.fireUntilHalt();這里涉及一個規則引擎的執行模式和執行緒問題,關于具體細節,我們后續討論,
5.10 date-effective屬性
date-effective屬性用于指定規則的生效時間,即只有當前系統時間大于等于設定的時間或者日期規則才有可能觸發,默認日期格式為:dd-MMM-yyyy,用戶也可以自定義日期格式,
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.AttributesDateEffectiveEntity
/*
用于測驗Drools 屬性: date-effective
*/
rule "rule_attributes_date_effective"
// date-effective "20-七月-2021"
date-effective "2021-02-20"
when
$attributesDateEffectiveEntity:AttributesDateEffectiveEntity(num > 1)
then
System.out.println("規則 rule_attributes_date_effective 觸發");
end
第二步:撰寫單元測驗
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
AttributesDateEffectiveEntity attributesDateEffectiveEntity = new AttributesDateEffectiveEntity();
attributesDateEffectiveEntity.setNum(20);
kieSession.insert(attributesDateEffectiveEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
注意:需要在VM引數上加上日期格式:-Ddrools.dateformat=yyyy-MM-dd,在生產環境所在規則引擎的JVM設定中,也需要設定此引數,以保證開發和生產的一致性,
5.11 date-expires屬性
date-expires屬性用于指定規則的失效時間,即只有當前系統時間小于設定的時間或者日期規則才有可能觸發,默認日期格式為:dd-MMM-yyyy,用戶也可以自定義日期格式,
第一步:撰寫規則檔案/resource/rules/dateexpires.drl
package rules
import com.ityml.drools.entity.AttributesDateExpiresEntity
/*
用于測驗Drools 屬性: date-expires
*/
rule "rule_attributes_date_expires"
date-expires "2021-06-20"
when
$attributesDateExpiresEntity:AttributesDateExpiresEntity(num > 1)
then
System.out.println("規則 rule_attributes_date_expires 觸發");
end
第二步:撰寫單元測驗
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
AttributesDateExpiresEntity attributesDateExpiresEntity = new AttributesDateExpiresEntity();
attributesDateExpiresEntity.setNum(20);
kieSession.insert(attributesDateExpiresEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
注意:需要在VM引數上加上日期格式:-Ddrools.dateformat=yyyy-MM-dd,在生產環境所在規則引擎的JVM設定中,也需要設定此引數,以保證開發和生產的一致性,
6. Drools高級語法
前面章節我們已經知道了一套完整的規則檔案內容構成如下:
| 關鍵字 | 描述 |
|---|---|
| package | 包名,只限于邏輯上的管理,同一個包名下的查詢或者函式可以直接呼叫 |
| import | 用于匯入類或者靜態方法 |
| global | 全域變數 |
| function | 自定義函式 |
| query | 查詢 |
| rule end | 規則體 |
本章節我們就來學習其中的幾個關鍵字,
6.1 global全域變數
global關鍵字用于在規則檔案中定義全域變數,它可以讓應用程式的物件在規則檔案中能夠被訪問,可以用來為規則檔案提供資料或服務,
語法結構為:global 物件型別 物件名稱
在使用global定義的全域變數時有兩點需要注意:
1、如果物件型別為包裝型別時,在一個規則中改變了global的值,那么只針對當前規則有效,對其他規則中的global不會有影響,可以理解為它是當前規則代碼中的global副本,規則內部修改不會影響全域的使用,
2、如果物件型別為集合型別或JavaBean時,在一個規則中改變了global的值,對java代碼和所有規則都有效,
下面我們通過代碼進行驗證:
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.GlobalEntity
/*
用于測驗Drools 全域變數 : global
*/
global java.lang.Integer globalCount
global java.util.List globalList
rule "rule_global_1"
when
$globalEntity:GlobalEntity(num > 1)
then
System.out.println("規則 rule_global_1 開始...");
globalCount++ ;
globalList.add("張三");
globalList.add("李四");
System.out.println(globalCount);
System.out.println(globalList);
System.out.println("規則 rule_global_1 結束...");
end
rule "rule_global_2"
when
$globalEntity:GlobalEntity(num > 1)
then
System.out.println("規則 rule_global_2 開始...");
System.out.println(globalCount);
System.out.println(globalList);
System.out.println("規則 rule_global_2 結束...");
end
第二步:撰寫單元測驗
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
GlobalEntity globalEntity = new GlobalEntity();
globalEntity.setNum(20);
ArrayList<Object> globalList = new ArrayList<>();
Integer globalCount = 10;
kieSession.setGlobal("globalCount", 10);
kieSession.setGlobal("globalList", globalList);
kieSession.insert(globalEntity);
kieSession.fireAllRules();
kieSession.dispose();
System.out.println("globalCount=" + globalCount);
System.out.println("globalList=" + globalList);
}
注意:
1-后面的代碼中定義了全域變數以后,前面的test都需要加,不然會出錯,
2-屬性當中的 關于時間的屬性,如果涉及格式問題,也不要忘記,jvm啟動引數添加相關配置
6.2 query查詢
query查詢提供了一種查詢working memory中符合約束條件的Fact物件的簡單方法,它僅包含規則檔案中的LHS部分,不用指定“when”和“then”部分并且以end結束,具體語法結構如下:
query 查詢的名稱(可選引數)
LHS
end
具體操作步驟:
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.QueryEntity
/*
用于測驗Drools 方法: query
*/
//無參查詢
query "query_1"
$queryEntity:QueryEntity(age>20)
end
//有參查詢
query "query_2"(Integer qAge,String qName)
$queryEntity:QueryEntity(age > qAge && name == qName)
end
第二步:撰寫單元測驗
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
QueryEntity queryEntity1= new QueryEntity();
QueryEntity queryEntity2= new QueryEntity();
QueryEntity queryEntity3= new QueryEntity();
queryEntity1.setName("張三").setAge(10);
queryEntity2.setName("李四").setAge(20);
queryEntity3.setName("王五").setAge(30);
kieSession.insert(queryEntity1);
kieSession.insert(queryEntity2);
kieSession.insert(queryEntity3);
QueryResults results1 = kieSession.getQueryResults("query_1");
QueryResults results2 = kieSession.getQueryResults("query_2", 1, "張三");
for (QueryResultsRow queryResultsRow : results1) {
QueryEntity queryEntity = (QueryEntity) (queryResultsRow.get("$queryEntity"));
System.out.println(queryEntity);
}
for (QueryResultsRow queryResultsRow : results2) {
QueryEntity queryEntity = (QueryEntity) (queryResultsRow.get("$queryEntity"));
System.out.println(queryEntity);
}
kieSession.fireAllRules();
kieSession.dispose();
}
6.3 function函式
function關鍵字用于在規則檔案中定義函式,就相當于java類中的方法一樣,可以在規則體中呼叫定義的函式,使用函式的好處是可以將業務邏輯集中放置在一個地方,根據需要可以對函式進行修改,
函式定義的語法結構如下:
function 回傳值型別 函式名(可選引數){ //邏輯代碼}
具體操作步驟:
第一步:撰寫規則檔案/resources/rules/function.drl
package rules
import com.ityml.drools.entity.FunctionEntity
/*
用于測驗Drools 方法: function
*/
//定義一個 假發 方法
function Integer add(Integer num){
return num+10;
}
rule "function"
when
$functionEntity:FunctionEntity(num>20)
then
Integer result = add($functionEntity.getNum());
System.out.println(result);
end
第二步:撰寫單元測驗
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
FunctionEntity functionEntity = new FunctionEntity();
functionEntity.setNum(30);
kieSession.insert(functionEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
6.4 條件-LHS加強
前面我們已經知道了在規則體中的LHS部分是介于when和then之間的部分,主要用于模式匹配,只有匹配結果為true時,才會觸發RHS部分的執行,本章節我們會針對LHS部分學習幾個新的用法,
6.4.1 復合值限制in/not in
復合值限制是指超過一種匹配值的限制條件,類似于SQL陳述句中的in關鍵字,Drools規則體中的LHS部分可以使用in或者not in進行復合值的匹配,具體語法結構如下:
Object(field in (比較值1,比較值2...))
舉例:
package rules
import com.ityml.drools.entity.LhsInEntity
/*
用于測驗Drools LHS: in not in
*/
rule "lhs_in"
when
$lhsInEntity:LhsInEntity(name in ("張三","李四","王五"))
then
System.out.println("規則 lhs_in 觸發");
end
rule "lhs_not_in"
when
$lhsInEntity:LhsInEntity(name not in ("張三","李四","王五"))
then
System.out.println("規則 lhs_not_in 觸發");
end
6.4.2 條件元素eval
eval用于規則體的LHS部分,并回傳一個Boolean型別的值,語法結構如下:
eval(運算式)
舉例:
package rules
import com.ityml.drools.entity.LhsEvalEntity
/*
用于測驗Drools LHS: in not in
*/
rule "lhs_eval"
when
$lhsInEntity:LhsEvalEntity(age > 10) and eval(2>1)
then
System.out.println("規則 lhs_eval 觸發");
end
6.4.3 條件元素not
not用于判斷Working Memory中是否存在某個Fact物件,如果不存在則回傳true,如果存在則回傳false,語法結構如下:
not Object(可選屬性約束)
舉例:
package rules
import com.ityml.drools.entity.LhsNotEntity
/*
用于測驗Drools LHS: not
*/
rule "lhs_not"
when
not $lhsNotEntity:LhsNotEntity(age > 10)
then
System.out.println("規則 lhs_not 觸發");
end
6.4.4 條件元素exists
exists的作用與not相反,用于判斷Working Memory中是否存在某個Fact物件,如果存在則回傳true,不存在則回傳false,語法結構如下:
exists Object(可選屬性約束)
舉例:
package rules
import com.ityml.drools.entity.LhsEvalEntity
/*
用于測驗Drools LHS: exists
*/
rule "lhs_exists"
when
exists $lhsInEntity:LhsEvalEntity(age > 10)
then
System.out.println("規則 lhs_eval 觸發");
end
Java代碼:
package com.ityml.drools.client;
import com.ityml.drools.DroolsApplicationTests;
import com.ityml.drools.entity.LhsExistsEntity;
import com.ityml.drools.entity.LhsNotEntity;
import org.junit.jupiter.api.Test;
import org.kie.api.KieBase;
import org.kie.api.runtime.KieSession;
import javax.annotation.Resource;
/**
* @author ityml
* @date 2021-06-17 23:46
*/
public class LhsNotTest extends DroolsApplicationTests {
@Resource
public KieBase kieBase;
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
LhsNotEntity lhsNotEntity = new LhsNotEntity();
lhsNotEntity.setAge(1);
kieSession.insert(lhsNotEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
}
可能有人會有疑問,我們前面在LHS部分進行條件撰寫時并沒有使用exists也可以達到判斷Working Memory中是否存在某個符合條件的Fact元素的目的,那么我們使用exists還有什么意義?
兩者的區別:當向Working Memory中加入多個滿足條件的Fact物件時,使用了exists的規則執行一次,不使用exists的規則會執行多次,
例如:
規則檔案(只有規則體):
package rules
import com.ityml.drools.entity.LhsExistsEntity
/*
用于測驗Drools LHS: exists
*/
rule "lhs_exists_1"
when
exists $lhsExistsEntity:LhsExistsEntity(age > 10)
then
System.out.println("規則 lhs_exists_1 觸發");
end
rule "lhs_exists_2"
when
$lhsExistsEntity:LhsExistsEntity(age > 10)
then
System.out.println("規則 lhs_exists_2 觸發");
end
Java代碼:
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
LhsExistsEntity lhsExistsEntity = new LhsExistsEntity();
lhsExistsEntity.setAge(30);
LhsExistsEntity lhsExistsEntity2 = new LhsExistsEntity();
lhsExistsEntity2.setAge(30);
kieSession.insert(lhsExistsEntity);
kieSession.insert(lhsExistsEntity2);
kieSession.fireAllRules();
kieSession.dispose();
}
上面第一個規則只會執行一次,因為Working Memory中存在兩個滿足條件的Fact物件,第二個規則會執行兩次,
6.4.5 規則繼承
規則之間可以使用extends關鍵字進行規則條件部分的繼承,類似于java類之間的繼承,
例如:
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
LhsExistsEntity lhsExistsEntity = new LhsExistsEntity();
lhsExistsEntity.setAge(30);
LhsExistsEntity lhsExistsEntity2 = new LhsExistsEntity();
lhsExistsEntity2.setAge(30);
kieSession.insert(lhsExistsEntity);
kieSession.insert(lhsExistsEntity2);
kieSession.fireAllRules();
kieSession.dispose();
}
6.5 結果-RHS
規則檔案的RHS部分的主要作用是通過插入,洗掉或修改作業記憶體中的Fact資料,來達到控制規則引擎執行的目的,Drools提供了一些方法可以用來操作作業記憶體中的資料,操作完成后規則引擎會重新進行相關規則的匹配,原來沒有匹配成功的規則在我們修改資料完成后有可能就會匹配成功了,
6.5.1 insert方法
insert方法的作用是向作業記憶體中插入資料,并讓相關的規則重新匹配,
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.RhsInsertEntity
/*
用于測驗Drools RHS: insert
*/
rule "rhs_insert_1"
when
$rhsInsertEntity:RhsInsertEntity(age <= 10)
then
RhsInsertEntity rhsInsertEntity = new RhsInsertEntity();
rhsInsertEntity.setAge(15);
insert(rhsInsertEntity);
System.out.println("規則 rhs_insert_1 觸發");
end
rule "rhs_insert_2"
when
$rhsInsertEntity:RhsInsertEntity(age <=20 && age>10)
then
RhsInsertEntity rhsInsertEntity = new RhsInsertEntity();
rhsInsertEntity.setAge(25);
insert(rhsInsertEntity);
System.out.println("規則 rhs_insert_2 觸發");
end
rule "rhs_insert_3"
when
$rhsInsertEntity:RhsInsertEntity(age > 20 )
then
System.out.println("規則 rhs_insert_3 觸發");
end
第二步:撰寫單元測驗
public void test(){
KieSession kieSession = kieBase.newKieSession();
RhsInsertEntity rhsInsertEntity = new RhsInsertEntity();
rhsInsertEntity.setAge(5);
kieSession.insert(rhsInsertEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
通過控制臺輸出可以發現,3個規則都觸發了,這是因為首先進行規則匹配時只有第一個規則可以匹配成功,但是在第一個規則中向作業記憶體中插入了一個資料導致重新進行規則匹配,此時第二個規則可以匹配成功,在第二個規則中同樣向作業記憶體中插入了一個資料導致重新進行規則匹配,那么第三個規則就出發了,
6.5.2 update方法
update方法的作用是更新作業記憶體中的資料,并讓相關的規則重新匹配, (要避免死回圈)
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.RhsUpdateEntity
/*
用于測驗Drools RHS: update
*/
rule "rhs_update_1"
when
$rhsUpdateEntity:RhsUpdateEntity(age <= 10)
then
$rhsUpdateEntity.setAge(15);
update($rhsUpdateEntity);
System.out.println("規則 rhs_update_1 觸發");
end
rule "rhs_update_2"
when
$rhsUpdateEntity:RhsUpdateEntity(age <=20 && age>10)
then
$rhsUpdateEntity.setAge(25);
update($rhsUpdateEntity);
System.out.println("規則 rhs_update_2 觸發");
end
rule "rhs_update_3"
when
$rhsUpdateEntity:RhsUpdateEntity(age > 20 )
then
System.out.println("規則 rhs_update_3 觸發");
end
第二步:撰寫單元測驗
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
RhsUpdateEntity rhsUpdateEntity = new RhsUpdateEntity();
rhsUpdateEntity.setAge(5);
kieSession.insert(rhsUpdateEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
通過控制臺的輸出可以看到規則檔案中定義的三個規則都觸發了,
在更新資料時需要注意防止發生死回圈,
6.5.3 modify方法
modify方法的作用跟update一樣,是更新作業記憶體中的資料,并讓相關的規則重新匹配,只不過語法略有區別 (要避免死回圈)
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.RhsModifyEntity
/*
用于測驗Drools RHS: modify
*/
rule "rhs_modify_1"
when
$rhsModifyEntity:RhsModifyEntity(age <= 10)
then
modify($rhsModifyEntity){
setAge(15)
}
System.out.println("規則 rhs_modify_1 觸發");
end
rule "rhs_modify_2"
when
$rhsModifyEntity:RhsModifyEntity(age <=20 && age>10)
then
modify($rhsModifyEntity){
setAge(25)
}
System.out.println("規則 rhs_modify_2 觸發");
end
rule "rhs_modify_3"
when
$rhsModifyEntity:RhsModifyEntity(age > 20 )
then
System.out.println("規則 rhs_modify_3 觸發");
end
第二步:撰寫單元測驗
@Test
public void test(){
KieSession kieSession = kieBase.newKieSession();
RhsModifyEntity rhsModifyEntity = new RhsModifyEntity();
rhsModifyEntity.setAge(5);
kieSession.insert(rhsModifyEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
通過控制臺的輸出可以看到規則檔案中定義的三個規則都觸發了,
在更新資料時需要注意防止發生死回圈,
6.5.4 retract/delete方法
retract方法的作用是洗掉作業記憶體中的資料,并讓相關的規則重新匹配,
第一步:撰寫規則檔案
package rules
import com.ityml.drools.entity.RhsRetractEntity
/*
用于測驗Drools RHS: retract
*/
rule "rhs_retract_1"
when
$rhsRetractEntity:RhsRetractEntity(age <= 10)
then
// retract($rhsRetractEntity);
System.out.println("規則 rhs_retract_1 觸發");
end
rule "rhs_retract_2"
when
$rhsRetractEntity:RhsRetractEntity(age <= 10)
then
System.out.println("規則 rhs_retract_2 觸發");
end
第二步:撰寫單元測驗
public void test(){
KieSession kieSession = kieBase.newKieSession();
RhsRetractEntity rhsRetractEntity = new RhsRetractEntity();
rhsRetractEntity.setAge(5);
kieSession.insert(rhsRetractEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
通過控制臺輸出可以發現,只有第一個規則觸發了,因為在第一個規則中將作業記憶體中的資料洗掉了導致第二個規則并沒有匹配成功,
6.6 RHS加強
RHS部分是規則體的重要組成部分,當LHS部分的條件匹配成功后,對應的RHS部分就會觸發執行,一般在RHS部分中需要進行業務處理,
在RHS部分Drools為我們提供了一個內置物件,名稱就是drools,本小節我們來介紹幾個drools物件提供的方法,
6.5.1 halt
halt方法的作用是立即終止后面所有規則的執行,
package rules
import com.ityml.drools.entity.RhsHaftEntity
/*
用于測驗Drools RHS: haft
*/
rule "rhs_haft_1"
when
$rhsHaftEntity:RhsHaftEntity(age <= 10)
then
drools.halt();
System.out.println("規則 rhs_haft_1 觸發");
end
rule "rhs_haft_2"
when
$rhsHaftEntity:RhsHaftEntity(age <= 20)
then
System.out.println("規則 rhs_haft_2 觸發");
end
6.5.2 getWorkingMemory
getWorkingMemory方法的作用是回傳作業記憶體物件,
rule "rhs_get_working_memory_1"
when
$rhsDroolsMethodsEntity:RhsDroolsMethodsEntity(age <= 10)
then
System.out.println(drools.getWorkingMemory());
System.out.println("規則 rhs_get_working_memory_1 觸發");
end
6.5.3 getRule
getRule方法的作用是回傳規則物件,
rule "rhs_rule_2"
when
$rhsDroolsMethodsEntity:RhsDroolsMethodsEntity(age <=20)
then
System.out.println(drools.getRule());
System.out.println("規則 rhs_rule_2 觸發");
end
6.6 規則檔案編碼規范(重要)
我們在進行drl型別的規則檔案撰寫時盡量遵循如下規范:
- 所有的規則檔案(.drl)應統一放在一個規定的檔案夾中,如:/rules檔案夾
- 書寫的每個規則應盡量加上注釋,注釋要清晰明了,言簡意賅
- 同一型別的物件盡量放在一個規則檔案中,如所有Student型別的物件盡量放在一個規則檔案中
- 規則結果部分(RHS)盡量不要有條件陳述句,如if(...),盡量不要有復雜的邏輯和深層次的嵌套陳述句
- 每個規則最好都加上salience屬性,明確執行順序
- Drools默認dialect為"Java",盡量避免使用dialect "mvel"
7. WorkBench
7.1 WorkBench簡介
WorkBench是KIE組件中的元素,也稱為KIE-WB,是Drools-WB與JBPM-WB的結合體,它是一個可視化的規則編輯器,WorkBench其實就是一個war包,
WorkBench經過幾次版本迭代,已經不提供tomcat啟動的war包,綜合考慮,本課程仍然采用 tomcat版本作為演示,
環境:
- apache-tomcat-9.0.29
- kie-drools-wb-7.6.0.Final-tomcat8 下載地址:Drools - Download
說明:
準備jar包:需要放到tomcat lib中,否則啟動失敗
具體安裝步驟:
7.1.1 配置 Tomcat
1.修改tomcat-user.xml,添加用戶
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<!--定義admin角色-->
<role rolename="admin"/>
<!--定義一個用戶,用戶名為kie,密碼為kie,對應的角色為admin角色-->
<user username="kie-web" password="kie-web123" roles="admin"/>
<user username="admin" password="admin" roles="manager-gui,manager-script,manager-jmx,manager-status"/>
</tomcat-users>
此賬號密碼用于登錄WorkBench管理控制臺
2.修改server.xml
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
<Valve className="org.kie.integration.tomcat.JACCValve"/>
</Host>
host節點下添加
3.復制jar到tomcat根目錄的lib下面:
kie-tomcat-integration-7.10.0.Final.jar
javax.security.jacc-api-1.5.jar
slf4j-api-1.7.25.jar
4.復制 kie-drools-wb-7.6.0.Final-tomcat8.war 到tomcat webapp下面并修改成kie-web.war
7.1.2啟動服務器
啟動tomcat
訪問http://localhost:8080/kie-web,可以看到WorkBench的登錄頁面,使用前面創建的kie-web/kie-web123登錄

登錄成功后進入系統首頁:

7.2WorkBench使用
7.2.1創建空間、專案
首頁中點擊 project,創建空間



我們創建一個 ityml 的作業空間,點擊 Save,保存,

點擊作業空間當中的 ityml,進入空間

點擊Add Project添加專案

成功后,我們可以看見下圖

左上角的導航條,可以在空間和project之間切換
7.2.2創建資料物件和drl檔案
切換到pro1專案內,點擊 Create New Assert

選中資料物件:

輸入Order,點擊確定,成功后跳轉如下頁面

Order相當于我們代碼中的物體類,在左側 Project Explorer視圖中,可以看見專案結構
接下來添加欄位,點擊添加欄位按鈕:

ID 位置,輸入java bean的欄位,標簽是備注資訊,型別選擇對應的欄位型別,保存,點擊創建,關閉彈窗,點擊創建并繼續,可以繼續創建,

點擊右上角的保存,至此,一個資料物件我們就創建完成,可以在源代碼中查看代碼內容,
接下來我們創建一個drl檔案,創建程序跟創建bean類似,drl檔案內容如下
package com.ityml.pro1;
rule "rule_1"
when
$order:Order(age > 10)
then
System.out.print("rule run...");
end
保存之后,點擊導航潭訓到專案主頁
7.2.3 設定KieBase+KieSession
專案首頁點擊Settings

選擇知識庫跟會話


彈出視窗,輸入Kiebase名稱即可,我們以kb1為例

同理,我們補充完軟體包資訊,添加只是會話,即kiesession

操作完成后,不要忘記保存,此時,我們可在Project Explorer視圖中,resource/META-INF/kmodule.xml中看見如下資訊
<kmodule xmlns="http://www.drools.org/xsd/kmodule" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<kbase name="kb1" default="false" eventProcessingMode="stream" equalsBehavior="identity" packages="com.ityml.pro1">
<ksession name="ks1" type="stateful" default="true" clockType="realtime"/>
</kbase>
</kmodule>
導航回到專案首頁,進行編譯發布

發布成功后,我們可以在maven倉庫中看到對應的jar

也可以訪問:http://localhost:8080/kie-web/maven2/com/ityml/pro1/1.0.0/pro1-1.0.0.jar 驗證是否發布成功
7.2.4 代碼使用
@Test
public void test() throws Exception{
//通過此URL可以訪問到maven倉庫中的jar包
//URL地址構成:http://ip地址:Tomcat埠號/WorkBench工程名/maven2/坐標/版本號/xxx.jar
String url = "http://localhost:8080/kie-web/maven2/com/ityml/pro1/1.0.0/pro1-1.0.0.jar";
KieServices kieServices = KieServices.Factory.get();
UrlResource resource = (UrlResource) kieServices.getResources().newUrlResource(url);
//認證
resource.setUsername("kie-web");
resource.setPassword("kie-web123");
resource.setBasicAuthentication("enabled");
KieRepository repository = kieServices.getRepository();
//通過輸入流讀取maven倉庫中的jar包資料,包裝成KieModule模塊添加到倉庫中
KieModule kieModule = repository.addKieModule(kieServices.getResources().newInputStreamResource(resource.getInputStream()));
KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
KieSession session = kieContainer.newKieSession();
Order order = new Order();
order.setName("張三");
order.setAge(30);
session.insert(order);
session.fireAllRules();
session.dispose();
}
我們用URL流的方式,獲取jar資源,并構造kiesession物件,即可動態訪問workbench中的規則
8 其他
8.1 有狀態session和無狀態session
無狀態session
無狀態的KIE會話是一個不使用推理來對事實進行反復修改的會話,在無狀態的KIE會話中,來自KIE會話先前呼叫的資料(先前的會話狀態)在會話呼叫之間被丟棄,而在有狀態的KIE會話中,這些資料被保留,一個無狀態的KIE會話的行為類似于一個函式,因為它產生的結果是由KIE基礎的內容和被傳入KIE會話以在特定時間點執行的資料決定的,KIE會話對以前傳入KIE會話的任何資料都沒有記憶,
使用方法類似如下代碼:
@Test
public void testStatelessSession() {
StatelessKieSession statelessKieSession = kieBase.newStatelessKieSession();
List<Command> cmds = new ArrayList<>();
KieSessionEntity kieSessionEntity = new KieSessionEntity();
kieSessionEntity.setNum(10);
kieSessionEntity.setValid(false);
cmds.add(CommandFactory.newInsert(kieSessionEntity, "kieSessionEntity"));
statelessKieSession.execute(CommandFactory.newBatchExecution(cmds));
System.out.println(kieSessionEntity);
}
簡單說來,無狀態session執行的時候,不需要呼叫 fireAllRules(),也不需要執行dispose(),代碼執行完execute之后,即銷毀所有的資料,
使用場景:比如上述的校驗num
驗證資料: 比如計算積分,按揭房貸等
路有訊息:比如對郵件排序,發送郵件等,行為類的場景
有狀態session
有狀態的KIE會話是一個使用推理來對事實進行反復修改的會話,在有狀態的KIE會話中,來自KIE會話先前呼叫的資料(先前的會話狀態)在會話呼叫之間被保留,而在無狀態的KIE會話中,這些資料被丟棄了,
對比無狀態session,有狀態session呼叫fireAllRules()的時候采取匹配規則,就會執行規則匹配,除非遇見dispose()
示例:
資料模型
public class Room {
private String name;
// Getter and setter methods
}
public class Sprinkler {
private Room room;
private boolean on;
// Getter and setter methods
}
public class Fire {
private Room room;
// Getter and setter methods
}
public class Alarm { }
規則檔案
rule "When there is a fire turn on the sprinkler"
when
Fire($room : room)
$sprinkler : Sprinkler(room == $room, on == false)
then
modify($sprinkler) { setOn(true) };
System.out.println("Turn on the sprinkler for room "+$room.getName());
end
rule "Raise the alarm when we have one or more fires"
when
exists Fire()
then
insert( new Alarm() );
System.out.println( "Raise the alarm" );
end
rule "Cancel the alarm when all the fires have gone"
when
not Fire()
$alarm : Alarm()
then
delete( $alarm );
System.out.println( "Cancel the alarm" );
end
rule "Status output when things are ok"
when
not Alarm()
not Sprinkler( on == true )
then
System.out.println( "Everything is ok" );
end
代碼
KieSession ksession = kContainer.newKieSession();
String[] names = new String[]{"kitchen", "bedroom", "office", "livingroom"};
Map<String,Room> name2room = new HashMap<String,Room>();
for( String name: names ){
Room room = new Room( name );
name2room.put( name, room );
ksession.insert( room );
Sprinkler sprinkler = new Sprinkler( room );
ksession.insert( sprinkler );
}
ksession.fireAllRules();
輸出
Console output
> Everything is ok
此時還可以繼續輸入
Fire kitchenFire = new Fire( name2room.get( "kitchen" ) );
Fire officeFire = new Fire( name2room.get( "office" ) );
FactHandle kitchenFireHandle = ksession.insert( kitchenFire );
FactHandle officeFireHandle = ksession.insert( officeFire );
ksession.fireAllRules();
Console output
> Raise the alarm
> Turn on the sprinkler for room kitchen
> Turn on the sprinkler for room office
繼續輸入
ksession.delete( kitchenFireHandle );
ksession.delete( officeFireHandle );
ksession.fireAllRules();
輸出
Console output
> Cancel the alarm
> Turn off the sprinkler for room office
> Turn off the sprinkler for room kitchen
> Everything is ok
使用場景:
-
監測,如監測股票市場并使購買程序自動化
-
診斷,如運行故障查找程序或醫療診斷程序
-
物流,如包裹跟蹤和配送供應
-
確保合規性,如驗證市場交易的合法性
參考檔案:
【1】 百度百科:規則引擎 :https://baike.baidu.com/item/規則引擎/3076955?fr=aladdin
【2】 開源規則引擎 drools:https://blog.csdn.net/sdmxdzb/article/details/81461744
【3】 drools官網:Drools - Business Rules Management System (Java?, Open Source)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/440984.html
標籤:Java
上一篇:面試官:Redis中哈希資料型別的內部實作方式是什么?
下一篇:如何解釋uops.info?
