【java規則引擎】基本語法和相關屬性介紹

Love Lenka發表於2017-02-22

一個規則的語法資訊

【1】條件部分(LSH部分)
===>規則pattern之間的連線條件符號:
  (1)LHS 部分是由一個或多個條件組成,條件又稱之為 pattern(匹配模式),多個 pattern之間用可以使用 and 或 or 來進行連線,同時還可以使用小括號來確定 pattern 的優先順序
  (2)pattern 沒有符號連線,在 Drools,當中在 pattern 中沒有連線符號,那麼就用 and 來作為預設連線,所以在該規則的 LHS 部分中兩個 pattern 只有都滿足了才會返回 true。
  (3)宣告變數語法:[繫結變數名:]Object([field 約束])


===>規則pattern內部約束條件連線符號:
  (1)對於物件內部的多個約束的連線,可以採用“&&”(and)、“||”(or)和“,”(and)來實現.
  (2)這三個連線符號如果沒有用小括號來顯示的定義優先順序的話,那麼它們的執行順序是:“&&”(and)、“||”(or)和“,”。
  (3)表面上看“,”與“&&”具有相同的含義,但是有一點需要注意,“,”與“&&”和“||”不能混合使用,也就是說在有“&&”或“||”出現的 LHS 當中,是不可以有“,”連線符出現的,反之亦然。


===>規則pattern的內部模式的條件比較運算子號
  (1)在 Drools5當中共提供了十二種型別的比較運算子,分別是: >、 >=、 <、 <=、 = =、 !=、 contains、 not contains、memberof、not memberof、matches、not matches;
  (2)contains 只能用於物件的某個 Collection/Array 型別的欄位與另外一個值進行比較,是不是包含,作為比較的值可以是一個靜態的值,也可以是一個變數(繫結變數或者是一個 global 物件)
  (3)not contains 作用與 contains 作用相反,not contains 是用來判斷一個 Fact 物件的某個欄位(Collection/Array 型別)是不是不包含一個指定的物件,和 contains 比較符相同,它也只能用在物件的 field 當中
  (4)memberOf 是用來判斷某個 Fact 物件的某個欄位是否在一個集合(Collection/Array)當中,用法與 contains 有些類似,但也有不同,memberOf 的語法如下:Object(fieldName memberOf value[Collection/Array])
  (5)not memberof該運算子與 memberOf 作用洽洽相反,是用來判斷 Fact 物件當中某個欄位值是不是中某個集合(Collection/Array)當中,同時被比較的集合物件只能是一個變數
  (6)matches 是用來對某個 Fact 的欄位與標準的 Java 正規表示式進行相似匹配,被比較的字串可以是一個標準的 Java 正規表示式,但有一點需要注意,那就是正規表示式字串當中不用考慮“\”的轉義問題.matches 使用語法如下:Object(fieldName matches “正規表示式”)
  (7)與 matches 作用相反,是用來將某個 Fact 的欄位與一個 Java 標準正規表示式進行匹配,看是不是能與正規表示式匹配。not matches 使用語法如下:Object(fieldname not matches “正規表示式”)因為 not matches 用法與 matches 一樣,只是作用相反


【2】結果部分(RSH部分)
===>RHS 部分是規則真正要做事情的部分,可以將因條件滿足而要觸發的動作寫在該部分當中,在 RHS 當中可以使用 LHS 部分當中定義的繫結變數名、設定的全域性變數、或者是直接編寫 Java 程式碼(對於要用到的 Java 類,需要在規則檔案當中用 import 將類匯入後方能使用,這點和 Java 檔案的編寫規則相同)

===>在規則當中 LHS 就是用來放置條件的,所以在 RHS 當中雖然可以直接編寫Java 程式碼,但不建議在程式碼當中有條件判斷,如果需要條件判斷,那麼請重新考慮將其放在LHS 當中,否則就違背了使用規則的初衷

===>在 RHS 裡面,提供了一些對當前 Working Memory 實現快速操作的宏函式或物件,比如 insert/insertLogical、 update 和 retract 就可以實現對當前 Working Memory中的 Fact 物件進行新增、刪除或者是修改
  
  (1)函式 insert 的作用與我們在 Java 類當中呼叫 StatefulKnowledgeSession 物件的 insert 方法的作用相同,都是用來將一個 Fact 物件插入到當前的 Working Memory 當中。它的基本用法格式如下:insert(new Object());
  一旦呼叫 insert 宏函式,那麼 Drools 會重新與所有的規則再重新匹配一次,對於沒有設定 no-loop 屬性為 true 的規則,如果條件滿足,不管其之前是否執行過都會再執行一次,這個特性不僅存在於 insert 宏函式上,後面介紹的 update、retract 宏函式同樣具有該特性,所以在某些情況下因考慮不周呼叫 insert、update 或 retract 容易發生死迴圈,這點大家需要注意。

  (2)insertLogical 作用與 insert 類似,它的作用也是將一個 Fact 物件插入到當前的 Working Memroy 當中

  (3)update 函式意義與其名稱一樣,用來實現對當前 Working Memory 當中的 Fact 進行更新,update 宏函式的作用與 StatefulSession 物件的 update 方法的作用基本相同,都是用來告訴當前的 Working Memory 該 Fact 物件已經發生了變化。它的用法有兩種形式,一種是直接更新一個 Fact 物件,另一種為透過指定 FactHandle 來更新與指定 FactHandle 對應的 Fact 物件
  update函式的兩種用法:update(new Object());, update(new FactHandle(),new Object());
  從第二種用法格式上可以看出,它可以支援建立一個新的 Fact 物件,從而把 FactHandle物件指定的 Fact 物件替換掉,從而實現物件的全新更新
  和 StatefulSession 的 retract 方法一樣,宏函式 retract 也是用來將 Working Memory 當中某個 Fact 物件從 Working Memory 當中刪除,下面就透過一個例子來說明 retract 宏函式的用法。






【3】規則屬性部分
===>"salience"的屬性:該屬性的作用是透過一個數字來確認規則執行的優先順序,數字越大,執行越靠前.同時它的值可以是一個負數。預設情況下,規則的 salience 預設值為 0,所以如果我們不手動設定規則的 salience 屬性,那麼它的執行順序是隨機的。

===>"no-loop" 屬性:為 true 來實現規則對於同一個fact,只會被匹配一次。即便是呼叫insert(),update()等宏函式,也只會被執行一次。

===>"date-effective"屬性:預設情況下,date-effective 可接受的日期格式為“dd-MMM-yyyy”,規則生效時間

===>"date-expires"屬性:規則失效時間

===>"enabled"屬性:enabled 屬性比較簡單,它是用來定義一個規則是否可用的。該屬性的值是一個布林值,預設該屬性的值為 true,表示規則是可用的,如果手工為一個規則新增一個 enabled 屬性,並且設定其 enabled 屬性值為 false,那麼引擎就不會執行該規則

===>"dialect"屬性:該屬性用來定義規則當中要使用的語言型別,目前 Drools5 版本當中支援兩種型別的語言:mvel 和 java

===>"duration"屬性:對於一個規則來說,如果設定了該屬性,那麼規則將在該屬性指定的值之後在另外一個執行緒裡觸發。該屬性對應的值為一個長整型,單位是毫秒

===>"lock-on-active"屬性:當在規則上使用 ruleflow-group 屬性或 agenda-group 屬性的時候,將 lock-on-action 屬性的值設定為 true,可能避免因某些 Fact 物件被修改而使已經執行過的規則再次被啟用執行
  可以看出該屬性與 no-loop 屬性有相似之處,no-loop 屬性是為了避免 Fact 修改或呼叫了 insert、retract、update 之類而導致規則再次啟用執行,這裡的 lock-on-action 屬性也是起這個作用,lock-on-active 是 no-loop 的增強版屬性,它主要作用在使用 ruleflow-group 屬性或 agenda-group 屬性的時候。lock-on-active 屬性預設值為 false。

===>"activation-group"屬性:該屬性的作用是將若干個規則劃分成一個組,用一個字串來給這個組命名,這樣在執行的時候,具有相同 activation-group 屬性的規則中只要有一個會被執行,其它的規則都將不再執行。也就是說,在一組具有相同 activation-group 屬性的規則當中,只有一個規則會被執行,其它規則都將不會被執行。
  當然對於具有相同 activation-group 屬性的規則當中究竟哪一個會先執行,則可以用類似 salience 之類屬性來實現

===>"agenda-group"屬性:
  規則的呼叫與執行是透過 StatelessSession 或 StatefulSession 來實現的,一般的順序是建立一個 StatelessSession 或 StatefulSession,將各種經過編譯的規則的 package 新增到 session當中,接下來將規則當中可能用到的 Global 物件和 Fact 物件插入到 Session 當中,最後呼叫fireAllRules 方法來觸發、執行規則。在沒有呼叫最後一步 fireAllRules 方法之前,所有的規則及插入的 Fact 物件都存放在一個名叫 Agenda 表的物件當中,這個 Agenda 表中每一個規則及與其匹配相關業務資料叫做 Activation,在呼叫 fireAllRules 方法後,這些 Activation 會依次執行,這些位於 Agenda 表中的 Activation 的執行順序在沒有設定相關用來控制順序的屬性時(比如 salience 屬性),它的執行順序是隨機的,不確定的。
  Agenda Group 是用來在 Agenda 的基礎之上,對現在的規則進行再次分組,具體的分組方法可以採用為規則新增 agenda-group 屬性來實現
  agenda-group 屬性的值也是一個字串,透過這個字串,可以將規則分為若干個Agenda Group,預設情況下,引擎在呼叫這些設定了 agenda-group 屬性的規則的時候需要顯示的指定某個 Agenda Group 得到 Focus (焦點),這樣位於該 Agenda Group 當中的規則才會觸發執行,否則將不執行
  statefulSession.getAgenda().getAgendaGroup("002").setFocus();
  這句程式碼的作用是先得到當前的 Agenda,再透過 Agenda 得到名為 002 的 Agenda Group物件,最後把 Focus 設定到名為 002 的 Agenda Group 當中,這個位於名為 002 的 Agenda Group中的 rule2 規則會執行,而位於名為 001 的 Agenda Group 當中的 rule1 則不會被執行,因為名為 001 的 Agenda Group 沒有得到 Focus。
  實際應用當中 agenda-group 可以和 auto-focus 屬性一起使用,這樣就不會在程式碼當中顯示的為某個 Agenda Group 設定 Focus 了。一旦將某個規則的 auto-focus 屬性設定為 true,那麼即使該規則設定了 agenda-group 屬性,我們也不需要在程式碼當中顯示的設定 Agenda Group的 Focus 了。


===>"auto-focus"屬性:
  前面我們也提到 auto-focus 屬性,它的作用是用來在已設定了 agenda-group 的規則上設定該規則是否可以自動獨取 Focus,如果該屬性設定為 true,那麼在引擎執行時,就不需要顯示的為某個 Agenda Group 設定 Focus,否則需要。對於規則的執行的控制,還可以使用 Agenda Filter 來實現。在 Drools 當中,提供了一個名為 org.drools.runtime.rule.AgendaFilter 的 Agenda Filter 介面,使用者可以實現該介面,透過規則當中的某些屬性來控制規則要不要執行。 org.drools.runtime.rule.AgendaFilter 介面只有一個方法需要實現,方法體如下:
  public boolean accept(Activation activation);


public class TestAgendaFilter implements AgendaFilter {
    private String startName;
    public TestAgendaFilter(String startName){
      this.startName=startName;
    }
    public boolean accept(Activation activation) {
      String ruleName=activation.getRule().getName();
      if(ruleName.startsWith(this.startName)){
        return true;
      }else{
        return false;
      }
    }
}

從實現類中可以看到,我們採用的過濾方法是規則名的字首,透過 Activation 得到當前 的 Rule 物件,然後得到當前規則的 name,再用這個 name 與給定的 name 字首進行比較, 如果相同就返回 true就執行當前規則,否則就返回 false,不執行當前規則。

  AgendaFilter filter=new TestAgendaFilter("rule1");
  statefulSession.fireAllRules(filter);


===>"ruleflow-group"屬性:
  在使用規則流的時候要用到 ruleflow-group 屬性,該屬性的值為一個字串,作用是用來將規則劃分為一個個的組,然後在規則流當中透過使用 ruleflow-group 屬性的值,從而使用對應的規則。




【4】規則檔案中的函式
(1)概念
  函式是定義在規則檔案當中一程式碼塊,作用是將在規則檔案當中若干個規則都會用到的 業務操作封裝起來,實現業務程式碼的複用,減少規則編寫的工作量。
  函式的編寫位置可以是規則檔案當中 package 宣告後的任何地方,Drools 當中函式宣告 的語法格式如程式碼清單 2-47 所示:
  
  程式碼清單 2-47
  function void/Object functionName(Type arg...) {
    /*函式體的業務程式碼*/
  }

  

(2)例子

package test
import java.util.List;
import java.util.ArrayList;
/*
一個測試函式
用來向Customer物件當中新增指定數量的Order物件的函式
*/
function void setOrder(Customer customer,int orderSize) {
  List ls=new ArrayList();
  for(int i=0;i<orderSize;i++){
    Order order=new Order();
    ls.add(order);
  }
  customer.setOrders(ls);
}
/*
測試規則
*/
rule "rule1"
when
  $customer :Customer();
then
  setOrder($customer,5);
  System.out.println("rule 1 customer has order
size:"+$customer.getOrders().size());
end

/*
測試規則
*/
rule "rule2"
when
  $customer :Customer();
then
  setOrder($customer,10);
  System.out.println("rule 2 customer has order
size:"+$customer.getOrders().size());
end


(2)特殊應用
  實際應用當中,可以考慮使用在 Java 類當中定義靜態方法的辦法來替代在規則檔案當 中定義函式。我們知道 Java 類當中的靜態方法,不需要將該類例項化就可以使用該方法, 利用這種特性, Drools 為我們提供了一個特殊的 import 語句: import function,透過該 import 語句,可以實現將一個 Java 類中靜態方法引入到一個規則檔案當中,使得該檔案當中的規 則可以像使用普通的 Drools 函式一樣來使用 Java 類中某個靜態方法。

  package test;   
  public class RuleTools {
    public static void printInfo(String name){
      System.out.println("your name is :"+name);
    }
  }


  package test
  import function test.RuleTools.printInfo;
    /*
    測試規則
    */
    rule "rule1"
    when
      eval(true);
    then
      printInfo("高傑");
    end
  


【5】規則檔案中的查詢
(1)無引數的查詢

在 Drools 當中查詢以 query 關鍵字開始,以 end 關鍵字結束,在 package 當中一個查詢 要有唯一的名稱,查詢的內容就是查詢的條件部分,條件部分內容的寫法與規則的 LHS 部 分寫法完全相同


查詢的格式如下:
query "query name"
  #conditions
end
程式碼清單 2-53 是一個簡單的查詢示例。
程式碼清單 2-53

query "testQuery"
  customer:Customer(age>30,orders.size >10)
end

查 詢 的 調 用 是 由 StatefulSession 完 成 的 , 通 過 調 用 StatefulSession 對 象 的 getQueryResults(String queryName) 方 法 實 現 對 查 詢 的 調 用 , 該 方 法 的 調 用 會 返 回 一 個 QueryResults 物件,QueryResults 是一個類似於 Collection 介面的集合物件,在它當中存放 在若干個 QueryResultsRow 物件,透過 QueryResultsRow 可以得到對應的 Fact 物件,從而實 現根據條件對當前 WorkingMemory 當中 Fact 物件的查詢。


QueryResults  queryResults=statefulSession.getQueryResults("testQuery");

for(QueryResultsRow qr:queryResults){
  Customer cus=(Customer)qr.get("customer");
  //列印查詢結果
  System.out.println("customer name :"+cus.getName());
}

statefulSession.dispose();


(2)有引數查詢
和函式一樣,查詢也可以接收外部傳入引數,對於可以接收外部引數的查詢格式如下:
query "query name" (Object obj,...)
  #conditions
end
和不帶引數的查詢相比,唯一不同之外就是在查詢名稱後面多了一個用括號括起來的輸 入引數,查詢可接收多個引數,多個引數之間用“,”分隔,每個引數都要有對應的型別聲 明,程式碼清單 2-55 演示了一個簡單的帶引數的查詢示例。

程式碼清單 2-55
query "testQuery"(int $age,String $gender)
  customer:Customer(age>$age,gender==$gender)
end
在這個查詢當中,有兩個外部引數需要傳入,一個是型別為 int 的 $age;一個是型別為 String 的$gender(這裡傳入引數變數名前新增字首“$”符號,是為了和條件表示式中相關 變數區別開來)

對於帶引數的查詢,可以採用 StatefulSession 提供的
  getQueryResults(String queryName,new Object[]{})
方法來實現,這個方法中第一個引數為查詢的名稱,第二個 Object 物件陣列既為要輸入的引數集合
 

相關文章