Drools入门之规则when
复合值限制in/not in
复合值限制是指超过一种匹配值的限制条件,如Sql语句中的in,语法格式与sql多匹配相似。以括号为第二参数,括号内比较值以逗号分隔,比较值可以是变量、文字、返回值或标识符等,其内部功能与“!=”“==”运算符的多限制列表类似。
public class RulesWhen {
public static void main(String[] args) {
KieServices kieService = KieServices.Factory.get();
KieContainer kieContainer = kieService.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("testWhen");
Person person1 = new Person();
person1.setClassName("三班");
kieSession.insert(person1);
kieSession.fireAllRules();
kieSession.dispose();
}
}
rule "in rule1"
when
Person(className in ("一班","二班","三班"))
then
System.out.println("验证in的复合限制规则");
end
条件元素eval
条件元素eval是在最开始的测试用例中就使用过的。它可以是任何语义代码,并返回一个boolean类型,也可以绑定规则LHS中的变量、函数或直接写常量进行比较。但在实际编码过程中,要尽可能地少用eval作为比较符,因为会导致引擎的性能问题。读者可以参考书中经典Hello World章节中的用例,或者在funcation函数中进行测试。
条件元素not
条件元素not是判断在工作内存中是否还存在某个值,当not EC成立时就代表当前工作内存中不存在EC,可以看成“一定没有这个值”。
rule "not rule"
when
not School()
then
System.out.println("不存在School");
end
条件元素exists
条件元素exists的功能与not的功能是相反的,指在工作内存中是否存在某个东西,可以看作“至少有一个”。
rule "exists rule"
when
exists Person()
then
System.out.println("存在Person");
end
条件元素forall
条件元素forall的功能与eval()的功能是相似的,通过模式匹配对forall进行一个判断,当完全匹配时,forall为true。
rule "for all"
when
forall($p:Person(name=="张三") Person(this == $p,age == 30))
then
System.out.println("测试forall");
end
条件元素from
条件元素from是一个很有意思的约束,它可以让用户指定任意的资源,用于LHS部分的数据匹配,也可以用来对集合进行遍历,还可以用来对Java服务进行访问并就结果进行遍历。
新建一个Teacher类
package com.domain;
public class Teacher {
private String teacherName;
public Teacher(){}
public Teacher(String teacherName) {
this.teacherName = teacherName;
}
public String getTeacherName() {
return teacherName;
}
public void setTeacherName(String teacherName) {
this.teacherName = teacherName;
}
}
public class RulesWhen {
public static void main(String[] args) {
KieServices kieService = KieServices.Factory.get();
KieContainer kieContainer = kieService.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("testWhen");
Person person1 = new Person();
person1.setClassName("三班");
person1.setName("张三");
person1.setAge(30);
person1.setTeacher(new Teacher("王老师"));
kieSession.insert(person1);
Teacher teacher = new Teacher("李老师");
kieSession.insert(teacher);
kieSession.fireAllRules();
kieSession.dispose();
}
}
然后再Person类中新增私有属性private Teacher teacher
rule "from rule"
when
$p:Person($pt:teacher)
$t:Teacher(teacherName == "王老师") from $pt
then
System.out.println("测试from");
end
上述规则文件内容也可以换成另外一种方式,其内容为:
rule "from rule2"
when
$p:Person()
$t:Teacher(teacherName == "王老师") from $p.teacher
then
System.out.println("测试from2");
end
条件元素from支持对象源,返回一个对象集合,在这种情况下,from将会遍历集合中的所有对象,并分别匹配它们每一个对象值。
rule "from rule3"
when
$t:Teacher()
$p:Person(className == "二班") from $t.personList
then
System.out.println("测试from3,person"+$p);
end
public class RulesWhen {
public static void main(String[] args) {
KieServices kieService = KieServices.Factory.get();
KieContainer kieContainer = kieService.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("testWhen");
Person person1 = new Person();
person1.setClassName("一班");
person1.setName("张三");
person1.setAge(30);
Person person2 = new Person();
person2.setClassName("二班");
person2.setName("李四");
person2.setAge(20);
Teacher teacher = new Teacher("李老师");
person1.setTeacher(teacher);
person2.setTeacher(teacher);
List<Person> personList = new ArrayList<Person>();
personList.add(person1);
personList.add(person2);
teacher.setPersonList(personList);
kieSession.insert(person1);
kieSession.insert(person2);
kieSession.insert(teacher);
kieSession.fireAllRules();
kieSession.dispose();
}
}
使用form时,设计者必须要注意使用属性功能,特别是与lock-on-active规则属性联合使用时,因为该属性可能产生不一样的结果。
条件元素collect
条件元素collect从组织结构上看,它需要结合from来使用,collect从字面意思来看是一个收集的功能,也就是说from是使用遍历的,而from collect是用来汇总的。而且在collect后的参数中还可以是匹配、遍历、收集、统计的功能,因此collect是一个十分强大的功能。
rule "collect rule1"
when
$al:ArrayList() from collect($p:Person(className=="一班"))
then
System.out.println("测试collect,person"+$al);
end
rule "collect rule2"
when
$al:ArrayList(size == 2) from collect($p:Person(className=="一班"))
then
System.out.println("测试collect,al size="+$al.size());
end
@Test
public void testFromCollect(){
KieServices kieService = KieServices.Factory.get();
KieContainer kieContainer = kieService.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("testWhen");
Person person1 = new Person();
person1.setClassName("一班");
person1.setName("张三");
person1.setAge(30);
Person person2 = new Person();
person2.setClassName("二班");
person2.setName("李四");
person2.setAge(20);
Person person3 = new Person();
person3.setClassName("二班");
person3.setName("王五");
person3.setAge(25);
Person person4 = new Person();
person4.setClassName("一班");
person4.setName("赵六");
person4.setAge(18);
kieSession.insert(person1);
kieSession.insert(person2);
kieSession.insert(person3);
kieSession.insert(person4);
kieSession.fireAllRules();
kieSession.dispose();
}
条件元素accumulate
条件元素accumulate是一个更为灵活的collect,它可以实现collect做不了的事。如条件元素accumulate的参数可以求一些不同的值,这是collect做不到的。
它主要做的事是允许规则迭代整个对象的集合,为每个元素定制执行动作,并在结束时返回一个结果对象,accumulate不仅支持预定义的累积函数的使用,而且可以使用其他内置函数,重要的是可使用自定义函数进行特殊化操作。常用的accumulate函数有求最大值、求最小值和求合等。
rule "accumulate rule1"
when
accumulate(Person($age:age),$min:min($age),$max:max($age),$sum:sum($age))
then
System.out.println("accumulate,min="+$min + ",max="+$max+",sum="+$sum);
end
输出结果
accumulate,min=18,max=30,sum=93.0
Drools附带有内置的accumulate功能,包括average(平均值)、min(最小值)、max(最大值)、count(统计)、sum(求和)、collectList(返回List)和collectSet(返回HastSet)。
第二种形式的accumulate只支持身后兼容,下面是inline的语法结构:
<result pattern>from accumulate(<source pattern>,init(<init code>),
action(<action code>),
reverse(<reverse code>),
result(<result expression>))
语法分析如下:
<init code>
:init是做初始化用的,简单地说,在source pattern遍历完之后,就已经触发,类似for的开头。
<action code>
:action会执行所有满足条件的源对象进行操作,类似for的方法体。在里面可写Java脚本。
<reverse code>
:这是一个可选的被选方言的语义代码块,如果存在,将为不再匹配资源模式的每个资源对象的执行。这个代码块的目的是不在<action code>
块中做任何计算。所以,当一个资源对象被修改或删除时,引擎可能做递减计算,极大地提升了这些操作的性能。
<result expression>
:返回值,是根据action上面两个遍历出来的结果进行一个返回,这个返回值中也可以进行计算。
<result pattern>
:返回值类型,在<result expression>
返回值的类型中再一次进行匹配,如果匹配不成功则返回false。编辑规则文件accumulate.drl,并添加“测试accumulatefrom第二种用法”规则,其内容为(注意加粗部分):
rule "accumulate rule2"
when
$total:Integer() from
accumulate(Person($value:age),
init(Integer total = 0;),
action(total += $value;),
result(total)
)
then
System.out.println("accumulate,$total="+$total);
end
init是初始化,action是遍历并计算,result是返回结果,这个规则可以返回类型,而且是在from前面定义的类型。上述例子中的返回值是total,它是一个Integer包装类,所以在from前面只能用Intger()来接收,当然要看返回值的具体类型,也可以返回String。但返回值类型必须有toString()才能正常返回。这里需要提醒设计者,设计的方言必须按语法进行规则的编码。
reverse是可选项,编辑Person.java文件添加“private Double dous;”并实现get set方法。新增规则
rule "accumulate rule3"
when
$total:Double() from
accumulate(Person($age:age),
init(Double total = 0.0;),
action(total += $age; System.out.println(total+">>>>>");),
reverse(total-=$age; System.out.println(total+"<<<<<<");),
result(total)
)
then
System.out.println("accumulate,$total="+$total);
end
rule "accumulate rule4"
when
$ps:Person(dous>=3)
then
$ps.setDous(1.2);
update($ps)
System.out.println("dous:"+$ps.dous);
end
@Test
public void testAccumulate(){
KieServices kieService = KieServices.Factory.get();
KieContainer kieContainer = kieService.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("testWhen");
Person person1 = new Person();
person1.setClassName("一班");
person1.setName("张三");
person1.setDous(5.0);
person1.setAge(30);
Person person2 = new Person();
person2.setClassName("二班");
person2.setName("李四");
person2.setAge(20);
Person person3 = new Person();
person3.setClassName("二班");
person3.setName("王五");
person3.setAge(25);
Person person4 = new Person();
person4.setClassName("一班");
person4.setName("赵六");
person4.setAge(18);
kieSession.insert(person1);
kieSession.insert(person2);
kieSession.insert(person3);
kieSession.insert(person4);
kieSession.fireAllRules();
kieSession.dispose();
}
}
输出日志
18.0>>>>>
43.0>>>>>
63.0>>>>>
93.0>>>>>
accumulate,$total=93.0
dous:1.2
63.0<<<<<<
93.0>>>>>
accumulate,$total=93.0
针对上述的例子做以下总结:
- accumulate的使用,有一个很重要的函数action。这个函数提供了匹配源模式的执行动作。
- 将action看成两个状态,当源对象匹配源模式时,定会触发action,将触发过action的源对象称为有状态的(等同于标记),反之为无状态的。
- 当传入的源对象在RHS中或其他规则的RHS中发生改变(update,insert…)时,所有满足条件规则的将会再次被激活。
- 当规则再次被执行且遇到accumulate时,则有状态的源对象会先执行reverse函数进行“回滚”操作,并将修改后的源对象再次与accumulate条件进行比较,若比较为true,则该对象(被修改过的)会再次触发action函数;若比较为false,则不会执行action函数。
- 当规则再次被执行且遇到accumulate时,则无状态的源对象会与accumulate条件进行比较。若比较为true,则该对象(被修改过的)会第一次触发action函数;若比较为false,则不会执行action函数。
根据上述所有测试的结果得出以下结论。
-
accumulate有3种形式,分别是inline、
$min:min(XXX)
和min()
。 -
inline形式虽然官方不推荐,但不代表不能使用。
-
$min:min(XXX)
只是其中的一种写法,如果在函数前面加了$XXX
变量,则accumulate不是使用from方式,至于如何取$XXX
变量的值,与模式方式是一样的。根据上面的介绍,是可以通过在函数的结束符“;”后面加条件约束,引用的方法也同模式方式一样。例如,在规则代码accumulate的第一种写法中加一个条件,其内容为:rule "测试 Accumulate 第一种 取对象中的最大值和最小值" when accumulate(Person($age:age),$min:min($age),$max:max($age),$sum:sum($age);) then System.out.println("传入的对象最小值为"+$min+"最大值为"+$max+"求合"+$sum); end
-
min()写法,前面没有加任何的变量引用说明,当然也不可能在函数的结束符“;”后面再加条件约束,并且想要使用函数中返回的结果,在这种没有引用变量的前提下,则必须要引用from关键字,但from关键字有一个问题,即返回的结果。只有在使用函数时不能有变量引用,才能正常使用from。
-
使用accumulate语法时,如果source pattern结束符后面函数的结果有变量,那么不能使用from,且使用from返回结果必须是一个。上述说的其实都是accumulate中自带的一些函数和语法,accumulate的强大功能远不止如此。
上述说的其实都是accumulate中自带的一些函数和语法,accumulate的强大功能远不止如此。accumulate函数可以自定义,实现一个新的accumulate函数,所以需要创建一个Java类,该类实现AccumulateFunction接口,在7.10版本中与官方说明不一样。规则使用自定义accumulate时,需要加关键字,如import accumulate com.rulesAccumulate.TestAccunmulateneeded factorial。getResult在自定义的类中可以写多个,同一个类可以写多个函数,并且可以对原来的方法进行修改。继承了AccumulateFunction接口相当于要重写里面的内置函数。
内置的函数(总和、平均值等)是由引擎自动导入的,只有用户自定义定制的累积函数需要显式导入。新建TestAccunmulateneeded并实现AccumulateFunction目录为com/rulesAccumulate,其内容为:
public class TestAccunmulateneeded implements AccumulateFunction { public static class Factorial implements Externalizable{ public Factorial(){} public double total = 1; public void writeExternal(ObjectOutput out) throws IOException { System.out.println("writeExternal"); out.writeDouble(total); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("readExternal"); total=in.readDouble(); } } public Serializable createContext() { System.out.println("createContext"); return new Factorial(); } public void init(Serializable serializable) throws Exception { Factorial factorial = (Factorial) serializable; factorial.total = 1; System.out.println("init"); } public void accumulate(Serializable serializable, Object o) { Factorial factorial = (Factorial) serializable; factorial.total = ((Number)o).doubleValue(); System.out.println("accumulate"); } public void reverse(Serializable serializable, Object o) throws Exception { Factorial factorial = (Factorial) serializable; factorial.total /= ((Number)o).doubleValue(); System.out.println("reverse"); } public Object getResult(Serializable serializable) throws Exception { Factorial factorial = (Factorial) serializable; Double d = factorial.total == 1 ? 1 : factorial.total; System.out.println("getResult"); return d; } public boolean supportsReverse() { System.out.println("supportsReverse"); return true; } public Class<?> getResultType() { System.out.println("getResultType"); return Number.class; } public void writeExternal(ObjectOutput out) throws IOException { System.out.println("writeExternal father"); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("readExternal father"); } }
import com.domain.Person import accumulate com.accunmlate01.TestAccunmulateneeded factorial rule "isAccumulate" when accumulate(Person($value:age != null),$factorial:factorial($value)) then System.out.println("自定义函数》》"+$factorial); end
日志输出
createContext supportsReverse init supportsReverse accumulate supportsReverse accumulate supportsReverse accumulate supportsReverse accumulate getResult 自定义函数》》30.0
通过上述操作,就可以实现自定义函数来操作特殊业务了。它的用法和sum、min内置函数相似,但是自定义的名称如果是sum、min等,是不会对原方法进行重写的。
条件元素规则继承
条件元素规则继承与Java相似。在学习Drools规则引擎的语法过程中,大部分操作都离不开使用Java脚本对一类fact对象进行操作,因为Drools规则引擎是基于Java开发的开源技术。编写Java代码时,如果发现有重复代码,一般的做法是写一些公共方法或通过继承的方式进行重构。Drools规则引擎的语法也存在这类条件继承的逻辑关系,其目的是为了减少相同代码的出现,从而方便代码的管理与优化。
一般写法
rule "extends1"
when
Person(name=="张三")
then
System.out.println("extends1");
end
rule "extends2"
when
Person(name=="张三")
Person(age == 30)
then
System.out.println("extends2");
end
继承写法
rule "extends1"
when
Person(name=="张三")
then
System.out.println("extends1");
end
rule "extends2" extends "extends1"
when
Person(age == 30)
then
System.out.println("extends2");
end
条件元素do对应多then同条件
条件元素do对应多then同条件,实际项目中用到的地方并不多。规则继承允许避免重写条件设置多个规则,如使用do关键词可以有效地避免规则then部分出现多次相同的条件。它们基本上是用一个标识符来标记额外的子句,以制订一个规则体现为几个。确定规则应该指向定义的then部分。例如,如果想要写出规则继承中看到的两条规则作为一个单一规则,可以新建do.drl文件,目录为rules/isDoThen,其内容为:
rule "do then"
when
Person(age == 30)
do[then01]
then
System.out.println("--------");
then[then01]
System.out.println("********");
end
日志
********
--------
从结果中看出,两个then都被输出了,因为在LHS部分中让其先执行then[then01]的RHS部分。do在RHS部分多了一个带“name”的then,这里就先称之为then的名称,然后正常insert实体Person到fact中,调用规则后,当条件满足时,都会被输出,只是先输出了“*****”。
使用do关键字标记可以去一个具体的后果,即then。在所有的条件规则都是true时,两个then都会执行,如果定义了两个不同的规则和规则继承一样,必须非常小心地使用这个特性。虽然小规模的业务使用do关键字可以节省很多重写的工作,但从长远来看,并不是非常合适,不仅无形中提高了规则的可读性,而且结果不可预测。
- 使用do时,do的参数与then的参数必须有对应关系。
- then可以不与do成对出现,如规则testDoNo3。
- 使用do时,do后的参数在RHS中是必须要存在的,参数是一个指针。
- 使用修改时,在非update情况下,then部分都会被执行。
- 使用修改时,在update情况下,then部分是有优先级的,是根据do的顺序控制的,但没有参数的then可能会改变其他的RHS部分。
- 使用do时,顺序控制是至关重要的。
- 无参数的then,优先级可能会低于指定的do。
- do只能在其他条件元素之后使用。