对于Drools Rule的理解
一个规则可以包含三个部分:
- 属性部分:定义当前规则执行的一些属性等,比如是否可被重复执行、过期时间、生效时间等。
- 条件部分(LHS):定义当前规则的条件,如 when Message(); 判断当前workingMemory中是否存在Message对象。
- 结果部分(RHS):这里可以写普通java代码,即当前规则条件满足后执行的操作,可以直接调用Fact对象的方法来操作应用。
属性部分:
规则的属性有以下几种
activation-group、agenda-group、auto-focus、date-effective、date-expires、dialect、duration、duration-value、enabled、lock-on-active、no-loop、ruleflow-group、salience
几个重要的属性:
- activation-group
该属性的作用是将若干个规则划分成一个组,用一个字符串来给这个组命名,这样在执行的时候,具有相同activation-group 属性的规则中只要有一个会被执行,其它的规则都将不再执行。也就是说,在一组具有相同activation-group 属性的规则当中,只有一个规则会被执行,其它规则都将不会被执行。
- 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); 在该方法当中提供了一个Activation 参数,通过该参数我们可以得到当前正在执行的规则对象或其它一些属性,该方法要返回一个布尔值,该布尔值就决定了要不要执行当前这个规则,返回true 就执行规则,否则就不执行
与agenda-group配合使用,设置焦点的是否可以自动获取boolean型,默认值为false
- agenda-group
Agenda Group 是用来在Agenda 的基础之上,对现在的规则进行再次分组,具体的分组方法可以采用为规则添加agenda-group 属性来实现。agenda-group 属性的值也是一个字符串,通过这个字符串,可以将规则分为若干个Agenda Group,默认情况下,引擎在调用这些设置了agenda-group 属性的规则的时候需要显示的指定某个Agenda Group 得到Focus(焦点),这样位于该Agenda Group 当中的规则才会触发执行,否则将不执行
基于Agenda将规则分组;只有当某个Agenda组获取到焦点(focus)时,该组的规则才会被执行string型,默认值为MAIN规则的调用与执行是通过StatelessSession 或StatefulSession 来实现的,一般的顺序是创建一个StatelessSession 或StatefulSession,将各种经过编译的规则的package 添加到session当中,接下来将规则当中可能用到的Global 对象和Fact 对象插入到Session 当中,最后调用fireAllRules 方法来触发、执行规则。在没有调用最后一步fireAllRules 方法之前,所有的规则及插入的Fact 对象都存放在一个名叫Agenda 表的对象当中,这个Agenda 表中每一个规则及与其匹配相关业务数据叫做Activation,在调用fireAllRules 方法后,这些Activation 会依次执行,这些位于Agenda 表中的Activation 的执行顺序在没有设置相关用来控制顺序的属性时(比如salience 属性),它的执行顺序是随机的,不确定的。
- no-loop
drools提供了一些api,可以对当前传入workingMemory中的Fact对象进行修改或者个数的增减,比如上述的update方法,就是将当前的workingMemory中的Message类型的Fact对象进行属性更新,这种操作会触发规则的重新匹配执行,可以理解为Fact对象更新了,所以规则需要重新匹配一遍。update之后,之前的修改都会生效。当然对Fact对象数据的修改并不是一定需要调用update才可以生效,简单的使用set方法设置就可以完成,这里类似于java的引用调用,所以何时使用update是一个需要仔细考虑的问题,一旦不慎,极有可能会造成规则的死循环。上述的no-loop true,即设置当前的规则,只执行一次,如果本身的RHS部分有update等触发规则重新执行的操作,也不要再次执行当前规则。
- lock-on-active
当在规则上使用ruleflow-group属性或agenda-group属性的时候,将lock-on-active 属性的值设置为true,可避免因某些Fact对象被修改而使已经执行过的规则再次被激活执行。可以看出该属性与no-loop属性有相似之处,no-loop属性是为了避免Fact被修改或调用了insert、retract、update之类的方法而导致本规则再次激活执行,这里的lock-on-active 属性起同样的作用,lock-on-active是no-loop的增强版属性,它主要作用在使用ruleflow-group属性或agenda-group属性的时候。lock-on-active属性默认值为false。与no-loop不同的是lock-on-active可以避免其他规则修改FACT对象导致规则的重新执行。
- date-effective、date-expires、enabled
顾名思义
- salience
它的作用是用来设置规则执行的优先级,salience 属性的值是一个数字,数字越大执行优先级越高,同时它的值可以是一个负数。默认情况下,规则的salience 默认值为0,所以如果我们不手动设置规则的salience 属性,那么它的执行顺序是随机的
int型,默认值为0
- rule-flow-group
基于ruleflow将规则分组string型,无默认值,作用是用来将规则划分为一个个的组,然后在规则流当中通过使用ruleflow-group 属性的值,从而使用对应的规则
作用等同于agenda-group。二选一即可
- dialect:
该属性用来定义规则当中要使用的语言类型,目前Drools支持两种语言:mvel 和java,默认使用的java 语言
- duration:
如果设置了该属性,那么规则将在该属性指定的值之后在另外一个线程里触发。该属性对应的值为一个长整型,单位是毫秒
设置DRL文件开始执行之后延迟多长时间开始执行这条规则
long型,无默认值
条件部分(LHS)
- when:规则条件开始。条件可以单个,也可以多个,多个条件一次排列,比如
when
eval(true)
$customer:Customer()
$message:Message(status==0)
上述罗列了三个条件,当前规则只有在这三个条件都匹配的时候才会执行RHS部分。
eval(true):是一个默认的api,true 无条件执行,类似于 while(true)
$message:Message(status==0) 这句话标示的:当前的workingMemory存在Message类型并且status属性的值为0的Fact对象,这个对象通常是通过外部java代码插入或者在已执行规则的RHS部分中insert进去的。
"$message"代表着当前条件的引用的Message实例。在后续的条件部分和RHS部分中,可以使用这个名称对该FACT对象进行修改或者调用其方法。可选。
条件可以有组合,比如:
Message(status==0 || (status > 1 && status <=100))如果条件全部是 &&关系,可以使用“,”来替代,但是两者不能混用
- Drools提供了十二中类型比较操作符:
> , >= , < , <= , == , !=
contains / not contains / memberOf / not memberOf /matches/ not matches
contains, not contains:顾名思义
memberOf:判断某个Fact属性值是否在某个集合中。
not memberOf:顾名思义
matches:正则表达式匹配,与java不同的是,不用考虑'/'的转义问题
not matches:顾名思义
结果部分(RHS)
当满足规则条件,则进入规则结果部分执行。
RHS可以是纯java代码,比如:
then
System.out.println("OK"); //会在控制台打印出ok
end
RHS可以调用Fact的方法,比如
$message.setXXX(value);
RHS可以使用Drools 原生的方法:
insert:往当前workingMemory中插入一个新的Fact对象
update:更新workingMemory中对应的Fact对象,会触发规则的再次执行
modify:修改,与update语法不同,结果都是更新操作
retract:删除workingMemory中对应的Fact对象
以上操作可能会触发规则的再次执行。具体看规则中no-loop与lock-on-active属性的设置。
RHS也可以调用规则文件中定义的方法,方法的定义使用 function 关键字
function void console {
System.out.println();
StringUtils.getId();// 调用外部静态方法,StringUtils必须使用import导入,getId()必须是静态方法
}
关于规则执行
插入Product(discount=1)执行以下规则:
- Demo 1
import com.drools.model.Product;
rule updateDistcount
salience 9
no-loop true
when
productObj:Product(discount > 0);
then
productObj.setDiscount(productObj.getDiscount() + 1);
System.out.println(productObj.getDiscount());
update(productObj);
end
rule otherRule
salience 1
when
productObj : Product(discount > 1);
then
System.out.println("被触发了" + productObj.getDiscount());
end
执行结果为:
2
被触发了2
- Demo 2
去掉no-loop
import com.drools.model.Product;
rule updateDistcount
salience 9
/*no-loop true*/
when
productObj:Product(discount > 0);
then
productObj.setDiscount(productObj.getDiscount() + 1);
System.out.println(productObj.getDiscount());
update(productObj);
end
rule otherRule
salience 1
when
productObj : Product(discount > 1);
then
System.out.println("被触发了" + productObj.getDiscount());
end
执行结果为:
2
3
4
5
6
7
.....
结果分析:
第一条规则未设置no-loop属性,当update后Working Memory中的Fact被修改,重新触发规则。
第二条规则未执行是由于优先级(salience)问题。更详细的原因后续补充..
- Demo 3
去掉update操作
import com.drools.model.Product;
rule updateDistcount
salience 9
no-loop true
when
productObj:Product(discount > 0);
then
productObj.setDiscount(productObj.getDiscount() + 1);
System.out.println(productObj.getDiscount());
/*update(productObj);*/
end
rule otherRule
salience 1
when
productObj : Product(discount > 1);
then
System.out.println("被触发了" + productObj.getDiscount());
end
执行结果为:"2"。分析如下:
rete算法会将fact匹配到的规则做记录列表,然后按照匹配列表中的规则RHS按照salience的次序执行。
所以它是这样一个过程:接受数据输入、匹配业务规则、做出业务决策。
当一条规则导致FACT变更时,可能会导致以上过程重新执行(未设置no-loop、lock-on-active属性)
- Demo 4
第二条规则加入lock-on-active true
package org.drools.example.api.dynamic
import org.drools.example.api.model.Product
rule updateDistcount
salience 9
no-loop true
when
productObj:Product(discount > 0);
then
productObj.setDiscount(productObj.getDiscount() + 1);
System.out.println(productObj.getDiscount());
update(productObj);
end
rule otherRule
lock-on-active true
salience 1
when
productObj : Product(discount > 1);
then
System.out.println("被触发了" + productObj.getDiscount());
end
执行结果:"2"。结果分析如下:
详见lock-on-active属性
Drools Kie Api 中几个核心概念:
- KieServices
通过KieServices.Factory.get()方式获得,是一个单例的、线程安全的,为其他Kie工具提供服务
KieServices是Kie项目的中心,通过其可以获取的各种对象来完成规则构建、管理和执行等操作
其中有的方法分为两大类:getX()和newX(),其中,get只会返回一个对应单例对象的引用,new则会重新创建一个对象
- KieContainer
从KieServices中获得,其会借助KieProject来初始化、构造KieModule并将放入KieRepository中
是一个给定的KieModule中所有KieBase的存放容器
- KieRepository
KieRepository是一个单例对象,它是一个存放KieModule的仓库,KieModule由kmodule.xml文件定义.
- KieProject
KieContainer可以通过KieProject来查找KieModule定义的信息,并根据这些信息构造KieBase和KieSession;
KieProject 为物理概念;通过KieProject中的KieModule文件可定义KieBase、KieSession。
- ClasspathKieProject
ClasspathKieProject实现了KieProject接口,它提供了根据类路径中的META-INF/kmodule.xml文件构造KieModule的能力
- KieBase
KieBase就是一个知识仓库,包含了若干的规则、流程、方法等,但是不包含运行时的数据
- KieSession
KieSession就是一个跟Drools引擎打交道的会话,其基于KieBase创建
KieContainer创建KieSession是一种较为方便的做法,其实他本质上是从KieBase中创建出来
- 几个核心概念之间的关系
KieProject即一个规则工程,为物理概念其对应的逻辑概念为KieModule。
KieProject包含了一个kmodule.xml文件。其中定义了kmodule、kbase和ksession等属性。
kmodule.xml
kmodule里面包含了一个或多个kbase。
每一个kbase都有一个唯一的名字(name),不能重复。
packages 对应的值是命名空间。规则引擎会根据这里定义的包来查找规则定义文件。可以定义多个包,以逗号分隔。
每一个kbase下面包含一个或多个ksession。
每一个ksession都有一个唯一的名字(name),不能重复。
下面内容为纯摘录部分,待后续整理
使用规则工程(最终形态为k-jar)
首先创建KieServices
然后通过getKieClasspathContainer方法获得KieContainer
KieContainer根据KieProject中的kmodule文件(ClasspathKieProject实现的)
创建KieModule并放入KieRepository
然后KieContainer创建KieBase
之后KieBase创建KieSession规则会话到规则引擎
通过KieSession即可执行规则或Process。