概述(Overview)
以.drl為擴展名的文件,是Drools中的規則文件,規則文件的編寫,遵循Drools規則語法。下面詳細介紹一下Drools規則文件語法。具體參考官方文檔: https://docs.jboss.org/drools/release/7.0.0.Final/drools-docs/html_single/index.html#_droolslanguagereferencechapter
DRL文件的整體結構如下:
package package-name
imports
globals
functions
queries
rules
對于上述元素,其順序在drl文件中的順序不重要,除了package-name
,必須是drl文件的第一個元素。上述所有的元素都是可選的,接下來我們將詳細介紹上述元素。
規則(Rule)組成
一個簡單的規則結構如下所示:
rule "name"
attributes
when
LHS
then
RHS
end
通常,規則文件中是不需要標點符號的。即使是規則名“name”
上的雙引號也是可選的。attributes
展示規則的執行方式,也是可選的。LHS
是規則的條件部分,遵循一個固定的語法規則,在下面的內容會詳細介紹。RHS
通常是一個可以本地執行的代碼塊。
關鍵字(Key Words)
從Drools 5開始,提出了“軟”關鍵字和“硬”關鍵字的概念。硬關鍵字是保留的,不允許開發人員在編寫規則文件時使用。硬關鍵字有如下幾個:
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
注釋(Comments)
注釋是規則文件中一段會被規則引擎自動忽略的一段文本。drl文件中的注釋采用類Java語法的方式,可以分為兩類:單行注釋和多行注釋。
單行注釋
單行注釋可以簡單的使用雙斜杠"http://"來標識。語法解析器會自動忽視其后的所有內容。樣例如下:
rule "Testing Comments"
when
// this is a single line comment
eval( true ) // this is a comment in the same line of a pattern
then
// this is a comment inside a semantic code block
end
另外需要注意的是,“#”開頭的單行注釋在drl文件中已經被棄用。
多行注釋
多行注釋主要用于在代碼塊外對整個文件進行注釋,以"/"開頭和"/"結尾的之間所有的內容都會被語法解析器解釋為注釋。樣例如下:
rule "Test Multi-line Comments"
when
/* this is a multi-line comment
in the left hand side of a rule */
eval( true )
then
/* and this is a multi-line comment
in the right hand side of a rule */
end
錯誤信息(Error Messages)
Drools 5 開始提出了標準的錯誤信息。標準化的目的是為了使開發者可以更準確更簡單定位錯誤問題所在。接下來將介紹如正確理解和識別錯誤信息。
錯誤信息格式
標準的錯誤信息格式如下所示。
錯誤信息包含以下幾個部分:
- 1st Block: 表明當前錯誤的錯誤碼。
- 2st Block: 錯誤可能發生的行和列。
- 3st Block: 錯誤信息描述。
- 4st Block: 指明錯誤發生的規則,函數,模板,查詢等。
- 5st Block: 指明錯誤發生于何種模式。一般不是強制性的。
錯誤碼描述
-
101: No viable alternative
錯誤碼101指明了最常見的錯誤,語法解析器無法找到替代方案。下面有一些常見的例子:
1: rule one
2: when
3: exists Foo()
4: exits Bar() // "exits"
5: then
6: end
上述示例會產生如下錯誤信息:
[ERR 101] Line 4:4 no viable alternative at input 'exits' in rule one
上述例子中的exits != exists
, 解析器找不到exits
的替代方案,于是報錯。下面我們可以再看一個例子。
1: package org.drools.examples;
2: rule
3: when
4: Object()
5: then
6: System.out.println("A RHS");
7: end
現在,上述的代碼會產生如下錯誤信息:
[ERR 101] Line 3:2 no viable alternative at input 'WHEN'
這里when
是一個關鍵字,語法解析器在這里會遇到一個問題:rule沒有文件名,而when
是一個規則的條件部分。因此報錯。下面還有一個相同類型錯誤的示例:
1: rule simple_rule
2: when
3: Student( name == "Andy )
4: then
5: end
這里雙引號缺少了另一半,因此會報錯。
-
102: Mismatched input
該錯誤表明,語法解析器在當前輸入位置中未找到一個特定的符號。下面有一些例子:
1: rule simple_rule
2: when
3: foo3 : Bar(
上述示例會產生如下錯誤:
[ERR 102] Line 0:-1 mismatched input '<eof>' expecting ')' in rule simple_rule in pattern Bar
要解決上述問題,需要完善上述規則語句。接下來的實例會產生多個錯誤:
1: package org.drools.examples;
2:
3: rule "Avoid NPE on wrong syntax"
4: when
5: not( Cheese( ( type == "stilton", price == 10 ) || ( type == "brie", price == 15 ) ) from $cheeseList )
6: then
7: System.out.println("OK");
8: end
上述代碼會產生如下錯誤:
[ERR 102] Line 5:36 mismatched input ',' expecting ')' in rule "Avoid NPE on wrong syntax" in pattern Cheese
[ERR 101] Line 5:57 no viable alternative at input 'type' in rule "Avoid NPE on wrong syntax
該錯誤信息與前一個錯誤相關,這里只需要將```,```用```&&```替換就好了。
* **103: Failed predicate**
出現該錯誤信息的原因是一個驗證的語義謂詞被驗證為false。通常這些語義謂詞用于識別軟關鍵字。以下是一個示例:
1: package nesting;
2: dialect "mvel"
3:
4: import org.drools.compiler.Person
5: import org.drools.compiler.Address
6:
7: fdsfdsfds
8:
9: rule "test something"
10: when
11: p: Person( name=="Michael" )
12: then
13: p.name = "other";
14: System.out.println(p.name);
15: end
我們可以得到如下錯誤信息:
* ```[ERR 103] Line 7:0 rule 'rule_key' failed predicate: {(validateIdentifierKey(DroolsSoftKeywords.RULE))}? in rule
fdsfdsfds
是個無效的關鍵字,語法解析器無法將其識別成一個軟關鍵字。
-
104: Trailing semi-colon not allowed
該錯誤信息與eval
從句相關,當分號不是出現在其表達式的結尾時可能報此錯誤。可以看一下如下示例。
1: rule simple_rule
2: when
3: eval(abc();)
4: then
5: end
由于在eval
中以分號結尾,我們可以得到如下錯誤信息:
[ERR 104] Line 3:4 trailing semi-colon not allowed in rule simple_rule
該錯誤很容易修復,只要移除eval
中的分號即可。
-
105: Early Exit
drl文件中的子規則至少能被匹配選擇一次,但是當子規則不能匹配任何東西的時候,就會報這個錯。
以下是一個示例:
1: template test_error
2: aa s 11;
3: end
該示例會報以下錯誤信息:
[ERR 105] Line 2:2 required (…?)+ loop did not match anything at input 'aa' in template test_error
Other Messages
開發中可能還會遇到一些其他意想不到的問題,這些問題可以向Drools的開發團隊求助。
包(Package)
package
是一系列rule
或其他相關構件如imports, globals
的容器。這個成員之間相互關聯,一個package
代表了一個命名空間,其中的每個rule
的名字都是唯一的,package
名字本身就是命名空間,與實際的文件和文件夾沒有任何關系。常見的結構是,一個文件包含多個rule
的文件就定義成一個包。以下的線路圖表明了一個包中包含的所有組成元素。
?? 需要注意的是,一個包必須有一個命名空間,且其聲明必須遵守Java命名規范。
package
語句必須出現在包的首行,其他組成部分的出現順序無關緊要。其中package
語句結尾的分號;
是可選的。
import
Drools文件中的import
語句功能與Java中的import
語句功能類似。使用import
時,必須指定對象的全稱限定路徑和類型名。Drools會自動導入Java相同名字的包中的所有類,也會導入java.lang.*
。
global
??
global
用于定義全局變量。用于使應用對象對一系列規則有效。通常,用于向規則提供全局的數據和服務,特別是一些用于規則序列的應用服務,如日志、規則序列中累加的值等。全局的變量是不會插入Woking Memory
中的,另外,全局變量不要用于建立規則的條件部分,除非它是一個不會改變的常量。全部變量的改變不會通知到規則引擎,規則引擎不跟蹤全局變量的變化,因為他們并沒有加入到Woking Memory
中。全局變量使用不當,會產生很多不可思議的結果。如果多個包中同時定義了相同標識符的全局變量,那么這些全局變量必須是相同類型,并會引用一個相同的全局值。為了更好地使用全局變量,必須遵循以下規則:
- 在規則文件中定義常量,并使用這些常量。
global java.util.List myGlobalList;
rule "Using a global"
when
eval( true )
then
myGlobalList.add( "Hello World" );
end
- 在工作內存中,為全局變量設值。在從內存中獲取所有的
fact
之前,最好將所有的全局變量設置。例如:
List list = new ArrayList();
KieSession kieSession = kiebase.newKieSession();
kieSession.setGlobal( "myGlobalList", list );
全局變量并不是用來在規則間共享數據,而且最好不要用于在規則間共享數據。規則總是對工作內存的狀態產生推理和反應,因此,如果想在規則之間傳遞數據,可以將這些數據作為facts
傳入工作內存。因為規則引擎并不會關心和跟蹤這些全局變量的變化。
函數(Function)
??
function
提供了一種在規則源文件中插入語義代碼的方式,與在普通Java類中不同。他們需要幫助類,否則不能做任何事情。(實際上,編譯器會針對這些句子自動產生幫助類。)在規則中使用函數的最主要優點就是你可以把所有邏輯放在一個地方,你可以根據需要更改這些函數的邏輯。函數通常用于在規則的then
部分調用某些動作,特別是一些經常被用到的而傳入參數不一樣的動作。典型的
function
格式如下:
function String hello(String name) {
return "Hello "+name+"!";
}
需要注意的是,這里我們使用了function
關鍵字,即使它并不是Java語言的一部分。參數和返回值的定義和傳入與Java語言一致。當然,參數和返回值并不是必須的。另外,我們可以使用一個幫助類中的靜態方法,如:Foo.hello()
。
??Drools支持函數的導入,我們所要做的就是:
import function my.package.Foo.hello
不需要考慮函數的定義和導入方式,我們可以直接在需要的地方直接使用函數名調用這個函數。例如:
rule "using a static function"
when
eval( true )
then
System.out.println( hello( "Bob" ) );
end
類型聲明(Type Declaration)
在規則引擎中,類型聲明有兩個目的:允許新的類型聲明;允許元數據類型的聲明。
- 聲明新類型:Drools工作時會將外部的普通Java對象作為事實。但是有時候使用者想要自己定義一些規則引擎可以直接使用的模型,而不用擔心用Java之類的底層語言來創建對象。另外有時候,當一個域模型已經建立,但是用戶想或者需要用一些主要在推理過程中使用的實體來完善這個模型。
- 聲明元數據:事實往往會有一些與之相關的元數據信息。元信息的樣本包含的任何種類的數據都不能代表事實的屬性,且在該事實類型的所有實例中都是不變的。這些元信息在規則引擎的運行和推理古城中需要被查詢。
聲明新類型
為了定義新類型,我們需要使用關鍵字``declare,然后是一系列域,最終以
end關鍵字結尾。一個新的
fact必須有一系列域,否則規則引擎會去
classpath中尋找一個存在的
fact```類,如果沒找到,會報錯。下面給出定義新類型的幾個例子。
Example . Declaring a new fact type: Address
declare Address
number : int
streetName : String
city : String
end
上面的例子中我們定義了一個新類型Address
,這個fact
有三個屬性,每個屬性都具有一個Java中有效的數據類型。同樣的,我們可以再定義一個數據類型:
Example . Declaring a new fact type: Person
declare Person
name : String
dateOfBirth : java.util.Date
address : Address
end
我們可以看一下上面的例子,dateOfBirth
是Java中的java.util.Date
類型,address
是我們剛才聲明的類型。為了不用寫全稱限定名,我們可以先使用import
來導入要使用的類型,例如:
Avoiding the need to use fully qualified class names by using import
import java.util.Date
declare Person
name : String
dateOfBirth : Date
address : Address
end
當我們聲明一個新的fact
類型時,Drools會在編譯期間生成實現自一個表示該fact
類型的Java類的字節碼。這個生成的Java類
Example : generated Java class for the previous Person fact typedeclaration
public class Person implements Serializable {
private String name;
private java.util.Date dateOfBirth;
private Address address;
// empty constructor
public Person() {...}
// constructor with all fields
public Person( String name, Date dateOfBirth, Address address ) {...}
// if keys are defined, constructor with keys
public Person( ...keys... ) {...}
// getters and setters
// equals/hashCode
// toString
}
該類型生成的class是一個普通的Java類,可以在規則中直接使用,就向其他 fact
一樣。見如下例子:
Using the declared types in rules
rule "Using a declared Type"
when
$p : Person( name == "Bob" )
then
// Insert Mark, who is Bob's mate.
Person mark = new Person();
mark.setName("Mark");
insert( mark );
end
聲明枚舉類型
DRL同時支持聲明枚舉類型。該類型聲明需要另外一種關鍵字enum
,然后以都好分割可接收值的列表,最后以分號結束。
declare enum DaysOfWeek
SUN("Sunday"),MON("Monday"),TUE("Tuesday"),WED("Wednesday"),THU("Thursday"),FRI("Friday"),SAT("Saturday");
fullName : String
end
聲明完成之后,該枚舉類型可以用于之后的規則中。
rule "Using a declared Enum"
when
$p : Employee( dayOff == DaysOfWeek.MONDAY )
then
...
end
聲明元數據
在Drools中元數據會被分配給一系列不同對象的構造:fact
類型,fact
屬性和規則。Drools使用@
符號來引出元數據,使用使用如下格式:
@metadata_key( metadata_value )
其中metadata_value
是可選的。
?? Drools允許聲明任何任意元數據屬性,但是當其他屬性在運行時僅僅對查詢有效時,有些屬性對于規則引擎來說具有不同的意義。Drools允許為fact
類型和fact
屬性聲明元數據。所有的元數據在該屬性被分配到fact
類型前聲明,而在向一個特定屬性分配值之前聲明。
Example 115. Declaring metadata attributes for fact types and attributes
import java.util.Date
declare Person
@author( Bob )
@dateOfCreation( 01-Feb-2009 )
name : String @key @maxLength( 30 )
dateOfBirth : Date
address : Address
end
上面的例子中,聲明了兩個fact
類型(@author
和 @dateOfCreation
)的元數據元素,另外為name
屬性聲明了兩個(@key
和@maxLength
)元數據。其中@key
沒有必須值,所有括號和值均被省略了。
規則(Rule)
?? 一個規則,指定當(
when
)一系列特定的條件發生時(左手邊LHS),然后(then
)做出一系列相應的動作(右手邊RHS)。一個常見的問題就是:為什么使用when
而不是if
,因為if
通常是執行流程中特定時間點的一部分,是一個需要檢查的條件。相反的,when
指明的是一個不綁定于任何特定判斷序列或時間點的條件判斷,它在規則引擎的聲明周期內的任何一個條件發生的情況下都可能觸發,不管這個條件是否遇到,這些動作都會執行。?? 一個規則在一個包中必須具有獨一無二的名字。如果在一個DRL文件中重復定義兩個相同名字的規則,在加載的時候就會報錯。如果向包中添加一個名字已經存在的規則,該規則會覆蓋掉之前的同名規則。如果一個規則命中存在空格符,最好使用雙引號將規則名包括起來。
?? 規則的屬性不是必須的,且屬性最好寫成一行。
?? 規則中的LHS在關鍵字
when
的后面,同樣的,RHS應該在關鍵字then
的后面,規則最后以關鍵字end
結尾。另外,規則不準嵌套。
Example . Rule Syntax Overview
rule "<name>"
<attribute>*
when
<conditional element>*
then
<action>*
end
Example . A simple rule
rule "Approve if not rejected"
salience -100
agenda-group "approval"
when
not Rejection()
p : Policy(approved == false, policyState:status)
exists Driver(age > 25)
Process(status == policyState)
then
log("APPROVED: due to no objections.");
p.setApproved(true);
end
規則屬性
規則屬性顯式地聲明了對規則行為的影響,有些規則屬性很簡單,有些規則屬性是復雜的子系統的一部分,如規則流。為了從Drools中獲得更多東西,我們需要確保對每一個規則屬性均有正確的認識。
常用的規則屬性有如下:
-
no-loop
- 默認值:false
- type: Boolean
當規則序列更改了一個fact
,會導致該規則會被重新觸發,以至于產生一個無限循環。當設置為true時,當前規則只會被激活一次。
-
ruleflow-group
- 默認值:N/A
- type: String
ruleflow
是Drools的特色之一,可以讓你自己控制規則的命中。同一個ruleflow-group
中的所有規則只有當該組激活時才能被命中。
-
lock-on-active
- 默認值:false
- type: Boolean
不管何時ruleflow-group
和agenda-group
被激活,只要其中的所有規則將lock-on-active
設置為true,那么這些規則都不會再被激活,不管一開始怎么更新,這些匹配的規則都不會被激活。這是no-loop
屬性的增強,因為這些變化現在不僅僅是規則自身的變化。
-
salience
- 默認值:0
- type: Integer
任何規則都有一個默認為0的salience
屬性,該屬性可以為0,正數和負數。salience
表示規則的優先級,值越大其在激活隊列中的優先級越高。Drools支持使用動態的salience
,可以使用一個包含動態約束變量的表達式來表示。如下所示
Dynamic Salience
rule "Fire in rank order 1,2,.."
salience( -$rank )
when
Element( $rank : rank,... )
then
...
end
-
agenda-group
- 默認值:MAIN
- type: String
agenda-group
允許用戶將Agenda分割成多個部分以提供更多的運行控制。
-
auto-focus
- 默認值:false
- type: Boolean
當一個規則被激活時auto-focus
為true,而且該規則的agenda-group
還沒有focus,當該agenda-group
focus時,允許該規則潛在命中。
-
activation-group
- 默認值:N/A
- type: String
屬于同一個activation-group的規則會進行唯一命中。也就是說同一個activation-group中的規則,只要有一個命中,其他的規則都會被取消激活狀態,這樣這些規則就不會被命中。
-
dialect
- 默認值:as specified by the package
- type: String
dialect用于指明規則中使用的代碼的語言種類,目前支持兩種語言,"java"或"mvel"。
-
date-effective
- 默認值:N/A
- type: String (包含日期和時間)
當前系統時間在date-effective之后,該規則才會被激活。
-
date-effective
- 默認值:N/A
- type: String (包含日期和時間)
當前系統時間在date-effective之后,該規則不會再被激活。
-
duration
- 默認值:無
- type: long (包含日期和時間)
duration
用于表示一個規則在一定時間之后才會被命中,如果它還是激活狀態的話。
LHS語法
LHS是規則的條件部分的統稱,由零到多條條件元素組成。如果LHS為空,默認為是條件部分一直為true。當一個新的WorkingMemory session創建的時候,會被激活和觸發。
Example. Rule without a Conditional Element
rule "no CEs"
when
// empty
then
... // actions (executed once)
end
// The above rule is internally rewritten as:
rule "eval(true)"
when
eval( true )
then
... // actions (executed once)
end
LHS中的條件元素基于一個或多個模式,最常用的條件元素是and
。當然如果LHS中有多個不互相連接的模式時,默認使用隱式的and
。
Implicit and
rule "2 unconnected patterns"
when
Pattern1()
Pattern2()
then
... // actions
end
// The above rule is internally rewritten as:
rule "2 and connected patterns"
when
Pattern1()
and Pattern2()
then
... // actions
end
模式
模式是最終要的條件元素,它可以隱式地匹配所有插入到WorkingMemory中的所有fact
。一個模式具有0個或多個約束條件和一個可選的模式組合。模式的結構圖如下所示:
下面給出一個最簡單的模式的例子
Person()
這里的類型為Person,該模式意味著將匹配WorkingMemory中的所有Person對象。該類型不需要是一個真實 fact
對象的類。模式可以指向超類甚至是接口,這樣可以匹配多個不同類的facts
。例如:
Object() // matches all objects in the working memory
模式的括號中條件定義了模式在何種條件下滿足。如下所示:
Person( age == 100 )
為了引用匹配的對象,可以使用一個模式綁定參數如:$p
。
Example . Pattern with a binding variable
rule ...
when
$p : Person()
then
System.out.println( "Person " + $p );
end
$符號是非強制性的,只是用于在復雜的規則中方便標識,將其與變量及域區分開來。
約束
- 什么是約束?
約束是一個返回true
或false
的表達式,如下例所示:
Person( 5 < 6 ) // just an example, as constraints like this would be useless in a real pattern
約束本質上是一個與Java表達式稍微有點不同的表達式,例如equals()
等價于==
。接下來我們深入理解一下。
- Java Beans屬性獲取。
任何一個bean的屬性都可以被直接使用,bean屬性的獲取也可以使用標準的Java bean getter: getMyProperty() or isMyProperty()
。例如:
//use directly
Person( age == 50 )
// this is the same as:
Person( getAge() == 50 )
同時,Drools還支持嵌套的屬性獲取方式,如:
//use directly
Person( address.houseNumber == 50 )
// this is the same as:
Person( getAddress().getHouseNumber() == 50 )
當然,約束中的條件表達式是支持Java表達式的,下面幾個例子都是正確的:
Person( age == 50 )
Person( age > 100 && ( age % 10 == 0 ) )
Person( Math.round( weight / ( height * height ) ) < 25.0 )
- 逗號分隔符 AND
逗號分隔約束,具有隱含的AND的含義。
// Person is at least 50 and weighs at least 80 kg
Person( age > 50, weight > 80 )
// Person is at least 50, weighs at least 80 kg and is taller than 2 meter.
Person( age > 50, weight > 80, height > 2 )
逗號運算符不能出現在復合的約束表達式中,如
// Do NOT do this: compile error
Person( ( age > 50, weight > 80 ) || height > 2 )
// Use this instead
Person( ( age > 50 && weight > 80 ) || height > 2 )
- 綁定變量
屬性值可以綁定到一個變量中:
// 2 persons of the same age
Person( $firstAge : age ) // binding
Person( age == $firstAge ) // constraint expression
- 分組訪問嵌套對象屬性
可以先看一個例子:
Person( name == "mark", address.city == "london", address.country == "uk" )
Person( name == "mark", address.( city == "london", country == "uk") )
也就是對嵌套對象屬性的訪問,可以組合在一個括號里面。
- 內聯強制類型轉換
當處理嵌套對象時,往往需要將其轉換成子類,可以通過使用#
符號來完成。如下例所示:
Person( name == "mark", address#LongAddress.country == "uk" )
在該例子中將 Address
轉換成LongAddress
。如果類型轉換失敗,該值會被認為是false。當然,類型轉換也支持全稱限定名稱。如下所示:
Person( name == "mark", address#org.domain.LongAddress.country == "uk" )
當然,在同一個表達式中使用多級內聯轉換也是可行的。如下所示:
Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )
另外,Drools同樣支持instanceof操作。
Person( name == "mark", address instanceof LongAddress, address.country == "uk" )
- 特殊文字支持
除了正常的Java文字,Drools還支持以下特殊的文字:- 日期文字。