文章目錄
- 系列文章索引
- 五、規則屬性
- 1、enabled屬性
- 2、dialect屬性
- 3、salience屬性
- 4、no-loop屬性
- 5、activation-group屬性
- 6、agenda-group屬性
- 7、auto-focus屬性
- 8、timer屬性
- 9、date-effective屬性
- 10、date-expires屬性
- 六、Drools高級語法
- 1、global全局變量
- 2、query查詢
- 3、function函數
- 4、LHS加強
- (1)復合值限制in/not in
- (2)條件元素eval
- (3)條件元素not
- (4)條件元素exists
- (5)規則繼承
- 5、RHS加強
- (1)halt 終止后續規則
- (2)getWorkingMemory
- (3)getRule
- 6、規則文件編碼規范
系列文章索引
規則引擎Drools使用,0基礎入門規則引擎Drools(一)基礎入門
規則引擎Drools使用,0基礎入門規則引擎Drools(二)高級語法
規則引擎Drools使用,0基礎入門規則引擎Drools(三)整合springboot
規則引擎Drools使用,0基礎入門規則引擎Drools(四)WorkBench控制臺
規則引擎Drools使用,0基礎入門規則引擎Drools(五)實戰+決策表
五、規則屬性
前面我們已經知道了規則體的構成如下:
rule "ruleName"attributeswhenLHSthenRHS
end
Drools中提供的屬性如下表(部分屬性):
屬性名 | 說明 |
---|---|
salience | 指定規則執行優先級 |
dialect | 指定規則使用的語言類型,取值為java和mvel |
enabled | 指定規則是否啟用 |
date-effective | 指定規則生效時間 |
date-expires | 指定規則失效時間 |
activation-group | 激活分組,具有相同分組名稱的規則只能有一個規則觸發 |
agenda-group | 議程分組,只有獲取焦點的組中的規則才有可能觸發 |
timer | 定時器,指定規則觸發的時間 |
auto-focus | 自動獲取焦點,一般結合agenda-group一起使用 |
no-loop | 防止死循環 |
1、enabled屬性
enabled屬性對應的取值為true和false,默認值為true。
用于指定當前規則是否啟用,如果設置的值為false則當前規則無論是否匹配成功都不會觸發。
rule "rule_comparison_notMemberOf"//指定當前規則不可用,當前規則無論是否匹配成功都不會執行enabled falsewhenComparisonOperatorEntity(names not memberOf list)thenSystem.out.println("規則rule_comparison_notMemberOf觸發");
end
2、dialect屬性
dialect屬性用于指定當前規則使用的語言類型,取值為java和mvel,默認值為java。
注:mvel是一種基于java語法的表達式語言。
mvel像正則表達式一樣,有直接支持集合、數組和字符串匹配的操作符。
mvel還提供了用來配置和構造字符串的模板語言。
mvel表達式內容包括屬性表達式,布爾表達式,方法調用,變量賦值,函數定義等。
3、salience屬性
salience屬性用于指定規則的執行優先級,取值類型為Integer。數值越大越優先執行
。每個規則都有一個默認的執行順序,如果不設置salience屬性,規則體的執行順序為由上到下。
可以通過創建規則文件salience.drl來測試salience屬性,內容如下:
//當前規則文件用于測試執行優先級
package testsalience//定義第一個規則
rule "rule_1"salience 10//指定規則執行的優先級,數值越大越優先wheneval(true)//返回true,即當前規則匹配成功thenSystem.out.println("規則:rule_1觸發了...");
end//定義第二個規則
rule "rule_2"salience 11wheneval(true)//返回true,即當前規則匹配成功thenSystem.out.println("規則:rule_2觸發了...");
end//定義第三個規則
rule "rule_3"salience 5wheneval(true)//返回true,即當前規則匹配成功thenSystem.out.println("規則:rule_3觸發了...");
end
通過控制臺可以看到,規則文件執行的順序是按照我們設置的salience值由大到小順序執行的。
建議在編寫規則時使用salience屬性明確指定執行優先級。
4、no-loop屬性
no-loop屬性用于防止死循環
,當規則通過update之類的函數修改了Fact對象時,可能使當前規則再次被激活從而導致死循環。取值類型為Boolean,默認值為false。測試步驟如下:
第一步:編寫規則文件/resource/rules/noloop.drl
//當前規則文件用于測試noloop防止規則執行時死循環問題
package testnoloop
import com.drools.Studentrule "rule_noloop"no-loop true//使用no-loop解決死循環問題when$s:Student(age == 50)thenupdate($s);//調用update方法會導致相關規則重新匹配System.out.println("規則:rule_noloop觸發了...");
end
通過控制臺可以看到,我們沒有設置no-loop屬性的值,所以發生了死循環。設置no-loop的值為true再次測試則不會發生死循環。
5、activation-group屬性
activation-group屬性是指激活分組,取值為String類型。具有相同分組名稱的規則只能有一個規則被觸發
。
第一步:編寫規則文件/resources/rules/activationgroup.drl
//當前規則文件用于測試activation-group屬性
package testactivationgrouprule "rule_activationgroup_1"activation-group "mygroup"//對于同一個組內的規則,只能有一個觸發salience 5when//如果條件不寫,默認為true,表示規則匹配成功thenSystem.out.println("規則:rule_activationgroup_1觸發了...");
endrule "rule_activationgroup_2"activation-group "mygroup"salience 10when//如果條件不寫,默認為true,表示規則匹配成功thenSystem.out.println("規則:rule_activationgroup_2觸發了...");
end
通過控制臺可以發現,上面的兩個規則因為屬于同一個分組,所以只有一個觸發了。同一個分組中的多個規則如果都能夠匹配成功,具體哪一個最終能夠被觸發可以通過salience屬性確定。
6、agenda-group屬性
agenda-group屬性為議程分組,屬于另一種可控的規則執行方式。用戶可以通過設置agenda-group來控制規則的執行,只有獲取焦點的組中的規則才會被觸發。
第一步:創建規則文件/resources/rules/agendagroup.drl
package testagendagroup
/*此規則文件用于測試agenda-group屬性
*/
rule "rule_agendagroup_1"agenda-group "myagendagroup_1"whenthenSystem.out.println("規則rule_agendagroup_1觸發");
end
?
rule "rule_agendagroup_2"agenda-group "myagendagroup_1"whenthenSystem.out.println("規則rule_agendagroup_2觸發");
end
//========================================================
rule "rule_agendagroup_3"agenda-group "myagendagroup_2"whenthenSystem.out.println("規則rule_agendagroup_3觸發");
end
?
rule "rule_agendagroup_4"agenda-group "myagendagroup_2"whenthenSystem.out.println("規則rule_agendagroup_4觸發");
end
第二步:編寫單元測試
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
?
//設置焦點,對應agenda-group分組中的規則才可能被觸發
kieSession.getAgenda().getAgendaGroup("myagendagroup_1").setFocus();
?
kieSession.fireAllRules();
kieSession.dispose();
通過控制臺可以看到,只有獲取焦點的分組中的規則才會觸發。與activation-group不同的是,activation-group定義的分組中只能夠有一個規則可以被觸發,而agenda-group分組中的多個規則都可以被觸發。
7、auto-focus屬性
auto-focus屬性為自動獲取焦點,取值類型為Boolean,默認值為false。一般結合agenda-group屬性使用,當一個議程分組未獲取焦點時,可以設置auto-focus屬性來控制。
第一步:修改/resources/rules/agendagroup.drl文件內容如下
package testagendagroup
?
rule "rule_agendagroup_1"agenda-group "myagendagroup_1"whenthenSystem.out.println("規則rule_agendagroup_1觸發");
end
?
rule "rule_agendagroup_2"agenda-group "myagendagroup_1"whenthenSystem.out.println("規則rule_agendagroup_2觸發");
end
//========================================================
rule "rule_agendagroup_3"agenda-group "myagendagroup_2"auto-focus true //自動獲取焦點whenthenSystem.out.println("規則rule_agendagroup_3觸發");
end
?
rule "rule_agendagroup_4"agenda-group "myagendagroup_2"auto-focus true //自動獲取焦點whenthenSystem.out.println("規則rule_agendagroup_4觸發");
end
第二步:編寫單元測試
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
kieSession.fireAllRules();
kieSession.dispose();
通過控制臺可以看到,設置auto-focus屬性為true的規則都觸發了。
8、timer屬性
timer屬性可以通過定時器的方式指定規則執行的時間,使用方式有兩種:
方式一:timer (int: <initial delay> <repeat interval>?)
此種方式遵循java.util.Timer對象的使用方式,第一個參數表示幾秒后執行,第二個參數表示每隔幾秒執行一次,第二個參數為可選。
方式二:timer(cron: <cron expression>)
此種方式使用標準的unix cron表達式的使用方式來定義規則執行的時間。
第一步:創建規則文件/resources/rules/timer.drl
package testtimer
import java.text.SimpleDateFormat
import java.util.Date
/*此規則文件用于測試timer屬性
*/
?
rule "rule_timer_1"timer (5s 2s) //含義:5秒后觸發,然后每隔2秒觸發一次whenthenSystem.out.println("規則rule_timer_1觸發,觸發時間為:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
end
?
rule "rule_timer_2"timer (cron:0/1 * * * * ?) //含義:每隔1秒觸發一次whenthenSystem.out.println("規則rule_timer_2觸發,觸發時間為:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
end
第二步:編寫單元測試
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
final KieSession kieSession = kieClasspathContainer.newKieSession();
?
new Thread(new Runnable() {public void run() {//啟動規則引擎進行規則匹配,直到調用halt方法才結束規則引擎kieSession.fireUntilHalt();}
}).start();
?
Thread.sleep(10000);
//結束規則引擎
kieSession.halt();
kieSession.dispose();
注意:單元測試的代碼和以前的有所不同,因為我們規則文件中使用到了timer進行定時執行,需要程序能夠持續一段時間才能夠看到定時器觸發的效果。
9、date-effective屬性
date-effective屬性用于指定規則的生效時間,即只有當前系統時間大于等于
設置的時間或者日期規則才有可能觸發。默認日期格式為:dd-MMM-yyyy。用戶也可以自定義日期格式。
第一步:編寫規則文件/resources/rules/dateeffective.drl
package testdateeffective
/*此規則文件用于測試date-effective屬性
*/
rule "rule_dateeffective_1"date-effective "2023-11-22 10:00"//date-effective屬性用于指定當前規則生效時間whenthenSystem.out.println("規則rule_dateeffective_1觸發");
end
第二步:編寫單元測試
//設置日期格式
System.setProperty("drools.dateformat","yyyy-MM-dd HH:mm");
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
kieSession.fireAllRules();
kieSession.dispose();
注意:上面的代碼需要設置日期格式,否則我們在規則文件中寫的日期格式和默認的日期格式不匹配程序會報錯。
10、date-expires屬性
date-expires屬性用于指定規則的失效時間,即只有當前系統時間小于設置的時間或者日期規則才有可能觸發。默認日期格式為:dd-MMM-yyyy。用戶也可以自定義日期格式。
第一步:編寫規則文件/resource/rules/dateexpires.drl
package testdateexpires
/*此規則文件用于測試date-expires屬性
*/
?
rule "rule_dateexpires_1"date-expires "2023-11-22 10:00" //date-expires屬性用于指定當前規則的失效時間whenthenSystem.out.println("規則rule_dateexpires_1觸發");
end
第二步:編寫單元測試
//設置日期格式
System.setProperty("drools.dateformat","yyyy-MM-dd HH:mm");
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
kieSession.fireAllRules();
kieSession.dispose();
注意:上面的代碼需要設置日期格式,否則我們在規則文件中寫的日期格式和默認的日期格式不匹配程序會報錯。
六、Drools高級語法
1、global全局變量
global關鍵字用于在規則文件中定義全局變量,它可以讓應用程序的對象在規則文件中能夠被訪問。可以用來為規則文件提供數據或服務。
語法結構為:global 對象類型 對象名稱
在使用global定義的全局變量時有兩點需要注意:
1、如果對象類型為包裝類型時,在一個規則中改變了global的值,那么只針對當前規則有效,對其他規則中的global不會有影響。可以理解為它是當前規則代碼中的global副本,規則內部修改不會影響全局的使用。
2、如果對象類型為集合類型或JavaBean時,在一個規則中改變了global的值,對java代碼和所有規則都有效。
下面我們通過代碼進行驗證:
第一步:創建UserService類
public class UserService {public void save(){System.out.println("UserService.save()...");}
}
第二步:編寫規則文件/resources/rules/global.drl
package testglobal
/*此規則文件用于測試global全局變量
*/
?
global java.lang.Integer count //定義一個包裝類型的全局變量
global com.drools.UserService userService //定義一個JavaBean類型的全局變量
global java.util.List gList //定義一個集合類型的全局變量
?
rule "rule_global_1"whenthencount += 10; //全局變量計算,只對當前規則有效,其他規則不受影響userService.save();//調用全局變量的方法gList.add("itcast");//向集合類型的全局變量中添加元素,Java代碼和所有規則都受影響gList.add("itglobal");System.out.println("count=" + count);System.out.println("gList.size=" + gList.size());
end
?
rule "rule_global_2"whenthenuserService.save();System.out.println("count=" + count);System.out.println("gList.size=" + gList.size());
end
第三步:編寫單元測試
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
?
//設置全局變量,名稱和類型必須和規則文件中定義的全局變量名稱對應
kieSession.setGlobal("userService",new UserService());
kieSession.setGlobal("count",5);
List list = new ArrayList();//size為0
kieSession.setGlobal("gList",list);
?
kieSession.fireAllRules();
kieSession.dispose();
?
//因為在規則中為全局變量添加了兩個元素,所以現在的size為2
System.out.println(list.size());
結果:
UserService.save()…
count=15
gList.size=2
UserService.save()…
count=5
gList.size=2
2
2、query查詢
query查詢提供了一種查詢working memory中符合約束條件的Fact對象
的簡單方法。它僅包含規則文件中的LHS部分,不用指定“when”和“then”部分并且以end結束。具體語法結構如下:
query 查詢的名稱(可選參數)LHS
end
具體操作步驟:
第一步:編寫規則文件/resources/rules/query.drl
package testquery
import com.drools.Student
/*此規則文件用于測試query查詢
*/
?
//不帶參數的查詢
//當前query用于查詢Working Memory中age>10的Student對象
query "query_1"$student:Student(age > 10)
end
?
//帶有參數的查詢
//當前query用于查詢Working Memory中age>10同時name需要和傳遞的參數name相同的Student對象
query "query_2"(String sname)$student:Student(age > 20 && name == sname)
end
第二步:編寫單元測試
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
?
Student student1 = new Student();
student1.setName("張三");
student1.setAge(12);
?
Student student2 = new Student();
student2.setName("李四");
student2.setAge(8);
?
Student student3 = new Student();
student3.setName("王五");
student3.setAge(22);
?
//將對象插入Working Memory中
kieSession.insert(student1);
kieSession.insert(student2);
kieSession.insert(student3);
?
//調用規則文件中的查詢
QueryResults results1 = kieSession.getQueryResults("query_1");
int size = results1.size();
System.out.println("size=" + size);
for (QueryResultsRow row : results1) {Student student = (Student) row.get("$student");System.out.println(student);
}
?
//調用規則文件中的查詢
QueryResults results2 = kieSession.getQueryResults("query_2","王五");
size = results2.size();
System.out.println("size=" + size);
for (QueryResultsRow row : results2) {Student student = (Student) row.get("$student");System.out.println(student);
}
//kieSession.fireAllRules();
kieSession.dispose();
結果:
size=2
com.drools.Student@74cec793
com.drools.Student@ec0c838
size=1
com.drools.Student@ec0c838
3、function函數
function關鍵字用于在規則文件中定義函數,就相當于java類中的方法一樣。可以在規則體中調用定義的函數。使用函數的好處是可以將業務邏輯集中放置在一個地方,根據需要可以對函數進行修改。
函數定義的語法結構如下:
function 返回值類型 函數名(可選參數){//邏輯代碼
}
具體操作步驟:
第一步:編寫規則文件/resources/rules/function.drl
package testfunction
import com.itheima.drools.entity.Student
/*此規則文件用于測試function函數
*/
?
//定義一個函數
function String sayHello(String name){return "hello " + name;
}
?
rule "rule_function_1"when$student:Student(name != null)then//調用上面定義的函數String ret = sayHello($student.getName());System.out.println(ret);
end
第二步:編寫單元測試
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
?
Student student = new Student();
student.setName("小明");
?
kieSession.insert(student);
?
kieSession.fireAllRules();
kieSession.dispose();
4、LHS加強
(1)復合值限制in/not in
復合值限制是指超過一種匹配值的限制條件,類似于SQL語句中的in關鍵字。Drools規則體中的LHS部分可以使用in或者not in進行復合值的匹配。具體語法結構如下:
Object(field in (比較值1,比較值2...))
舉例:
$s:Student(name in ("張三","李四","王五"))
$s:Student(name not in ("張三","李四","王五"))
(2)條件元素eval
eval用于規則體的LHS部分,并返回一個Boolean類型的值。語法結構如下:
eval(表達式)
舉例:
eval(true)
eval(false)
eval(1 == 1)
(3)條件元素not
not用于判斷Working Memory中是否存在某個Fact對象,如果不存在則返回true,如果存在則返回false。語法結構如下:
not Object(可選屬性約束)
舉例:
not Student()
not Student(age < 10)
(4)條件元素exists
exists的作用與not相反,用于判斷Working Memory中是否存在某個Fact對象,如果存在則返回true,不存在則返回false。語法結構如下:
exists Object(可選屬性約束)
舉例:
exists Student()
exists Student(age < 10 && name != null)
可能有人會有疑問,我們前面在LHS部分進行條件編寫時并沒有使用exists也可以達到判斷Working Memory中是否存在某個符合條件的Fact元素的目的,那么我們使用exists還有什么意義?
兩者的區別:當向Working Memory中加入多個滿足條件的Fact對象時,使用了exists的規則執行一次,不使用exists的規則會執行多次。
例如:
規則文件(只有規則體):
rule "使用exists的規則"whenexists Student()thenSystem.out.println("規則:使用exists的規則觸發");
end
?
rule "沒有使用exists的規則"whenStudent()thenSystem.out.println("規則:沒有使用exists的規則觸發");
end
Java代碼:
kieSession.insert(new Student());
kieSession.insert(new Student());
kieSession.fireAllRules();
上面第一個規則只會執行一次,因為Working Memory中存在兩個滿足條件的Fact對象,第二個規則會執行兩次。
(5)規則繼承
規則之間可以使用extends關鍵字進行規則條件部分的繼承,類似于java類之間的繼承。
例如:
rule "rule_1"whenStudent(age > 10)thenSystem.out.println("規則:rule_1觸發");
end
?
rule "rule_2" extends "rule_1" //繼承上面的規則when/*此處的條件雖然只寫了一個,但是從上面的規則繼承了一個條件,所以當前規則存在兩個條件,即Student(age < 20)和Student(age > 10)*/Student(age < 20) thenSystem.out.println("規則:rule_2觸發");
end
5、RHS加強
RHS部分是規則體的重要組成部分,當LHS部分的條件匹配成功后,對應的RHS部分就會觸發執行。一般在RHS部分中需要進行業務處理。
在RHS部分Drools為我們提供了一個內置對象,名稱就是drools
。
(1)halt 終止后續規則
halt方法的作用是立即終止后面所有規則的執行。
package testhalt
rule "rule_halt_1"whenthenSystem.out.println("規則:rule_halt_1觸發");drools.halt();//立即終止后面所有規則執行
end
?
//當前規則并不會觸發,因為上面的規則調用了halt方法導致后面所有規則都不會執行
rule "rule_halt_2"whenthenSystem.out.println("規則:rule_halt_2觸發");
end
(2)getWorkingMemory
getWorkingMemory方法的作用是返回工作內存對象。
package testgetWorkingMemory
rule "rule_getWorkingMemory"whenthenSystem.out.println(drools.getWorkingMemory());
end
(3)getRule
getRule方法的作用是返回規則對象。
package testgetRule
rule "rule_getRule"whenthenSystem.out.println(drools.getRule());
end
6、規則文件編碼規范
我們在進行drl類型的規則文件編寫時盡量遵循如下規范:
- 所有的規則文件(.drl)應統一放在一個規定的文件夾中,如:/rules文件夾
- 書寫的每個規則應盡量加上注釋。注釋要清晰明了,言簡意賅
- 同一類型的對象盡量放在一個規則文件中,如所有Student類型的對象盡量放在一個規則文件中
- 規則結果部分(RHS)盡量不要有條件語句,如if(…),盡量不要有復雜的邏輯和深層次的嵌套語句
- 每個規則最好都加上salience屬性,明確執行順序
- Drools默認dialect為"Java",盡量避免使用dialect “mvel”