1. Drools基礎語法
1.1 規則文件構成
在使用Drools時非常重要的一個工作就是編寫規則文件,通常規則文件的后綴為.drl。
drl是Drools Rule Language的縮寫。在規則文件中編寫具體的規則內容。
一套完整的規則文件內容構成如下:
| 關鍵字 | 描述 |
|---|---|
| package | 包名,只限於邏輯上的管理,同一個包名下的查詢或者函數可以直接調用 |
| import | 用於導入類或者靜態方法 |
| global | 全局變量 |
| function | 自定義函數 |
| query | 查詢 |
| rule end | 規則體 |
Drools支持的規則文件,除了drl形式,還有Excel文件類型的。
1.2 規則體語法結構
規則體是規則文件內容中的重要組成部分,是進行業務規則判斷、處理業務結果的部分。
規則體語法結構如下:
rule "ruleName"
attributes
when
LHS
then
RHS
end
rule:關鍵字,表示規則開始,參數為規則的唯一名稱。
attributes:規則屬性,是rule與when之間的參數,為可選項。
when:關鍵字,后面跟規則的條件部分。
LHS(Left Hand Side):是規則的條件部分的通用名稱。它由零個或多個條件元素組成。如果LHS為空,則它將被視為始終為true的條件元素。
then:關鍵字,后面跟規則的結果部分。
RHS(Right Hand Side):是規則的后果或行動部分的通用名稱。
end:關鍵字,表示一個規則結束。
1.3 注釋
在drl形式的規則文件中使用注釋和Java類中使用注釋一致,分為單行注釋和多行注釋。
單行注釋用"//"進行標記,多行注釋以"/"開始,以"/"結束。如下示例:
//規則rule1的注釋,這是一個單行注釋
rule "rule1"
when
then
System.out.println("rule1觸發");
end
/*
規則rule2的注釋,
這是一個多行注釋
*/
rule "rule2"
when
then
System.out.println("rule2觸發");
end
1.4 Pattern模式匹配
前面我們已經知道了Drools中的匹配器可以將Rule Base中的所有規則與Working Memory中的Fact對象進行模式匹配,那么我們就需要在規則體的LHS部分定義規則並進行模式匹配。LHS部分由一個或者多個條件組成,條件又稱為pattern。
pattern的語法結構為:綁定變量名:Object(Field約束)
其中綁定變量名可以省略,通常綁定變量名的命名一般建議以$開始。如果定義了綁定變量名,就可以在規則體的RHS部分使用此綁定變量名來操作相應的Fact對象。Field約束部分是需要返回true或者false的0個或多個表達式。
例如我們的入門案例中:
//規則二:所購圖書總價在100到200元的優惠20元
rule "book_discount_2"
when
//Order為類型約束,originalPrice為屬性約束
$order:Order(originalPrice < 200 && originalPrice >= 100)
then
$order.setRealPrice($order.getOriginalPrice() - 20);
System.out.println("成功匹配到規則二:所購圖書總價在100到200元的優惠20元");
end
通過上面的例子我們可以知道,匹配的條件為:
1、工作內存中必須存在Order這種類型的Fact對象-----類型約束
2、Fact對象的originalPrice屬性值必須小於200------屬性約束
3、Fact對象的originalPrice屬性值必須大於等於100------屬性約束
以上條件必須同時滿足當前規則才有可能被激活。
綁定變量既可以用在對象上,也可以用在對象的屬性上。例如上面的例子可以改為:
//規則二:所購圖書總價在100到200元的優惠20元
rule "book_discount_2"
when
$order:Order($op:originalPrice < 200 && originalPrice >= 100)
then
System.out.println("$op=" + $op);
$order.setRealPrice($order.getOriginalPrice() - 20);
System.out.println("成功匹配到規則二:所購圖書總價在100到200元的優惠20元");
end
LHS部分還可以定義多個pattern,多個pattern之間可以使用and或者or進行連接,也可以不寫,默認連接為and。
//規則二:所購圖書總價在100到200元的優惠20元
rule "book_discount_2"
when
$order:Order($op:originalPrice < 200 && originalPrice >= 100) and
$customer:Customer(age > 20 && gender=='male')
then
System.out.println("$op=" + $op);
$order.setRealPrice($order.getOriginalPrice() - 20);
System.out.println("成功匹配到規則二:所購圖書總價在100到200元的優惠20元");
end
1.5 比較操作符
Drools提供的比較操作符,如下表:
| 符號 | 說明 |
|---|---|
| > | 大於 |
| < | 小於 |
| >= | 大於等於 |
| <= | 小於等於 |
| == | 等於 |
| != | 不等於 |
| contains | 檢查一個Fact對象的某個屬性值是否包含一個指定的對象值 |
| not contains | 檢查一個Fact對象的某個屬性值是否不包含一個指定的對象值 |
| memberOf | 判斷一個Fact對象的某個屬性是否在一個或多個集合中 |
| not memberOf | 判斷一個Fact對象的某個屬性是否不在一個或多個集合中 |
| matches | 判斷一個Fact對象的屬性是否與提供的標准的Java正則表達式進行匹配 |
| not matches | 判斷一個Fact對象的屬性是否不與提供的標准的Java正則表達式進行匹配 |
前6個比較操作符和Java中的完全相同,下面我們重點學習后6個比較操作符。
1.5.1 語法
-
contains | not contains語法結構
Object(Field[Collection/Array] contains value)
Object(Field[Collection/Array] not contains value)
-
memberOf | not memberOf語法結構
Object(field memberOf value[Collection/Array])
Object(field not memberOf value[Collection/Array])
-
matches | not matches語法結構
Object(field matches "正則表達式")
Object(field not matches "正則表達式")
1.5.2 操作步驟
第一步:創建實體類,用於測試比較操作符
package com.itheima.drools.entity;
import java.util.List;
/**
* 實體類
* 用於測試比較操作符
*/
public class ComparisonOperatorEntity {
private String names;
private List<String> list;
public String getNames() {
return names;
}
public void setNames(String names) {
this.names = names;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
}
第二步:在/resources/rules下創建規則文件comparisonOperator.drl
package comparisonOperator
import com.itheima.drools.entity.ComparisonOperatorEntity
/*
當前規則文件用於測試Drools提供的比較操作符
*/
//測試比較操作符contains
rule "rule_comparison_contains"
when
ComparisonOperatorEntity(names contains "張三")
ComparisonOperatorEntity(list contains names)
then
System.out.println("規則rule_comparison_contains觸發");
end
//測試比較操作符not contains
rule "rule_comparison_notContains"
when
ComparisonOperatorEntity(names not contains "張三")
ComparisonOperatorEntity(list not contains names)
then
System.out.println("規則rule_comparison_notContains觸發");
end
//測試比較操作符memberOf
rule "rule_comparison_memberOf"
when
ComparisonOperatorEntity(names memberOf list)
then
System.out.println("規則rule_comparison_memberOf觸發");
end
//測試比較操作符not memberOf
rule "rule_comparison_notMemberOf"
when
ComparisonOperatorEntity(names not memberOf list)
then
System.out.println("規則rule_comparison_notMemberOf觸發");
end
//測試比較操作符matches
rule "rule_comparison_matches"
when
ComparisonOperatorEntity(names matches "張.*")
then
System.out.println("規則rule_comparison_matches觸發");
end
//測試比較操作符not matches
rule "rule_comparison_notMatches"
when
ComparisonOperatorEntity(names not matches "張.*")
then
System.out.println("規則rule_comparison_notMatches觸發");
end
第三步:編寫單元測試
//測試比較操作符
@Test
public void test3(){
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
ComparisonOperatorEntity comparisonOperatorEntity = new ComparisonOperatorEntity();
comparisonOperatorEntity.setNames("張三");
List<String> list = new ArrayList<String>();
list.add("張三");
list.add("李四");
comparisonOperatorEntity.setList(list);
//將數據提供給規則引擎,規則引擎會根據提供的數據進行規則匹配,如果規則匹配成功則執行規則
kieSession.insert(comparisonOperatorEntity);
kieSession.fireAllRules();
kieSession.dispose();
}
1.6 執行指定規則
通過前面的案例可以看到,我們在調用規則代碼時,滿足條件的規則都會被執行。那么如果我們只想執行其中的某個規則如何實現呢?
Drools給我們提供的方式是通過規則過濾器來實現執行指定規則。對於規則文件不用做任何修改,只需要修改Java代碼即可,如下:
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
ComparisonOperatorEntity comparisonOperatorEntity = new ComparisonOperatorEntity();
comparisonOperatorEntity.setNames("張三");
List<String> list = new ArrayList<String>();
list.add("張三");
list.add("李四");
comparisonOperatorEntity.setList(list);
kieSession.insert(comparisonOperatorEntity);
//通過規則過濾器實現只執行指定規則
kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rule_comparison_memberOf"));
kieSession.dispose();
1.7 關鍵字
Drools的關鍵字分為:硬關鍵字(Hard keywords)和軟關鍵字(Soft keywords)。
硬關鍵字是我們在規則文件中定義包名或者規則名時明確不能使用的,否則程序會報錯。軟關鍵字雖然可以使用,但是不建議使用。
硬關鍵字包括:true false null
軟關鍵字包括:lock-on-active date-effective date-expires no-loop auto-focus activation-group agenda-group ruleflow-group entry-point duration package import dialect salience enabled attributes rule extend when then template query declare function global eval not in or and exists forall accumulate collect from action reverse result end over init
1.8 Drools內置方法
規則文件的RHS部分的主要作用是通過插入,刪除或修改工作內存中的Fact數據,來達到控制規則引擎執行的目的。Drools提供了一些方法可以用來操作工作內存中的數據,操作完成后規則引擎會重新進行相關規則的匹配,原來沒有匹配成功的規則在我們修改數據完成后有可能就會匹配成功了。
創建如下實體類:
package com.itheima.drools.entity;
import java.util.List;
/**
* 學生
*/
public class Student {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
1.8.1 update方法
update方法的作用是更新工作內存中的數據,並讓相關的規則重新匹配。
第一步:編寫規則文件/resources/rules/student.drl,文件內容如下
package student
import com.itheima.drools.entity.Student
/*
當前規則文件用於測試Drools提供的內置方法
*/
rule "rule_student_age小於10歲"
when
$s:Student(age < 10)
then
$s.setAge(15);
update($s);//更新數據,導致相關的規則會重新匹配
System.out.println("規則rule_student_age小於10歲觸發");
end
rule "rule_student_age小於20歲同時大於10歲"
when
$s:Student(age < 20 && age > 10)
then
$s.setAge(25);
update($s);//更新數據,導致相關的規則會重新匹配
System.out.println("規則rule_student_age小於20歲同時大於10歲觸發");
end
rule "rule_student_age大於20歲"
when
$s:Student(age > 20)
then
System.out.println("規則rule_student_age大於20歲觸發");
end
第二步:編寫單元測試
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
Student student = new Student();
student.setAge(5);
//將數據提供給規則引擎,規則引擎會根據提供的數據進行規則匹配,如果規則匹配成功則執行規則
kieSession.insert(student);
kieSession.fireAllRules();
kieSession.dispose();
通過控制台的輸出可以看到規則文件中定義的三個規則都觸發了。
在更新數據時需要注意防止發生死循環。
1.8.2 insert方法
insert方法的作用是向工作內存中插入數據,並讓相關的規則重新匹配。
第一步:修改student.drl文件內容如下
package student
import com.itheima.drools.entity.Student
/*
當前規則文件用於測試Drools提供的內置方法
*/
rule "rule_student_age等於10歲"
when
$s:Student(age == 10)
then
Student student = new Student();
student.setAge(5);
insert(student);//插入數據,導致相關的規則會重新匹配
System.out.println("規則rule_student_age等於10歲觸發");
end
rule "rule_student_age小於10歲"
when
$s:Student(age < 10)
then
$s.setAge(15);
update($s);
System.out.println("規則rule_student_age小於10歲觸發");
end
rule "rule_student_age小於20歲同時大於10歲"
when
$s:Student(age < 20 && age > 10)
then
$s.setAge(25);
update($s);
System.out.println("規則rule_student_age小於20歲同時大於10歲觸發");
end
rule "rule_student_age大於20歲"
when
$s:Student(age > 20)
then
System.out.println("規則rule_student_age大於20歲觸發");
end
第二步:編寫單元測試
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
Student student = new Student();
student.setAge(10);
//將數據提供給規則引擎,規則引擎會根據提供的數據進行規則匹配,如果規則匹配成功則執行規則
kieSession.insert(student);
kieSession.fireAllRules();
kieSession.dispose();
通過控制台輸出可以發現,四個規則都觸發了,這是因為首先進行規則匹配時只有第一個規則可以匹配成功,但是在第一個規則中向工作內存中插入了一個數據導致重新進行規則匹配,此時第二個規則可以匹配成功。在第二個規則中進行了數據修改導致第三個規則也可以匹配成功,以此類推最終四個規則都匹配成功並執行了。
1.8.3 retract方法
retract方法的作用是刪除工作內存中的數據,並讓相關的規則重新匹配。
第一步:修改student.drl文件內容如下
package student
import com.itheima.drools.entity.Student
/*
當前規則文件用於測試Drools提供的內置方法
*/
rule "rule_student_age等於10歲時刪除數據"
/*
salience:設置當前規則的執行優先級,數值越大越優先執行,默認值為0.
因為當前規則的匹配條件和下面規則的匹配條件相同,為了保證先執行當前規則,需要設置優先級
*/
salience 100
when
$s:Student(age == 10)
then
retract($s);//retract方法的作用是刪除工作內存中的數據,並讓相關的規則重新匹配。
System.out.println("規則rule_student_age等於10歲時刪除數據觸發");
end
rule "rule_student_age等於10歲"
when
$s:Student(age == 10)
then
Student student = new Student();
student.setAge(5);
insert(student);
System.out.println("規則rule_student_age等於10歲觸發");
end
rule "rule_student_age小於10歲"
when
$s:Student(age < 10)
then
$s.setAge(15);
update($s);
System.out.println("規則rule_student_age小於10歲觸發");
end
rule "rule_student_age小於20歲同時大於10歲"
when
$s:Student(age < 20 && age > 10)
then
$s.setAge(25);
update($s);
System.out.println("規則rule_student_age小於20歲同時大於10歲觸發");
end
rule "rule_student_age大於20歲"
when
$s:Student(age > 20)
then
System.out.println("規則rule_student_age大於20歲觸發");
end
第二步:編寫單元測試
KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();
Student student = new Student();
student.setAge(10);
//將數據提供給規則引擎,規則引擎會根據提供的數據進行規則匹配,如果規則匹配成功則執行規則
kieSession.insert(student);
kieSession.fireAllRules();
kieSession.dispose();
通過控制台輸出可以發現,只有第一個規則觸發了,因為在第一個規則中將工作內存中的數據刪除了導致第二個規則並沒有匹配成功。
