drools规则属性(rule attributes)的使用

242 阅读13分钟

一、介绍

规则属性是您可以添加到业务规则以修改规则行为的附加规范。 在 DRL 文件中,您通常在规则条件和操作的上方定义规则属性,多个属性位于单独的行中,格式如下:

rule "rule_name"
    // Attribute
    // Attribute
    when
        // Conditions
    then
        // Actions
end

二、常见的规则属性

规则属性解释举例
salience定义规则优先级,是一个整数。当在激活队列中排序时,salience的值越大,优先级越高salience 99
enabled定义规则是否启用. true 启用,false 禁用,默认值是trueenabled true
date-effective包含时间和日期的字符串,当当前时间大于date-effective时,该规则才会被激活。这个时间格式可以修改,见下方具体的用法date-effective "4-5月-2022"
date-expires设置规则的过期时间,时间格式和上方一样。date-expires "4-5月-2022"
no-loop布尔值,默认值为false, 定义当当前规则规则的结果修改了fact对象时,是否可以再次执行该规则。true:不可以, false:可以,可能会导致死循环。指的是当前规则的修改,如果别的规则修改了,还会导致该规则的触发no-loop true
agenda-groupAgenda groups允许您对agenda进行分区,以提供对规则组的更多执行控制。 只有获得焦点的议程组中的规则才能被激活。 ,但是这个里面有个特例,如果某个规则没有配置 agenda-group,但是它模式匹配成功了,那么会被分到默认的组(main),这个main组的规则也会执行。agenda-group "GroupName"
auto-focus布尔值,仅适用于Agenda-Group内的规则。当值为true时,下次激活该规则时,会将焦点自动给这个Agenda groupauto-focus true
activation-group表示该组下的规则只有一个规则会被执行,该组下其余激活的规则会被取消执行。 但是别的组激活的规则可能会被执行。activation-group "GroupName"
durationlong类型的值,如果在这个时间之后规则还成立,那么执行该规则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代码

项目结构.jpg

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_1no-loop true表示如果当前规则的RHS部分,对Fact对象进行了修改,则不会再次触发该规则。那如果是no_loop_rule_2修改了,会导致该规则的触发吗?答案是会触发,如果我不想被触发呢?那么使用lock-on-active可以实现。
no_loop_rule_2no-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,激活的组是在栈顶。参考如下图:

agenda-groups.jpg 参考链接: stackoverflow.com/questions/6…

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
 */
@Getter
@Setter
@AllArgsConstructor
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]

可以看到只有规则二执行了,说明阻止了规则一的执行。

四、完整代码

gitee.com/huan1993/sp…

五、参考链接

1、docs.drools.org/7.69.0.Fina…
2、 stackoverflow.com/questions/6…