進行中  
          。。。
          日歷
          <2006年8月>
          303112345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789
          統計
          • 隨筆 - 13
          • 文章 - 1
          • 評論 - 3
          • 引用 - 0

          導航

          常用鏈接

          留言簿(1)

          隨筆檔案

          文章檔案

          相冊

          link

          搜索

          •  

          最新隨筆

          最新評論

          閱讀排行榜

          評論排行榜

           

          使用 Drools 規則引擎實現業務邏輯

          使用聲明性編程方法編寫程序的業務邏輯

          developerWorks
          文檔選項
          將此頁作為電子郵件發送

          將此頁作為電子郵件發送

          未顯示需要 JavaScript 的文檔選項

          樣例代碼


          最新推薦

          Java 應用開發源動力 - 下載免費軟件,快速啟動開發


          級別: 中級

          Ricardo Olivieri (roliv@us.ibm.com), 軟件工程師, IBM

          2006 年 6 月 19 日

          使用規則引擎可以通過降低實現復雜業務邏輯的組件的復雜性,降低應用程序的維護和可擴展性成本。本文展示了如何使用 Drools 規則引擎讓 Java? 應用程序更適應變化。Drools 的一個好處是具有允許將 Java 代碼直接嵌入規則文件中的附加語法。

          要求施加在當今軟件產品上的大多數復雜性是行 為和功能方面的,從而導致組件實現具有復雜的業務邏輯。實現 J2EE 或 J2SE 應用程序中業務邏輯最常見的方法是編寫 Java 代碼來實現需求文檔的規則和邏輯。在大多數情況下,該代碼的錯綜復雜性使得維護和更新應用程序的業務邏輯成為一項令人畏懼的任務,甚至對于經驗豐富的開發 人員來說也是如此。任何更改,不管多么簡單,仍然會產生重編譯和重部署成本。

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

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

          更多事情在變化……

          俗話說得好,“惟一不變的是變化。”軟件應用程序的業務邏輯正是如此,尤其是當今開發的軟件應用程序。出于以下原因,實現應用程序業務邏輯的組件可能必須更改:

          • 在開發期間或部署后修復代碼缺陷
          • 應付特殊狀況,即客戶一開始沒有提到要將業務邏輯考慮在內
          • 處理客戶已更改的業務目標
          • 符合組織對敏捷或迭代開發過程的使用

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

          Drools 是用 Java 語言編寫的開放源碼規則引擎,使用 Rete 算法(參閱 參考資料) 對所編寫的規則求值。Drools 允許使用聲明方式表達業務邏輯。可以使用 Java/XML 語法編寫規則,這對于入門 Drools 十分有用,因為您可以將 Java 代碼直接嵌入規則文件中。還可以使用 Groovy/XML 語法或 Python/XML 語法在 Drools 中編寫規則。Drools 還具有其他優點:

          • 非常活躍的社區
          • 易用
          • 快速的執行速度
          • 在 Java 開發人員中流行
          • JSR 94 兼容(JSR 94 是 Java Rule Engine API)(參閱 參考資料
          • 免費

          因此,您應該熟悉使用 Eclipse IDE 開發 Java 代碼。您應該熟悉 JUnit 測試框架并知道如何在 Eclipse 中使用。您還應該相當了解 XML。

          要解決的問題

          本文展示如何使用 Drools 作為示例 Java 應用程序中業務邏輯層的一部分。下列假設為應用程序解決的虛構問題設置了場景:

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

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

          • 在發運每臺機器之前,XYZ 在其上執行多個測試。

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

          • 當將測試分配給計算機時,也將測試到期日期 分配給機器。分配給計算機的測試不能晚于該到期日期執行。到期日期值取決于分配給機器的測試。

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

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

          • 分配測試和到期日期的邏輯一個月更改多次。當開發人員需要使用 Java 代碼實現該邏輯時,必須經歷一個冗長乏味的過程。
          何時使用規則引擎?

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

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

          1. 必須重新編譯應用程序代碼。
          2. 在測試中轉環境中刪除代碼。
          3. 由數據質量審核員檢查代碼。
          4. 由托管環境架構師批準更改。
          5. 計劃代碼部署。

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

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

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

          目前,在為機器分配測試和到期日期時必須遵循以下業務規則:

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

          • 如果計算機是 Type2 且其中一個功能為 DNS Server,則應執行 Test4 和 Test5。

          • 如果計算機是 Type2 且其中一個功能為 DDNS Server,則應執行 Test2 和 Test3。

          • 如果計算機是 Type2 且其中一個功能為 Gateway,則應執行 Test3 和 Test4。

          • 如果計算機是 Type2 且其中一個功能為 Router,則應執行 Test1 和 Test3。

          • 如果 Test1 是要在計算機上執行的測試之一,則測試到期日期距離機器的創建日期 3 天。該規則優先于測試到期日期的所有下列規則。

          • 如果 Test2 是要在計算機上執行的測試之一,則測試到期日期距離機器的創建日期 7 天。該規則優先于測試到期日期的所有下列規則。

          • 如果 Test3 是要在計算機上執行的測試之一,則測試到期日期距離機器的創建日期 10 天。該規則優先于測試到期日期的所有下列規則。

          • 如果 Test4 是要在計算機上執行的測試之一,則測試到期日期距離機器的創建日期 12 天。該規則優先于測試到期日期的所有下列規則。

          • 如果 Test5 是要在計算機上執行的測試之一,則測試到期日期距離機器的創建日期 14 天。

          捕獲為機器分配測試和測試到期日期的上述業務規則的當前 Java 代碼如清單 1 所示:


          清單 1. 使用 if-else 語句實現業務規則邏輯
          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 中的代碼不是太復雜,但也并不簡單。如果要對其進行更改,需要十分小心。一堆互相纏繞的 if-else 語句正試圖捕獲已經為應用程序標識的業務邏輯。如果您對業務規則不甚了解,就無法一眼看出代碼的意圖。





          回頁首


          導入示例程序

          使用 Drools 規則的示例程序附帶在本文的 ZIP 存檔中(參閱 下載)。程序使用 Drools 規則文件以聲明方法表示上一節定義的業務規則。我建議您在繼續之前下載 ZIP 存檔。它包含要導入到 Eclipse 工作區的 Eclipse (v3.1) Java 項目。選擇該選項以導入 Existing Projects into Workspace(參見圖 1):


          圖 1. 將示例程序導入到 Eclipse 工作區
          將示例程序導入到 Eclipse 工作區

          然后選擇下載的存檔文件并將其導入工作區中。您將在工作區中發現一個名為 DroolsDemo 的新 Java 項目,如圖 2 所示:


          圖 2. 導入到工作區中的示例程序
          導入到 Eclipse 工作區中的示例程序

          如果啟用了 Build automatically 選項,則代碼應該已編譯并可供使用。如果未啟用該選項,則現在構建 DroolsDemo 項目。





          回頁首


          檢查代碼

          現在來看一下示例程序中的代碼。該程序的 Java 類的核心集合位于 demo 包中。在該包中可以找到 MachineTest 域對象類。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 變量)—— 保存分配的測試到期日期值。
          • testsCollection 對象)—— 保存分配的測試集合。

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

          出于簡潔目的,機器的創建日期值設置為創建 Machine 類的實例時的當前時間。如果這是真實的應用程序,創建時間將設置為機器最終構建完成并準備測試的實際時間。

          Test 類的實例表示可以分配給機器的測試。Test實例由其 idname 惟一描述,如清單 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 規則引擎對 Machine 類的實例求值。基于 Machine 實例的 typefunctions 屬性的值,規則引擎確定應分配給 teststestsDueTime 屬性的值。

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

          RulesEngine 類

          demo 中比較重要(如果不是最重要的)的一個類是 RulesEngine 類。該類的實例用作封裝邏輯以訪問 Drools 類的包裝器對象。可以在您自己的 Java 項目中容易地重用該類,因為它所包含的邏輯不是特定于示例程序的。清單 4 展示了該類的屬性和構造函數:


          清單 4. RulesEngine 類的實例變量和構造函數
          																				
          public class RulesEngine {

          private static Logger logger = Logger.getLogger(RulesEngine.class);

          private RuleBase rules;
          private String rulesFile;
          private boolean debug = false;

          public RulesEngine(String rulesFile) throws RulesEngineException {
          super();
          this.rulesFile = rulesFile;
          try {
          rules = RuleBaseLoader.loadFromInputStream(this.getClass()
          .getResourceAsStream("/rules/" + rulesFile));
          } catch (Exception e) {
          throw new RulesEngineException("Could not load rules file: "
          + rulesFile, e);
          }
          }
          ...

          在清單 4 中可以看到,RulesEngine 類的構造函數接受字符串值形式的參數,該值表示包含業務規則集合的文件的名稱。該構造函數使用 RuleBaseLoader 類的靜態 loadFromInputStream() 方法將規則文件中包含的規則加載到內存中。(注意,該代碼假設規則文件位于程序類路徑中名為 rules 的文件夾中。)loadFromInputStream() 方法返回 Drools RuleBase 類的實例,它被分配給 RulesEngine 類的 rules 屬性。可以將 RulesBase 類的實例看作規則文件中所包含規則的內存中表示。

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


          清單 5. RulesEngine 類的 executeRules() 方法
          																				
          public List executeRules(WorkingEnvironmentCallback callback)
          throws RulesEngineException {

          try {
          WorkingMemory workingMemory = rules.newWorkingMemory();
          if (debug) {
          workingMemory.addEventListener(
          new DebugWorkingMemoryEventListener());
          }
          callback.initEnvironment(workingMemory);
          workingMemory.fireAllRules();
          return workingMemory.getObjects();
          } catch (FactException fe) {
          logFactException(fe);
          throw new RulesEngineException(
          "Exception occurred while attempting to execute "
          + "rules file: " + rulesFile, fe);
          }
          }

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

          規則結果的執行在調用 WorkingMemory 對象的 fireAllRules() 方法時執行。您可能奇怪(我希望您如此)知識是如何斷言到 WorkingMemory 實例中的。如果仔細看一下該方法的簽名,將會注意到所傳遞的參數是 WorkingEnvironmentCallback 接口的實例。executeRules() 方法的調用者需要創建實現該接口的對象。該接口只需要開發人員實現一個方法(參見清單 6 ):


          清單 6. WorkingEnvironmentCallback 接口
          																				
          public interface WorkingEnvironmentCallback {
          void initEnvironment(WorkingMemory workingMemory) throws FactException;
          }

          所以,應該是 executeRules() 方法的調用者將知識斷言到 WorkingMemory 實例中的。稍后將展示這是如何實現的。

          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("testRules.xml");
          this.testDAO = testDAO;
          }

          public void assignTests(final Machine machine) {
          rulesEngine.executeRules(new WorkingEnvironmentCallback() {
          public void initEnvironment(WorkingMemory workingMemory)
          throws FactException {

          workingMemory.assertObject(machine);
          Iterator functions = machine.getFunctions().iterator();
          while (functions.hasNext()) {
          workingMemory.assertObject(functions.next());
          }
          workingMemory.setApplicationData("testDAO", testDAO);
          };
          });
          }
          }

          TestsRulesEngine 類只有兩個實例變量。rulesEngine 屬性是 RulesEngine 類的實例。testDAO 屬性保存對 TestDAO 接口的具體實現的引用。rulesEngine 對象使用 “testRules.xml” 字符串作為其構造函數的參數來進行實例化。testRules.xml 文件以聲明方式捕獲 要解決的問題 中的業務規則。TestsRulesEngine 類的 assignTests() 方法調用 RulesEngine 類的 executeRules() 方法。在該方法中,創建了 WorkingEnvironmentCallback 接口的匿名實例,然后該實例被作為參數傳遞給 executeRules() 方法。

          如果查看 assignTests() 方法的實現,可以看到知識是如何斷言到 WorkingMemory 實例中的。WorkingMemory 類的 assertObject() 方法被調用以聲明在對規則求值時規則引擎應使用的知識。在這種情況下,知識由 Machine 類的實例和該機器的功能組成。被斷言的對象用于對規則的條件求值。

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

          TestsRulesEngine 類是示例程序中惟一的 Java 代碼,它包含專門致力于為機器分配測試和測試到期日期的實現的邏輯。該類中的邏輯永遠不需要更改,即使業務規則需要更新時。





          回頁首


          Drools 規則文件

          如前所述,testRules.xml 文件包含規則引擎為機器分配測試和測試到期日期所遵循的規則。它使用 Java/XML 語法表達所包含的規則。

          Drools 規則文件具有一個名為 rule-set 的根元素,它由一個或多個 rule 元素組成。每個 rule 規則由一個或多個 parameter 元素、一個或多個 condition 元素以及一個 consequence 元素組成。rule-set 元素還可以具有一個或多個 import 元素、一個或多個 application-data 元素以及一個 functions 元素。

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


          清單 8. testRules.xml 文件的第一部分
          <rule-set name="Tests assignment rules" xmlns="http://drools.org/rules"
          xmlns:java="http://drools.org/semantics/java"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
          <java:import>demo.Machine</java:import>
          <java:import>demo.Test</java:import>
          <java:import>demo.TestDAO</java:import>
          <java:import>java.util.Calendar</java:import>
          <java:import>java.sql.Timestamp</java:import>
          <java:import>java.lang.String</java:import>
          <application-data identifier="testDAO">TestDAO</application-data>
          ...

          在清單 8 中,可以看到根元素 rule-set 具有 name 屬性,用于標識該規則集合。import 元素允許規則執行引擎知道在哪里查找將在規則中使用的對象的類定義。application-data 元素允許規則引擎知道某個對象應該可以從規則中訪問,但該對象不應是用于對規則條件求值的知識的一部分。該元素具有 identifier 屬性,它應該與調用 WorkingMemory 類的 setApplicationData() 方法時使用的 identifier 相匹配(參見 清單 7)。

          functions 元素可以包含一個或多個 Java 函數的定義(參見清單 9)。如果看到 consequence 元素(稍后將討論)中重復的代碼,則應該提取該代碼并將其編寫為 functions 元素中的一個 Java 函數。但是,在使用該元素時要謹慎,因為您應該避免在 Drools 規則文件中編寫復雜的 Java 代碼。該元素中定義的 Java 函數應該簡短易懂。這不是 Drools 的技術限制。如果想要在規則文件中編寫復雜的 Java 代碼,也可以。但這樣做可能會讓您的代碼更加難以測試、調試和維護。復雜的 Java 代碼應該是 Java 類的一部分。如果需要 Drools 規則執行引擎調用復雜的 Java 代碼,則可以將對包含復雜代碼的 Java 類的引用作為應用程序數據傳遞給規則引擎。


          清單 9. testRules.xml 文件中定義的 Java 函數
          																				
          <java:functions>
          public static void setTestsDueTime(Machine machine, int numberOfDays) {
          setTestsDueTime(machine, Calendar.DATE, numberOfDays);
          }
          public static void setTestsDueTime(Machine machine, int field, int amount) {
          Calendar calendar = Calendar.getInstance();
          calendar.setTime(machine.getCreationTs());
          calendar.add(field, amount);
          machine.setTestsDueTime(new Timestamp(calendar.getTimeInMillis()));
          }
          </java:functions>
          ...

          清單 10 展示了在 testRules.xml 文件中找到的第一個規則:


          清單 10. testRules.xml 文件中定義的第一個規則
          																				
          <rule name="Tests for type1 machine" salience="100">
          <parameter identifier="machine">
          <java:class>
          Machine
          </java:class>
          </parameter>
          <java:condition>machine.getType().equals("Type1")</java:condition>
          <java:consequence>
          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);
          drools.assertObject(test1);
          drools.assertObject(test2);
          drools.assertObject(test5);
          </java:consequence>
          </rule>

          如清單 10 所示,rule 元素具有惟一標識 rule-setrulename 屬性。可以看到清單 10 中顯示的規則只接受一個參數:Machine 對象。如果返回 清單 7,將會看到 Machine 對象被斷言到 WorkingMemory 對象中。同一對象被作為參數傳遞給該規則。condition 元素對 Machine 實例(知識的一部分)求值以確定是否應執行規則的結果。如果條件等于 true,則啟動或執行結果。consequence 元素具有一個或多個 Java 語言語句。通過快速瀏覽該規則,可以很容易地識別出這是下列業務規則的實現:

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

          可能看起來有點怪的語句只有最后三條 Java 語句,它們是 consequence 元素的一部分。回憶 要解決的問題 中的業務規則,應分配給測試到期日期的值取決于分配給機器的測試。所以分配給機器的測試需要成為對規則求值時規則執行引擎應使用的知識的一部分。這正是 consequence 元素中最后三條語句要做的事情。這些語句使用一個名為 drools 的變量(可用于任何結果塊中)以更新規則引擎中的知識。

          確定規則執行順序

          規則的另一個重要的方面是可選的 salience 屬性。使用它可以讓規則執行引擎知道應該啟動規則集合中規則的結果塊的順序。具有最高顯著值的規則的結果塊首先執行;具有第二高顯著值的規則的結果塊第二執行,依此類推。當您需要讓規則按預定義順序啟動時,這一點非常重要,很快您將會看到。

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


          清單 11. testRules.xml 文件中與測試分配有關的其他規則
          																				
          <rule name="Tests for type2, DNS server machine" salience="100">
          <parameter identifier="machine">
          <java:class>
          Machine
          </java:class>
          </parameter>
          <parameter identifier="function">
          <java:class>
          String
          </java:class>
          </parameter>
          <java:condition>machine.getType().equals("Type2")</java:condition>
          <java:condition>function.equals("DNS Server")</java:condition>
          <java:consequence>
          Test test5 = testDAO.findByKey(Test.TEST5);
          Test test4 = testDAO.findByKey(Test.TEST4);
          machine.getTests().add(test5);
          machine.getTests().add(test4);
          drools.assertObject(test4);
          drools.assertObject(test5);
          </java:consequence>
          </rule>

          <rule name="Tests for type2, DDNS server machine" salience="100">
          <parameter identifier="machine">
          <java:class>
          Machine
          </java:class>
          </parameter>
          <parameter identifier="function">
          <java:class>
          String
          </java:class>
          </parameter>
          <java:condition>machine.getType().equals("Type2")</java:condition>
          <java:condition>function.equals("DDNS Server")</java:condition>
          <java:consequence>
          Test test2 = testDAO.findByKey(Test.TEST2);
          Test test3 = testDAO.findByKey(Test.TEST3);
          machine.getTests().add(test2);
          machine.getTests().add(test3);
          drools.assertObject(test2);
          drools.assertObject(test3);
          </java:consequence>
          </rule>

          <rule name="Tests for type2, Gateway machine" salience="100">
          <parameter identifier="machine">
          <java:class>
          Machine
          </java:class>
          </parameter>
          <parameter identifier="function">
          <java:class>
          String
          </java:class>
          </parameter>
          <java:condition>machine.getType().equals("Type2")</java:condition>
          <java:condition>function.equals("Gateway")</java:condition>
          <java:consequence>
          Test test3 = testDAO.findByKey(Test.TEST3);
          Test test4 = testDAO.findByKey(Test.TEST4);
          machine.getTests().add(test3);
          machine.getTests().add(test4);
          drools.assertObject(test3);
          drools.assertObject(test4);
          </java:consequence>
          </rule>

          <rule name="Tests for type2, Router machine" salience="100">
          <parameter identifier="machine">
          <java:class>
          Machine
          </java:class>
          </parameter>
          <parameter identifier="function">
          <java:class>
          String
          </java:class>
          </parameter>
          <java:condition>machine.getType().equals("Type2")</java:condition>
          <java:condition>function.equals("Router")</java:condition>
          <java:consequence>
          Test test3 = testDAO.findByKey(Test.TEST3);
          Test test1 = testDAO.findByKey(Test.TEST1);
          machine.getTests().add(test3);
          machine.getTests().add(test1);
          drools.assertObject(test1);
          drools.assertObject(test3);
          </java:consequence>
          </rule>
          ...

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


          清單 12. testRules.xml 文件中與測試到期日期分配有關的規則
          																				
          <rule name="Due date for Test 5" salience="50">
          <parameter identifier="machine">
          <java:class>
          Machine
          </java:class>
          </parameter>
          <parameter identifier="test">
          <java:class>
          Test
          </java:class>
          </parameter>
          <java:condition>test.getId().equals(Test.TEST5)</java:condition>
          <java:consequence>
          setTestsDueTime(machine, 14);
          </java:consequence>
          </rule>

          <rule name="Due date for Test 4" salience="40">
          <parameter identifier="machine">
          <java:class>
          Machine
          </java:class>
          </parameter>
          <parameter identifier="test">
          <java:class>
          Test
          </java:class>
          </parameter>
          <java:condition>test.getId().equals(Test.TEST4)</java:condition>
          <java:consequence>
          setTestsDueTime(machine, 12);
          </java:consequence>
          </rule>

          <rule name="Due date for Test 3" salience="30">
          <parameter identifier="machine">
          <java:class>
          Machine
          </java:class>
          </parameter>
          <parameter identifier="test">
          <java:class>
          Test
          </java:class>
          </parameter>
          <java:condition>test.getId().equals(Test.TEST3)</java:condition>
          <java:consequence>
          setTestsDueTime(machine, 10);
          </java:consequence>
          </rule>

          <rule name="Due date for Test 2" salience="20">
          <parameter identifier="machine">
          <java:class>
          Machine
          </java:class>
          </parameter>
          <parameter identifier="test">
          <java:class>
          Test
          </java:class>
          </parameter>
          <java:condition>test.getId().equals(Test.TEST2)</java:condition>
          <java:consequence>
          setTestsDueTime(machine, 7);
          </java:consequence>
          </rule>

          <rule name="Due date for Test 1" salience="10">
          <parameter identifier="machine">
          <java:class>
          Machine
          </java:class>
          </parameter>
          <parameter identifier="test">
          <java:class>
          Test
          </java:class>
          </parameter>
          <java:condition>test.getId().equals(Test.TEST1)</java:condition>
          <java:consequence>
          setTestsDueTime(machine, 3);
          </java:consequence>
          </rule>

          這些規則的實現與用于分配測試的規則的實現有一點相似,但我發現它們更有趣一些,原因有三。

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

          第二,僅當 Test 類的實例成為知識的一部分(即,包含在工作內存中)時,才能對規則的 condition 元素求值,該元素用于為 testsDueTime 屬性分配值。這看起來非常合乎邏輯,因為如果 Test 類的實例不在工作內存中,則規則執行引擎無法執行這些規則的條件中包含的比較。如果您想知道 Test 實例何時成為知識的一部分,那么回憶一下在執行與測試分配有關的規則的結果塊時,一個或多個 Test 實例曾被斷言到工作內存中(參見 清單 10清單 11)。

          第三,注意這些規則的結果塊相當簡短。原因在于在所有結果塊中調用了規則文件的 functions 元素中定義的 setTestsDueTime() Java 方法。該方法為 testsDueTime 屬性實際分配值。





          回頁首


          測試代碼

          既然已經仔細檢查了實現業務規則邏輯的代碼,現在應該檢查它是否能工作。要執行示例程序,運行 demo.test 包中的 TestsRulesEngineTest JUnit 測試。

          在該測試中,創建了 5 個 Machine 對象,每個對象具有不同的屬性集合(序號、類型和功能)。為這五個 Machine 對象的每一個都調用 TestsRulesEngine 類的 assignTests() 方法。一旦 assignTests() 方法完成其執行,就執行斷言以驗證 testRules.xml 中指定的業務規則邏輯是否正確(參見清單 13)。可以修改 TestsRulesEngineTest JUnit 類以多添加幾個具有不同屬性的 Machine 實例,然后使用斷言驗證結果是否跟預期一樣。


          清單 13. testTestsRulesEngine() 方法中用于驗證業務邏輯實現是否正確的斷言
          																				
          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);
          ...





          回頁首


          關于知識的其他備注

          值得一提的是,除了將對象斷言至工作內存之外,還可以在工作內存中修改對象或從中撤回對象。可以在規則的結果塊中進行這些操作。如果在結果塊中修改作為當前知識一部分的對象,并且所修改的屬性被用在 condition 元素中以確定是否應啟動規則,則應在結果塊中調用 drools 實例的 modifyObject() 方法。調用 modifyObject() 方法時,您讓 Drools 規則引擎知道對象已更新且使用該對象的一個或多個屬性的任何條件(或任何規則)應再次求值以確定條件的結果現在是 true 還是 false。這意味著甚至當前活動規則(在其結果塊中修改對象的規則)的條件都可以再次求值,這可能導致規則再次啟動,并可能導致無限循環。如果不希望這種情況發生,則應該包括 rule 元素的可選 no-loop 屬性并將其賦值為 true

          清單 14 用兩個規則的定義的偽代碼演示了這種情況。Rule 1 修改 objectAproperty1。然后它調用 drools 變量的 modifyObject(),以允許規則執行引擎知道該更新,從而觸發對引用 objectA 規則的 condition 元素的重新求值。因此,啟動 Rule 1 的條件應再次求值。因為該條件應再次等于 trueproperty2 的值仍相同,因為它在結果塊中未更改),Rule 1 應再次啟動,從而導致無限循環的執行。為了避免這種情況,添加 no-loop 屬性并將其賦值為 true,從而避免當前活動規則再次執行。


          清單 14. 修改工作內存中的對象并使用規則元素的 no-loop 屬性
          																				
          ...
          <rule name="Rule 1" salience="100" no-loop="true">
          <parameter identifier="objectA">
          <java:class>
          ClassA
          </java:class>
          </parameter>
          <java:condition>objectA.getProperty2().equals(...)</java:condition>
          <java:consequence>
          Object value = ...
          objectA.setProperty1(value);
          drools.modifyObject(objectA);
          </java:consequence>
          </rule>

          <rule name="Rule 2" salience="100">
          <parameter identifier="objectA">
          <java:class>
          ClassA
          </java:class>
          </parameter>
          <parameter identifier="objectB">
          <java:class>
          ClassB
          </java:class>
          </parameter>
          <java:condition>objectA.getProperty1().equals(objectB)</java:condition>
          ...
          <java:consequence>
          ...
          </java:consequence>
          </rule>
          ...

          如果對象不再是知識的一部分,則應將該對象從工作內存中撤回(參見清單 15)。通過在結果塊中調用 drools 對象的 retractObject() 方法實現這一點。當從工作內存中移除對象之后,引用該對象的(屬于任何規則的)任何 condition 元素將不被求值。因為對象不再作為知識的一部分存在,所以規則沒有啟動的機會。


          清單 15. 從工作內存中撤回對象
          																				
          ...
          <rule name="Rule 1" salience="100" >
          <parameter identifier="objectA">
          <java:class>
          ClassA
          </java:class>
          </parameter>
          <parameter identifier="objectB">
          <java:class>
          ClassB
          </java:class>
          </parameter>
          <java:condition>...</java:condition>
          <java:condition>...</java:condition>
          <java:consequence>
          Object value = ...
          objectA.setProperty1(value);
          drools.retractObject(objectB);
          </java:consequence>
          </rule>

          <rule name="Rule 2" salience="90">
          <parameter identifier="objectB">
          <java:class>
          ClassB
          </java:class>
          </parameter>
          <java:condition>objectB.getProperty().equals(...)</java:condition>
          ...
          <java:consequence>
          ...
          </java:consequence>
          </rule>
          ...

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





          回頁首


          結束語

          使用規則引擎可以顯著降低實現 Java 應用程序中業務規則邏輯的組件的復雜性。使用規則引擎以聲明方法表達規則的應用程序比不這樣做的應用程序更有可能容易維護和擴展。正如您所看到的, Drools 是一種功能強大的靈活的規則引擎實現。使用 Drools 的特性和能力,您應該能夠以聲明方式實現應用程序的復雜業務邏輯。Drools 使得學習和使用聲明編程對于 Java 開發人員來說相當容易,因為它具有 Java 語義模塊以允許使用 Java/XML 語法表達規則。

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






          回頁首


          下載

          描述 名字 大小 下載方法
          Sample Java project that uses Drools j-DroolsDemo.zip 5KB HTTP
          關于下載方法的信息 Get Adobe? Reader?




          回頁首
          posted on 2006-08-18 16:23 kongjia 閱讀(579) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
           
          Copyright © kongjia Powered by: 博客園 模板提供:滬江博客
          主站蜘蛛池模板: 安龙县| 同德县| 北票市| 政和县| 龙泉市| 松潘县| 吉安市| 若尔盖县| 吉林省| 射洪县| 墨玉县| 彩票| 沙湾县| 临泽县| 淳化县| 长顺县| 璧山县| 汝阳县| 海伦市| 太仓市| 阿克| 天全县| 德庆县| 十堰市| 凉城县| 句容市| 朝阳县| 堆龙德庆县| 永清县| 皋兰县| 嵊州市| 黄陵县| 永年县| 卫辉市| 黎平县| 石嘴山市| 开原市| 五河县| 郑州市| 高州市| 天全县|