1. 程式人生 > >規則引擎-----Drools入門系列

規則引擎-----Drools入門系列

  • Drools入門系列(一)HelloWorld
  • Drools入門系列(二)HelloWorld詳解之Sample.drl
  • Drools入門系列(三)HelloWorld詳解之kmodule.xml
  • Drools入門系列(四)HelloWorld詳解之JUnit Test類
  • Drools入門系列(五)KIE概論
  • Drools入門系列(六)KIE之基礎API詳解
  • Drools入門系列(七)KIE之kmodule.xml
  • Drools入門系列(八)以編碼方式完成kmodule的定義

Drools入門系列(一)HelloWorld

1、什麼是Drools

Drools是用Java語言編寫的開放原始碼的規則引擎。

那什麼是規則引擎呢?參考自 百度百科 裡面的定義:

規則引擎由推理引擎發展而來,是一種嵌入在應用程式中的元件,實現了將業務決策從應用程式程式碼中分離出來,並使用預定義的語義模組編寫業務決策。接受資料輸入,解釋業務規則,並根據業務規則做出業務決策。

Drools使用RETE演算法對規則進行求值,在Drools6.0(當前最新版本)中還引進了PHREAK演算法,Drools 允許使用宣告方式表達業務邏輯。可以使用非 XML 的本地語言編寫規則,從而便於學習和理解。並且,還可以將 Java 程式碼直接嵌入到規則檔案中,這令 Drools 的學習更加吸引人。Drools 還具有其他優點:

  • 非常活躍的社群支援
  • 易用
  • 快速的執行速度
  • 在 Java 開發人員中流行
  • 與 Java Rule Engine API(JSR 94)相容

2、一些說明

目前本人正在學習Drools過程中,準備編寫一個系列文章,來記錄自己的學習過程和學習心得。由於本人目前也是新手,因而文章中不可避免有著一些錯誤的理解,這個只有希望讀者自己來判斷了。

本系列文章基於當前最新版本Drools 6.0.1 Final版本。

在學習Drools的過程中,我也會編寫一些學習的例子,這些例子都放在GitHub上:

https://github.com/XiongZhijun/nut-drools.git

然後我會通過一些tag來標記一些例子,讀者可以自己checkout相應的tag來檢視例子,一般這些tag都會有相應的註釋的。

Git入門學習可以參考:Git入門資料

3、Hello World

從GitHub上下載nut-drools工程(使用上節裡面的地址),checkout training_1標籤,就可以看到HelloWorld的例子。工程結構如下: 
工程結構圖

drools-helloworld-project 
這是一個典型的Maven工程,包含pom.xml檔案,有src/main/java、src/main/resources,以及相應的測試目錄。其中:

src/main/resources/META-INF :該目錄中存放了一個kmodule.xml檔案,該檔案中聲明瞭若干已經定義了的規則、流程檔案。 
src/main/resources :該目錄的子目錄dtables和rules中存放了定義了規則的規則檔案,本例中包含了兩種定義規則的方式,一種是通過DRL(字尾.drl)檔案來定義的,一種是通過Excel檔案(字尾.xls)來定義的。 
src/test/java :該目錄中定義了單元測試用例,就是直接測試執行規則的。 
現在可以直接執行單元測試,檢視測試結果了。

4、Drools and Eclipse

Drools提供了Eclipse外掛,可以在 http://www.jboss.org/drools/downloads 頁面進行下載“Drools and jBPM Tools”,在這個下載包裡面就包含有Eclipse外掛。

裝好外掛後可以使用Drools透檢視,然後就可以直接建立Drools Project了: 
1 
new-drools-project-1 
2 
new-drools-project-2 
3 
new-drools-project-3 
在這一步可以選擇一些例子,這樣在工程建立好之後就會有相應的例子程式了,上面提供的HelloWorld就是這個裡面的自動建立的例子。 
4 
new-drools-project-4 
輸入好GroupId、AtifactId、Version後點擊Finish就可以建立好一個工程了。

大家可以注意到這個工程也是一個Maven結構的工程,除了沒有pom.xml之外。不知道沒有pom.xml檔案這個是Drools有自己的考慮之外呢,還是這是一個bug。

PS:drools 6.5 的時候,是可以使用Eclipse外掛進行建立maven工程了。
  •  

Drools入門系列(二)——HelloWorld詳解之Sample.drl

我們來先看一下一個標準的規則檔案定義檔案是怎麼樣的:

package os.nut.drools

import os.nut.drools.Message;

rule "Hello World"
    when
        m : Message( status == Message.HELLO, myMessage : message )
    then
        System.out.println( myMessage );
        m.setMessage( "Goodbye cruel world" );
        m.setStatus( Message.GOODBYE );
        update( m );
end

rule "GoodBye"
    when
        Message( status == Message.GOODBYE, myMessage : message )
    then
        System.out.println( myMessage );
end

Java程式設計師可以很清楚看出來,這個規則檔案跟一個Java檔案非常類似,裡面包含了很多Java語句,不用懷疑,這些就是Java程式碼,而不是類Java語法的程式碼。

上面這個規則檔案裡面包含4個部分:package、import和兩個rule:

  • package:package語句的定義跟Java裡面的package語句類似,唯一不同的就是在DRL檔案中package後面跟的包名是不需要跟檔案目錄有對應關係的,上例就可以看出來這個不同:package定義是os.nut.drools,而所在的目錄是rules。

  • import:import語句的含義跟java中是一樣的,就是如果在本檔案中需要使用某些類的話,需要通過import語句引入進來,如果需要的類在 package定義的包中就不需要再引入了,這個跟Java的概念是一致的。在上例中的import語句其實是沒有必要的。

  • rule:上例定義了兩個rule,也就是定義了兩個規則。一個規則以rule關鍵字開始,以end關鍵字結束。上面一個rule包含了三個部分,分別是name、when、then。

  • name緊跟在rule關鍵字之後,可以是一個以引號(雙引號、單引號均可)包含的字串,可以包含空格等字元,如果字串只包含字母、數字、下劃線(也就是Java變數命名規則)的話,也可以不用引號,但是推薦使用引號。

  • when語句的意思就是執行下面then語句的條件,也就是說當when條件滿足的情況下,才會執行then,類似於Java裡面的if語句,為什麼用when而不用if呢,這是因為when代表的意思是當什麼“ 事件 ”發生時,當什麼“ 事實 ”存在時,然後執行then。

  • then語句就是執行的動作,就是當什麼事件發生,或者什麼事實存在時,執行的動作序列。then後面的語句也就是Action。

  • 事件和事實是什麼東西呢?在Drools裡面這兩個分別稱之為“Event”和“Fact”,“事件”其實也是“事實”,只不過是一種特殊的事實而已。

  • 事實是什麼東西呢?一個事實其實就是一個POJO,只不過這個Java物件是存放在一個特殊的空間裡面,這個空間就是“Working Memory”,所有存放在Working Memory裡面的物件都是事實(Fact)。when語句就是檢查在Working Memory裡面是不是存在滿足條件的事實。

  • 事件呢?怎麼個特殊法?這個我們可以暫時不用管它,到後面學習CEP的時候自然就會理解了。CEP是什麼?現在不用管它! 
    when語句解讀:

m : Message( status == Message.HELLO, myMessage : message )

上面是rule “Hello World”的when語句。這個語句是什麼意思呢?它的意思就是:

當存在一個Message物件,並且這個Message的status欄位值為Message.HELLO的時候,就可以執行下面的then語句了。用自然語言描述就是:當存在一個狀態為HELLO的訊息的事實時,就執行下面的動作,否則就不做。

其中Message()就是執行型別匹配,意思就是要求Working Memory中存在型別為Message的物件(事實),然後status==Message.HELLO語句呢,就是約束條件,表示該Message物件的status欄位為HELLO才符合條件。

另外的m和myMessage分別表示什麼呢?m加冒號的意思是將這個Message物件賦值給m,而myMessage加冒號表示將這個Message物件的message欄位的值賦值給myMessage變數。然後在下面的then語句中使用這些定義的變量了。 
then語句解讀:

System.out.println( myMessage );
m.setMessage( "Goodbye cruel world" );
m.setStatus( Message.GOODBYE );
update( m );

這個例子裡面前三句都是普通的Java語句,唯一不同的就是下面這個update語句,這個語句的意思就是通知規則引擎m物件發生變化了,m是什麼?m就是一個存放在Working Memory裡面的一個Message事實,這句話就是說m這個事實發生了變化,那麼規則引擎就需要重新進行規則運算,在本例中就是會在執行了update之後執行下面的“GoodBye”規則。

為什麼執行“GoodBye”規則?GoodBye規則需要匹配的是status為GOODBYE的Message事實,但是一開始並沒有這樣的事實存在,只有當“Hello World”規則執行到了update語句的時候,更新了Message事實,這個時候規則引擎重新運算規則,WorkingMemory中就存在status為GOODBYE的Message事實了,“GoodBye”規則就會運行了,這個從控制檯輸出中就可以看出來了。


Drools入門系列(三)——HelloWorld詳解之kmodule.xml

kmodule.xml檔案存放在src/main/resources/META-INF/資料夾下。

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
    <kbase name="rules" packages="rules">
        <ksession name="ksession-rules"/>
    </kbase>
    <kbase name="dtables" packages="dtables">
        <ksession name="ksession-dtables"/>
    </kbase>
</kmodule>

這個kmodule.xml的檔案的定義非常簡單,其實也很容易理解:

  • 一個kmodule裡面包含了兩個kbase,這個也是我們這個例子裡面的兩個用例,分別對應drl規則檔案的例子,一個是對應Excel表格的規則例子。

  • 每一個kbase都有一個name,可以取任意字串,但是不能重名。

  • 然後都有一個packages,可以看到packages裡面的字串其實就是src/main/resources下面的資料夾的名稱,或者叫包名,規則引擎會根據這裡定義的包來查詢規則定義檔案。可以同時定義多個包,以逗號分隔開來就行。

  • 每一個kbase下面可以包含多個ksession,當然本例中都自定義了一個。

  • 每一個ksession都有一個name,名字也可以是任意字串,但是也不能重複。

  • kbase和ksession裡面的name屬性是全域性不能重複的。

  • kbase和ksession中其實還有很多其它的屬性,但是在這裡不是很重要,就先不提了,後面我們會一一講解的。

這樣一個kmodule.xml檔案就建立好了。

看完這個大家肯定都會有疑問:kmodule、kbase、ksession是什麼東東?有什麼含義嗎?在接下來的章節中會一一解答的,這裡大家知道這樣的東西,然後依葫蘆畫瓢就可以了,在初學過程中這樣也就夠了。


Drools入門系列(四)——HelloWorld詳解之JUnit Test類

測試類 
在本例中,有兩個測試類,分別是DroolsTest和DecisionTableTest,分別對應DRL規則檔案的測試和Excel表格規則的測試。兩者的結果是一樣的。基於Excel的方式我們先不管它,後面我會開闢專門的章節來講述。 
DroolsBaseTest.java

public abstract class DroolsBaseTest {

    protected KieServices kieServices;
    protected KieContainer kieContainer;

    @Before
    public void setUp() {
        kieServices = KieServices.Factory.get();
        kieContainer = kieServices.getKieClasspathContainer();
    }

}

這是一個抽象類,就是將一些單元測試的公共的程式碼抽取到了本類中,在這裡定義了兩個物件kieServices和kieContainer,這個是我們執行規則時必備的兩個物件,這兩個物件的具體意義,我們後面再討論,這裡只需要知道他們是我們執行規則必備的物件就可以了。

DroolsTest.java

public class DroolsTest extends DroolsBaseTest {

    @Test
    public void test() {
        KieSession kSession = kieContainer.newKieSession("ksession-rules");
        Message message = new Message();
        message.setMessage("Hello World");
        message.setStatus(Message.HELLO);
        kSession.insert(message);
        kSession.fireAllRules();
    }
}

這個單元測試類繼承自DroolsBaseTest,演示了一個規則執行的例子。

  • 利用kieContainer物件建立一個新的KieSession,建立session的時候我們傳入了一個name:“ksession-rules”,這個字串很眼熟吧,這個就是我們定義的kmodule.xml檔案中定義的ksession的name。

  • KieSession就是一個到規則引擎的連結,通過它就可以跟規則引擎通訊,並且發起執行規則的操作。

  • 然後通過kSession.fireAllRules方法來通知規則引擎執行規則。

這樣一個完整的Drools例子就完成了,包含了規則定義(DRL檔案編寫)、模組定義(kmodule.xml編寫)、執行程式碼編寫三個過程。


Drools入門系列(五)——KIE概論

1、引言

在上一章節我們用到了幾個類和他們的物件:KieServices、KieContainer、KieSession,新入門的人肯定很困惑,這幾個類都是幹啥的,都有什麼作用啊?然後再kmodule.xml配置檔案裡面配置了kbase、ksession,這些東西都是什麼玩意?本章以及後面可能的幾章就是要解決這些問題。

2、什麼是KIE?

KIE是jBoss裡面一些相關專案的統稱,下圖就是KIE代表的一些專案,其中我們比較熟悉的就有jBPM和Drools。

這些專案都有一定的關聯關係,並且存在一些通用的API,比如說涉及到構建(building)、部署(deploying)和載入(loading)等方面的,這些API就都會以KIE作為字首來表示這些是通用的API。前面看到的一些KieServices、KieContainer、KieSession類就都是KIE的公共API。

總的來說,就是jBoss通過KIE將jBPM和Drools等相關專案進行了一個整合,統一了他們的使用方式。像KieServices這些KIE類就是整合後的結果,在Drools中這樣使用,在jBPM裡面也是這樣使用。 
KIE內部結構圖

3、KIE專案生命週期

一個Drools應用專案其實就是一個KIE專案,KIE的生命週期其實就是Drools和jBPM這些專案的生命週期。

KIE專案生命週期包含:編寫(Author)、構建(Build)、測試(Test)、部署(Deploy)、使用(Utilize)、執行(Run)、互動(Work)、管理(Manage)。

編寫:編寫就是編寫規則檔案或者流程檔案; 
構建:就是構建一個可以釋出部署的元件,在KIE中就是構建一個jar檔案; 
測試:在部署到應用程式之前需要對規則或者流程進行測試; 
部署:就是將jar部署到應用程式,KIE利用Maven倉庫來進行釋出和部署; 
使用:就是載入jar檔案,並通過KieContainer對jar檔案進行解析,然後建立KieSession; 
執行:系統通過KieSession物件的API跟Drools引擎進行互動,執行規則或者流程; 
互動:使用者通過命令列或者UI跟引擎進行互動; 
管理:管理KieSession或者KieContainer物件。

4、KIE & Maven

通過前面的知識我們瞭解到Drools工程其實就是一個Maven工程,有著Maven工程標準的結構,然後Drools在這個基礎上也定義了一個自己的儲存結構: 
Maven專案結構 
drools的標準儲存結構就是在src/main/resources資料夾下面儲存規則檔案(包括DRL檔案和Excel檔案),然後在META-INF資料夾下面建立一個kmodule.xml檔案用來儲存規則定義宣告。

Drools專案最終都是打包成jar然後進行釋出部署的(KIE專案生命週期提到的),這樣定義工程結構和打包釋出方式的根本原因就是——Maven! 
maven 
上圖描述了KIE專案(包括Drools)的打包、釋出、部署過程,就是一個KIE專案按照上面定義的工程結構進行設計開發,然後通過mvn deploy命令釋出到Maven倉庫,然後應用程式可以通過mvn install將釋出好的jar包下載安裝到本地應用程式中,最後通過KieServices等API就可以直接使用這些釋出好的規則了。

為什麼我們寫的JUnit Test類裡面驅動一個規則的程式碼非常簡單,就是因為Drools定義了上面的一套規範,按照規範來編寫、釋出、部署規則之後就可以確保以最簡單的方式來使用Drools等KIE專案。這也是慣例優於配置的一種體現。

所以我們說一個Drools專案工程就是一個Maven專案工程,或者說一個KIE專案工程就是一個Maven工程。

KIE也提供了一種策略,能夠讓應用程式在執行時,能夠動態監測Maven倉庫中Drools專案jar元件的版本更新情況,然後可以根據配置動態更新Drools釋出包,實現熱插拔功能,這個是通過KieScanner API實現的。


Drools入門系列(六)——KIE之基礎API詳解

在有些術語使用的時候,我有時候會用KIE專案、KIE引擎或者Drools專案、Drools引擎,大家應該理解KIE是Drools等專案的一個統稱,所以在大多數情況下KIE或者特指Drools都是差不多的。

現在我們開始瞭解KIE的相關API,在這個helloworld例子中,我們接觸過如下這些類和介面: 
基本類和介面 
我們通過KieServices物件得到一個KieContainer,然後KieContainer根據session name來新建一個KieSession,最後通過KieSession來執行規則。

KieServices:

該介面提供了很多方法,可以通過這些方法訪問KIE關於構建和執行的相關物件,比如說可以獲取KieContainer,利用KieContainer來訪問KBase和KSession等資訊;可以獲取KieRepository物件,利用KieRepository來管理KieModule等。

KieServices就是一箇中心,通過它來獲取的各種物件來完成規則構建、管理和執行等操作。

KieContainer:

可以理解KieContainer就是一個KieBase的容器,KieBase是什麼呢?

KieBase:

KieBase就是一個知識倉庫,包含了若干的規則、流程、方法等,在Drools中主要就是規則和方法,KieBase本身並不包含執行時的資料之類的,如果需要執行規則KieBase中的規則的話,就需要根據KieBase建立KieSession。 
KieSession:

KieSession就是一個跟Drools引擎打交道的會話,其基於KieBase建立,它會包含執行時資料,包含“事實 Fact”,並對執行時資料事實進行規則運算。我們通過KieContainer建立KieSession是一種較為方便的做法,其實他本質上是從KieBase中創建出來。的。

KieSession就是應用程式跟規則引擎進行互動的會話通道。

建立KieBase是一個成本非常高的事情,KieBase會建立知識(規則、流程)倉庫,而建立KieSession則是一個成本非常低的事情,所以KieBase會建立快取,而KieSession則不必。

較為完善的類關係如下: 
詳細關係 
KieRepository:

KieRepository是一個單例物件,它是一個存放KieModule的倉庫,KieModule由kmodule.xml檔案定義(當然不僅僅只是用它來定義)。

KieProject:

KieContainer通過KieProject來初始化、構造KieModule,並將KieModule存放到KieRepository中,然後KieContainer可以通過KieProject來查詢KieModule定義的資訊,並根據這些資訊構造KieBase和KieSession。

ClasspathKieProject:

ClasspathKieProject實現了KieProject介面,它提供了根據類路徑中的META-INF/kmodule.xml檔案構造KieModule的能力,也就是我們能夠基於Maven構造Drools元件的基本保障之一。

意味著只要我們按照前面提到過的Maven工程結構組織我們的規則檔案或流程檔案,我們就能夠只用很少的程式碼完成模型的載入和構建。 
現在我們知道了可以通過ClasspathKieProject來解析kmodule.xml檔案來構建KieModule,那麼整個過程是如何進行的呢?kmodule.xml裡面的kbase、ksession和KieBase和KieSession又是什麼關係呢?下一章節我們繼續。


Drools入門系列(七)——KIE之kmodule.xml

一個標準的kmodule.xml檔案:

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
    <kbase name="rules" packages="rules">
        <ksession name="ksession-rules"/>
    </kbase>
    <kbase name="dtables" packages="dtables">
        <ksession name="ksession-dtables"/>
    </kbase>
</kmodule>

上一章節我們提到了ClasspathKieProject根據kmodule.xml檔案來構造KieModule物件。KieModule是什麼呢?跟KieBase、KieSession之間又是什麼關係呢?我們看一下: 
KieBase KieModule KieSession 
上圖可以以關係圖的方式來理解,而不是準確表示類之間的關係。

從總的來說,左邊的介面描述了一種“定義”,而右邊的介面則描述的是一種執行時物件,右邊的執行時物件是根據左邊的定義創建出來的。

然後我們看到很多的“包含”關係,左邊的關係體現的就是在kmodule.xml檔案中的kmodule、kbase和ksession的定義和從上到下的包含關係。

在ClasspathKieProject類中,會根據kmodule.xml檔案的定義,將其解析並生成成KieModuleModel、KieBaseModel、KieSessionModel物件,基於這個原理,那麼我們也可以拋開kmodule.xml檔案,通過程式設計的方式建立這些Model物件,這個在稍後的章節中會講到。

在執行時,KieContainer會根據*Model物件來建立KieModule、KieBase、KieSession物件。其中KieModule和KieBase只會建立一次,而KieSession則有可能建立多次,因為KieSession的建立成本很低,同時KieSession包含了執行時的資料,所以可以銷燬、建立若干次。

kmodule.xml檔案中的kbase和ksession標籤都有很多的屬性,這些屬性對映到Java物件的時候就對應著相關的物件的欄位,下面我們詳細瞭解一下有那些屬性: 
ps:懶得打字了,汗,大家別嫌棄 
kbase的屬性: 
kbase的屬性
ksession的屬性: 
ksession的屬性
這樣我們就可以通過kmodule.xml檔案來定義KieModule了,ClasspathKieProject會自動解析classpath下面的所有META-INF/kmodule.xml檔案,然後解析成KieModule物件供Drools引擎使用。


Drools入門系列(八)——以編碼方式完成kmodule的定義

在Git裡面checkout training_2這個例子,就會發現在多了一個KieFileSystemTest單元測試:

public class KieFileSystemTest {

    @Test
    public void test() {
        KieServices kieServices = KieServices.Factory.get();
        KieResources resources = kieServices.getResources();
        KieModuleModel kieModuleModel = kieServices.newKieModuleModel();//1

        KieBaseModel baseModel = kieModuleModel.newKieBaseModel(
                "FileSystemKBase").addPackage("rules");//2
        baseModel.newKieSessionModel("FileSystemKSession");//3
        KieFileSystem fileSystem = kieServices.newKieFileSystem();

        String xml = kieModuleModel.toXML();
        System.out.println(xml);//4
        fileSystem.writeKModuleXML(xml);//5

        fileSystem.write("src/main/resources/rules/rule.drl", resources
                .newClassPathResource("kiefilesystem/KieFileSystemTest.drl"));//6

        KieBuilder kb = kieServices.newKieBuilder(fileSystem);
        kb.buildAll();//7
        if (kb.getResults().hasMessages(Level.ERROR)) {
            throw new RuntimeException("Build Errors:\n"
                    + kb.getResults().toString());
        }
        KieContainer kContainer = kieServices.newKieContainer(kieServices
                .getRepository().getDefaultReleaseId());

        assertNotNull(kContainer.getKieBase("FileSystemKBase"));
        KieSession kSession = kContainer.newKieSession("FileSystemKSession");

        kSession.fireAllRules();
    }
}

這個用例演示瞭如何利用編碼的方式來構建kmodule了,整個流程很簡單,就是:

  1. 先建立KieModuleModel;
  2. 再建立KieBaseModel;
  3. 然後建立 KieSessionModel;
  4. 建立完成之後可以生產一個xml檔案,就是kmodule.xml檔案了;
  5. 將這個xml檔案寫入到KieFileSystem中;
  6. 然後將規則檔案等寫入到KieFileSystem中;
  7. 最後通過KieBuilder進行構建就將該kmodule加入到KieRepository中了。這樣就將自定義的kmodule加入到引擎中了,就可以按照之前的方法進行使用了。

參考資料

[1] http://www.tuicool.com/articles/3EFNV3M 
[2] http://www.tuicool.com/articles/JV7J7zr 
[3] http://www.tuicool.com/articles/ememuq 
[4] http://www.tuicool.com/articles/InMjei 
[5] http://www.tuicool.com/articles/b2yqeq 
[6] http://www.tuicool.com/articles/jeIVjiy 
[7]http://www.tuicool.com/articles/22au6zV 
[8] http://www.tuicool.com/articles/qqIFvy