規則引擎-----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了:
new-drools-project-1
new-drools-project-2
new-drools-project-3
在這一步可以選擇一些例子,這樣在工程建立好之後就會有相應的例子程式了,上面提供的HelloWorld就是這個裡面的自動建立的例子。
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裡面也是這樣使用。
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在這個基礎上也定義了一個自己的儲存結構:
drools的標準儲存結構就是在src/main/resources資料夾下面儲存規則檔案(包括DRL檔案和Excel檔案),然後在META-INF資料夾下面建立一個kmodule.xml檔案用來儲存規則定義宣告。
Drools專案最終都是打包成jar然後進行釋出部署的(KIE專案生命週期提到的),這樣定義工程結構和打包釋出方式的根本原因就是——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之間又是什麼關係呢?我們看一下:
上圖可以以關係圖的方式來理解,而不是準確表示類之間的關係。
從總的來說,左邊的介面描述了一種“定義”,而右邊的介面則描述的是一種執行時物件,右邊的執行時物件是根據左邊的定義創建出來的。
然後我們看到很多的“包含”關係,左邊的關係體現的就是在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的屬性:
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了,整個流程很簡單,就是:
- 先建立KieModuleModel;
- 再建立KieBaseModel;
- 然後建立 KieSessionModel;
- 建立完成之後可以生產一個xml檔案,就是kmodule.xml檔案了;
- 將這個xml檔案寫入到KieFileSystem中;
- 然後將規則檔案等寫入到KieFileSystem中;
- 最後通過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