Drools規則引擎實踐直白總結

夢在旅途發表於2021-06-29

Drools規則引擎,網上大把相關的文章介紹,但我感覺不夠直白,理解有些困難,且知識點沒有集中比較分散、有些還是引版本的內容,對與新手來說上手可能比較慢,而且比較容易走彎路,故我在深入研究並實踐於專案中後,在空閒時間花費精力整理了這篇文章,分享出來,便大家快速上手。

1. 建立Drools環境(引入Drools相關依賴包、現在都流行spring boot,故最簡單有效的依賴才是最好的,kie-spring內部自行依賴了drools相關核心的依賴包)

        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-spring</artifactId>
            <version>7.55.0.Final</version>
        </dependency>

2. 瞭解Drools語法及其含義(LHS、RHS、Fact)

  1. DRL檔案基本格式:

    package rules.testwrod //包名,必需,這是邏輯上,與物理路徑無關
    import xxxxx; //可選,匯入要使用的類名(還支援直接匯入靜態方法)
    global java.util.List myGlobalList;//可選,定義全域性變數(該變數由外部setGlobal傳入)
    
    function getResult(...){ //可選,自定義函式
        
    }
    
    query "query_gt_0"(...) //可選,自定義查詢(僅只有LHS內容)
    	$result:規則Pattern
    end
    
    rule “test001” //規則名稱,必需,且需唯一
    when //規則開始關鍵字,必需
    //這裡如果為空 則表示 eval(true); LHS內容(即:規則條件)
    then //規則條件結束關鍵字,必需,後面部份則是RHS內容(即:規則觸發的邏輯)
    System.out.println(“hello drools!”);
    end //規則結束關鍵字
    
  2. 涉及的名詞解釋:

    1. LHS:條件部分又被稱之為 Left Hand Side,簡稱為 LHS,在一個規則當中 when 與 then 中
      間的部分就是 LHS 部分。在 LHS 當中,可以包含 0~n 個條件,如果 LHS 部分沒空的話,
      那麼引擎會自動新增一個 eval(true)的條件,由於該條件總是返回 true,所以 LHS 為空的規
      則總是返回 true。LHS涉及的匹配元素用法如下:

      • Pattern 模式,語法:事實型別(約束),其中約束是可選的,如:Person(age>18),意思是:匹配工作記憶體中是Person型別且age>18,若存在則為true,即命中該條規則;Pattern 模式支援多個,之間使用空格或換行即可;【通俗點:當前工作記憶體中有沒有這個型別的物件(fact)】

      • 欄位約束,即Pattern 模式中括號中的部份,一般有:單值限制(如:age>18)、複合值限制(Person(sex in (0,1)),注:暫支援in與not in)和多限制(如:age>18 && age<30 或 age ( (> 30 && < 40) || (> 20 && < 25) )) 3種限制模式;欄位約束之間支援:||、&&、and、or、,(逗號即為AND)【通俗點:當前工作內中的這個型別(fact)的物件屬性還需滿足相關的約束條件】

      • 條件元素 eval,條件元素 eval 本實上是包羅永珍的,它允許執行任何語義程式碼(返回一個 boolean 原型)【通俗點:動態解釋執行程式碼邏輯,與js的eval有類似功能】

      • 條件元素 not,用於檢查在工作記憶體中不存在某東西。把"not"看作“一定沒有……”的意思

      • 條件元素 exists,用於檢查在工作記憶體中存在某型別(fact)。把"exists"看作“至少有一個……”的意思。(如果匹配到多個事實fact物件,也只會觸發執行一次RHS中邏輯)

      • 條件元素 forall,用於檢查在工作記憶體中所有的物件(fact)都必需滿足Pattern 模式,若有1個不滿足,則為false,只有全部滿足才為true;(如果匹配到多個事實fact物件,也只會觸發執行一次RHS中邏輯)

      • 條件元素 from, 讓使用者指定任意的資源,用於 LHS 模式的資料匹配。這允許引擎在非工作記憶體資料的基礎上進行推斷。資料來源可以是一個繫結變數的一個子欄位,或者方法呼叫的結果。它是一個超強結構,允許開箱即可與其他應用程式元件或框架整合使用【通俗點:from後面是指定一個自定義的資料來源,from前面是from後面結果得到的,類似sql中的select field=value from table;】

      • 條件元素 collect,允許規則在來自特定資源或工作記憶體的一個物件集合上進行推斷【通俗點:就是將符合匹配到多個事實fact物件累加到一起形成一個Collection集合】

      • 條件元素 accumulate,是一個更靈活強大的 collect 形式,它主要做的事是允許規則迭代整個
        一個物件的集合,為每個元素定製執行動作,並在結束時返回一個結果物件, accumulate
        既支援預定義的累積函式的使用,或也可以使用內聯的自定義程式碼,簡化的語法如下:

        accumulate( <source pattern 源模式>; <functions 函式 > [;] ),其中函式除了內建的還可以自定義JAVA函式,只需使用import accumulate 型別(該型別需實現AccumulateFunction介面) 自定義方法名;

        示例程式碼:

        accumulate(Message(createBy=="zuowj",$id:id);$countNum:count($id);$countNum>1)
        //含義:查詢工作記憶體中有Message型別的且過濾條件為(createBy=="zuowj")fact事實物件,並取出id,然後對所有的id進行count,最後判斷count的結果是否>1,轉換為SQL理解就是:
        //select id from Message where createBy='zuowj' group by id having count(id)>1;這樣應該好理解吧!
        

        inline 的語法結構:
        from accumulate(,init(),action(),reverse(),result() )<source>

        :這個表示源模式。用法:也就是我們常用手 Object(xx:XX 屬性) 這 個會去匹配每一個源物件。 :用法說明:init 是做初始化用的,簡單的說,在 source pattern 遍歷 完之後 就已經觸發,有點像 for 的開頭 : 用法說明:action 會執行所以滿足條件的源物件進行操作,像是 for 的方法體。在裡面可寫 java code : 這是一個可選的被選方言的語義程式碼塊,如果存在,將為不再匹配資 源模式的每個資源物件執行。這個程式碼塊的目的是不做在 塊中做的任何計算, 所以,當一個資源物件被修改或刪除收時,引擎可能做遞減計算,極大地提升了這些操作的 效能 : 返回值,是根據 action 上面兩個遍歷出來的結果進行一個返 回,這個返回值中也可以進行計算。 : 返回值型別,在返回值的型別再一次進行匹 配,如果匹配不成功則返回 false。

        示例程式碼:

        $res:String() from accumulate(Message(createBy=="zuowj",$cont:content),init(String allContent="";),action(allContent +=$cont;),result(allContent))
        //含義:for迴圈遍歷工作記憶體中Message型別且過濾條件為(createBy=="zuowj")的fact物件,初始化設定allContent="",每次執行allContent +=$cont,遍歷完成後將allContent返回給#res變更接收,類似JAVA for程式碼如下:
        // String res="",allContent="";
        //for (Object o:List<Object>){
         //   if(o instanceof Message && ((Message)o).getContent()=="zuowj"){
         //       allContent+=((Message)o).getContent();
         //   }    
        //}
        //res=allContent;
        
        <source>
    2. RHS:結果部分又被稱之為 Right Hand Side,簡稱為 RHS,在一個規則當中 then 後面部分就是 RHS,只有在 LHS 的所有條件都滿足時 RHS 部分才會執行。RHS 部分是規則真正要做事情的部分,可以將因條件滿足而要觸發的動作寫在該部分當中,在 RHS 當中可以使用 LHS 部分當中定義的繫結變數名、設定的全域性變數、或者是直接編寫 Java 程式碼(對於要用到的 Java 類,需要在規則檔案當中用 import 將類匯入後方能使用,這點和 Java 檔案的編寫規則相同,且不建議在RHS中寫條件判斷,如果需要條件判斷,那麼請重新考慮將其放在 LHS 當中,否則就違背了使用規則的初衷。),同時在 RHS 裡面,還提供了一些對當前 Working Memory 實現快速操作的巨集函式或物件,比如 insert/insertLogical、update/modify 和 retract 就可以實現對當前 WorkingMemory 中的 Fact 物件進行新增、修改或者是刪除;如果您覺得還要使用 Drools 當中提供的其它方法,那麼您還可以使用另一外巨集物件 drools,通過該物件可以使用更多的操作當前 Working Memory 的方法;同時 Drools 還提供了一個名為 kcontext 的巨集物件,使我們可以通過該物件直接訪問當前 Working Memory 的 KnowledgeRuntime。另外,通過註冊Channel實現命中規則後通過channels[通道名].send傳送訊息,並傳遞給JAVA程式碼的訂閱回撥方法;

    3. function:函式類似JAVA中的有返回值的方法,將RHS中涉及一些重複的動作封裝定義成函式(支援定義入參),能夠有效的簡少重複邏輯的編寫,但注意,函式的應用是不建議直接寫在LHS塊中的,若需使用需使用eval關鍵字,類似:eval(hello("夢在旅途"));

    4. query:查詢是一種搜尋工作記憶體中與指定條件匹配的事實的簡單方法。因此,它只包含規則的
      LHS 的結構,因此既不指定“when”也不指定“then”。查詢具有可選的引數集,每個引數
      可以可選地鍵入。如果未給出型別,則假定型別為 Object。引擎將根據需要嘗試強制值。
      查詢名稱對於 KieBase 是全域性的;因此不要向同一 RuleBase 的不同包新增相同名稱的查詢。
      要返回結果,請使用 ksession.getQueryResults(“name”),其中“name”是查詢
      的名稱。這將返回查詢結果的列表,這允許您檢索與查詢匹配的物件。查詢以 query 關鍵字開始,以 end 關鍵字結束,在 package 當中一個查詢要有唯一的名稱,查詢的內容就是查詢的條件部分,條件部分內容的寫法與規則的 LHS 部分寫法非常相同。

    5. global:全域性變數(類似java中的final static定義的變數 ),同一個session中所有rule都共享使用(如果多個包使用相同的識別符號宣告瞭全域性變數,那麼它們必須有相同的型別,並且它們所有都會引用相同的全域性變數的值),全域性變數沒有被插入到工作記憶體,因此,全域性變數絕不能被用來在規則中建立條件,除非它是一個恆定不變的值。引擎不能知道全域性變數的改變,不能跟蹤它們的變化。還需注意:常量值是不能改變的、包裝類是不能改變的、類似 javaBean,List 這類的操作,是可以改變內容的,但記憶體地址不會變;

  3. Drools的屬性說明(一般在在rule 名稱 與when之前設定屬性):

    1. Salience 優先順序,作用是用來設定規則執行的 優先順序, salience 屬性的值是一個數字,數字越大執行優先順序越高,同時它的值可以是一個負數。預設情況下,規則的 salience 預設值為 0;
    2. no-loop 防止死迴圈,作用是用來控制已經執行過的規則在條件再次滿足時是否再次執行,no-loop 屬性的值是一個布林型,預設情況下規則的no-loop 屬性的值為 false,如果 no-loop 屬性值為true,那麼就表示該規則只會被引擎檢查一次,如果滿足條件就執行規則的 RHS 部分;
    3. date- effective 日期比較小於等於,該屬性是用來控制規則只有在到達後才會觸發,在規則執行時,引擎會自動拿當前作業系統的時候與 date-effective 設定的時間值進行比對,只有 當前系統時間>=date-effective 設定的時間值時,規則才會觸發執行,否則執行將不執行;
    4. date- exspires 日期比較大於,該屬性的作用與 date-effective 屬性恰恰相反,當前系統時間<date-expires 值,date-expires 的作用是用來設定規則的有效期,引擎在執行規則的時候,會檢查規則有沒有 date-expires 屬性,如果有的話,那麼會將這個屬性的值與當前系統時間進行比對,如果大於系統時間,那麼規則就執行,否則就不執行;
    5. Dialect 方言,該屬性用來定義規則當中要使用的語言型別,支援 mvel 和 java,預設是java;
    6. Enabled 是否可用,用來定義一個規則是否可用的,如是設為false則不會執行該規則,預設為true;
    7. lock- on-active 規則只執行一次,當在規則上使用 ruleflow-group屬性或 agenda-group 屬性的時候,將 lock-on-action屬性的值設定為 true,可能避免因某些 Fact 物件被修改而使已經執行過的規則再次被啟用執行;
    8. activation-group 分組,作用是將若干個規則劃分成一個組,用一個字串來給這個組命名,這樣在執行的時候, 具有相同 activation- - group 屬性的規則中只要有一個會被執行,其它的規則都將不再執行;
    9. 其它的:agenda- group 議程分組、auto-focus 焦點分組;
    10. ruleflow-group 規則流,在使用規則流的時候要用到 ruleflow-group 屬性,該屬性的值為一個字串,作用是用來將規則劃分為一個個的組,然後在規則流當中通過使用 ruleflow-group 屬性的值,從而使用對應的規則,該屬性會通過流程的走向確定要執行哪一條規則。在規則流中有具體的說明。
  4. drools中相關核心型別說明:

    1. fact:即將一個普通的 JavaBean 插入到規則的 WorkingMemory 當中後的物件(如:kieSession.insert( javaBean物件))。規則可以對 Fact物件進行任意的讀寫操作,當一個 JavaBean 插入到 workingMemory 當中變成 Fact 之後(返回一個FactHandler),Fact 物件不是原來的 JavaBean物件的副本,而是原來 JavaBean 物件的引用;
    2. KieServices:就是一箇中心,通過它來獲取的各種物件來完成規則構建、管理和執行等操作;(KieServices.Factory.get() 獲得)
    3. KieContainer:是一個 KieBase 的容器,利用 KieContainer 來訪問 KBase 和 KSession 等資訊;(KieServices.newKieContainer()獲得)
    4. KieBase:可以理解為是一個知識倉庫,包含了若干的規則、流程、方法等,在 Drools 中主
      要就是規則和方法,KieBase 本身並不包含執行時的資料之類的,如果需要執行規則 KieBase
      中的規則的話,就需要根據 KieBase 建立 KieSession;(KieContainer.getKieBase() 或 newKieBase()獲得)
    5. KieSession:就是一個跟 Drools 引擎打交道的會話,基於 KieBase 建立,它會包含執行時資料,包含“事實 Fact”,並對執行時資料事實進行規則運算;分為兩類:有狀態的 KieSession(在多次與規則引擎進行互動中,維護會話的狀態)、無狀態的 StatelessKieSession(隔離了每次與規則引擎的互動,不會維護會話的狀態);(KieBase.newStatelessKieSession() 或 newKieSession()獲得)
    6. KieRepository:是一個單例物件,它是一個存放 KieModule 的倉庫;
    7. KieProject:KieContainer 通過 KieProject 來初始化、構造 KieModule,並將 KieModule 存放到 KieRepository 中,然後 KieContainer 可以通過 KieProject 來查詢 KieModule 定義的資訊,並根據這些資訊構造 KieBase 和KieSession;
    8. ClasspathKieProject:ClasspathKieProject 實現了 KieProject 介面,它提供了根據類路徑中的 META-INF/kmodule.xml 檔案構造 KieModule 的能力,也就是我們能夠基於 Maven 構造 Drools 元件的基本保障之一;

3. 幾種實現執行Drools規則引擎方法

  1. 直接使用KieHelper動態的將規則drl字串新增到規則引擎中並執行:

            String drl = "package zuowenjun.drools.rule.demo\n" +
                    "import cn.zuowenjun.model.Message;\n" +
                    "import java.util.List;\n" +
                    "rule \"test rule 1\"\n" +
                    "when \n" +
                    "$res:String() from accumulate(Message(createBy==\"zuowj\",$cont:content),init(String allContent=\"\";),action(allContent +=$cont;),result(allContent))"+
                    "then\n" +
                    "System.out.println($res +\"---rule 2\");\n" +
                    "end";
    
            KieBase kieBase = new KieHelper().addContent(drl, ResourceType.DRL).build();
            StatelessKieSession kieSession = kieBase.newStatelessKieSession();
            kieSession.execute(list);
    
  2. 直接使用KieHelper動態的將drl檔案新增到規則引擎中並執行:

    //rule.drl檔案(放在resources自定義rules目錄中,注:路徑可自定義)
    package zuowenjun.drools.rule.demo
    import cn.zuowenjun.model.Message;
    
    rule "test rule2"
    when
        $msg:Message(createBy=="zuowj")
    then
        System.out.println("hello zuowj! --rule2");
        $msg.setReplyBy("rule2");
    end
    

    注:如下使用的是ResourceFactory.newClassPathResource獲取drl檔案,其實裡面封裝了很多的獲取資源的方式(如:newFileResource、newByteArrayResource、newInputStreamResource等)

          //JAVA程式碼:
          Resource resource = ResourceFactory.newClassPathResource("rules/rule.drl");
            KieHelper helper = new KieHelper();
            KieBase kieBase = helper.addResource(resource, ResourceType.DRL).build();
            StatelessKieSession kieSession = kieBase.newStatelessKieSession();
            kieSession.execute(msg);
    
  3. 直接通過drools spring配置檔案實現規則新增及執行:

    <!--在resources目錄中新增drools spring的配置檔案(如:spring-drools.xml) -->
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:kie="http://drools.org/schema/kie-spring"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://drools.org/schema/kie-spring http://drools.org/schema/kie-spring.xsd">
    <kie:kmodule id="kmodule">
        <kie:kbase name="kbase" packages="zuowenjun.drools.rules">
        </kie:kbase>
    </kie:kmodule>
        <bean id="kiePostProcessor" class="org.kie.spring.KModuleBeanFactoryPostProcessor"/>
    </beans>
    

    JAVA程式碼:

    //配置,此處只需通過@ImportResource匯入配置檔案,自動註冊成BEAN即可,當然這裡是一個單獨配置檔案,實際也可以直接放在spring boot 的applcation的啟動類上即可。
    @Configuration
    @ImportResource("classpath:spring-drools.xml")
    public class DroolsBeansConfig {
    
    }
    
    //BEAN類中直接使用即可
    @Component
    public class RuleDemo {
        @Autowired
        private KieBase kbase;//KieBase是單例
        
            public Object checkRule(Message msg){
            StatelessKieSession kieSession = kbase.newStatelessKieSession();//session這裡儘可能每次都重新建立,成本也比較低,不要搞成單例的,這裡是無狀態的,用有狀態的也行
            kieSession.execute(msg);
            return msg;
        }
        
    }
    
    //如下是上面所有例項中用到的Message類(普通的javaBean)
    public class Message {
        private Long id;
        private String title;
        private String createBy;
        private Date createDate;
        private String content;
        private Long enabledFlag;
        private Boolean isReply;
        private String replyBy;
        private Date replyDate;
        private String replyContent;
        //省略getter、setter方法 ...
    }
    
  4. 還有一種是通過動態建立Kjar來實規則新增及執行,關鍵步驟如下:

    建立 pom.xml-》建立 kmodule.xml-》新增規則內容-》後面是建立session-》執行即可;

    程式碼就不再貼出了,可詳見網上資源。

4. Drl規則內容幾種寫法測試程式碼

 public Object checkRule(Object msg) {
        List<String> drlContentList=new ArrayList<>();

     	//當一個Fact物件為集合物件時的判斷
        //這個是當把某個集合(List)當成一個fact傳入工作記憶體中後,規則有效
        drlContentList.add("package zuowenjun.drools.rule.demo\n" +
                "import cn.zuowenjun.model.Message;\n" +
                "import java.util.List;\n" +
                "rule \"test rule 0\"\n" +
                "when \n" +
                "$list:List(size>0) \n" +
                "$msg:Message(createBy==\"zuowj\") from $list \n " +
                "then\n" +
                "System.out.println(\"hello zuowj! ---rule 0\");\n" +
                "$msg.setReplyBy(\"rule 0\");\n" +
                "end");

     	//普通Pattern 模式+欄位約束
        drlContentList.add("package zuowenjun.drools.rule.demo\n" +
                "import cn.zuowenjun.model.Message;\n" +
                "rule \"test rule 1\"\n" +
                "when \n" +
                "$msg:Message(createBy==\"zuowj\")\n " +
                "then\n" +
                "System.out.println(\"hello zuowj! ---rule 1\");\n" +
                "$msg.setReplyBy(\"rule 1\");\n" +
                "end");

     	//accumulate 內聯方式(類似for迴圈處理)
        drlContentList.add("package zuowenjun.drools.rule.demo\n" +
                "import cn.zuowenjun.model.Message;\n" +
                "rule \"test rule 2\"\n" +
                "when \n" +
                "exists(Message(createBy==\"zuowj\"))\n"+
                "$res:String() from accumulate(Message(createBy==\"zuowj\",$cont:content),init(String allContent=\"\";),action(allContent +=$cont;),result(allContent))"+
                "then\n" +
                "System.out.println($res +\"---rule 2\");\n" +
                "end");

     	//accumulate 普通函式方式
        drlContentList.add("package zuowenjun.drools.rule.demo\n" +
                "import cn.zuowenjun.model.Message;\n" +
                "rule \"test rule 2-2\"\n" +
                "when \n" + "accumulate(Message(createBy==\"zuowj\",$id:id);$countNum:count($id);$countNum>1) \n"+
                "then\n" +
                "System.out.println(\"count number:\"+ $countNum +\"---rule 2-2\");\n" +
                "end");

     	//not,不滿足時
        drlContentList.add("package zuowenjun.drools.rule.demo\n" +
               "import cn.zuowenjun.model.Message;\n" +
                "rule \"test rule 3\"\n" +
                "when not Message()\n" +
                "then\n" +
                "System.out.println(\"no message don't say hello! ---rule 3\");\n" +
                "end");

     	//exists,匹配執行一次
        drlContentList.add("package zuowenjun.drools.rule.demo\n" +
                "import cn.zuowenjun.model.Message;\n" +
                "rule \"test rule 4\"\n" +
                "when exists(Message(createBy==\"zuowj\"))\n" +
                "then\n" +
                "System.out.println(\"exists Message(createBy==zuowj) fact! ---rule 4\");\n" +
                "end");

     	//forall,工作記憶體中所有fact物件必需都滿足時才匹配規則
        drlContentList.add("package zuowenjun.drools.rule.demo\n" +
                "import cn.zuowenjun.model.Message;\n" +
                "rule \"test rule 5\"\n" +
                "when forall(Message(createBy==\"zuowj\"))\n" +
                "then\n" +
                "System.out.println(\"for all Message(createBy==zuowj) fact! ---rule 5\");\n" +
                "end");

     	//collect,將工作記憶體中所有fact物件新增到同一個集合中
        drlContentList.add("package zuowenjun.drools.rule.demo\n" +
                "import cn.zuowenjun.model.Message;\n" +
                "rule \"test rule 6\"\n" +
                "when Message() && $msgs:List(size>=9) from collect(Message(createBy==\"zuowj\"))\n" +
                "then\n" +
                "System.out.println(\"collect all Message fact(size=\" + $msgs.size() +\")! ---rule 6\");\n" +
                "end");


        KieHelper kieHelper=new KieHelper();
        for(String drl:drlContentList){
            kieHelper.addContent(drl,ResourceType.DRL);
        }


        KieBase kieBase = kieHelper.build();
        StatelessKieSession kieSession = kieBase.newStatelessKieSession();
        if (msg instanceof List){
            kieSession.execute((List<?>)msg);
        } else{
            kieSession.execute(msg);
        }

        return msg;
    }

//單元測試
/**
 * @author zuowenjun
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {FmsHelperApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class RuleTests {

    @Autowired
    private RuleDemo ruleDemo;

    @Test
    public void testRule() {
        List<Message> msgList=new ArrayList<>();
        for(int i=1;i<=10;i++) {
            int n=i;
            Message msg = new Message() {
                {
                    setCreateBy("zuowj");
                    setContent("hello drools" + String.valueOf(n));
                }
            };
            if (n==1){
                msg.setCreateBy("zuowenjun.cn");
            }
            msgList.add(msg);
        }

        Object obj = ruleDemo.checkRule(msgList);
        System.out.println(JsonUtils.deserializer(obj));
    }
 }

5. 規則引擎引發的舉一反三,自己實現一個規則引擎

思路:1.定義規則內容(即:規則執行單元),2.定義貫穿整個規則執行鏈條的上下文,內部就放fact、global等,具體實現參照如下示例程式碼【注意:如果僅是示例測試程式碼,並不規範,僅為演示提供思路】,整個規則執行採取:責任鏈的設計模式,即:每個規則只負責滿足自己條件的執行邏輯,最後更新上下文中相關的內容。

//規則鏈上下文,裡面就包含fact集合,全域性物件及執行過的rule
    public class RuleChainContext {
        public List<Object> factList;
        public static Map<String, Object> global;
        public RuleUnit execedRule;
    }
    
 //規則執行單元抽象類(這裡用抽象類而沒有用介面,是因為我要限定組織邏輯,可以理解為模板用法)
    public abstract class RuleUnit {
        public RuleUnit nextExecedRule;

        protected String name;

        public abstract String getName();

        public abstract boolean matchWhen(RuleChainContext context);

        public abstract void doThen(RuleChainContext context);

        public final void execute(RuleChainContext context) {
            if (matchWhen(context)) {
                doThen(context);
            }
            if (context.execedRule == null) {
                context.execedRule = this;
            }
            context.execedRule.nextExecedRule = this;
        }

    } 

通過單元測試模擬呼叫:

 @Test
    public void testDefRules() {
        List<RuleUnit> ruleUnitList = new ArrayList<>();
        ruleUnitList.add(new RuleUnit() {
            @Override
            public String getName() {
                name= "rule-1";
                return name;
            }

            @Override
            public boolean matchWhen(RuleChainContext context) {
                return context.factList.stream().anyMatch(f->f instanceof Integer && 1==(Integer)f);
            }

            @Override
            public void doThen(RuleChainContext context) {
                System.out.println("rule[include 1] do");
                //TODO:context
            }
        });

        ruleUnitList.add(new RuleUnit() {
            @Override
            public String getName() {
                name= "rule-2";
                return name;
            }

            @Override
            public boolean matchWhen(RuleChainContext context) {
                return context.factList.stream().anyMatch(f->f instanceof Integer && 2==(Integer)f);
            }

            @Override
            public void doThen(RuleChainContext context) {
                System.out.println("rule[include 2] do");
                //TODO:context
            }
        });

        RuleChainContext context=new RuleChainContext();
        context.factList=new ArrayList<>();
        context.factList.add(1);//加入1則觸發規則1
        context.factList.add(2);//加入2則觸發規則2,若減少規則相應減少

        for(RuleUnit ruleUnit:ruleUnitList){
            ruleUnit.execute(context);
        }

        System.out.println("result context:\n" + JsonUtils.deserializer(context));

    }

最終結果:

rule[include 1] do
rule[include 2] do
result context:
{"factList":[1,2],"execedRule":{"nextExecedRule":{"nextExecedRule":null,"name":"rule-2"},"name":"rule-1"}}

從輸出的結果可以看出,還是可以達到規則引擎的簡單效果的,當然如果想在生產環境實際應用自己實現的“類規則引擎”程式碼,實現規則與執行分開,也可將規則執行單元(RuleUnit)實現類單獨放到一個JAR包,然後再借助於URLClassLoader實現動態載入並新增自定義的實現規則執行單元(RuleUnit)的類,最後執行即可。【.NET方面的同學實現亦同理】

注:文中相關名詞解釋來源於網上,並非原創,我這裡僅為知識點總結!

可參考相關drools系列文章:

Drools_miemieY89-CSDN部落格

邵飛翔的圖書館 (360doc.com)

相關文章