大家在日常開發中,肯定遇到過一些業務規則變來變去的需求,比如:會員積分系統(今天要新注冊會員送10積分,明天要改成注冊送優惠券,后天搞活動要改成注冊自動變成高級會員...),此類需求,一般都是通過寫if分支來實現的,參考下面:
if (規則條件1){ //處理1 } else if (規則條件2){ //處理2 } else if (規則條件3){ //處理3 } ...
這種代碼毫無營養,而且很枯燥,有沒有辦法,將業務規則從代碼中抽離出來,以后規則變了,不用改代碼,只改規則配置就行?
今天要介紹的Drools,可以很好的解決此類問題,Drools是一個業務規則管理的開源框架,現在歸到jboss旗下,本文將介紹一些基本的用法,方便大家快速上手。
一、添加依賴項
<properties> <drools.version>6.5.0.Final</drools.version> <lombok.version>1.18.2</lombok.version> </properties> <dependencies> <dependency> <groupId>org.drools</groupId> <artifactId>drools-compiler</artifactId> <version>${drools.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> </dependencies>
注:不同版本的drools,api有較大差異,本文采用6.5.0.Final版本,其它版本的用法請自行參考官方文檔。(lombok是可選的,建議加上,簡化java代碼書寫)
二、新建一個演示用的pojo類Message
package com.cnblogs.yjmyzz.drools.demo.model; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @Setter @Getter @AllArgsConstructor public class Message { public enum MessageType { HI, GOODBYE, CHAT } private MessageType messageType; private String target; }
很簡單,不用多說,我們要模擬的場景是針對不同的messageType及target(也就是不同的業務規則 ),代碼能做出不同的處理。
三、編寫業務規則drl文件
drl 是drools rule的縮寫,大概長這個樣子:(規則文件一般放在resources資源目錄或下面的子目錄中),將下面的內容保存在hello.drl中
package com.cnblogs.yjmyzz.drools; import com.cnblogs.yjmyzz.drools.demo.model.Message; import java.util.concurrent.atomic.AtomicInteger; global String temp; global AtomicInteger count; //函數示例 function void print(String messgae){ System.out.println(messgae); } //規則1 rule "say-hi" when $message: Message(Message.MessageType.HI.equals(messageType) && target!=null) then print("hi," + $message.getTarget() + ", welcome to drools\n"); end
這里面可以分成幾個部分:
3.1 package部分
這個是用來管理包的,跟java的package概念類似,多個rule文件時,可以按包來管理rule代碼。
3.2 import
drl 規則文件中,可以直接使用java定義好的類,只需要import進來就好。
3.3 global
相當於全局變量聲明,多個規則文件中可共享該變量(后面會演示這一用法),要注意的是:共享全局變量建議不要用Integer這種"簡單"類型,這樣無法在規則文件中修改變量的"值",建議用復雜類型(比如上面的AtomicInteger)
3.4 function
即:函數,可以定義一些共用函數,在本drl文件被其它規則共用。
3.5 rule ... when ... then ... end
這個就是真正的規則了,rule后面的"say-hi"為規則名稱,when后面的相當於判斷條件(注:聲明條件的同時,還能聲明所謂fact"變量"-[不太准確,暫且這樣叫吧],$message: Message(...) 這里就相當於把后面一串東西,保存在$message這個fact"變量中)
小結一下:上面這個規則,相當於,如果Message的實例,其messageType為HI,且target值不為空,就打印輸出一句話。
很簡單吧,我們再加點難度,多加幾個規則 :
package com.cnblogs.yjmyzz.drools; import com.cnblogs.yjmyzz.drools.demo.model.Message; import java.util.concurrent.atomic.AtomicInteger; global String temp; global AtomicInteger count; //函數示例 function void print(String messgae){ System.out.println(messgae); } //規則1 rule "say-hi" when $message: Message(Message.MessageType.HI.equals(messageType) && target!=null) then print("hi," + $message.getTarget() + ", welcome to drools\n"); end rule "say-goodbye" when $message: Message(Message.MessageType.GOODBYE.equals(messageType) && target!=null) then print("bye bye ," + $message.getTarget() + "\n"); end rule "chat-and-goodbye" when $message: Message(Message.MessageType.CHAT.equals(messageType) && target!=null) then print($message.getTarget() + ", nice to meet you. But I have to go."); //將MessageType設置成GOODBYE $message.setMessageType(Message.MessageType.GOODBYE); //更新fact,以便觸發規則"say-goodbye" update($message); end rule "give-me-money" salience -1 //規則觸發的優先級,值越大,越先觸發 when $message: Message(target.equals("beggar")) then print("5毛拿好"); end rule "give-me-rice" salience 1 when $message: Message(target.equals("beggar")) then print("給你個包子吧"); end //本規則的效果:如果target="loop",會循環觸發,真到10次后停下 rule "loop" // no-loop //加上這行后,將禁止循環觸發 when $message: Message(target.equals("loop") && count.get()<10) then print("\n我會每隔1秒觸發,10次后停止!" + count.addAndGet(1)); Thread.sleep(1000); update($message) end
解釋下:
a: "chat-and-goodbye" 這條規則,如果messageType=CHAT,會修改$message.messageType為GOODBYE,然后update($mesage),相當於修改了Message實例后,會重新匹配say-goodbye規則
b:"give-me-money"、"give-me-rice" 這二個規則設置了salience,其實就是優先級,值越大,該規則越優先匹配。
c: "loop" 最后一條規則,這里用到了一個全局變量count,每次該規則且匹配到以后,計數器+1,然后再update,又匹配到本條規則,最終規則就是循環觸發10次。
一個項目里,可以同時有多個規則文件,還可以再加一個hello2.drl,演示共享變量
package com.cnblogs.yjmyzz.drools; import com.cnblogs.yjmyzz.drools.demo.model.Message; rule "global-demo" salience -99 when $message: Message(target.equals("beggar")) then System.out.println(temp); end
這里打印了共享變量temp(前提是target="begger")
四、resource/META-INF里放置kmodule.xml文件
內容如下:
<?xml version="1.0" encoding="UTF-8"?> <kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule"> <kbase name="hello" packages="hello"> <ksession name="ksession-hello"/> </kbase> </kmodule>
這個文件的主要作用之一,是在運行時,讓drools知道加載哪些drl文件。注意:這里packages="hello",就表示加載classpath:resources/hello下的drl文件。
最后項目的文件結構類似這樣:
五、跑一把
HelloApp內容如下:
package com.cnblogs.yjmyzz.drools.demo; import com.cnblogs.yjmyzz.drools.demo.model.Message; import org.kie.api.KieServices; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; import java.util.concurrent.atomic.AtomicInteger; public class HelloApp { public static void main(String[] args) { KieContainer kContainer = null; try { KieServices ks = KieServices.Factory.get(); kContainer = ks.getKieClasspathContainer(); KieSession kSession = kContainer.newKieSession("ksession-hello"); Message message1 = new Message(Message.MessageType.HI, "楊過"); kSession.insert(message1); kSession.fireAllRules(); Message message2 = new Message(Message.MessageType.GOODBYE, "姑姑"); kSession.insert(message2); kSession.fireAllRules(); Message message3 = new Message(Message.MessageType.CHAT, "美羊羊"); kSession.insert(message3); kSession.fireAllRules(); Message message4 = new Message(null, "beggar"); kSession.setGlobal("temp", "我是誰?我在哪?我要干什么?"); kSession.insert(message4); kSession.fireAllRules(); Message message5 = new Message(null, "loop"); kSession.setGlobal("count", new AtomicInteger(0)); kSession.insert(message5); kSession.fireAllRules(); } catch (Exception e) { e.printStackTrace(); } finally { if (kContainer != null) { kContainer.dispose(); } } } }
注意下共享變量,即:message4,message5部分,一般是在規則觸發前提前把共享變量先設置好初始值,最終輸出如下:
hi,楊過, welcome to drools //規則:say-hi bye bye ,姑姑 //規則:say-goodbye 美羊羊, nice to meet you. But I have to go. //規則:chat-and-goodbye bye bye ,美羊羊 //規則: say-goodbye(2次匹配成功) 給你個包子吧 //規則:give-me-rice 5毛拿好 //規則:give-me-money 我是誰?我在哪?我要干什么?//hello2.drl中的規則"global-demo" 我會每隔1秒觸發,10次后停止!1 //規則:loop循環10次 我會每隔1秒觸發,10次后停止!2 我會每隔1秒觸發,10次后停止!3 我會每隔1秒觸發,10次后停止!4 我會每隔1秒觸發,10次后停止!5 我會每隔1秒觸發,10次后停止!6 我會每隔1秒觸發,10次后停止!7 我會每隔1秒觸發,10次后停止!8 我會每隔1秒觸發,10次后停止!9 我會每隔1秒觸發,10次后停止!10
參考文章:
小明歷險記:規則引擎drools教程一
drools 6.5.0官方文檔
Learn Drools: Part I
Learn Drools: Part II (Cross Product)
Learn Drools: Part III (Filter Facts)
Learn Drools (Part 4): Inferences
Learn Drools (Part 5): Truth Maintenance
Learn Drools (Part 6): Rules and Statistics
Learn Drools (Part 7): Salience
Drools語法篇之Global全局變量
《Drools7.0.0.Final規則引擎教程》第4章 global全局變量
附:文件示例代碼drools-helloworld可從github下載