日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用 Drools 规则引擎实现业务逻辑,可调试drl文件

發(fā)布時間:2024/4/17 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用 Drools 规则引擎实现业务逻辑,可调试drl文件 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

?http://www.srcsky.com/tech/arts/389.html

代碼下載http://download.csdn.net/detail/zhy011525/2462313

?

  • 使用 Drools 規(guī)則引擎實現(xiàn)業(yè)務(wù)邏輯

  • 時間:2010-04-02來源:ibm作者:Ricardo閱讀:0 次字體:[ 大中 小 ]
  •   要求施加在當(dāng)今軟件產(chǎn)品上的大多數(shù)復(fù)雜性是行為和功能方面的,從而導(dǎo)致組件實現(xiàn)具有復(fù)雜的業(yè)務(wù)邏輯。實現(xiàn) J2EE 或 J2SE 應(yīng)用程序中業(yè)務(wù)邏輯最常見的方法是編寫 Java 代碼來實現(xiàn)需求文檔的規(guī)則和邏輯。在大多數(shù)情況下,該代碼的錯綜復(fù)雜性使得維護和更新應(yīng)用程序的業(yè)務(wù)邏輯成為一項令人畏懼的任務(wù),甚至對于經(jīng)驗豐富的開發(fā)人員來說也是如此。任何更改,不管多么簡單,仍然會產(chǎn)生重編譯和重部署成本。

      規(guī)則引擎試圖解決(或者至少降低)應(yīng)用程序業(yè)務(wù)邏輯的開發(fā)和維護中固有的問題和困難。可以將規(guī)則引擎看作實現(xiàn)復(fù)雜業(yè)務(wù)邏輯的框架。大多數(shù)規(guī)則引擎允許您使用聲明性編程來表達對于某些給定信息或知識有效的結(jié)果。您可以專注于已知為真的事實及其結(jié)果,也就是應(yīng)用程序的業(yè)務(wù)邏輯。

      有多個規(guī)則引擎可供使用,其中包括商業(yè)和開放源碼選擇。商業(yè)規(guī)則引擎通常允許使用專用的類似英語的語言來表達規(guī)則。其他規(guī)則引擎允許使用腳本語言(比如 Groovy 或 Python)編寫規(guī)則。這篇更新的文章為您介紹 Drools 引擎,并使用示例程序幫助您理解如何使用 Drools 作為 Java 應(yīng)用程序中業(yè)務(wù)邏輯層的一部分。

      更多事情在變化……

      俗話說得好,“惟一不變的是變化。”軟件應(yīng)用程序的業(yè)務(wù)邏輯正是如此。出于以下原因,實現(xiàn)應(yīng)用程序業(yè)務(wù)邏輯的組件可能必須更改:

      在開發(fā)期間或部署后修復(fù)代碼缺陷

      應(yīng)付特殊狀況,即客戶一開始沒有提到要將業(yè)務(wù)邏輯考慮在內(nèi)

      處理客戶已更改的業(yè)務(wù)目標(biāo)

      符合組織對敏捷或迭代開發(fā)過程的使用

      如果存在這些可能性,則迫切需要一個無需太多復(fù)雜性就能處理業(yè)務(wù)邏輯更改的應(yīng)用程序,尤其是當(dāng)更改復(fù)雜 if-else 邏輯的開發(fā)人員并不是以前編寫代碼的開發(fā)人員時。

      Drools 是用 Java 語言編寫的開放源碼規(guī)則引擎,使用 Rete 算法對所編寫的規(guī)則求值。Drools 允許使用聲明方式表達業(yè)務(wù)邏輯。可以使用非 XML 的本地語言編寫規(guī)則,從而便于學(xué)習(xí)和理解。并且,還可以將 Java 代碼直接嵌入到規(guī)則文件中,這令 Drools 的學(xué)習(xí)更加吸引人。Drools 還具有其他優(yōu)點:

      非常活躍的社區(qū)支持

      易用

      快速的執(zhí)行速度

      在 Java 開發(fā)人員中流行

      與 Java Rule Engine API(JSR 94)兼容

      免費

      當(dāng)前 Drools 版本

      在編寫本文之際,Drools 規(guī)則引擎的最新版本是 4.0.4。這是一個重要更新。雖然現(xiàn)在還存在一些向后兼容性問題,但這個版本的特性讓 Drools 比以前更有吸引力。例如,用于表達規(guī)則的新的本地語言比舊版本使用的 XML 格式更簡單,更優(yōu)雅。這種新語言所需的代碼更少,并且格式易于閱讀。

      另一個值得注意的進步是,新版本提供了用于 Eclipse IDE(Versions 3.2 和 3.3)的一個 Drools 插件。我強烈建議您通過這個插件來使用 Drools。它可以簡化使用 Drools 的項目開發(fā),并且可以提高生產(chǎn)率。例如,該插件會檢查規(guī)則文件是否有語法錯誤,并提供代碼完成功能。它還使您可以調(diào)試規(guī)則文件,將調(diào)試時間從數(shù)小時減少到幾分鐘。您可以在規(guī)則文件中添加斷點,以便在規(guī)則執(zhí)行期間的特定時刻檢查對象的狀態(tài)。這使您可以獲得關(guān)于規(guī)則引擎在特定時刻所處理的知識(knowledge)(在本文的后面您將熟悉這個術(shù)語)的信息。

      要解決的問題

      本文展示如何使用 Drools 作為示例 Java 應(yīng)用程序中業(yè)務(wù)邏輯層的一部分。為了理解本文,您應(yīng)該熟悉使用 Eclipse IDE 開發(fā)和調(diào)試 Java 代碼。并且,您還應(yīng)該熟悉 JUnit 測試框架,并知道如何在 Eclipse 中使用它。

      下列假設(shè)為應(yīng)用程序解決的虛構(gòu)問題設(shè)置了場景:

      名為 XYZ 的公司構(gòu)建兩種類型的計算機機器:Type1 和 Type2。機器類型按其架構(gòu)定義。

      XYZ 計算機可以提供多種功能。當(dāng)前定義了四種功能:DDNS Server、DNS Server、Gateway 和 Router。

      在發(fā)運每臺機器之前,XYZ 在其上執(zhí)行多個測試。

      在每臺機器上執(zhí)行的測試取決于每臺機器的類型和功能。目前,定義了五種測試:Test1、Test2、Test3、Test4 和 Test5。

      當(dāng)將測試分配給一臺計算機時,也將測試到期日期 分配給該機器。分配給計算機的測試不能晚于該到期日期執(zhí)行。到期日期值取決于分配給機器的測試。

      XYZ 使用可以確定機器類型和功能的內(nèi)部開發(fā)的軟件應(yīng)用程序,自動化了執(zhí)行測試時的大部分過程。然后,基于這些屬性,應(yīng)用程序確定要執(zhí)行的測試及其到期日期。

      目前,為計算機分配測試和測試到期日期的邏輯是該應(yīng)用程序的已編譯代碼的一部分。包含該邏輯的組件用 Java 語言編寫。

      分配測試和到期日期的邏輯一個月更改多次。當(dāng)開發(fā)人員需要使用 Java 代碼實現(xiàn)該邏輯時,必須經(jīng)歷一個冗長乏味的過程。

      何時使用規(guī)則引擎

      并非所有應(yīng)用程序都應(yīng)使用規(guī)則引擎。如果業(yè)務(wù)邏輯代碼包括很多 if-else 語句,則應(yīng)考慮使用一個規(guī)則引擎。維護復(fù)雜的 Boolean 邏輯可能是非常困難的任務(wù),而規(guī)則引擎可以幫助您組織該邏輯。當(dāng)您可以使用聲明方法而非命令編程語言表達邏輯時,變化引入錯誤的可能性會大大降低。

      如果代碼變化可能導(dǎo)致大量的財政損失,則也應(yīng)考慮規(guī)則引擎。許多組織在將已編譯代碼部署到托管環(huán)境中時具有嚴格的規(guī)則。例如,如果需要修改 Java 類中的邏輯,在更改進入生產(chǎn)環(huán)境之前,將會經(jīng)歷一個冗長乏味的過程:

      必須重新編譯應(yīng)用程序代碼。

      在測試中轉(zhuǎn)環(huán)境中刪除代碼。

      由數(shù)據(jù)質(zhì)量審核員檢查代碼。

      由托管環(huán)境架構(gòu)師批準更改。

      計劃代碼部署。

      即使對一行代碼的簡單更改也可能花費組織的幾千美元。如果需要遵循這些嚴格規(guī)則并且發(fā)現(xiàn)您頻繁更改業(yè)務(wù)邏輯代碼,則非常有必要考慮使用規(guī)則引擎。

      對客戶的了解也是該決策的一個因素。盡管您使用的是一個簡單的需求集合,只需 Java 代碼中的簡單實現(xiàn),但是您可能從上一個項目得知,您的客戶具有在開發(fā)周期期間甚至部署之后添加和更改業(yè)務(wù)邏輯需求的傾向(以及財政和政治資源)。如果從一開始就選擇使用規(guī)則引擎,您可能會過得舒服一些。

      因為在對為計算機分配測試和到期日期的邏輯進行更改時,公司會發(fā)生高額成本,所以 XYZ 主管已經(jīng)要求軟件工程師尋找一種靈活的方法,用最少的代價將對業(yè)務(wù)規(guī)則的更改 “推” 至生產(chǎn)環(huán)境。于是 Drools 走上舞臺了。工程師決定,如果它們使用規(guī)則引擎來表達確定哪些測試應(yīng)該執(zhí)行的規(guī)則,則可以節(jié)省更多時間和精力。他們將只需要更改規(guī)則文件的內(nèi)容,然后在生產(chǎn)環(huán)境中替換該文件。對于他們來說,這比更改已編譯代碼并在將已編譯代碼部署到生產(chǎn)環(huán)境中時進行由組織強制的冗長過程要簡單省時得多(參閱側(cè)欄 何時使用規(guī)則引擎)。

      目前,在為機器分配測試和到期日期時必須遵循以下業(yè)務(wù)規(guī)則:

      如果計算機是 Type1,則只能在其上執(zhí)行 Test1、Test2 和 Test5。

      如果計算機是 Type2 且其中一個功能為 DNS Server,則應(yīng)執(zhí)行 Test4 和 Test5。

      如果計算機是 Type2 且其中一個功能為 DDNS Server,則應(yīng)執(zhí)行 Test2 和 Test3。

      如果計算機是 Type2 且其中一個功能為 Gateway,則應(yīng)執(zhí)行 Test3 和 Test4。

      如果計算機是 Type2 且其中一個功能為 Router,則應(yīng)執(zhí)行 Test1 和 Test3。

      如果 Test1 是要在計算機上執(zhí)行的測試之一,則測試到期日期距離機器的創(chuàng)建日期 3 天。該規(guī)則優(yōu)先于測試到期日期的所有下列規(guī)則。

      如果 Test2 是要在計算機上執(zhí)行的測試之一,則測試到期日期距離機器的創(chuàng)建日期 7 天。該規(guī)則優(yōu)先于測試到期日期的所有下列規(guī)則。

      如果 Test3 是要在計算機上執(zhí)行的測試之一,則測試到期日期距離機器的創(chuàng)建日期 10 天。該規(guī)則優(yōu)先于測試到期日期的所有下列規(guī)則。

      如果 Test4 是要在計算機上執(zhí)行的測試之一,則測試到期日期距離機器的創(chuàng)建日期 12 天。該規(guī)則優(yōu)先于測試到期日期的所有下列規(guī)則。

      如果 Test5 是要在計算機上執(zhí)行的測試之一,則測試到期日期距離機器的創(chuàng)建日期 14 天。

      捕獲為機器分配測試和測試到期日期的上述業(yè)務(wù)規(guī)則的當(dāng)前 Java 代碼如清單 1 所示:

    清單 1. 使用 if-else 語句實現(xiàn)業(yè)務(wù)規(guī)則邏輯

    Machine machine = ... 
    // Assign tests 
    Collections.sort(machine.getFunctions()); 
    int index; 
     
    if (machine.getType().equals("Type1")) { 
      Test test1 = ... 
      Test test2 = ... 
      Test test5 = ... 
      machine.getTests().add(test1); 
      machine.getTests().add(test2); 
      machine.getTests().add(test5); 
    } else if (machine.getType().equals("Type2")) { 
      index = Collections.binarySearch(machine.getFunctions(), "Router"); 
      if (index >= 0) { 
       Test test1 = ... 
       Test test3 = ... 
       machine.getTests().add(test1); 
       machine.getTests().add(test3); 
      } 
      index = Collections.binarySearch(machine.getFunctions(), "Gateway"); 
      if (index >= 0) { 
       Test test4 = ... 
       Test test3 = ... 
       machine.getTests().add(test4); 
       machine.getTests().add(test3); 
      } 
    ... 
    } 
     
    // Assign tests due date 
    Collections.sort(machine.getTests(), new TestComparator()); 
    ... 
    Test test1 = ... 
    index = Collections.binarySearch(machine.getTests(), test1); 
    if (index >= 0) { 
      // Set due date to 3 days after Machine was created 
      Timestamp creationTs = machine.getCreationTs(); 
      machine.setTestsDueTime(...); 
      return; 
    } 
     
    index = Collections.binarySearch(machine.getTests(), test2); 
    if (index >= 0) { 
      // Set due date to 7 days after Machine was created 
      Timestamp creationTs = machine.getCreationTs(); 
      machine.setTestsDueTime(...); 
      return; 
    } 
    ... 

      清單 1 中的代碼不是太復(fù)雜,但也并不簡單。如果要對其進行更改,需要十分小心。一堆互相纏繞的 if-else 語句正試圖捕獲已經(jīng)為應(yīng)用程序標(biāo)識的業(yè)務(wù)邏輯。如果您對業(yè)務(wù)規(guī)則不甚了解,就無法一眼看出代碼的意圖。

      導(dǎo)入示例程序

      使用 Drools 規(guī)則的示例程序附帶在本文的 ZIP 存檔中。程序使用 Drools 規(guī)則文件以聲明方法表示上一節(jié)定義的業(yè)務(wù)規(guī)則。它包含一個 Eclipse 3.2 Java 項目,該項目是使用 Drools 插件和 4.0.4 版的 Drools 規(guī)則引擎開發(fā)的。請遵循以下步驟設(shè)置示例程序:

      下載 ZIP 存檔(參見 下載)。

      下載并安裝 Drools Eclipse 插件。

      在 Eclipse 中,選擇該選項以導(dǎo)入 Existing Projects into Workspace,如圖 1 所示:

    圖 1. 將示例程序?qū)氲?Eclipse 工作區(qū)


      然后選擇下載的存檔文件并將其導(dǎo)入工作區(qū)中。您將在工作區(qū)中發(fā)現(xiàn)一個名為 DroolsDemo 的新 Java 項目,如圖 2 所示:

    圖 2. 導(dǎo)入到工作區(qū)中的示例程序


      如果啟用了 Build automatically 選項,則代碼應(yīng)該已編譯并可供使用。如果未啟用該選項,則現(xiàn)在構(gòu)建 DroolsDemo 項目。

      檢查代碼

      現(xiàn)在來看一下示例程序中的代碼。該程序的 Java 類的核心集合位于 demo 包中。在該包中可以找到 Machine 和 Test 域?qū)ο箢?。Machine 類的實例表示要分配測試和測試到期日期的計算機機器。下面來看 Machine 類,如清單 2 所示:

    清單 2. Machine 類的實例變量

    public class Machine { 
     
      private String type; 
      private List functions = new ArrayList(); 
      private String serialNumber; 
      private Collection tests = new HashSet(); 
      private Timestamp creationTs; 
      private Timestamp testsDueTime; 
     
      public Machine() { 
       super(); 
       this.creationTs = new Timestamp(System.currentTimeMillis()); 
      } 
      ... 

      在清單 2 中可以看到 Machine 類的屬性有:

      type(表示為 string 屬性)—— 保存機器的類型值。

      functions (表示為 list)—— 保存機器的功能。

      testsDueTime (表示為 timestamp 變量)—— 保存分配的測試到期日期值。

      tests (Collection 對象)—— 保存分配的測試集合。

      注意,可以為機器分配多個測試,而且一個機器可以具有一個或多個功能。

      出于簡潔目的,機器的創(chuàng)建日期值設(shè)置為創(chuàng)建 Machine 類的實例時的當(dāng)前時間。如果這是真實的應(yīng)用程序,創(chuàng)建時間將設(shè)置為機器最終構(gòu)建完成并準備測試的實際時間。

      Test 類的實例表示可以分配給機器的測試。Test實例由其 id 和 name 惟一描述,如清單 3 所示:

    清單 3. Test 類的實例變量

    public class Test { 
     
      public static Integer TEST1 = new Integer(1); 
      public static Integer TEST2 = new Integer(2); 
      public static Integer TEST3 = new Integer(3); 
      public static Integer TEST4 = new Integer(4); 
      public static Integer TEST5 = new Integer(5); 
     
      private Integer id; 
      private String name; 
      private String description; 
      public Test() { 
       super(); 
      } 
      ... 

      示例程序使用 Drools 規(guī)則引擎對 Machine 類的實例求值?;?Machine 實例的 type 和 functions 屬性的值,規(guī)則引擎確定應(yīng)分配給 tests 和 testsDueTime 屬性的值。

      在 demo 包中,還會發(fā)現(xiàn) Test 對象的數(shù)據(jù)訪問對象 (TestDAOImpl) 的實現(xiàn),它允許您按照 ID 查找 Test 實例。該數(shù)據(jù)訪問對象極其簡單;它不連接任何外部資源(比如關(guān)系數(shù)據(jù)庫)以獲得 Test 實例。相反,在其定義中硬編碼了預(yù)定義的 Test 實例集合。在現(xiàn)實世界中,您可能會具有連接外部資源以檢索 Test 對象的數(shù)據(jù)訪問對象。

      RulesEngine 類

      demo 中比較重要(如果不是最重要的)的一個類是 RulesEngine 類。該類的實例用作封裝邏輯以訪問 Drools 類的包裝器對象??梢栽谀约旱?Java 項目中容易地重用該類,因為它所包含的邏輯不是特定于示例程序的。清單 4 展示了該類的屬性和構(gòu)造函數(shù):

    清單 4. RulesEngine 類的實例變量和構(gòu)造函數(shù)

    public class RulesEngine { 
     
      private RuleBase rules; 
      private boolean debug = false; 
     
      public RulesEngine(String rulesFile) throws RulesEngineException { 
       super(); 
       try { 
         // Read in the rules source file 
         Reader source = new InputStreamReader(RulesEngine.class 
          .getResourceAsStream("/" + rulesFile)); 
         // Use package builder to build up a rule package 
         PackageBuilder builder = new PackageBuilder(); 
         // This parses and compiles in one step 
         builder.addPackageFromDrl(source); 
         // Get the compiled package 
         Package pkg = builder.getPackage(); 
         // Add the package to a rulebase (deploy the rule package). 
         rules = RuleBaseFactory.newRuleBase(); 
         rules.addPackage(pkg); 
       } catch (Exception e) { 
         throw new RulesEngineException( 
          "Could not load/compile rules file: " + rulesFile, e); 
       } 
      } 
      ... 

      在清單 4 中可以看到,RulesEngine 類的構(gòu)造函數(shù)接受字符串值形式的參數(shù),該值表示包含業(yè)務(wù)規(guī)則集合的文件的名稱。該構(gòu)造函數(shù)使用 PackageBuilder 類的實例解析和編譯源文件中包含的規(guī)則。(注意: 該代碼假設(shè)規(guī)則文件位于程序類路徑中名為 rules 的文件夾中。)然后,使用 PackageBuilder 實例將所有編譯好的規(guī)則合并為一個二進制 Package 實例。然后,使用這個實例配置 Drools RuleBase 類的一個實例,后者被分配給 RulesEngine 類的 rules 屬性。可以將這個類的實例看作規(guī)則文件中所包含規(guī)則的內(nèi)存中表示。

      清單 5 展示了 RulesEngine 類的 executeRules() 方法:

    清單 5. RulesEngine 類的 executeRules() 方法

    public void executeRules(WorkingEnvironmentCallback callback) { 
      WorkingMemory workingMemory = rules.newStatefulSession(); 
      if (debug) { 
       workingMemory 
         .addEventListener(new DebugWorkingMemoryEventListener()); 
      } 
      callback.initEnvironment(workingMemory); 
      workingMemory.fireAllRules(); 
    } 

      executeRules() 方法幾乎包含了 Java 代碼中的所有魔力。調(diào)用該方法執(zhí)行先前加載到類構(gòu)造函數(shù)中的規(guī)則。Drools WorkingMemory 類的實例用于斷言或聲明知識,規(guī)則引擎應(yīng)使用它來確定應(yīng)執(zhí)行的結(jié)果。(如果滿足規(guī)則的所有條件,則執(zhí)行該規(guī)則的結(jié)果。)將知識當(dāng)作規(guī)則引擎用于確定是否應(yīng)啟動規(guī)則的數(shù)據(jù)或信息。例如,規(guī)則引擎的知識可以包含一個或多個對象及其屬性的當(dāng)前狀態(tài)。

      規(guī)則結(jié)果在調(diào)用 WorkingMemory 對象的 fireAllRules() 方法時執(zhí)行。您可能奇怪(我希望您如此)知識是如何插入到 WorkingMemory 實例中的。如果仔細看一下該方法的簽名,將會注意到所傳遞的參數(shù)是 WorkingEnvironmentCallback 接口的實例。executeRules() 方法的調(diào)用者需要創(chuàng)建實現(xiàn)該接口的對象。該接口只需要開發(fā)人員實現(xiàn)一個方法(參見清單 6 ):

    清單 6. WorkingEnvironmentCallback 接口

    public interface WorkingEnvironmentCallback { 
      void initEnvironment(WorkingMemory workingMemory) throws FactException; 
    } 

      所以,應(yīng)該是 executeRules() 方法的調(diào)用者將知識插入到 WorkingMemory 實例中的。稍后將展示這是如何實現(xiàn)的。

      TestsRulesEngine 類

      清單 7 展示了 TestsRulesEngine 類,它也位于 demo 包中:

    清單 7. TestsRulesEngine 類

    public class TestsRulesEngine { 
     
      private RulesEngine rulesEngine; 
      private TestDAO testDAO; 
     
      public TestsRulesEngine(TestDAO testDAO) throws RulesEngineException { 
       super(); 
       rulesEngine = new RulesEngine("testRules1.drl"); 
       this.testDAO = testDAO; 
      } 
     
      public void assignTests(final Machine machine) { 
       rulesEngine.executeRules(new WorkingEnvironmentCallback() { 
         public void initEnvironment(WorkingMemory workingMemory) { 
          // Set globals first before asserting/inserting any knowledge! 
          workingMemory.setGlobal("testDAO", testDAO); 
          workingMemory.insert(machine); 
         }; 
       }); 
      } 
    } 

      TestsRulesEngine 類只有兩個實例變量。rulesEngine 屬性是 RulesEngine 類的實例。 testDAO 屬性保存對 TestDAO 接口的具體實現(xiàn)的引用。 rulesEngine 對象是使用 "testRules1.drl" 字符串作為其構(gòu)造函數(shù)的參數(shù)實例化的。testRules1.drl 文件以聲明方式捕獲 要解決的問題 中的業(yè)務(wù)規(guī)則。 TestsRulesEngine 類的 assignTests() 方法調(diào)用 RulesEngine 類的 executeRules() 方法。在這個方法中,創(chuàng)建了 WorkingEnvironmentCallback 接口的一個匿名實例,然后將該實例作為參數(shù)傳遞給 executeRules() 方法。

      如果查看 assignTests() 方法的實現(xiàn),可以看到知識是如何插入到 WorkingMemory 實例中的。 WorkingMemory 類的 insert() 方法被調(diào)用以聲明在對規(guī)則求值時規(guī)則引擎應(yīng)使用的知識。在這種情況下,知識由 Machine 類的一個實例組成。被插入的對象用于對規(guī)則的條件求值。

      如果在對條件求值時,需要讓規(guī)則引擎引用未 用作知識的對象,則應(yīng)使用 WorkingMemory 類的 setGlobal() 方法。在示例程序中,setGlobal() 方法將對 TestDAO 實例的引用傳遞給規(guī)則引擎。然后規(guī)則引擎使用 TestDAO 實例查找它可能需要的任何 Test 實例。

      TestsRulesEngine 類是示例程序中惟一的 Java 代碼,它包含專門致力于為機器分配測試和測試到期日期的實現(xiàn)的邏輯。該類中的邏輯永遠不需要更改,即使業(yè)務(wù)規(guī)則需要更新時也是如此。

      Drools 規(guī)則文件

      如前所述,testRules.xml 文件包含規(guī)則引擎為機器分配測試和測試到期日期所遵循的規(guī)則。它使用 Drools 本地語言表達所包含的規(guī)則。

      Drools 規(guī)則文件有一個或多個 rule 聲明。每個 rule 聲明由一個或多個 conditional 元素以及要執(zhí)行的一個或多個 consequences 或 actions 組成。一個規(guī)則文件還可以有多個(即 0 個或多個)import 聲明、多個 global 聲明以及多個 function 聲明。

      理解 Drools 規(guī)則文件組成最好的方法是查看一個真正的規(guī)則文件。下面來看 testRules1.drl 文件的第一部分,如清單 8 所示:

    清單 8. testRules1.drl 文件的第一部分

    package demo; 
     
    import demo.Machine; 
    import demo.Test; 
    import demo.TestDAO; 
    import java.util.Calendar; 
    import java.sql.Timestamp; 
    global TestDAO testDAO; 

      在清單 8 中,可以看到 import 聲明如何讓規(guī)則執(zhí)行引擎知道在哪里查找將在規(guī)則中使用的對象的類定義。global 聲明讓規(guī)則引擎知道,某個對象應(yīng)該可以從規(guī)則中訪問,但該對象不應(yīng)是用于對規(guī)則條件求值的知識的一部分。可以將 global 聲明看作規(guī)則中的全局變量。對于 global 聲明,需要指定它的類型(即類名)和想要用于引用它的標(biāo)識符(即變量名)。global 聲明中的這個標(biāo)識符名稱應(yīng)該與調(diào)用 WorkingMemory 類的 setGlobal() 方法時使用的標(biāo)識符值匹配,在此即為 testDAO (參見 清單 7)。

      function 關(guān)鍵詞用于定義一個 Java 函數(shù)(參見 清單 9)。如果看到 consequence(稍后將討論)中重復(fù)的代碼,則應(yīng)該提取該代碼并將其編寫為一個 Java 函數(shù)。但是,這樣做時要小心,避免在 Drools 規(guī)則文件中編寫復(fù)雜的 Java 代碼。規(guī)則文件中定義的 Java 函數(shù)應(yīng)該簡短易懂。這不是 Drools 的技術(shù)限制。如果想要在規(guī)則文件中編寫復(fù)雜的 Java 代碼,也可以。但這樣做可能會讓您的代碼更加難以測試、調(diào)試和維護。復(fù)雜的 Java 代碼應(yīng)該是 Java 類的一部分。如果需要 Drools 規(guī)則執(zhí)行引擎調(diào)用復(fù)雜的 Java 代碼,則可以將對包含復(fù)雜代碼的 Java 類的引用作為全局數(shù)據(jù)傳遞給規(guī)則引擎。

    清單 9. testRules1.drl 文件中定義的 Java 函數(shù)

    function void setTestsDueTime(Machine machine, int numberOfDays) { 
      setDueTime(machine, Calendar.DATE, numberOfDays); 
    } 
     
    function void setDueTime(Machine machine, int field, int amount) { 
      Calendar calendar = Calendar.getInstance(); 
      calendar.setTime(machine.getCreationTs()); 
      calendar.add(field, amount); 
      machine.setTestsDueTime(new Timestamp(calendar.getTimeInMillis())); 
    } 
     ... 

      清單 10 展示了 testRules1.drl 文件中定義的第一個規(guī)則:

    清單 10. testRules1.drl 中定義的第一個規(guī)則

    rule "Tests for type1 machine" 
    salience 100 
    when 
      machine : Machine( type == "Type1" ) 
    then 
      Test test1 = testDAO.findByKey(Test.TEST1); 
      Test test2 = testDAO.findByKey(Test.TEST2); 
      Test test5 = testDAO.findByKey(Test.TEST5); 
      machine.getTests().add(test1); 
      machine.getTests().add(test2); 
      machine.getTests().add(test5); 
      insert( test1 ); 
      insert( test2 ); 
      insert( test5 ); 
    end 

      如清單 10 所示,rule 聲明有一個惟一標(biāo)識它的 name。還可以看到,when 關(guān)鍵詞定義規(guī)則中的條件塊,then 關(guān)鍵詞定義結(jié)果塊。清單 10 中顯示的規(guī)則有一個引用 Machine 對象的條件元素。如果回到 清單 7 可以看到, Machine 對象被插入到 WorkingMemory 對象中。這正是這個規(guī)則中使用的對象。條件元素對 Machine 實例(知識的一部分)求值,以確定是否應(yīng)執(zhí)行規(guī)則的結(jié)果。如果條件元素等于 true,則啟動或執(zhí)行結(jié)果。從清單 10 中還可以看出,結(jié)果只不過是一個 Java 語言語句。通過快速瀏覽該規(guī)則,可以很容易地識別出這是下列業(yè)務(wù)規(guī)則的實現(xiàn):

      如果計算機是 Type1,則只能在該機器上執(zhí)行 Test1、Test2 和 Test5。

      因此,該規(guī)則的條件元素檢查( Machine 對象的) type 屬性是否為 Type1。 (在條件元素中,只要對象遵從 Java bean 模式,就可以直接訪問對象的屬性,而不必調(diào)用 getter 方法。)如果該屬性的值為 true,那么將 Machine 實例的一個引用分配給 machine 標(biāo)識符。然后,在規(guī)則的結(jié)果塊使用該引用,將測試分配給 Machine 對象。

      在該規(guī)則中,惟一看上去有些奇怪的語句是最后三條結(jié)果語句?;貞?“要解決的問題” 小節(jié)中的業(yè)務(wù)規(guī)則,應(yīng)該分配為測試到期日期的值取決于分配給機器的測試。因此,分配給機器的測試需要成為規(guī)則執(zhí)行引擎在對規(guī)則求值時所使用的知識的一部分。這正是這三條語句的作用。這些語句使用一個名為 insert 的方法更新規(guī)則引擎中的知識。

      確定規(guī)則執(zhí)行順序

      規(guī)則的另一個重要的方面是可選的 salience 屬性。使用它可以讓規(guī)則執(zhí)行引擎知道應(yīng)該啟動規(guī)則的結(jié)果語句的順序。具有最高顯著值的規(guī)則的結(jié)果語句首先執(zhí)行;具有第二高顯著值的規(guī)則的結(jié)果語句第二執(zhí)行,依此類推。當(dāng)您需要讓規(guī)則按預(yù)定義順序啟動時,這一點非常重要,很快您將會看到。

      testRules1.drl 文件中接下來的四個規(guī)則實現(xiàn)與機器測試分配有關(guān)的其他業(yè)務(wù)規(guī)則(參見清單 11)。這些規(guī)則與剛討論的第一個規(guī)則非常相似。注意,salience 屬性值對于前五個規(guī)則是相同的;不管這五個規(guī)則的啟動順序如何,其執(zhí)行結(jié)果將相同。如果結(jié)果受規(guī)則的啟動順序影響,則需要為規(guī)則指定不同的顯著值。

    清單 11. testRules1.drl 文件中與測試分配有關(guān)的其他規(guī)則

    rule "Tests for type2, DNS server machine" 
    salience 100 
    when 
      machine : Machine( type == "Type2", functions contains "DNS Server") 
    then 
      Test test5 = testDAO.findByKey(Test.TEST5); 
      Test test4 = testDAO.findByKey(Test.TEST4); 
      machine.getTests().add(test5); 
      machine.getTests().add(test4); 
      insert( test4 ); 
      insert( test5 ); 
    end 
     
    rule "Tests for type2, DDNS server machine" 
    salience 100 
    when 
      machine : Machine( type == "Type2", functions contains "DDNS Server") 
    then 
      Test test2 = testDAO.findByKey(Test.TEST2); 
      Test test3 = testDAO.findByKey(Test.TEST3); 
      machine.getTests().add(test2); 
      machine.getTests().add(test3); 
      insert( test2 ); 
      insert( test3 ); 
    end 
     
    rule "Tests for type2, Gateway machine" 
    salience 100 
    when 
      machine : Machine( type == "Type2", functions contains "Gateway") 
    then 
      Test test3 = testDAO.findByKey(Test.TEST3); 
      Test test4 = testDAO.findByKey(Test.TEST4); 
      machine.getTests().add(test3); 
      machine.getTests().add(test4); 
      insert( test3 ); 
      insert( test4 ); 
    end 
     
    rule "Tests for type2, Router machine" 
    salience 100 
    when 
      machine : Machine( type == "Type2", functions contains "Router") 
    then 
      Test test3 = testDAO.findByKey(Test.TEST3); 
      Test test1 = testDAO.findByKey(Test.TEST1); 
      machine.getTests().add(test3); 
      machine.getTests().add(test1); 
      insert( test1 ); 
      insert( test3 ); 
    end 
    ... 

      清單 12 展示了 Drools 規(guī)則文件中的其他規(guī)則。您可能已經(jīng)猜到,這些規(guī)則與測試到期日期的分配有關(guān):

    清單 12. testRules1.drl 文件中與測試到期日期分配有關(guān)的規(guī)則

    rule "Due date for Test 5" 
    salience 50 
    when 
      machine : Machine() 
      Test( id == Test.TEST5 ) 
    then 
      setTestsDueTime(machine, 14); 
    end 
     
    rule "Due date for Test 4" 
    salience 40 
    when 
      machine : Machine() 
      Test( id == Test.TEST4 ) 
    then 
      setTestsDueTime(machine, 12); 
    end 
     
    rule "Due date for Test 3" 
    salience 30 
    when 
      machine : Machine() 
      Test( id == Test.TEST3 ) 
    then 
      setTestsDueTime(machine, 10); 
    end 
     
    rule "Due date for Test 2" 
    salience 20 
    when 
      machine : Machine() 
      Test( id == Test.TEST2 ) 
    then 
      setTestsDueTime(machine, 7); 
    end 
     
    rule "Due date for Test 1" 
    salience 10 
    when 
      machine : Machine() 
      Test( id == Test.TEST1 ) 
    then 
      setTestsDueTime(machine, 3); 
    end 

      這些規(guī)則的實現(xiàn)比用于分配測試的規(guī)則的實現(xiàn)要略微簡單一些,但我發(fā)現(xiàn)它們更有趣一些,原因有四。

      第一,注意這些規(guī)則的執(zhí)行順序很重要。結(jié)果(即,分配給 Machine 實例的 testsDueTime 屬性的值)受這些規(guī)則的啟動順序所影響。如果查看 要解決的問題 中詳細的業(yè)務(wù)規(guī)則,您將注意到用于分配測試到期日期的規(guī)則具有優(yōu)先順序。例如,如果已經(jīng)將 Test3、Test4 和 Test5 分配給機器,則測試到期日期應(yīng)距離機器的創(chuàng)建日期 10 天。原因在于 Test3 的到期日期規(guī)則優(yōu)先于 Test4 和 Test5 的測試到期日期規(guī)則。如何在 Drools 規(guī)則文件中表達這一點呢答案是 salience 屬性。為 testsDueTime 屬性設(shè)置值的規(guī)則的 salience 屬性值不同。Test1 的測試到期日期規(guī)則優(yōu)先于所有其他測試到期日期規(guī)則,所以這應(yīng)是要啟動的最后一個規(guī)則。換句話說,如果 Test1 是分配給機器的測試之一,則由該規(guī)則分配的值應(yīng)該是優(yōu)先使用的值。所以,該規(guī)則的 salience 屬性值最低:10。

      第二,每個規(guī)則有兩個條件元素。第一個元素只檢查工作內(nèi)存中是否存在一個 Machine 實例。(注意,這里不會對 Machine 對象的屬性進行比較。)當(dāng)這個元素等于 true 時,它將一個引用分配給 Machine 對象,而后者將在規(guī)則的結(jié)果塊被用到。如果不分配這個引用,那么就無法將測試到期日期分配給 Machine 對象。第二個條件元素檢查 Test 對象的 id 屬性。當(dāng)且僅當(dāng)這兩個條件元素都等于 true 時,才執(zhí)行規(guī)則的結(jié)果元素。

      第三,在 Test 類的一個實例成為知識的一部分(即,包含在工作內(nèi)存中)之前,Drools 規(guī)則執(zhí)行引擎不會(也不能)對這些規(guī)則的條件塊求值。這很符合邏輯,因為如果工作內(nèi)存中還沒有 Test 類的一個實例,那么規(guī)則執(zhí)行引擎就無法執(zhí)行這些規(guī)則的條件中所包含的比較。如果您想知道 Test 實例何時成為知識的一部分,那么可以回憶,在與分配測試相關(guān)規(guī)則的結(jié)果的執(zhí)行期間,一個或多個 Test 實例被插入到工作內(nèi)存中。(參見 清單 10 和 清單 11)。

      第四,注意這些規(guī)則的結(jié)果塊相當(dāng)簡短。原因在于在所有結(jié)果塊中調(diào)用了規(guī)則文件中之前使用 function 關(guān)鍵詞定義的 setTestsDueTime() Java 方法。該方法為 testsDueTime 屬性實際分配值。

      測試代碼

      既然已經(jīng)仔細檢查了實現(xiàn)業(yè)務(wù)規(guī)則邏輯的代碼,現(xiàn)在應(yīng)該檢查它是否能工作。要執(zhí)行示例程序,運行 demo.test 中的 TestsRulesEngineTest JUnit 測試。

      在該測試中,創(chuàng)建了 5 個 Machine 對象,每個對象具有不同的屬性集合(序號、類型和功能)。為這五個 Machine 對象的每一個都調(diào)用 TestsRulesEngine 類的 assignTests() 方法。一旦 assignTests() 方法完成其執(zhí)行,就執(zhí)行斷言以驗證 testRules1.drl 中指定的業(yè)務(wù)規(guī)則邏輯是否正確(參見清單 13)??梢孕薷?TestsRulesEngineTest JUnit 類以多添加幾個具有不同屬性的 Machine 實例,然后使用斷言驗證結(jié)果是否跟預(yù)期一樣。

    清單 13. testTestsRulesEngine() 方法中用于驗證業(yè)務(wù)邏輯實現(xiàn)是否正確的斷言

    public void testTestsRulesEngine() throws Exception { 
      while (machineResultSet.next()) { 
       Machine machine = machineResultSet.getMachine(); 
       testsRulesEngine.assignTests(machine); 
       Timestamp creationTs = machine.getCreationTs(); 
       Calendar calendar = Calendar.getInstance(); 
       calendar.setTime(creationTs); 
       Timestamp testsDueTime = machine.getTestsDueTime(); 
     
       if (machine.getSerialNumber().equals("1234A")) { 
         assertEquals(3, machine.getTests().size()); 
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST1))); 
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST2))); 
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST5))); 
         calendar.add(Calendar.DATE, 3); 
         assertEquals(calendar.getTime(), testsDueTime); 
     
       } else if (machine.getSerialNumber().equals("1234B")) { 
         assertEquals(4, machine.getTests().size()); 
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST5))); 
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST4))); 
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST3))); 
         assertTrue(machine.getTests().contains(testDAO.findByKey(Test.TEST2))); 
         calendar.add(Calendar.DATE, 7); 
         assertEquals(calendar.getTime(), testsDueTime); 
    ... 

      關(guān)于知識的其他備注

      值得一提的是,除了將對象插入至工作內(nèi)存之外,還可以在工作內(nèi)存中修改對象或從中撤回對象??梢栽谝?guī)則的結(jié)果塊中進行這些操作。如果在結(jié)果語句中修改作為當(dāng)前知識一部分的對象,并且所修改的屬性被用在 condition 元素中以確定是否應(yīng)啟動規(guī)則,則應(yīng)在結(jié)果塊中調(diào)用 update() 方法。調(diào)用 update() 方法時,您讓 Drools 規(guī)則引擎知道對象已更新且引用該對象的任何規(guī)則的任何條件元素(例如,檢查一個或多個對象屬性的值)應(yīng)再次求值,以確定條件的結(jié)果現(xiàn)在是 true 還是 false。這意味著甚至當(dāng)前活動規(guī)則(在其結(jié)果塊中修改對象的規(guī)則)的條件都可以再次求值,這可能導(dǎo)致規(guī)則再次啟動,并可能導(dǎo)致無限循環(huán)。如果不希望這種情況發(fā)生,則應(yīng)該包括 rule 的可選 no-loop 屬性并將其賦值為 true。

      清單 14 用兩個規(guī)則的定義的偽代碼演示了這種情況。Rule 1 修改 objectA 的 property1。然后它調(diào)用 update() 方法,以允許規(guī)則執(zhí)行引擎知道該更新,從而觸發(fā)對引用 objectA 的規(guī)則的條件元素的重新求值。因此,啟動 Rule 1 的條件應(yīng)再次求值。因為該條件應(yīng)再次等于 true(property2 的值仍相同,因為它在結(jié)果塊中未更改),Rule 1 應(yīng)再次啟動,從而導(dǎo)致無限循環(huán)的執(zhí)行。為了避免這種情況,添加 no-loop 屬性并將其賦值為 true,從而避免當(dāng)前活動規(guī)則再次執(zhí)行。

    清單 14. 修改工作內(nèi)存中的對象并使用規(guī)則元素的 no-loop 屬性

    ... 
    rule "Rule 1" 
    salience 100 
    no-loop true 
    when 
      objectA : ClassA (property2().equals(...)) 
    then 
      Object value = ... 
      objectA.setProperty1(value); 
      update( objectA ); 
    end 
     
    rule "Rule 2" 
    salience 100 
    when 
      objectB : ClassB() 
      objectA : ClassA ( property1().equals(objectB) ) 
      ... 
    then 
      ... 
    end 
    ... 

      如果對象不再是知識的一部分,則應(yīng)將該對象從工作內(nèi)存中撤回(參見清單 15)。通過在結(jié)果塊中調(diào)用 retract() 方法實現(xiàn)這一點。當(dāng)從工作內(nèi)存中移除對象之后,引用該對象的(屬于任何規(guī)則的)任何條件元素將不被求值。因為對象不再作為知識的一部分存在,所以規(guī)則沒有啟動的機會。

    清單 15. 從工作內(nèi)存中撤回對象

    ... 
    rule "Rule 1" 
    salience 100 
    when 
      objectB : ... 
      objectA : ... 
    then 
      Object value = ... 
      objectA.setProperty1(value); 
      retract(objectB); 
    end 
     
    rule "Rule 2" 
    salience 90 
    when 
      objectB : ClassB ( property().equals(...) ) 
    then 
     ... 
    end 
    ... 

      清單 15 包含兩個規(guī)則的定義的偽代碼。假設(shè)啟動兩個規(guī)則的條件等于 true。則應(yīng)該首先啟動 Rule 1,因為 Rule 1 的顯著值比 Rule 2 的高?,F(xiàn)在,注意在 Rule 1 的結(jié)果塊中,objectB 從工作內(nèi)存中撤回(也就是說,objectB 不再是知識的一部分)。該動作更改了規(guī)則引擎的 “執(zhí)行日程”,因為現(xiàn)在將不啟動 Rule 2。原因在于曾經(jīng)為真值的用于啟動 Rule 2 的條件不再為真,因為它引用了一個不再是知識的一部分的對象(objectB)。如果清單 15 中還有其他規(guī)則引用了 objectB,且這些規(guī)則尚未啟動,則它們將不會再啟動了。

      作為關(guān)于如何修改工作內(nèi)存中當(dāng)前知識的具體例子,我將重新編寫前面討論的規(guī)則源文件。業(yè)務(wù)規(guī)則仍然與 “要解決的問題” 小節(jié)中列出的一樣。但是,我將使用這些規(guī)則的不同實現(xiàn)取得相同的結(jié)果。按照這種方法,任何時候工作內(nèi)存中惟一可用的知識是 Machine 實例。換句話說,規(guī)則的條件元素將只針對 Machine 對象的屬性執(zhí)行比較。這與之前的方法有所不同,之前的方法還要對 Test 對象的屬性進行比較(參見 清單 12)。 這些規(guī)則的新實現(xiàn)被捕獲在示例應(yīng)用程序的 testRules2.drl 文件中。清單 16 展示了 testRules2.drl 中與分配測試相關(guān)的規(guī)則:

    清單 16. testRules2.drl 中與分配測試相關(guān)的規(guī)則

    rule "Tests for type1 machine" 
    lock-on-active true 
    salience 100 
     
    when 
      machine : Machine( type == "Type1" ) 
    then 
      Test test1 = testDAO.findByKey(Test.TEST1); 
      Test test2 = testDAO.findByKey(Test.TEST2); 
      Test test5 = testDAO.findByKey(Test.TEST5); 
      machine.getTests().add(test1); 
      machine.getTests().add(test2); 
      machine.getTests().add(test5); 
      update( machine ); 
    end 
     
    rule "Tests for type2, DNS server machine" 
    lock-on-active true 
    salience 100 
     
    when 
      machine : Machine( type == "Type2", functions contains "DNS Server") 
    then 
      Test test5 = testDAO.findByKey(Test.TEST5); 
      Test test4 = testDAO.findByKey(Test.TEST4); 
      machine.getTests().add(test5); 
      machine.getTests().add(test4); 
      update( machine ); 
    end 
     
    rule "Tests for type2, DDNS server machine" 
    lock-on-active true 
    salience 100 
     
    when 
      machine : Machine( type == "Type2", functions contains "DDNS Server") 
    then 
      Test test2 = testDAO.findByKey(Test.TEST2); 
      Test test3 = testDAO.findByKey(Test.TEST3); 
      machine.getTests().add(test2); 
      machine.getTests().add(test3); 
      update( machine ); 
    end 
     
    rule "Tests for type2, Gateway machine" 
    lock-on-active true 
    salience 100 
     
    when 
      machine : Machine( type == "Type2", functions contains "Gateway") 
    then 
      Test test3 = testDAO.findByKey(Test.TEST3); 
      Test test4 = testDAO.findByKey(Test.TEST4); 
      machine.getTests().add(test3); 
      machine.getTests().add(test4); 
      update( machine ); 
    end 
     
    rule "Tests for type2, Router machine" 
    lock-on-active true 
    salience 100 
     
    when 
      machine : Machine( type == "Type2", functions contains "Router") 
    then 
      Test test3 = testDAO.findByKey(Test.TEST3); 
      Test test1 = testDAO.findByKey(Test.TEST1); 
      machine.getTests().add(test3); 
      machine.getTests().add(test1); 
      update( machine ); 
    end 
    ... 

      如果將清單 16 中第一個規(guī)則的定義與 清單 10 中的定義相比較,可以看到,新方法沒有將分配給 Machine 對象的 Test 實例插入到工作內(nèi)存中,而是由規(guī)則的結(jié)果塊調(diào)用 update() 方法,讓規(guī)則引擎知道 Machine 對象已被修改。(Test 實例被添加/指定給它。) 如果看看清單 16 中其他的規(guī)則,應(yīng)該可以看到,每當(dāng)將測試分配給一個 Machine 對象時,都采用這種方法:一個或多個 Test 實例被分配給一個 Machine 實例,然后,修改工作知識,并通知規(guī)則引擎。

      還應(yīng)注意清單 16 中使用的 active-lock 屬性。該屬性的值被設(shè)為 true;如果不是這樣,在執(zhí)行這些規(guī)則時將陷入無限循環(huán)。將它設(shè)為 true 可以確保當(dāng)一個規(guī)則更新工作內(nèi)存中的知識時,最終不會導(dǎo)致對規(guī)則重新求值并重新執(zhí)行規(guī)則,也就不會導(dǎo)致無限循環(huán)。可以將 active-lock 屬性 看作 no-loop 屬性的加強版。 no-loop 屬性確保當(dāng)修改知識的規(guī)則更新后不會再被調(diào)用,而 active-lock 屬性則確保在修改知識以后,文件中的任何規(guī)則(其 active-lock 屬性被設(shè)為 true)不會重新執(zhí)行。

      清單 17 展示了其他規(guī)則有何更改:

    清單 17. testRules2.drl 中與分配測試到期日期有關(guān)的規(guī)則

    rule "Due date for Test 5" 
    salience 50 
    when 
      machine : Machine(tests contains (testDAO.findByKey(Test.TEST5))) 
    then 
      setTestsDueTime(machine, 14); 
    end 
     
    rule "Due date for Test 4" 
    salience 40 
    when 
      machine : Machine(tests contains (testDAO.findByKey(Test.TEST4))) 
    then 
      setTestsDueTime(machine, 12); 
    end 
     
    rule "Due date for Test 3" 
    salience 30 
    when 
      machine : Machine(tests contains (testDAO.findByKey(Test.TEST3))) 
    then 
      setTestsDueTime(machine, 10); 
    end 
     
    rule "Due date for Test 2" 
    salience 20 
    when 
      machine : Machine(tests contains (testDAO.findByKey(Test.TEST2))) 
    then 
      setTestsDueTime(machine, 7); 
    end 
     
    rule "Due date for Test 1" 
    salience 10 
    when 
      machine : Machine(tests contains (testDAO.findByKey(Test.TEST1))) 
    then 
      setTestsDueTime(machine, 3); 
    end 

      這些規(guī)則的條件元素現(xiàn)在檢查一個 Machine 對象的 tests 集合,以確定它是否包含特定的 Test 實例。因此,如前所述,按照這種方法,規(guī)則引擎只處理工作內(nèi)存中的一個對象(一個 Machine 實例),而不是多個對象(Machine 和 Test 實例)。

      要測試 testRules2.drl 文件,只需編輯示例應(yīng)用程序提供的 TestsRulesEngine 類(參見 清單 7):將 "testRules1.drl" 字符串改為 "testRules2.drl",然后運行 TestsRulesEngineTest JUnit 測試。所有測試都應(yīng)該成功,就像將 testRules1.drl 作為規(guī)則源一樣。

      關(guān)于斷點的注意事項

      如前所述,用于 Eclipse 的 Drools 插件允許在規(guī)則文件中設(shè)置斷點。要清楚,只有在調(diào)試作為 “Drools Application” 的程序時,才會啟用這些斷點。否則,調(diào)試器會忽略它們。

      例如,假設(shè)您想調(diào)試作為 “Drools Application” 的 TestsRulesEngineTest JUnit 測試類。在 Eclipse 中打開常見的 Debug 對話框。在這個對話框中,應(yīng)該可以看到一個 “Drools Application” 類別。在這個類別下,創(chuàng)建一個新的啟動配置。在這個新配置的 Main 選項卡中,應(yīng)該可以看到一個 Project 字段和一個 Main class 字段。對于 Project 字段,選擇 Drools4Demo 項目。對于 Main class 字段,輸入 junit.textui.TestRunner(參見圖 3)。

    圖 3. TestsRulesEngineTest 類的 Drools application 啟動配置(Main 選項卡)

      查看原圖(大圖)

      現(xiàn)在選擇 Arguments 選項卡并輸入 -t demo.test.TestsRulesEngineTest 作為程序參數(shù)(參見圖 4)。輸入該參數(shù)后,單擊對話框右下角的 Apply 按鈕,保存新的啟動配置。然后,可以單擊 Debug 按鈕,開始以 “Drools Application” 的形式調(diào)試 TestsRulesEngineTest JUnit 類。如果之前在 testRules1.drl 或 testRules2.drl 中添加了斷點,那么當(dāng)使用這個啟動配置時,調(diào)試器應(yīng)該會在遇到這些斷點時停下來。

    圖 4. TestsRulesEngineTest 類的 Drools Application 啟動配置(Arguments 選項卡)

      查看原圖(大圖)

      結(jié)束語

      使用規(guī)則引擎可以顯著降低實現(xiàn) Java 應(yīng)用程序中業(yè)務(wù)規(guī)則邏輯的組件的復(fù)雜性。使用規(guī)則引擎以聲明方法表達規(guī)則的應(yīng)用程序比其他應(yīng)用程序更容易維護和擴展。正如您所看到的,Drools 是一種功能強大的靈活的規(guī)則引擎實現(xiàn)。使用 Drools 的特性和能力,您應(yīng)該能夠以聲明方式實現(xiàn)應(yīng)用程序的復(fù)雜業(yè)務(wù)邏輯。Drools 使得學(xué)習(xí)和使用聲明式編程對于 Java 開發(fā)人員來說相當(dāng)容易。

      本文展示的 Drools 類是特定于 Drools 的。如果要在示例程序中使用另一種規(guī)則引擎實現(xiàn),代碼需要作少許更改。因為 Drools 是 JSR 94 兼容的,所以可以使用 Java Rule Engine API(如 JSR 94 中所指定)設(shè)計特定于 Drools 的類的接口。(Java Rule Engine API 用于 JDBC 在數(shù)據(jù)庫中的規(guī)則引擎。)如果使用該 API,則可以無需更改 Java 代碼而將規(guī)則引擎實現(xiàn)更改為另一個不同的實現(xiàn),只要這個不同的實現(xiàn)也是 JSR 94 兼容的。JSR 94 不解析包含業(yè)務(wù)規(guī)則的規(guī)則文件(在本文示例應(yīng)用程序中為 testRules1.drl)的結(jié)構(gòu)。文件的結(jié)構(gòu)將仍取決于您選擇的規(guī)則引擎實現(xiàn)。作為練習(xí),可以修改示例程序以使它使用 Java Rule Engine API,而不是使用 Java 代碼引用特定于 Drools 的類。

       本文示例源代碼或素材下載

?

總結(jié)

以上是生活随笔為你收集整理的使用 Drools 规则引擎实现业务逻辑,可调试drl文件的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。