【java规则引擎】基本语法和相关属性介绍
一个规则的语法信息
【1】条件部分(LSH部分)
===>规则pattern之间的连接条件符号:
(1)LHS 部分是由一个或多个条件组成,条件又称之为 pattern(匹配模式),多个 pattern之间用可以使用 and 或 or 来进行连接,同时还可以使用小括号来确定 pattern 的优先级
(2)pattern 没有符号连接,在 Drools,当中在 pattern 中没有连接符号,那么就用 and 来作为默认连接,所以在该规则的 LHS 部分中两个 pattern 只有都满足了才会返回 true。
(3)声明变量语法:[绑定变量名:]Object([field 约束])
===>规则pattern内部约束条件连接符号:
(1)对于对象内部的多个约束的连接,可以采用“&&”(and)、“||”(or)和“,”(and)来实现.
(2)这三个连接符号如果没有用小括号来显示的定义优先级的话,那么它们的执行顺序是:“&&”(and)、“||”(or)和“,”。
(3)表面上看“,”与“&&”具有相同的含义,但是有一点需要注意,“,”与“&&”和“||”不能混合使用,也就是说在有“&&”或“||”出现的 LHS 当中,是不可以有“,”连接符出现的,反之亦然。
===>规则pattern的内部模式的条件比较操作符号
(1)在 Drools5当中共提供了十二种类型的比较操作符,分别是: >、 >=、 <、 <=、 = =、 !=、 contains、 not contains、memberof、not memberof、matches、not matches;
(2)contains 只能用于对象的某个 Collection/Array 类型的字段与另外一个值进行比较,是不是包含,作为比较的值可以是一个静态的值,也可以是一个变量(绑定变量或者是一个 global 对象)
(3)not contains 作用与 contains 作用相反,not contains 是用来判断一个 Fact 对象的某个字段(Collection/Array 类型)是不是不包含一个指定的对象,和 contains 比较符相同,它也只能用在对象的 field 当中
(4)memberOf 是用来判断某个 Fact 对象的某个字段是否在一个集合(Collection/Array)当中,用法与 contains 有些类似,但也有不同,memberOf 的语法如下:Object(fieldName memberOf value[Collection/Array])
(5)not memberof该操作符与 memberOf 作用洽洽相反,是用来判断 Fact 对象当中某个字段值是不是中某个集合(Collection/Array)当中,同时被比较的集合对象只能是一个变量
(6)matches 是用来对某个 Fact 的字段与标准的 Java 正则表达式进行相似匹配,被比较的字符串可以是一个标准的 Java 正则表达式,但有一点需要注意,那就是正则表达式字符串当中不用考虑“\”的转义问题.matches 使用语法如下:Object(fieldName matches “正则表达式”)
(7)与 matches 作用相反,是用来将某个 Fact 的字段与一个 Java 标准正则表达式进行匹配,看是不是能与正则表达式匹配。not matches 使用语法如下:Object(fieldname not matches “正则表达式”)因为 not matches 用法与 matches 一样,只是作用相反
【2】结果部分(RSH部分)
===>RHS 部分是规则真正要做事情的部分,可以将因条件满足而要触发的动作写在该部分当中,在 RHS 当中可以使用 LHS 部分当中定义的绑定变量名、设置的全局变量、或者是直接编写 Java 代码(对于要用到的 Java 类,需要在规则文件当中用 import 将类导入后方能使用,这点和 Java 文件的编写规则相同)
===>在规则当中 LHS 就是用来放置条件的,所以在 RHS 当中虽然可以直接编写Java 代码,但不建议在代码当中有条件判断,如果需要条件判断,那么请重新考虑将其放在LHS 当中,否则就违背了使用规则的初衷
===>在 RHS 里面,提供了一些对当前 Working Memory 实现快速操作的宏函数或对象,比如 insert/insertLogical、 update 和 retract 就可以实现对当前 Working Memory中的 Fact 对象进行新增、删除或者是修改
(1)函数 insert 的作用与我们在 Java 类当中调用 StatefulKnowledgeSession 对象的 insert 方法的作用相同,都是用来将一个 Fact 对象插入到当前的 Working Memory 当中。它的基本用法格式如下:insert(new Object());
一旦调用 insert 宏函数,那么 Drools 会重新与所有的规则再重新匹配一次,对于没有设置 no-loop 属性为 true 的规则,如果条件满足,不管其之前是否执行过都会再执行一次,这个特性不仅存在于 insert 宏函数上,后面介绍的 update、retract 宏函数同样具有该特性,所以在某些情况下因考虑不周调用 insert、update 或 retract 容易发生死循环,这点大家需要注意。
(2)insertLogical 作用与 insert 类似,它的作用也是将一个 Fact 对象插入到当前的 Working Memroy 当中
(3)update 函数意义与其名称一样,用来实现对当前 Working Memory 当中的 Fact 进行更新,update 宏函数的作用与 StatefulSession 对象的 update 方法的作用基本相同,都是用来告诉当前的 Working Memory 该 Fact 对象已经发生了变化。它的用法有两种形式,一种是直接更新一个 Fact 对象,另一种为通过指定 FactHandle 来更新与指定 FactHandle 对应的 Fact 对象
update函数的两种用法:update(new Object());, update(new FactHandle(),new Object());
从第二种用法格式上可以看出,它可以支持创建一个新的 Fact 对象,从而把 FactHandle对象指定的 Fact 对象替换掉,从而实现对象的全新更新
和 StatefulSession 的 retract 方法一样,宏函数 retract 也是用来将 Working Memory 当中某个 Fact 对象从 Working Memory 当中删除,下面就通过一个例子来说明 retract 宏函数的用法。
【3】规则属性部分
===>"salience"的属性:该属性的作用是通过一个数字来确认规则执行的优先级,数字越大,执行越靠前.同时它的值可以是一个负数。默认情况下,规则的 salience 默认值为 0,所以如果我们不手动设置规则的 salience 属性,那么它的执行顺序是随机的。
===>"no-loop" 属性:为 true 来实现规则对于同一个fact,只会被匹配一次。即便是调用insert(),update()等宏函数,也只会被执行一次。
===>"date-effective"属性:默认情况下,date-effective 可接受的日期格式为“dd-MMM-yyyy”,规则生效时间
===>"date-expires"属性:规则失效时间
===>"enabled"属性:enabled 属性比较简单,它是用来定义一个规则是否可用的。该属性的值是一个布尔值,默认该属性的值为 true,表示规则是可用的,如果手工为一个规则添加一个 enabled 属性,并且设置其 enabled 属性值为 false,那么引擎就不会执行该规则
===>"dialect"属性:该属性用来定义规则当中要使用的语言类型,目前 Drools5 版本当中支持两种类型的语言:mvel 和 java
===>"duration"属性:对于一个规则来说,如果设置了该属性,那么规则将在该属性指定的值之后在另外一个线程里触发。该属性对应的值为一个长整型,单位是毫秒
===>"lock-on-active"属性:当在规则上使用 ruleflow-group 属性或 agenda-group 属性的时候,将 lock-on-action 属性的值设置为 true,可能避免因某些 Fact 对象被修改而使已经执行过的规则再次被激活执行
可以看出该属性与 no-loop 属性有相似之处,no-loop 属性是为了避免 Fact 修改或调用了 insert、retract、update 之类而导致规则再次激活执行,这里的 lock-on-action 属性也是起这个作用,lock-on-active 是 no-loop 的增强版属性,它主要作用在使用 ruleflow-group 属性或 agenda-group 属性的时候。lock-on-active 属性默认值为 false。
===>"activation-group"属性:该属性的作用是将若干个规则划分成一个组,用一个字符串来给这个组命名,这样在执行的时候,具有相同 activation-group 属性的规则中只要有一个会被执行,其它的规则都将不再执行。也就是说,在一组具有相同 activation-group 属性的规则当中,只有一个规则会被执行,其它规则都将不会被执行。
当然对于具有相同 activation-group 属性的规则当中究竟哪一个会先执行,则可以用类似 salience 之类属性来实现
===>"agenda-group"属性:
规则的调用与执行是通过 StatelessSession 或 StatefulSession 来实现的,一般的顺序是创建一个 StatelessSession 或 StatefulSession,将各种经过编译的规则的 package 添加到 session当中,接下来将规则当中可能用到的 Global 对象和 Fact 对象插入到 Session 当中,最后调用fireAllRules 方法来触发、执行规则。在没有调用最后一步 fireAllRules 方法之前,所有的规则及插入的 Fact 对象都存放在一个名叫 Agenda 表的对象当中,这个 Agenda 表中每一个规则及与其匹配相关业务数据叫做 Activation,在调用 fireAllRules 方法后,这些 Activation 会依次执行,这些位于 Agenda 表中的 Activation 的执行顺序在没有设置相关用来控制顺序的属性时(比如 salience 属性),它的执行顺序是随机的,不确定的。
Agenda Group 是用来在 Agenda 的基础之上,对现在的规则进行再次分组,具体的分组方法可以采用为规则添加 agenda-group 属性来实现
agenda-group 属性的值也是一个字符串,通过这个字符串,可以将规则分为若干个Agenda Group,默认情况下,引擎在调用这些设置了 agenda-group 属性的规则的时候需要显示的指定某个 Agenda Group 得到 Focus (焦点),这样位于该 Agenda Group 当中的规则才会触发执行,否则将不执行
statefulSession.getAgenda().getAgendaGroup("002").setFocus();
这句代码的作用是先得到当前的 Agenda,再通过 Agenda 得到名为 002 的 Agenda Group对象,最后把 Focus 设置到名为 002 的 Agenda Group 当中,这个位于名为 002 的 Agenda Group中的 rule2 规则会执行,而位于名为 001 的 Agenda Group 当中的 rule1 则不会被执行,因为名为 001 的 Agenda Group 没有得到 Focus。
实际应用当中 agenda-group 可以和 auto-focus 属性一起使用,这样就不会在代码当中显示的为某个 Agenda Group 设置 Focus 了。一旦将某个规则的 auto-focus 属性设置为 true,那么即使该规则设置了 agenda-group 属性,我们也不需要在代码当中显示的设置 Agenda Group的 Focus 了。
===>"auto-focus"属性:
前面我们也提到 auto-focus 属性,它的作用是用来在已设置了 agenda-group 的规则上设置该规则是否可以自动独取 Focus,如果该属性设置为 true,那么在引擎执行时,就不需要显示的为某个 Agenda Group 设置 Focus,否则需要。对于规则的执行的控制,还可以使用 Agenda Filter 来实现。在 Drools 当中,提供了一个名为 org.drools.runtime.rule.AgendaFilter 的 Agenda Filter 接口,用户可以实现该接口,通过规则当中的某些属性来控制规则要不要执行。 org.drools.runtime.rule.AgendaFilter 接口只有一个方法需要实现,方法体如下:
public boolean accept(Activation activation);
public class TestAgendaFilter implements AgendaFilter {
private String startName;
public TestAgendaFilter(String startName){
this.startName=startName;
}
public boolean accept(Activation activation) {
String ruleName=activation.getRule().getName();
if(ruleName.startsWith(this.startName)){
return true;
}else{
return false;
}
}
}
从实现类中可以看到,我们采用的过滤方法是规则名的前缀,通过 Activation 得到当前 的 Rule 对象,然后得到当前规则的 name,再用这个 name 与给定的 name 前缀进行比较, 如果相同就返回 true就执行当前规则,否则就返回 false,不执行当前规则。
AgendaFilter filter=new TestAgendaFilter("rule1");
statefulSession.fireAllRules(filter);
===>"ruleflow-group"属性:
在使用规则流的时候要用到 ruleflow-group 属性,该属性的值为一个字符串,作用是用来将规则划分为一个个的组,然后在规则流当中通过使用 ruleflow-group 属性的值,从而使用对应的规则。
【4】规则文件中的函数
(1)概念
函数是定义在规则文件当中一代码块,作用是将在规则文件当中若干个规则都会用到的 业务操作封装起来,实现业务代码的复用,减少规则编写的工作量。
函数的编写位置可以是规则文件当中 package 声明后的任何地方,Drools 当中函数声明 的语法格式如代码清单 2-47 所示:
代码清单 2-47
function void/Object functionName(Type arg...) {
/*函数体的业务代码*/
}
(2)例子
package test
import java.util.List;
import java.util.ArrayList;
/*
一个测试函数
用来向Customer对象当中添加指定数量的Order对象的函数
*/
function void setOrder(Customer customer,int orderSize) {
List ls=new ArrayList();
for(int i=0;i<orderSize;i++){
Order order=new Order();
ls.add(order);
}
customer.setOrders(ls);
}
/*
测试规则
*/
rule "rule1"
when
$customer :Customer();
then
setOrder($customer,5);
System.out.println("rule 1 customer has order
size:"+$customer.getOrders().size());
end
/*
测试规则
*/
rule "rule2"
when
$customer :Customer();
then
setOrder($customer,10);
System.out.println("rule 2 customer has order
size:"+$customer.getOrders().size());
end
(2)特殊应用
实际应用当中,可以考虑使用在 Java 类当中定义静态方法的办法来替代在规则文件当 中定义函数。我们知道 Java 类当中的静态方法,不需要将该类实例化就可以使用该方法, 利用这种特性, Drools 为我们提供了一个特殊的 import 语句: import function,通过该 import 语句,可以实现将一个 Java 类中静态方法引入到一个规则文件当中,使得该文件当中的规 则可以像使用普通的 Drools 函数一样来使用 Java 类中某个静态方法。
package test;
public class RuleTools {
public static void printInfo(String name){
System.out.println("your name is :"+name);
}
}
package test
import function test.RuleTools.printInfo;
/*
测试规则
*/
rule "rule1"
when
eval(true);
then
printInfo("高杰");
end
【5】规则文件中的查询
(1)无参数的查询
在 Drools 当中查询以 query 关键字开始,以 end 关键字结束,在 package 当中一个查询 要有唯一的名称,查询的内容就是查询的条件部分,条件部分内容的写法与规则的 LHS 部 分写法完全相同
查询的格式如下:
query "query name"
#conditions
end
代码清单 2-53 是一个简单的查询示例。
代码清单 2-53
query "testQuery"
customer:Customer(age>30,orders.size >10)
end
查 询 的 调 用 是 由 StatefulSession 完 成 的 , 通 过 调 用 StatefulSession 对 象 的 getQueryResults(String queryName) 方 法 实 现 对 查 询 的 调 用 , 该 方 法 的 调 用 会 返 回 一 个 QueryResults 对象,QueryResults 是一个类似于 Collection 接口的集合对象,在它当中存放 在若干个 QueryResultsRow 对象,通过 QueryResultsRow 可以得到对应的 Fact 对象,从而实 现根据条件对当前 WorkingMemory 当中 Fact 对象的查询。
QueryResults queryResults=statefulSession.getQueryResults("testQuery");
for(QueryResultsRow qr:queryResults){
Customer cus=(Customer)qr.get("customer");
//打印查询结果
System.out.println("customer name :"+cus.getName());
}
statefulSession.dispose();
(2)有参数查询
和函数一样,查询也可以接收外部传入参数,对于可以接收外部参数的查询格式如下:
query "query name" (Object obj,...)
#conditions
end
和不带参数的查询相比,唯一不同之外就是在查询名称后面多了一个用括号括起来的输 入参数,查询可接收多个参数,多个参数之间用“,”分隔,每个参数都要有对应的类型声 明,代码清单 2-55 演示了一个简单的带参数的查询示例。
代码清单 2-55
query "testQuery"(int $age,String $gender)
customer:Customer(age>$age,gender==$gender)
end
在这个查询当中,有两个外部参数需要传入,一个是类型为 int 的 $age;一个是类型为 String 的$gender(这里传入参数变量名前添加前缀“$”符号,是为了和条件表达式中相关 变量区别开来)
对于带参数的查询,可以采用 StatefulSession 提供的
getQueryResults(String queryName,new Object[]{})
方法来实现,这个方法中第一个参数为查询的名称,第二个 Object 对象数组既为要输入的参数集合