主頁 > 後端開發 > Drools規則引擎實踐直白總結

Drools規則引擎實踐直白總結

2021-06-30 06:13:55 後端開發

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

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 函式 > [;< 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 組件的基本保障之一;

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" />
    </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)

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/288682.html

標籤:其他

上一篇:Dubbo 的設計思想,真優秀!

下一篇:Spring Boot 集成 Flyway,資料庫也能做版本控制,太牛逼了!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more