天天看點

Drools規則引擎實踐直白總結

目錄

  • 1. 建立Drools環境(引入Drools相關依賴包、現在都流行spring boot,故最簡單有效的依賴才是最好的,kie-spring内部自行依賴了drools相關核心的依賴包)
  • 2. 了解Drools文法及其含義(LHS、RHS、Fact)
  • 3. 幾種實作運作Drools規則引擎方法
  • 4. Drl規則内容幾種寫法測試代碼
  • 5. 規則引擎引發的舉一反三,自己實作一個規則引擎

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

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

  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 函數 > [;< constraints >] ),其中函數除了内置的還可以自定義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 的文法結構:

        < result pattern >from accumulate(< source pattern >,init(< init code >),action(< action

        code >),reverse(< reverse code >),result(< result expression >) )

        < source pattern >:這個表示源模式。用法:也就是我們常用手 Object(xx:XX 屬性) 這個會去比對每一個源對象;

        < init code >:用法說明:init 是做初始化用的,簡單的說,在 source pattern 周遊完之後 就已經觸發,有點像 for 的開頭;

        < action code >: 用法說明:action 會執行是以滿足條件的源對象進行操作,像是 for的方法體。在裡面可寫 java code;

        < reverse code >: 這是一個可選的被選方言的語義代碼塊,如果存在,将為不再比對資源模式的每個資源對象執行。這個代碼塊的目的是不做在< action code > 塊中做的任何計算,是以,當一個資源對象被修改或删除收時,引擎可能做遞減計算,極大地提升了這些操作的性能;

        < result expression >: 傳回值,是根據 action 上面兩個周遊出來的結果進行一個返

        回,這個傳回值中也可以進行計算。

        < result pattern >: 傳回值類型,在< result expression >傳回值的類型再一次進行匹

        配,如果比對不成功則傳回 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;
                   
    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 元件的基本保障之一;

  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-》執行即可;

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

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 = {DroolsDemoApplication.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));
    }
 }
           

思路: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)

繼續閱讀