drools规则属性rule attributes的使用
- 4、疑问
一、介绍
规则属性
是您可以添加到业务规则以修改规则行为
的附加规范。 在 DRL
文件中,您通常在规则条件和操作的上方定义规则属性,多个属性位于单独的行中
,格式如下:
rule "rule_name"
// Attribute
// Attribute
when
// Conditions
then
// Actions
end
二、常见的规则属性
规则属性 | 解释 | 举例 |
salience | | salience 99 |
enabled | 定义规则是否启用. true 启用,false 禁用, | enabled true |
date-effective | 包含时间和日期的字符串,当当前时间大于 | date-effective “4-5月-2022” |
date-expires | 设置规则的过期时间,时间格式和上方一样。 | date-expires “4-5月-2022” |
no-loop | 布尔值,默认值为 | no-loop true |
agenda-group | | agenda-group “GroupName” |
auto-focus | 布尔值, | auto-focus true |
activation-group | 表示该组下的规则 | activation-group “GroupName” |
duration | long类型的值,如果 | duration 1000 |
timer | 一个字符串,标识用于调度规则的 int(间隔)或 cron 计时器定义。 | Example: timer ( cron:* 0/15 * * * ? ) (every 15 minutes) |
calendar | 定义Quartz calendar用于调度规则。 | |
lock-on-active | 一个布尔值,仅适用于规则流组或议程组中的规则。 选择该选项后,下次规则的规则流组变为活动状态或规则的议程组获得焦点时,规则无法再次激活,直到规则流组不再处于活动状态或议程组失去焦点。 这是 no-loop 属性的更强版本,因为匹配规则的激活被丢弃,无论更新的来源如何(不仅是规则本身)。 此属性非常适合计算规则,其中您有许多修改事实的规则并且您不希望任何规则重新匹配和再次触发。 | lock-on-active true |
dialect | 将 JAVA 或 MVEL 标识为用于规则中的代码表达式的语言的字符串。 默认情况下,该规则使用在包级别指定的方言。 此处指定的任何方言都会覆盖该规则的包方言设置。 | dialect “JAVA” |
三、部分规则属性案例
此处编写出规则文件和部分核心Java代码
1、salience
定义规则执行的优先级,salience的值越大,优先级越高
1、规则文件的编写
rule "salience_rule_1"
salience 4
when
then
System.out.println("rule 1");
end
rule "salience_rule_2"
salience 3
when
then
System.out.println("rule 2");
end
// 此处优先级的值是动态获取来的
rule "salience_rule_3"
salience $dynamicSalience
when
$dynamicSalience: Integer()
then
System.out.println("rule 3");
end
注意:
我们的salience_rule_3
的优先级的值是动态
来的,即是从工作内存中获取的。
2、java代码编写
public class DroolsSalienceApplication {
public static void main(String[] args) {
KieServices kieServices = KieServices.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
// 向工作内存中插入一个Integer值,salience_rule_3 需要用到这个优先级
kieSession.insert(10);
// 只匹配规则名称是已 salience_ 开头的规则,忽略其余的规则
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("salience_"));
kieSession.dispose();
}
}
kieSession.insert(10);
此处向工作内存中插入一个值,将会匹配到salience_rule_3
,然后动态修改它的优先级。
3、运行结果
rule 3
rule 1
rule 2
因为 salience
的值越大优先级越高,所以是这个顺序。
2、enabled
定义规则是否启用,true
启用 false
禁用
1、规则文件编写
package rules
rule "enabled_rule_1"
// 禁用此规则
enabled false
when
then
System.out.println("enabled_rule_1");
end
rule "enabled_rule_2"
// 启用此规则,默认就是启用
enabled true
when
then
System.out.println("enabled_rule_2");
end
enabled_rule_2
这个规则需要运行,enabled_rule_1
这个规则不能运行。
2、java代码编写
/**
* 测试规则的启用和禁用
*/
public class DroolsEnabledApplication {
public static void main(String[] args) {
KieServices kieServices = KieServices.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
// 只匹配规则名称是已 enabled_ 开头的规则,忽略其余的规则
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("enabled_"));
kieSession.dispose();
}
}
没有需要注意的地方
3、运行结果
enabled_rule_2
可以看到只有规则enabled_rule_2
输出了结果,而enabled_rule_1
被禁用了。
3、date-effective
定义规则什么时候启用,只有当前时间>
规则时间才会启用。需要注意默认的时间格式
,可以通过java代码进行修改。
1、规则文件编写
package rules
import java.text.SimpleDateFormat
import java.util.Date
// 规则一:输出当前时间
rule "date_effective_rule_1"
when
then
System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
end
// 规则二: 该规则会在2022-05-18 10:54:26之后被激活
rule "date_effective_rule_2"
date-effective "2022-05-18 10:54:26"
when
then
System.out.println("date_effective_rule_2执行了,规则允许被执行的时间应该在2022-05-18 10:54:26之后");
end
// 规则三: 该规则会在2023-05-18 10:54:26之后被激活
rule "date_effective_rule_3"
date-effective "2023-05-18 10:54:26"
when
then
System.out.println("date_effective_rule_3会在时间到了2023-05-18 10:54:26才激活");
end
规则一:输出当前时间
规则二: 该规则会在2022-05-18 10:54:26之后被激活
规则三: 该规则会在2023-05-18 10:54:26之后被激活
2、java代码编写
/**
* 测试规则在执行的时间之后才能执行
*/
public class DroolsDateEffectiveApplication {
public static void main(String[] args) {
// 设置日期格式,否则可能会报错(Wrong date-effective value: Invalid date input format: [2022-05-18 10:54:26] it should follow: [d-MMM-yyyy]]])
System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
KieServices kieServices = KieServices.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
// 只匹配规则名称是已 date_effective_ 开头的规则,忽略其余的规则
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("date_effective_"));
kieSession.dispose();
}
}
需要注意System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
这句,这个修改drools中的日期格式,因为规则中写的日期格式为date-effective "2023-05-18 10:54:26"
而默认的格式为d-MMM-yyyy
,不修会报错。
3、运行结果
当前时间:2022-05-18 10:59:38
date_effective_rule_2执行了,规则允许被执行的时间应该在2022-05-18 10:54:26之后
可以看到规则二执行了,规则三没有执行,因为规则三需要时间到达了2023-05-18 10:54:26
才执行,而当前时间不符合。
4、注意事项
如果出现了Wrong date-effective value: Invalid date input format: [2022-05-18 10:54:26] it should follow: [d-MMM-yyyy]]]
这个错误该怎么解决了,这是因为日期格式不正确。需要在java代码中进行如下设置System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss")
4、date-expires
定义规则的过期时间,即规则到了该时间之后就不可使用了。和date-effective
的用法类似,此处就不演示了。
5、no-loop
定义当当前规则
的结果修改了fact
对象时,是否可以再次执行该规则。可以防止死循环
1、规则文件编写
package rules
import java.util.concurrent.TimeUnit
import java.text.SimpleDateFormat
import java.util.Date
rule "no_loop_rule_1"
no-loop true
when
$i: Integer(intValue() < 20)
then
modify($i){
}
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " no_loop_rule_1 i=" + $i);
end
rule "no_loop_rule_2"
no-loop false
when
$i: Integer(intValue() < 20)
then
modify($i){
}
TimeUnit.SECONDS.sleep(1);
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " no_loop_rule_2 i=" + $i);
end
解释:
no_loop_rule_1
:no-loop true
表示如果当前规则的RHS
部分,对Fact对象进行了修改,则不会再次触发该规则。那如果是no_loop_rule_2
修改了,会导致该规则的触发吗?答案是
会触发,如果我不想被触发呢?那么使用lock-on-active
可以实现。
no_loop_rule_2
:no-loop false
表示如果当前规则的RHS
部分,对Fact对象进行了修改,那么还会再次匹配这个规则。
2、java代码编写
/**
* 测试规则是否可以再次被执行
*/
public class DroolsNoLoopApplication {
public static void main(String[] args) throws InterruptedException {
System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
KieServices kieServices = KieServices.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
kieSession.insert(10);
// 只匹配规则名称是已 no_loop_ 开头的规则,忽略其余的规则
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("no_loop_"));
// 睡眠5s,使规则文件中的规则执行完
TimeUnit.SECONDS.sleep(5);
kieSession.dispose();
}
}
此处 java 代码,睡眠了5s,是为了让规则执行。
3、运行结果
2022-05-18 11:42:29 no_loop_rule_1 i=10
2022-05-18 11:42:31 no_loop_rule_2 i=10
2022-05-18 11:42:31 no_loop_rule_1 i=10
2022-05-18 11:42:32 no_loop_rule_2 i=10
解释:
2022-05-18 11:42:29 no_loop_rule_1 i=10
: no_loop_rule_1被触发,由于RHS部分使用了modify
修改了规则内存中的对象,但是该规则存在 no-loop true 的属性,所以该规则没有再次被触发,即只输出了一次。
2022-05-18 11:42:30 no_loop_rule_2 i=10 2022-05-18 11:42:30 no_loop_rule_1 i=10
此时规则 no_loop_rule_2 执行了,由于该规则的 no-loop 为 false 并且使用了 modify 方法,所以该规则多次被触发了,从结果上看,貌似规则 no_loop_rule_1 又再次被触发了,不是应该不被触发吗,因为设置了no-loop true?因为这是no_loop_rule_2导致no_loop_rule_1触发的,而no_loop只对自身的RHS修改有效。
疑问:
那如果将 no-loop
换成lock-on-active
结果会一样吗?可以自己尝试一下看看结果。
6、agenda-group
将被模式匹配成功后的规则,进行分组,只有获得焦点的组
,才可以执行规则。但是这个里面有个特列,如果某个规则在模式匹配,匹配成功了,但是没有配置agenda-group,那么它会被分配到main
组,这个main
组的规则总是执行的。
agenda-group
的数据结构就类似stack
,激活的组是在栈顶。参考如下图:
参考链接: https://stackoverflow.com/questions/6870192/understanding-agenda-group-in-drools
1、规则文件编写
package rules
/**
agenda-group 的数据结构类似与栈,激活的组会被放置在栈顶,
`main`是默认组,总是存在的,即没有配置agenda-group的就是`main`,
`main`总是会执行的。
*/
rule "agenda_group_001_rule_1"
agenda-group "group-001"
when
then
System.out.println("agenda_group_001_rule_1");
end
rule "agenda_group_001_rule_2"
agenda-group "group-001"
when
then
System.out.println("agenda_group_001_rule_2");
end
rule "agenda_group_002_rule_3"
agenda-group "group-002"
when
then
System.out.println("agenda_group_002_rule_3");
end
rule "agenda_group_no_group_rule_4"
when
then
System.out.println("agenda_group_no_group_rule_4");
end
注意: 此处其实是 存在 3个组的,agenda_group_no_group_rule_4
如果模式匹配成功后会被分配到main
组,main
总是会被执行的。
2、java代码编写
/**
* 测试规则分组
*/
public class DroolsAgendaGroupApplication {
public static void main(String[] args) {
// 设置日期格式,否则可能会报错(Wrong date-effective value: Invalid date input format: [2022-05-18 10:54:26] it should follow: [d-MMM-yyyy]]])
System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
KieServices kieServices = KieServices.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
// 激活组
kieSession.getAgenda().getAgendaGroup("group-001").setFocus();
// 只匹配规则名称是已 agenda_group_ 开头的规则,忽略其余的规则
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("agenda_group_"));
kieSession.dispose();
}
}
激活group-001
分组。
3、运行结果
agenda_group_001_rule_1
agenda_group_001_rule_2
agenda_group_no_group_rule_4
解释:
agenda_group_no_group_rule_4
为什么会被输出呢?它没有定义agenda-group
啊,而且我们激活的也是group-001
分组,它不应该输出啊。这是应为这个规则模式匹配成功后被分配到了默认的main
组,而main
组一定会被执行的。
7、auto-focus
设置某个agenda-group
默认获取到焦点,和在java代码中使用kieSession.getAgenda().getAgendaGroup("group-001").setFocus();
或在drl文件中使用drools.setFocus(..)
一样。
8、activation-group
处于该分组中激活的规则,同一个组下,只有一个规则可以执行
,其余的会被取消执行。但是别的组中激活的规则还是可以执行的。
1、规则文件编写
package rules
rule "activation_group_001_rule_1"
activation-group "group-001"
salience 1
when
then
System.out.println("activation_group_001_rule_1");
end
rule "activation_group_001_rule_2"
activation-group "group-001"
salience 2
when
then
System.out.println("activation_group_001_rule_2");
end
rule "activation_group_002_rule_3"
activation-group "group-002"
when
then
System.out.println("activation_group_002_rule_3");
end
rule "activation_group_no_group_rule_4"
when
then
System.out.println("activation_group_no_group_rule_4");
end
activation-group "group-001"
此处对这个组的规则指定了优先级,优先级高的先执行,执行完之后,该组别的规则不执行。
2、java代码编写
public class DroolsActivationGroupApplication {
public static void main(String[] args) {
System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
KieServices kieServices = KieServices.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
// 只匹配规则名称是已 activation_group_ 开头的规则,忽略其余的规则
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("activation_group_"));
kieSession.dispose();
}
}
3、运行结果
activation_group_001_rule_2
activation_group_002_rule_3
activation_group_no_group_rule_4
可以看到分组group-001
中有2个规则,但是只执行了一个规则。
9、duration
long类型的值,单位毫秒,如果在这个时间之后规则还成立
,那么执行该规则。
1、规则文件编写
package rules
import java.text.SimpleDateFormat
import java.util.Date
rule "duration_rule_1"
// 延迟1s后执行规则
duration 1000
when
$i: Integer(intValue() < 10)
then
System.out.println(Thread.currentThread().getName() + ": " +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+
" duration_rule_1 $i:"+$i);
end
定义规则延迟1s
后进行执行。
2、java代码编写
/**
* 在多少毫秒后,如果条件还成立,则触发该规则
*/
public class DroolsDurationApplication {
public static void main(String[] args) throws InterruptedException {
System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
KieServices kieServices = KieServices.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
FactHandle factHandle = kieSession.insert(3);
// 只匹配规则名称是已 duration_ 开头的规则,忽略其余的规则
new Thread(() -> {
// 调用此方法会阻塞调用线程,直到 `kieSession.halt();`的调用
System.out.println("当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
kieSession.fireUntilHalt(new RuleNameStartsWithAgendaFilter("duration_"));
}, "fire-thread").start();
// 如果修改这个值,使得规则的条件不成立,看规则是否还执行
kieSession.update(factHandle, 4);
TimeUnit.SECONDS.sleep(2);
kieSession.halt();
kieSession.dispose();
}
}
注意:
1、我们调用出发所有规则执行的方法不在是fireAllRules
而是fireUntilHalt
。
2、fireUntilHalt
的调用会阻塞线程,直到调用halt
方法,因此fireUntilHalt
需要放置到另外的线程中调用。而且我们观察规则的执行,也是在这个线程中调用的。
3、运行结果
当前时间:2022-05-18 14:13:36
fire-thread: 2022-05-18 14:13:37 duration_rule_1 $i:4
可以看到,延迟1s
后规则执行了。
4、疑问
如果我们在1s
钟之内,将规则的条件修改成不成立,那么规则还执行吗?答案:
不执行。
10、lock-on-active
和rule flow groups or agenda groups
配合使用。
需求:
我们有2个规则,并且同属于一个组,规则二执行完之后,工作内存中的Fact对象的值发生了变化,导致规则一满足执行的条件,而规则一已经执行一遍了,此处需要阻止规则二的触发导致规则一的出触发。使用lock-on-active
即可实现。
1、规则文件编写
package rules
import com.huan.drools.lockonactive.Person
rule "lock_on_active_rule_01"
agenda-group "group-001"
lock-on-active true
when
$p: Person(age < 18)
then
System.out.println("lock_on_active_rule_01: 用户:[" + $p.getName() + "]当前的年龄是:[" + $p.getAge() + "]");
end
rule "lock_on_active_rule_02"
agenda-group "group-001"
when
$p: Person(name == "张三")
then
modify($p){
setAge(15)
}
System.out.println("lock_on_active_rule_02: 用户:[" + $p.getName() + "]当前的年龄是:[" + $p.getAge() + "]");
end
规则lock_on_active_rule_01
加了lock-on-active true
属性后,规则lock_on_active_rule_02
修改Fact
导致规则lock_on_active_rule_01
的条件成立,此时规则也是不会执行的。
2、java代码编写
/**
* 一个简单的实体类
*
* @author huan.fu
* @date 2022/5/18 - 14:34
*/
public class Person {
private String name;
private Integer age;
}
public class DroolsLockOnActiveApplication {
public static void main(String[] args) {
System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm:ss");
KieServices kieServices = KieServices.get();
KieContainer kieContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieContainer.newKieSession("rule-attributes-ksession");
// 激活组
kieSession.getAgenda().getAgendaGroup("group-001").setFocus();
Person person = new Person("张三", 20);
kieSession.insert(person);
// 只匹配规则名称是已 lock_on_active_ 开头的规则,忽略其余的规则
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("lock_on_active_"));
kieSession.dispose();
}
}
3、运行结果
lock_on_active_rule_02: 用户:[张三]当前的年龄是:[15]
可以看到只有规则二执行了,说明阻止了规则一的执行。
四、完整代码
https://gitee.com/huan1993/spring-cloud-parent/tree/master/drools/drools-drl-rule-attributes
五、参考链接
1、https://docs.drools.org/7.69.0.Final/drools-docs/html_single/index.html#rules-attributes-ref_drl-rules 2、 https://stackoverflow.com/questions/6870192/understanding-agenda-group-in-drools