Drools規(guī)則引擎的結(jié)構(gòu)示意圖
Drools相關(guān)概念
- 事實(shí)(Fact):對(duì)象之間及對(duì)象屬性之間的關(guān)系
- 規(guī)則(rule):是由條件和結(jié)論構(gòu)成的推理語(yǔ)句,一般表示為if...Then屁桑。一個(gè)規(guī)則的if部分稱為L(zhǎng)HS,then部分稱為RHS。
- 模式(module):就是指IF語(yǔ)句的條件。這里IF條件可能是有幾個(gè)更小的條件組成的大條件互婿。模式就是指的不能再繼續(xù)分割下去的最小的原子條件。
Drools原理
DRL解釋執(zhí)行流程
Drools規(guī)則是在Java應(yīng)用程序上運(yùn)行的,其要執(zhí)行的步驟順序由代碼確定。為了實(shí)現(xiàn)這一點(diǎn),Drools規(guī)則引擎將業(yè)務(wù)規(guī)則轉(zhuǎn)換成執(zhí)行樹(shù),如下圖所示:
當(dāng)?shù)竭_(dá)一個(gè)Fact與規(guī)則相匹配的節(jié)點(diǎn)時(shí)勋颖,規(guī)則評(píng)估會(huì)將規(guī)則操作與觸發(fā)數(shù)據(jù)添加到一個(gè)叫作議程(Agenda)的組件中,如果同一個(gè)Fact與多個(gè)規(guī)則相匹配,就認(rèn)為這些規(guī)則是沖突的,議程(Agenda)使用沖突解決策略(Conflict Resolution strategy)管理這些沖突規(guī)則的執(zhí)行順序。整個(gè)生命周期中,規(guī)則評(píng)估與規(guī)則執(zhí)行之間有著明確的分割勋锤。規(guī)則操作的執(zhí)行可能會(huì)導(dǎo)致Fact的更新,從而與其他規(guī)則相匹配,導(dǎo)致它們的觸發(fā),稱之為前向鏈接饭玲。
規(guī)則引擎工作方式
1.Pattern Matching:對(duì)新的數(shù)據(jù)和被修改的數(shù)據(jù)進(jìn)行規(guī)則的匹配稱為模式匹配.
2.Production Memory:被訪問(wèn)的規(guī)則.
3.Agenda:負(fù)責(zé)具體執(zhí)行推理算法中被激發(fā)規(guī)則的結(jié)論部分,同時(shí) Agenda 通過(guò)沖突決策策略管理這些沖突規(guī)則的執(zhí)行順序.
Drools 中規(guī)則沖突決策策略有:
- (1) 優(yōu)先級(jí)策略
- (2) 復(fù)雜度優(yōu)先策略
- (4) 廣度策略
- (5) 深度策略
- (6) 裝載序號(hào)策略
- (7) 隨機(jī)策略
4.Working Memory:被推理機(jī)進(jìn)行匹配的數(shù)據(jù).
5.Inference Engine:進(jìn)行匹配的引擎稱為推理機(jī).
推理機(jī)所采用的模式匹配算法有下列幾種:
(1) Linear
(2) RETE
(3) Treat
(4) Leaps
Drools有專門的規(guī)則語(yǔ)法drl,就是專門描述活動(dòng)的規(guī)則是如何執(zhí)行的
## rule.drl文件內(nèi)容如下
package com.alibaba.rules
import com.alibaba.Order
rule "zero"
no-loop true
lock-on-active true
salience 1
when
$s : Order(point <= 300)
then
System.out.println("無(wú)贈(zèng)品");
doSth($s);
end
rule "giftOne"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout > 300 && amout <= 500)
then
System.out.println("Dior限量口紅");
doSth($s);
end
rule "giftTwo"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout > 500 && amout <= 700)
then
System.out.println("TF限量口紅");
doSth($s);
end
rule "giftThree"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout > 700 && amout <= 900)
then
System.out.println("SK-II套裝");
doSth($s);
end
rule "giftFour"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout > 900 && amout <= 1100)
then
System.out.println("MCM雙肩包");
doSth($s);
end
rule "giftFive"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout > 1100)
then
System.out.println("RADO雷達(dá)限量表");
doSth($s);
end
說(shuō)明:
- package 與Java語(yǔ)言類似,drl的頭部需要有package和import的聲明,package不必和物理路徑一致叁执。
- import 導(dǎo)出java Bean的完整路徑,也可以將Java靜態(tài)方法導(dǎo)入調(diào)用茄厘。
- rule 規(guī)則名稱,需要保持唯一,可以無(wú)限次執(zhí)行
- no-loop 定義當(dāng)前的規(guī)則是否不允許多次循環(huán)執(zhí)行,默認(rèn)是false,也就是當(dāng)前的規(guī)則只要滿足條件,可以無(wú)限次執(zhí)行矮冬。
- lock-on-active 將lock-on-active屬性的值設(shè)置為true,可避免因某些Fact對(duì)象被修改而使已經(jīng)執(zhí)行過(guò)的規(guī)則再次被激活執(zhí)行。
- salience用來(lái)設(shè)置規(guī)則執(zhí)行的優(yōu)先級(jí),salience屬性的值是一個(gè)數(shù)字,數(shù)字越來(lái)優(yōu)先級(jí)越高,值也可以是負(fù)數(shù)次哈。默認(rèn)為0,如果不設(shè)置規(guī)則的salience屬性,那么執(zhí)行順序是隨機(jī)的胎署。
- when 條件語(yǔ)句,就是當(dāng)?shù)竭_(dá)什么條件的時(shí)候
- then 根據(jù)條件的結(jié)果,來(lái)執(zhí)行什么動(dòng)作
- end 規(guī)則結(jié)束
這里需要有一個(gè)配置文件告訴代碼規(guī)則文件drl在哪里,在drools中這個(gè)文件就是kmodule.xml窑滞,放置到resources/META-INF目錄下琼牧。
kmodule.xml內(nèi)容如下:
<kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.drools.org/xsd/kmodule">
<configuration>
<property key="drools.evaluator.supersetOf" value="org.mycompany.SupersetOfEvaluatorDefinition"/>
</configuration>
<kbase name="KBase1" default="true" eventProcessingMode="cloud" equalsBehavior="equality" declarativeAgenda="enabled" packages="org.domain.pkg1">
<ksession name="KSession2_1" type="stateful" default="true"/>
<ksession name="KSession2_2" type="stateless" default="false" beliefSystem="jtms"/>
</kbase>
<kbase name="KBase2" default="false" eventProcessingMode="stream" equalsBehavior="equality" declarativeAgenda="enabled" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1">
<ksession name="KSession3_1" type="stateful" default="false" clockType="realtime">
<fileLogger file="drools.log" threaded="true" interval="10"/>
<workItemHandlers>
<workItemHandler name="name" type="org.domain.WorkItemHandler"/>
</workItemHandlers>
<calendars>
<calendar name="monday" type="org.domain.Monday"/>
</calendars>
<listeners>
<ruleRuntimeEventListener type="org.domain.RuleRuntimeListener"/>
<agendaEventListener type="org.domain.FirstAgendaListener"/>
<agendaEventListener type="org.domain.SecondAgendaListener"/>
<processEventListener type="org.domain.ProcessListener"/>
</listeners>
</ksession>
</kbase>
</kmodule>
說(shuō)明:
Kmodule 中可以包含一個(gè)到多個(gè) kbase,分別對(duì)應(yīng) drl 的規(guī)則文件。
Kbase 需要一個(gè)唯一的 name,可以取任意字符串哀卫。
packages 為drl文件所在resource目錄下的路徑巨坊。注意區(qū)分drl文件中的package與此處的package不一定相同。多個(gè)包用逗號(hào)分隔此改。默認(rèn)情況下會(huì)掃描 resources目錄下所有(包含子目錄)規(guī)則文件趾撵。
kbase的default屬性,標(biāo)示當(dāng)前KieBase是不是默認(rèn)的,如果是默認(rèn)的則不用名稱就可以查找到該 KieBase,但每個(gè) module 最多只能有一個(gè)默認(rèn) KieBase。
kbase 下面可以有一個(gè)或多個(gè) ksession,ksession 的 name 屬性必須設(shè)置,且必須唯一共啃。
規(guī)則中的條件操作符
Drools提供了十二中類型比較操作符:< 占调、<=、>移剪、>=究珊、==、!=纵苛、contains剿涮、not contains、memberOf赶站、not memberOf、matches纺念、not matches贝椿,并且這些條件都可以組合使用。
- 條件組合:各種操作符可以組合使用
- Fact對(duì)象私有屬性的操作:RHS中對(duì)Fact對(duì)象private屬性的操作必須使用getter和setter方法陷谱,而RHS中則必須要直接用.的方法去使用.
- contains:對(duì)比是否包含操作烙博,操作的被包含目標(biāo)可以是一個(gè)復(fù)雜對(duì)象也可以是一個(gè)簡(jiǎn)單的值。
- matches:正則表達(dá)式匹配烟逊,與java不同的是渣窜,不用考慮'/'的轉(zhuǎn)義問(wèn)題
- memberOf:判斷某個(gè)Fact屬性值是否在某個(gè)集合中,與contains不同的是他被比較的對(duì)象是一個(gè)集合宪躯,而contains被比較的對(duì)象是單個(gè)值或者對(duì)象乔宿。
規(guī)則中的結(jié)果部分
- insert:往當(dāng)前workingMemory中插入一個(gè)新的Fact對(duì)象,會(huì)觸發(fā)規(guī)則的再次執(zhí)行访雪,除非使用no-loop限定详瑞;
- update:更新
- modify:修改掂林,與update語(yǔ)法不同,結(jié)果都是更新操作
- retract:刪除
- function:定義一個(gè)方法坝橡,如:
function void console { System.out.println(); StringUtils.getId();// 調(diào)用外部靜態(tài)方法泻帮,StringUtils必須使用import導(dǎo)入,getId()必須是靜態(tài)方法} - declare:可以在規(guī)則文件中定義一個(gè)class计寇,使用起來(lái)跟普通java對(duì)象相似锣杂,你可以在RHS部分中new一個(gè)并且使用getter和setter方法去操作其屬性。
Demo
工程結(jié)構(gòu)
依賴文件pom.xml
<!--規(guī)則引擎drools-->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>7.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>7.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-templates</artifactId>
<version>7.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>7.0.0.Final</version>
</dependency>
配置文件kmodule.drl
package com.rules
import model.ProtocolType
dialect "java"
rule "jk16"
when
$protocol : ProtocolType(data matches "^1A12.*16$" )
then
$protocol.setType("xx燃?xì)獗韰f(xié)議");
System.out.println("觸發(fā)規(guī)則1:"+$protocol.getData());
end
rule "jkstd"
when
$protocol : ProtocolType(data matches "18.*26",length == 10)
then
$protocol.setType("xx水表標(biāo)準(zhǔn)協(xié)議");
System.out.println("觸發(fā)規(guī)則2:"+$protocol.getData());
end
測(cè)試程序Main.java
@Slf4j
public class Main {
@Test
public void test() {
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("ksession-rules");
ProtocolType protocolType = new ProtocolType();
protocolType.setData("1A1212345616");
protocolType.setLength(12);
kSession.insert(protocolType);
kSession.fireAllRules();
kSession.dispose();
System.out.println(protocolType);
log.info(protocolType.getType());
}
}
觸發(fā)規(guī)則1:1A1212345616
ProtocolType(data=1A1212345616,length=12,type=xx燃?xì)獗韰f(xié)議)
2020-08-19 18:21:35 [main] [io.example.demo.Main.test(Main.java:35)] - [INFO] xx燃?xì)獗韰f(xié)議
Process finished with exit code 0
Drools中的坑
Drools與SpringBoot集成時(shí)番宁,與熱部署工具spring-boot-devtools存在類加載器沖突的問(wèn)題元莫,會(huì)導(dǎo)致所有的規(guī)則失效。在drools的官方網(wǎng)站中有人提出了這個(gè)問(wèn)題贝淤,并認(rèn)為是個(gè)bug柒竞,但是drools的開(kāi)發(fā)者認(rèn)為這不是一個(gè)bug。
drools用的是lancher classloader,而devtools會(huì)把我們自己編寫(xiě)的model認(rèn)為是會(huì)隨時(shí)更改的類播聪,所以采用的是Restart ClassLoader類加載器進(jìn)行加載(為了快速進(jìn)行熱部署)朽基,devtools會(huì)有兩個(gè)類加載器,一個(gè)Classloader加載那些不會(huì)改變的類(第三方Jar包)离陶,另一個(gè)ClassLoader加載會(huì)更改的類稼虎,稱為 Restart ClassLoader。當(dāng)采用Launcher ClassLoader加載的A 類與Restart Class Loader的A類進(jìn)行對(duì)比時(shí)招刨,發(fā)現(xiàn)不一致霎俩,所以drools引擎自然無(wú)法進(jìn)行識(shí)別。
所以解決辦法就是將devtools的maven依賴去掉即可沉眶,或者采用drools官網(wǎng)中說(shuō)明的其它方法打却。