• Drools规则属性,高级语法


    目录

    规则属性  attributes

    enabled属性

    dialect属性

    salience属性

    no-loop属性

    lock-on-active属性

    activation-group属性

    agenda-group属性

    auto-focus属性

    timer属性

    date-effective属性

    date-expires属性

    Drools高级语法

    global全局变量

    query查询

    function函数

    条件-LHS加强

    复合值限制in/not in

    条件元素eval

    条件元素not

    条件元素exists

    规则继承

    结果-RHS

    insert方法

    update方法

    在更新数据时需要注意防止发生死循环。

    modify方法

    retract/delete方法

    RHS加强

    halt

    getWorkingMemory

    getRule

    规则文件编码规范(重要)

    有状态session和无状态session


    规则属性  attributes

    前面我们已经知道了规则体的构成如下:

    1. rule "ruleName"
    2. attributes
    3. when
    4. LHS
    5. then
    6. RHS
    7. end

    Drools中提供的属性如下表(部分属性):

    enabled属性

    enabled属性对应的取值为true和false,默认值为true。

    用于指定当前规则是否启用,如果设置的值为false则当前规则无论是否匹配成功都不会触发

    1. package rules
    2. import com.mashibing.drools.entity.AttributesEnabledEntity
    3. /*
    4. 用于测试Drools 属性:enabled
    5. */
    6. //测试enabled
    7. rule "rule_attributes_enabled"
    8. enabled false
    9. when
    10. AttributesEnabledEntity(num > 10)
    11. then
    12. System.out.println("规则rule_attributes_enabled触发");
    13. end

    dialect属性

    dialect属性用于指定当前规则使用的语言类型,取值为java和mvel,默认值为java。

    注:mvel是一种基于java语法的表达式语言。

    虽然mvel吸收了大量的java语法,但作为一个表达式语言,还是有着很多重要的不同之处,以达到更高的效率,比如:mvel像正则表达式一样,有直接支持集合、数组和字符串匹配的操作符。

     除了表达式语言外,mvel还提供了用来配置和构造字符串的模板语言。

    mvel2.x表达式包含以下部分的内容:属性表达式,布尔表达式,方法调用,变量赋值,函数定义

    salience属性

    salience属性用于指定规则的执行优先级,取值类型为Integer。数值越大越优先执行。每个规则都有一个默认的执行顺序,如果不设置salience属性,规则体的执行顺序为由上到下。

    drl文件内容如下:

    1. package rules
    2. import com.mashibing.drools.entity.AttributesSalienceEntity
    3. /*
    4. 用于测试Drools 属性:salience
    5. */
    6. rule "rule_attributes_salience_1"
    7. when
    8. AttributesSalienceEntity(flag == true)
    9. then
    10. System.out.println("规则 rule_attributes_salience_1 触发");
    11. end
    12. rule "rule_attributes_salience_2"
    13. when
    14. AttributesSalienceEntity(flag == true)
    15. then
    16. System.out.println("规则 rule_attributes_salience_2 触发");
    17. end
    18. rule "rule_attributes_salience_3"
    19. when
    20. AttributesSalienceEntity(flag == true)
    21. then
    22. System.out.println("规则 rule_attributes_salience_3 触发");
    23. end

    通过控制台可以看到,由于以上三个规则没有设置salience属性,所以执行的顺序是按照规则文件中规则的顺序由上到下执行的。接下来我们修改一下文件内容:

    1. package rules
    2. import com.mashibing.drools.entity.AttributesSalienceEntity
    3. /*
    4. 用于测试Drools 属性:salience
    5. */
    6. rule "rule_attributes_salience_1"
    7. salience 10
    8. when
    9. AttributesSalienceEntity(flag == true)
    10. then
    11. System.out.println("规则 rule_attributes_salience_1 触发");
    12. end
    13. rule "rule_attributes_salience_2"
    14. salience 20
    15. when
    16. AttributesSalienceEntity(flag == true)
    17. then
    18. System.out.println("规则 rule_attributes_salience_2 触发");
    19. end
    20. rule "rule_attributes_salience_3"
    21. salience 1
    22. when
    23. AttributesSalienceEntity(flag == true)
    24. then
    25. System.out.println("规则 rule_attributes_salience_3 触发");
    26. end

    通过控制台可以看到,规则文件执行的顺序是按照我们设置的salience值由大到小顺序执行的。

    建议在编写规则时使用salience属性明确指定执行优先级。

    no-loop属性

    no-loop属性用于防止死循环,当规则通过update之类的函数修改了Fact对象时,可能使当前规则再次被激活从而导致死循环。取值类型为Boolean,默认值为false。测试步骤如下:

    第一步:编写规则文件

    1. package rules
    2. import com.mashibing.drools.entity.AttributesNoLoopEntity
    3. /*
    4. 用于测试Drools 属性:no-loop
    5. */
    6. rule "rule_attributes_noloop"
    7. //no-loop true
    8. when
    9. $attributesNoLoopEntity:AttributesNoLoopEntity(num > 1)
    10. then
    11. update($attributesNoLoopEntity)
    12. System.out.println("规则 rule_attributes_noloop 触发");
    13. end
    14. 第二步:编写单元测试
    15. @Test
    16. public void test(){
    17. KieSession kieSession = kieBase.newKieSession();
    18. AttributesNoLoopEntity attributesNoLoopEntity = new AttributesNoLoopEntity();
    19. attributesNoLoopEntity.setNum(20);
    20. kieSession.insert(attributesNoLoopEntity);
    21. kieSession.fireAllRules();
    22. kieSession.dispose();
    23. }

    通过控制台可以看到,由于我们没有设置no-loop属性的值,所以发生了死循环。接下来设置no-loop的值为true再次测试则不会发生死循环。

    lock-on-active属性

    lock-on-active这个属性,可以限制当前规则只会被执行一次,包括当前规则的重复执行不是本身触发的。取值类型为Boolean,默认值为false。测试步骤如下:

    1. 第一步:编写规则文件
    2. ```java
    3. package rules
    4. import com.mashibing.drools.entity.AttributesLockOnActiveEntity
    5. /*
    6. 用于测试Drools 属性:lock-on-active
    7. */
    8. rule "rule_attributes_lock_on_active_1"
    9. no-loop true
    10. when
    11. $attributesLockOnActiveEntity:AttributesLockOnActiveEntity(num > 1)
    12. then
    13. update($attributesLockOnActiveEntity)
    14. System.out.println("规则 rule_attributes_lock_on_active_1 触发");
    15. end
    16. rule "rule_attributes_lock_on_active_2"
    17. no-loop true
    18. lock-on-active true
    19. when
    20. $attributesLockOnActiveEntity:AttributesLockOnActiveEntity(num > 1)
    21. then
    22. update($attributesLockOnActiveEntity)
    23. System.out.println("规则 rule_attributes_lock_on_active_2 触发");
    24. end
    25. ```
    26. 第二步:编写单元测试
    27. ```java
    28. @Test
    29. public void test(){
    30. KieSession kieSession = kieBase.newKieSession();
    31. AttributesLockOnActiveEntity attributesLockOnActiveEntity = new AttributesLockOnActiveEntity();
    32. attributesLockOnActiveEntity.setNum(20);
    33. kieSession.insert(attributesLockOnActiveEntity);
    34. kieSession.fireAllRules();
    35. kieSession.dispose();
    36. }
    37. ```

    no-loop的作用是限制因为modify等更新操作导致规则重复执行,但是有一个限定条件,是当前规则中进行更新导致当前规则重复执行。而不是防止其他规则更新相同的fact对象,导致当前规则更新,lock-on-active可以看作是no-loop的加强版,不仅能限制自己的更新,还能限制别人的更新造成的死循环。

    activation-group属性

    activation-group属性是指激活分组,取值为String类型。具有相同分组名称的规则只能有一个规则被触发。

    1. 第一步:编写规则文件
    2. ```java
    3. package rules
    4. import com.mashibing.drools.entity.AttributesActivationGroupEntity
    5. /*
    6. 用于测试Drools 属性: activation-group
    7. */
    8. rule "rule_attributes_activation_group_1"
    9. activation-group "customGroup"
    10. when
    11. $attributesActivationGroupEntity:AttributesActivationGroupEntity(num > 1)
    12. then
    13. System.out.println("规则 rule_attributes_activation_group_1 触发");
    14. end
    15. rule "rule_attributes_activation_group_2"
    16. activation-group "customGroup"
    17. when
    18. $attributesActivationGroupEntity:AttributesActivationGroupEntity(num > 1)
    19. then
    20. System.out.println("规则 rule_attributes_activation_group_2 触发");
    21. end
    22. ```
    23. 第二步:编写单元测试
    24. ```java
    25. @Test
    26. public void test(){
    27. KieSession kieSession = kieBase.newKieSession();
    28. AttributesActivationGroupEntity attributesActivationGroupEntity = new AttributesActivationGroupEntity();
    29. attributesActivationGroupEntity.setNum(20);
    30. kieSession.insert(attributesActivationGroupEntity);
    31. kieSession.fireAllRules();
    32. kieSession.dispose();
    33. }
    34. ```

    通过控制台可以发现,上面的两个规则因为属于同一个分组,所以只有一个触发了。同一个分组中的多个规则如果都能够匹配成功,具体哪一个最终能够被触发可以通过salience属性确定。

    agenda-group属性

    agenda-group属性为议程分组,属于另一种可控的规则执行方式。用户可以通过设置agenda-group来控制规则的执行,只有获取焦点的组中的规则才会被触发。

    1. 第一步:编写规则文件
    2. ```java
    3. package rules
    4. import com.mashibing.drools.entity.AttributesAgendaGroupEntity
    5. /*
    6. 用于测试Drools 属性: agenda-group
    7. */
    8. rule "rule_attributes_agenda_group_1"
    9. agenda-group "customAgendaGroup1"
    10. when
    11. $attributesAgendaGroupEntity:AttributesAgendaGroupEntity(num > 1)
    12. then
    13. System.out.println("规则 rule_attributes_agenda_group_1 触发");
    14. end
    15. rule "rule_attributes_agenda_group_2"
    16. agenda-group "customAgendaGroup1"
    17. when
    18. $attributesAgendaGroupEntity:AttributesAgendaGroupEntity(num > 1)
    19. then
    20. System.out.println("规则 rule_attributes_agenda_group_2 触发");
    21. end
    22. rule "rule_attributes_activation_group_3"
    23. agenda-group "customAgendaGroup2"
    24. when
    25. $attributesAgendaGroupEntity:AttributesAgendaGroupEntity(num > 1)
    26. then
    27. System.out.println("规则 rule_attributes_activation_group_3 触发");
    28. end
    29. rule "rule_attributes_agenda_group_4"
    30. agenda-group "customAgendaGroup2"
    31. when
    32. $attributesAgendaGroupEntity:AttributesAgendaGroupEntity(num > 1)
    33. then
    34. System.out.println("规则 rule_attributes_agenda_group_4 触发");
    35. end
    36. ```
    37. 第二步:编写单元测试
    38. ```java
    39. @Test
    40. public void test(){
    41. KieSession kieSession = kieBase.newKieSession();
    42. AttributesAgendaGroupEntity attributesAgendaGroupEntity = new AttributesAgendaGroupEntity();
    43. attributesAgendaGroupEntity.setNum(20);
    44. kieSession.insert(attributesAgendaGroupEntity);
    45. kieSession.getAgenda().getAgendaGroup("customAgendaGroup2").setFocus();
    46. kieSession.fireAllRules();
    47. kieSession.dispose();
    48. }
    49. ```

    通过控制台可以看到,只有获取焦点的分组中的规则才会触发。与activation-group不同的是,activation-group定义的分组中只能够有一个规则可以被触发,而agenda-group分组中的多个规则都可以被触发。

    auto-focus属性

    auto-focus属性为自动获取焦点,取值类型为Boolean,默认值为false。一般结合agenda-group属性使用,当一个议程分组未获取焦点时,可以设置auto-focus属性来控制。

    1. 第一步:编写规则文件
    2. ```java
    3. package rules
    4. import com.mashibing.drools.entity.AttributesAutoFocusEntity
    5. /*
    6. 用于测试Drools 属性: auto-focus
    7. */
    8. rule "rule_attributes_auto_focus_1"
    9. agenda-group "customAgendaGroup1"
    10. when
    11. $attributesAutoFocusEntity:AttributesAutoFocusEntity(num > 1)
    12. then
    13. System.out.println("规则 rule_attributes_auto_focus_1 触发");
    14. end
    15. rule "rule_attributes_auto_focus_2"
    16. agenda-group "customAgendaGroup1"
    17. when
    18. $attributesAutoFocusEntity:AttributesAutoFocusEntity(num > 1)
    19. then
    20. System.out.println("规则 rule_attributes_auto_focus_2 触发");
    21. end
    22. rule "rule_attributes_auto_focus_3"
    23. agenda-group "customAgendaGroup2"
    24. // auto-focus true
    25. when
    26. $attributesAutoFocusEntity:AttributesAutoFocusEntity(num > 1)
    27. then
    28. System.out.println("规则 rule_attributes_auto_focus_3 触发");
    29. end
    30. rule "rule_attributes_auto_focus_4"
    31. agenda-group "customAgendaGroup2"
    32. when
    33. $attributesAutoFocusEntity:AttributesAutoFocusEntity(num > 1)
    34. then
    35. System.out.println("规则 rule_attributes_auto_focus_4 触发");
    36. end
    37. ```
    38. 第二步:编写单元测试
    39. ```java
    40. @Test
    41. public void test(){
    42. KieSession kieSession = kieBase.newKieSession();
    43. AttributesAutoFocusEntity attributesAutoFocusEntity = new AttributesAutoFocusEntity();
    44. attributesAutoFocusEntity.setNum(20);
    45. kieSession.insert(attributesAutoFocusEntity);
    46. kieSession.fireAllRules();
    47. kieSession.dispose();
    48. }
    49. ```

    timer属性

    timer属性可以通过定时器的方式指定规则执行的时间,使用方式有两种:

    方式一:timer (int: ?)

    此种方式遵循java.util.Timer对象的使用方式,第一个参数表示几秒后执行,第二个参数表示每隔几秒执行一次,第二个参数为可选。

    方式二:timer(cron: )

    此种方式使用标准的unix cron表达式的使用方式来定义规则执行的时间。

    第一步:编写规则文件

    1. package rules
    2. import com.mashibing.drools.entity.AttributesTimerEntity
    3. /*
    4. 用于测试Drools 属性: timer
    5. */
    6. rule "rule_attributes_timer_1"
    7. timer(5s 2s)
    8. when
    9. $attributesTimerEntity:AttributesTimerEntity(num > 1)
    10. then
    11. System.out.println("规则 rule_attributes_timer_1 触发");
    12. end
    13. rule "rule_attributes_timer_2"
    14. timer(cron:0/1 * * * * ?)
    15. when
    16. $attributesTimerEntity:AttributesTimerEntity(num > 1)
    17. then
    18. System.out.println("规则 rule_attributes_timer_2 触发");
    19. end
    20. ```
    21. 第二步:编写单元测试
    22. ```java
    23. @Test
    24. public void test() throws InterruptedException {
    25. KieSession kieSession = kieBase.newKieSession();
    26. AttributesTimerEntity attributesTimerEntity = new AttributesTimerEntity();
    27. attributesTimerEntity.setNum(20);
    28. kieSession.insert(attributesTimerEntity);
    29. kieSession.fireUntilHalt();
    30. Thread.sleep(10000);
    31. kieSession.halt();
    32. kieSession.dispose();
    33. }

    注意:如果规则中有用到了timer属性,匹配规则需要调用kieSession.fireUntilHalt();这里涉及一个规则引擎的执行模式和线程问题,关于具体细节,我们后续讨论。

    date-effective属性

    date-effective属性用于指定规则的生效时间,即只有当前系统时间大于等于设置的时间或者日期规则才有可能触发。默认日期格式为:dd-MMM-yyyy。用户也可以自定义日期格式。

    1. 第一步:编写规则文件
    2. ```java
    3. package rules
    4. import com.mashibing.drools.entity.AttributesDateEffectiveEntity
    5. /*
    6. 用于测试Drools 属性: date-effective
    7. */
    8. rule "rule_attributes_date_effective"
    9. // date-effective "20-七月-2021"
    10. date-effective "2021-02-20"
    11. when
    12. $attributesDateEffectiveEntity:AttributesDateEffectiveEntity(num > 1)
    13. then
    14. System.out.println("规则 rule_attributes_date_effective 触发");
    15. end
    16. ```
    17. 第二步:编写单元测试
    18. ```java
    19. @Test
    20. public void test(){
    21. KieSession kieSession = kieBase.newKieSession();
    22. AttributesDateEffectiveEntity attributesDateEffectiveEntity = new AttributesDateEffectiveEntity();
    23. attributesDateEffectiveEntity.setNum(20);
    24. kieSession.insert(attributesDateEffectiveEntity);
    25. kieSession.fireAllRules();
    26. kieSession.dispose();
    27. }
    28. ```

    注意:需要在VM参数上加上日期格式:-Ddrools.dateformat=yyyy-MM-dd,在生产环境所在规则引擎的JVM设置中,也需要设置此参数,以保证开发和生产的一致性。

    date-expires属性

    date-expires属性用于指定规则的失效时间,即只有当前系统时间小于设置的时间或者日期规则才有可能触发。默认日期格式为:dd-MMM-yyyy。用户也可以自定义日期格式。

    1. 第一步:编写规则文件/resource/rules/dateexpires.drl
    2. ```java
    3. package rules
    4. import com.mashibing.drools.entity.AttributesDateExpiresEntity
    5. /*
    6. 用于测试Drools 属性: date-expires
    7. */
    8. rule "rule_attributes_date_expires"
    9. date-expires "2021-06-20"
    10. when
    11. $attributesDateExpiresEntity:AttributesDateExpiresEntity(num > 1)
    12. then
    13. System.out.println("规则 rule_attributes_date_expires 触发");
    14. end
    15. ```
    16. 第二步:编写单元测试
    17. ```java
    18. @Test
    19. public void test(){
    20. KieSession kieSession = kieBase.newKieSession();
    21. AttributesDateExpiresEntity attributesDateExpiresEntity = new AttributesDateExpiresEntity();
    22. attributesDateExpiresEntity.setNum(20);
    23. kieSession.insert(attributesDateExpiresEntity);
    24. kieSession.fireAllRules();
    25. kieSession.dispose();
    26. }
    27. ```

    注意:需要在VM参数上加上日期格式:-Ddrools.dateformat=yyyy-MM-dd,在生产环境所在规则引擎的JVM设置中,也需要设置此参数,以保证开发和生产的一致性。

    Drools高级语法

    前面章节我们已经知道了一套完整的规则文件内容构成如下:

    global全局变量

    global关键字用于在规则文件中定义全局变量,它可以让应用程序的对象在规则文件中能够被访问。可以用来为规则文件提供数据或服务。

    语法结构为:global 对象类型 对象名称

    在使用global定义的全局变量时有两点需要注意:

    1、如果对象类型为包装类型时,在一个规则中改变了global的值,那么只针对当前规则有效,对其他规则中的global不会有影响。可以理解为它是当前规则代码中的global副本,规则内部修改不会影响全局的使用。

    2、如果对象类型为集合类型或JavaBean时,在一个规则中改变了global的值,对java代码和所有规则都有效。

    下面我们通过代码进行验证:

    1. 第一步:编写规则文件
    2. ```java
    3. package rules
    4. import com.mashibing.drools.entity.GlobalEntity
    5. /*
    6. 用于测试Drools 全局变量 : global
    7. */
    8. global java.lang.Integer globalCount
    9. global java.util.List globalList
    10. rule "rule_global_1"
    11. when
    12. $globalEntity:GlobalEntity(num > 1)
    13. then
    14. System.out.println("规则 rule_global_1 开始...");
    15. globalCount++ ;
    16. globalList.add("张三");
    17. globalList.add("李四");
    18. System.out.println(globalCount);
    19. System.out.println(globalList);
    20. System.out.println("规则 rule_global_1 结束...");
    21. end
    22. rule "rule_global_2"
    23. when
    24. $globalEntity:GlobalEntity(num > 1)
    25. then
    26. System.out.println("规则 rule_global_2 开始...");
    27. System.out.println(globalCount);
    28. System.out.println(globalList);
    29. System.out.println("规则 rule_global_2 结束...");
    30. end
    31. ```
    32. 第二步:编写单元测试
    33. ```java
    34. @Test
    35. public void test(){
    36. KieSession kieSession = kieBase.newKieSession();
    37. GlobalEntity globalEntity = new GlobalEntity();
    38. globalEntity.setNum(20);
    39. ArrayList globalList = new ArrayList<>();
    40. Integer globalCount = 10;
    41. kieSession.setGlobal("globalCount", 10);
    42. kieSession.setGlobal("globalList", globalList);
    43. kieSession.insert(globalEntity);
    44. kieSession.fireAllRules();
    45. kieSession.dispose();
    46. System.out.println("globalCount=" + globalCount);
    47. System.out.println("globalList=" + globalList);
    48. }
    49. ```
    50. 注意:

      1-后面的代码中定义了全局变量以后,前面的test都需要加,不然会出错。

      2-属性当中的 关于时间的属性,如果涉及格式问题,也不要忘记,jvm启动参数添加相关配置

      query查询

      query查询提供了一种查询working memory中符合约束条件的Fact对象的简单方法。它仅包含规则文件中的LHS部分,不用指定“when”和“then”部分并且以end结束。具体语法结构如下:

      1. ```
      2. query 查询的名称(可选参数)
      3. LHS
      4. end
      5. ```
      6. 具体操作步骤:
      7. 第一步:编写规则文件
      8. ```java
      9. package rules
      10. import com.mashibing.drools.entity.QueryEntity
      11. /*
      12. 用于测试Drools 方法: query
      13. */
      14. //无参查询
      15. query "query_1"
      16. $queryEntity:QueryEntity(age>20)
      17. end
      18. //有参查询
      19. query "query_2"(Integer qAge,String qName)
      20. $queryEntity:QueryEntity(age > qAge && name == qName)
      21. end
      22. ```
      23. 第二步:编写单元测试
      24. ```java
      25. @Test
      26. public void test(){
      27. KieSession kieSession = kieBase.newKieSession();
      28. QueryEntity queryEntity1= new QueryEntity();
      29. QueryEntity queryEntity2= new QueryEntity();
      30. QueryEntity queryEntity3= new QueryEntity();
      31. queryEntity1.setName("张三").setAge(10);
      32. queryEntity2.setName("李四").setAge(20);
      33. queryEntity3.setName("王五").setAge(30);
      34. kieSession.insert(queryEntity1);
      35. kieSession.insert(queryEntity2);
      36. kieSession.insert(queryEntity3);
      37. QueryResults results1 = kieSession.getQueryResults("query_1");
      38. QueryResults results2 = kieSession.getQueryResults("query_2", 1, "张三");
      39. for (QueryResultsRow queryResultsRow : results1) {
      40. QueryEntity queryEntity = (QueryEntity) (queryResultsRow.get("$queryEntity"));
      41. System.out.println(queryEntity);
      42. }
      43. for (QueryResultsRow queryResultsRow : results2) {
      44. QueryEntity queryEntity = (QueryEntity) (queryResultsRow.get("$queryEntity"));
      45. System.out.println(queryEntity);
      46. }
      47. kieSession.fireAllRules();
      48. kieSession.dispose();
      49. }
      50. ```

      function函数

      function关键字用于在规则文件中定义函数,就相当于java类中的方法一样。可以在规则体中调用定义的函数。使用函数的好处是可以将业务逻辑集中放置在一个地方,根据需要可以对函数进行修改。

      函数定义的语法结构如下:

      1. ```java
      2. function 返回值类型 函数名(可选参数){ //逻辑代码}
      3. ```
      4. 具体操作步骤:
      5. 第一步:编写规则文件/resources/rules/function.drl
      6. ```java
      7. package rules
      8. import com.mashibing.drools.entity.FunctionEntity
      9. /*
      10. 用于测试Drools 方法: function
      11. */
      12. //定义一个 假发 方法
      13. function Integer add(Integer num){
      14. return num+10;
      15. }
      16. rule "function"
      17. when
      18. $functionEntity:FunctionEntity(num>20)
      19. then
      20. Integer result = add($functionEntity.getNum());
      21. System.out.println(result);
      22. end
      23. ```
      24. 第二步:编写单元测试
      25. ```java
      26. @Test
      27. public void test(){
      28. KieSession kieSession = kieBase.newKieSession();
      29. FunctionEntity functionEntity = new FunctionEntity();
      30. functionEntity.setNum(30);
      31. kieSession.insert(functionEntity);
      32. kieSession.fireAllRules();
      33. kieSession.dispose();
      34. }
      35. ```

      条件-LHS加强

      前面我们已经知道了在规则体中的LHS部分是**介于when和then之间的部分**,主要用于模式匹配,只有匹配结果为true时,才会触发RHS部分的执行。本章节我们会针对LHS部分学习几个新的用法。

      复合值限制in/not in

      复合值限制是指超过一种匹配值的限制条件,类似于SQL语句中的in关键字。Drools规则体中的LHS部分可以使用in或者not in进行复合值的匹配。具体语法结构如下:

      1. **Object(field in (比较值1,比较值2...))**
      2. 举例:
      3. ```java
      4. package rules
      5. import com.mashibing.drools.entity.LhsInEntity
      6. /*
      7. 用于测试Drools LHS: in not in
      8. */
      9. rule "lhs_in"
      10. when
      11. $lhsInEntity:LhsInEntity(name in ("张三","李四","王五"))
      12. then
      13. System.out.println("规则 lhs_in 触发");
      14. end
      15. rule "lhs_not_in"
      16. when
      17. $lhsInEntity:LhsInEntity(name not in ("张三","李四","王五"))
      18. then
      19. System.out.println("规则 lhs_not_in 触发");
      20. end
      21. ```

      条件元素eval

      eval用于规则体的LHS部分,并返回一个Boolean类型的值。语法结构如下:

      1. **eval(表达式)**
      2. 举例:
      3. ```
      4. package rules
      5. import com.mashibing.drools.entity.LhsEvalEntity
      6. /*
      7. 用于测试Drools LHS: in not in
      8. */
      9. rule "lhs_eval"
      10. when
      11. $lhsInEntity:LhsEvalEntity(age > 10) and eval(2>1)
      12. then
      13. System.out.println("规则 lhs_eval 触发");
      14. end
      15. ```

      条件元素not

      not用于判断Working Memory中是否存在某个Fact对象,如果不存在则返回true,如果存在则返回false。语法结构如下:

      1. **not Object(可选属性约束)**
      2. 举例:
      3. ```java
      4. package rules
      5. import com.mashibing.drools.entity.LhsNotEntity
      6. /*
      7. 用于测试Drools LHS: not
      8. */
      9. rule "lhs_not"
      10. when
      11. not $lhsNotEntity:LhsNotEntity(age > 10)
      12. then
      13. System.out.println("规则 lhs_not 触发");
      14. end
      15. ```

      条件元素exists

      exists的作用与not相反,用于判断Working Memory中是否存在某个Fact对象,如果存在则返回true,不存在则返回false。语法结构如下:

      1. **exists Object(可选属性约束)**
      2. 举例:
      3. ```
      4. package rules
      5. import com.mashibing.drools.entity.LhsEvalEntity
      6. /*
      7. 用于测试Drools LHS: exists
      8. */
      9. rule "lhs_exists"
      10. when
      11. exists $lhsInEntity:LhsEvalEntity(age > 10)
      12. then
      13. System.out.println("规则 lhs_eval 触发");
      14. end
      15. ```
      16. Java代码:
      17. ```java
      18. package com.mashibing.drools.client;
      19. import com.mashibing.drools.DroolsApplicationTests;
      20. import com.mashibing.drools.entity.LhsExistsEntity;
      21. import com.mashibing.drools.entity.LhsNotEntity;
      22. import org.junit.jupiter.api.Test;
      23. import org.kie.api.KieBase;
      24. import org.kie.api.runtime.KieSession;
      25. import javax.annotation.Resource;
      26. /**
      27. * @author sunzhiqiang23
      28. * @date 2021-06-17 23:46
      29. */
      30. public class LhsNotTest extends DroolsApplicationTests {
      31. @Resource
      32. public KieBase kieBase;
      33. @Test
      34. public void test(){
      35. KieSession kieSession = kieBase.newKieSession();
      36. LhsNotEntity lhsNotEntity = new LhsNotEntity();
      37. lhsNotEntity.setAge(1);
      38. kieSession.insert(lhsNotEntity);
      39. kieSession.fireAllRules();
      40. kieSession.dispose();
      41. }
      42. }
      43. ```

      可能有人会有疑问,我们前面在LHS部分进行条件编写时并没有使用exists也可以达到判断Working Memory中是否存在某个符合条件的Fact元素的目的,那么我们使用exists还有什么意义?

      两者的区别:当向Working Memory中加入多个满足条件的Fact对象时,使用了exists的规则执行一次,不使用exists的规则会执行多次。

      例如:

      规则文件(只有规则体):

      1. ```java
      2. package rules
      3. import com.mashibing.drools.entity.LhsExistsEntity
      4. /*
      5. 用于测试Drools LHS: exists
      6. */
      7. rule "lhs_exists_1"
      8. when
      9. exists $lhsExistsEntity:LhsExistsEntity(age > 10)
      10. then
      11. System.out.println("规则 lhs_exists_1 触发");
      12. end
      13. rule "lhs_exists_2"
      14. when
      15. $lhsExistsEntity:LhsExistsEntity(age > 10)
      16. then
      17. System.out.println("规则 lhs_exists_2 触发");
      18. end
      19. ```
      20. Java代码:
      21. ```
      22. @Test
      23. public void test(){
      24. KieSession kieSession = kieBase.newKieSession();
      25. LhsExistsEntity lhsExistsEntity = new LhsExistsEntity();
      26. lhsExistsEntity.setAge(30);
      27. LhsExistsEntity lhsExistsEntity2 = new LhsExistsEntity();
      28. lhsExistsEntity2.setAge(30);
      29. kieSession.insert(lhsExistsEntity);
      30. kieSession.insert(lhsExistsEntity2);
      31. kieSession.fireAllRules();
      32. kieSession.dispose();
      33. }
      34. ```

      上面第一个规则只会执行一次,因为Working Memory中存在两个满足条件的Fact对象,第二个规则会执行两次。

      规则继承

      规则之间可以使用extends关键字进行规则条件部分的继承,类似于java类之间的继承。

      例如:

      1. //方案2
      2. rule "Give 10% discount to customers older than 60_s"
      3. when
      4. $c:Customer(age > 60)
      5. then
      6. System.out.println("方案2:Give 10% discount!");
      7. end
      8. rule "Give free parking to customers older than 60_s"
      9. extends "Give 10% discount to customers older than 60_s"
      10. when
      11. Car(owner == $c.id)
      12. then
      13. System.out.println("方案2:Free parking!");
      14. end

      方案2:Give 10% discount!

      方案2:Free parking!

       第二个rule使用extends基于第一个rule扩展了规则条目并配置了更多的数据处理方式,第二个规则依赖于第一规则中的规则条目,第一个规则改动conditions时第二个规则也会同时生效,维护难度的问题得到解决,但是写法上似乎还是比较繁琐。

      结果-RHS

      规则文件的`RHS`部分的主要作用是通过插入,删除或修改工作内存中的Fact数据,来达到控制规则引擎执行的目的。Drools提供了一些方法可以用来操作工作内存中的数据,操作完成后规则引擎会重新进行相关规则的匹配,原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功了。

      insert方法

      insert方法的作用是向工作内存中插入数据,并让相关的规则重新匹配。

      1. 第一步:编写规则文件
      2. ```java
      3. package rules
      4. import com.mashibing.drools.entity.RhsInsertEntity
      5. /*
      6. 用于测试Drools RHS: insert
      7. */
      8. rule "rhs_insert_1"
      9. when
      10. $rhsInsertEntity:RhsInsertEntity(age <= 10)
      11. then
      12. RhsInsertEntity rhsInsertEntity = new RhsInsertEntity();
      13. rhsInsertEntity.setAge(15);
      14. insert(rhsInsertEntity);
      15. System.out.println("规则 rhs_insert_1 触发");
      16. end
      17. rule "rhs_insert_2"
      18. when
      19. $rhsInsertEntity:RhsInsertEntity(age <=20 && age>10)
      20. then
      21. RhsInsertEntity rhsInsertEntity = new RhsInsertEntity();
      22. rhsInsertEntity.setAge(25);
      23. insert(rhsInsertEntity);
      24. System.out.println("规则 rhs_insert_2 触发");
      25. end
      26. rule "rhs_insert_3"
      27. when
      28. $rhsInsertEntity:RhsInsertEntity(age > 20 )
      29. then
      30. System.out.println("规则 rhs_insert_3 触发");
      31. end
      32. ```
      33. 第二步:编写单元测试
      34. ```java
      35. public void test(){
      36. KieSession kieSession = kieBase.newKieSession();
      37. RhsInsertEntity rhsInsertEntity = new RhsInsertEntity();
      38. rhsInsertEntity.setAge(5);
      39. kieSession.insert(rhsInsertEntity);
      40. kieSession.fireAllRules();
      41. kieSession.dispose();
      42. }
      43. ```

      通过控制台输出可以发现,3个规则都触发了,这是因为首先进行规则匹配时只有第一个规则可以匹配成功,但是在第一个规则中向工作内存中插入了一个数据导致重新进行规则匹配,此时第二个规则可以匹配成功。在第二个规则中同样向工作内存中插入了一个数据导致重新进行规则匹配,那么第三个规则就出发了。

      update方法

      update方法的作用是更新工作内存中的数据,并让相关的规则重新匹配。(要避免死循环)

      1. 第一步:编写规则文件
      2. ```java
      3. package rules
      4. import com.mashibing.drools.entity.RhsUpdateEntity
      5. /*
      6. 用于测试Drools RHS: update
      7. */
      8. rule "rhs_update_1"
      9. when
      10. $rhsUpdateEntity:RhsUpdateEntity(age <= 10)
      11. then
      12. $rhsUpdateEntity.setAge(15);
      13. update($rhsUpdateEntity);
      14. System.out.println("规则 rhs_update_1 触发");
      15. end
      16. rule "rhs_update_2"
      17. when
      18. $rhsUpdateEntity:RhsUpdateEntity(age <=20 && age>10)
      19. then
      20. $rhsUpdateEntity.setAge(25);
      21. update($rhsUpdateEntity);
      22. System.out.println("规则 rhs_update_2 触发");
      23. end
      24. rule "rhs_update_3"
      25. when
      26. $rhsUpdateEntity:RhsUpdateEntity(age > 20 )
      27. then
      28. System.out.println("规则 rhs_update_3 触发");
      29. end
      30. ```
      31. 第二步:编写单元测试
      32. ```java
      33. @Test
      34. public void test(){
      35. KieSession kieSession = kieBase.newKieSession();
      36. RhsUpdateEntity rhsUpdateEntity = new RhsUpdateEntity();
      37. rhsUpdateEntity.setAge(5);
      38. kieSession.insert(rhsUpdateEntity);
      39. kieSession.fireAllRules();
      40. kieSession.dispose();
      41. }
      42. ```

      通过控制台的输出可以看到规则文件中定义的三个规则都触发了。

      在更新数据时需要注意防止发生死循环。

      modify方法

      modify方法的作用跟update一样,是更新工作内存中的数据,并让相关的规则重新匹配。只不过语法略有区别   (要避免死循环)

      1. 第一步:编写规则文件
      2. ```java
      3. package rules
      4. import com.mashibing.drools.entity.RhsModifyEntity
      5. /*
      6. 用于测试Drools RHS: modify
      7. */
      8. rule "rhs_modify_1"
      9. when
      10. $rhsModifyEntity:RhsModifyEntity(age <= 10)
      11. then
      12. modify($rhsModifyEntity){
      13. setAge(15)
      14. }
      15. System.out.println("规则 rhs_modify_1 触发");
      16. end
      17. rule "rhs_modify_2"
      18. when
      19. $rhsModifyEntity:RhsModifyEntity(age <=20 && age>10)
      20. then
      21. modify($rhsModifyEntity){
      22. setAge(25)
      23. }
      24. System.out.println("规则 rhs_modify_2 触发");
      25. end
      26. rule "rhs_modify_3"
      27. when
      28. $rhsModifyEntity:RhsModifyEntity(age > 20 )
      29. then
      30. System.out.println("规则 rhs_modify_3 触发");
      31. end
      32. ```
      33. 第二步:编写单元测试
      34. ```java
      35. @Test
      36. public void test(){
      37. KieSession kieSession = kieBase.newKieSession();
      38. RhsModifyEntity rhsModifyEntity = new RhsModifyEntity();
      39. rhsModifyEntity.setAge(5);
      40. kieSession.insert(rhsModifyEntity);
      41. kieSession.fireAllRules();
      42. kieSession.dispose();
      43. }
      44. ```

      通过控制台的输出可以看到规则文件中定义的三个规则都触发了。

      在更新数据时需要注意防止发生死循环。

      retract/delete方法

      retract方法的作用是删除工作内存中的数据,并让相关的规则重新匹配。

      1. 第一步:编写规则文件
      2. ```java
      3. package rules
      4. import com.mashibing.drools.entity.RhsRetractEntity
      5. /*
      6. 用于测试Drools RHS: retract
      7. */
      8. rule "rhs_retract_1"
      9. when
      10. $rhsRetractEntity:RhsRetractEntity(age <= 10)
      11. then
      12. // retract($rhsRetractEntity);
      13. System.out.println("规则 rhs_retract_1 触发");
      14. end
      15. rule "rhs_retract_2"
      16. when
      17. $rhsRetractEntity:RhsRetractEntity(age <= 10)
      18. then
      19. System.out.println("规则 rhs_retract_2 触发");
      20. end
      21. ```
      22. 第二步:编写单元测试
      23. ```java
      24. public void test(){
      25. KieSession kieSession = kieBase.newKieSession();
      26. RhsRetractEntity rhsRetractEntity = new RhsRetractEntity();
      27. rhsRetractEntity.setAge(5);
      28. kieSession.insert(rhsRetractEntity);
      29. kieSession.fireAllRules();
      30. kieSession.dispose();
      31. }
      32. ```

      通过控制台输出可以发现,只有第一个规则触发了,因为在第一个规则中将工作内存中的数据删除了导致第二个规则并没有匹配成功。

      RHS加强

      RHS部分是规则体的重要组成部分,当LHS部分的条件匹配成功后,对应的RHS部分就会触发执行。一般在RHS部分中需要进行业务处理。

      在RHS部分Drools为我们提供了一个内置对象,名称就是drools。本小节我们来介绍几个drools对象提供的方法。

      halt

      halt方法的作用是立即终止后面所有规则的执行。

      1. ```java
      2. package rules
      3. import com.mashibing.drools.entity.RhsHaftEntity
      4. /*
      5. 用于测试Drools RHS: haft
      6. */
      7. rule "rhs_haft_1"
      8. when
      9. $rhsHaftEntity:RhsHaftEntity(age <= 10)
      10. then
      11. drools.halt();
      12. System.out.println("规则 rhs_haft_1 触发");
      13. end
      14. rule "rhs_haft_2"
      15. when
      16. $rhsHaftEntity:RhsHaftEntity(age <= 20)
      17. then
      18. System.out.println("规则 rhs_haft_2 触发");
      19. end
      20. ```

      getWorkingMemory

      getWorkingMemory方法的作用是返回工作内存对象。

      1. rule "rhs_get_working_memory_1"
      2. when
      3. $rhsDroolsMethodsEntity:RhsDroolsMethodsEntity(age <= 10)
      4. then
      5. System.out.println(drools.getWorkingMemory());
      6. System.out.println("规则 rhs_get_working_memory_1 触发");
      7. end

      getRule

      getRule方法的作用是返回规则对象。

      1. rule "rhs_rule_2"
      2. when
      3. $rhsDroolsMethodsEntity:RhsDroolsMethodsEntity(age <=20)
      4. then
      5. System.out.println(drools.getRule());
      6. System.out.println("规则 rhs_rule_2 触发");
      7. end

      规则文件编码规范(重要)

      我们在进行drl类型的规则文件编写时尽量遵循如下规范:

      所有的规则文件(.drl)应统一放在一个规定的文件夹中,如:/rules文件夹

      书写的每个规则应尽量加上注释。注释要清晰明了,言简意赅

      同一类型的对象尽量放在一个规则文件中,如所有Student类型的对象尽量放在一个规则文件中

      规则结果部分(RHS)尽量不要有条件语句,如if(...),尽量不要有复杂的逻辑和深层次的嵌套语句

      每个规则最好都加上salience属性,明确执行顺序

      Drools默认dialect为"Java",尽量避免使用dialect "mvel"

      有状态session和无状态session

      无状态session

      无状态的KIE会话是一个不使用推理来对事实进行反复修改的会话。在无状态的KIE会话中,来自KIE会话先前调用的数据(先前的会话状态)在会话调用之间被丢弃,而在有状态的KIE会话中,这些数据被保留。一个无状态的KIE会话的行为类似于一个函数,因为它产生的结果是由KIE基础的内容和被传入KIE会话以在特定时间点执行的数据决定的。KIE会话对以前传入KIE会话的任何数据都没有记忆。

      使用方法类似如下代码:
       

      1. @Test
      2. public void testStatelessSession() {
      3. StatelessKieSession statelessKieSession = kieBase.newStatelessKieSession();
      4. List cmds = new ArrayList<>();
      5. KieSessionEntity kieSessionEntity = new KieSessionEntity();
      6. kieSessionEntity.setNum(10);
      7. kieSessionEntity.setValid(false);
      8. cmds.add(CommandFactory.newInsert(kieSessionEntity, "kieSessionEntity"));
      9. statelessKieSession.execute(CommandFactory.newBatchExecution(cmds));
      10. System.out.println(kieSessionEntity);
      11. }

      简单说来,无状态session执行的时候,不需要调用 fireAllRules(),也不需要执行dispose(),代码执行完execute之后,即销毁所有的数据。

      使用场景:比如上述的校验num
      验证数据: 比如计算积分,按揭房贷等

      路有消息:比如对邮件排序,发送邮件等,行为类的场景

      有状态session

      有状态的KIE会话是一个使用推理来对事实进行反复修改的会话。在有状态的KIE会话中,来自KIE会话先前调用的数据(先前的会话状态)在会话调用之间被保留,而在无状态的KIE会话中,这些数据被丢弃了。

      对比无状态session,有状态session调用fireAllRules()的时候采取匹配规则,就会执行规则匹配,除非遇见dispose()

      示例:

      数据模型

      1. ```java
      2. public class Room {
      3. private String name;
      4. // Getter and setter methods
      5. }
      6. public class Sprinkler {
      7. private Room room;
      8. private boolean on;
      9. // Getter and setter methods
      10. }
      11. public class Fire {
      12. private Room room;
      13. // Getter and setter methods
      14. }
      15. public class Alarm { }
      16. ```
      17. 规则文件
      18. ```java
      19. rule "When there is a fire turn on the sprinkler"
      20. when
      21. Fire($room : room)
      22. $sprinkler : Sprinkler(room == $room, on == false)
      23. then
      24. modify($sprinkler) { setOn(true) };
      25. System.out.println("Turn on the sprinkler for room "+$room.getName());
      26. end
      27. rule "Raise the alarm when we have one or more fires"
      28. when
      29. exists Fire()
      30. then
      31. insert( new Alarm() );
      32. System.out.println( "Raise the alarm" );
      33. end
      34. rule "Cancel the alarm when all the fires have gone"
      35. when
      36. not Fire()
      37. $alarm : Alarm()
      38. then
      39. delete( $alarm );
      40. System.out.println( "Cancel the alarm" );
      41. end
      42. rule "Status output when things are ok"
      43. when
      44. not Alarm()
      45. not Sprinkler( on == true )
      46. then
      47. System.out.println( "Everything is ok" );
      48. end
      49. ```
      50. 代码
      51. ```java
      52. KieSession ksession = kContainer.newKieSession();
      53. String[] names = new String[]{"kitchen", "bedroom", "office", "livingroom"};
      54. Map name2room = new HashMap();
      55. for( String name: names ){
      56. Room room = new Room( name );
      57. name2room.put( name, room );
      58. ksession.insert( room );
      59. Sprinkler sprinkler = new Sprinkler( room );
      60. ksession.insert( sprinkler );
      61. }
      62. ksession.fireAllRules();
      63. ```
      64. 输出
      65. ```none
      66. Console output
      67. > Everything is ok
      68. ```
      69. 此时还可以继续输入
      70. ```
      71. Fire kitchenFire = new Fire( name2room.get( "kitchen" ) );
      72. Fire officeFire = new Fire( name2room.get( "office" ) );
      73. FactHandle kitchenFireHandle = ksession.insert( kitchenFire );
      74. FactHandle officeFireHandle = ksession.insert( officeFire );
      75. ksession.fireAllRules();
      76. ```
      77. ```
      78. Console output
      79. > Raise the alarm
      80. > Turn on the sprinkler for room kitchen
      81. > Turn on the sprinkler for room office
      82. ```
      83. 继续输入
      84. ```
      85. ksession.delete( kitchenFireHandle );
      86. ksession.delete( officeFireHandle );
      87. ksession.fireAllRules();
      88. ```
      89. 输出
      90. ```
      91. Console output
      92. > Cancel the alarm
      93. > Turn off the sprinkler for room office
      94. > Turn off the sprinkler for room kitchen
      95. > Everything is ok
      96. ```

      使用场景:

      - 监测,如监测股票市场并使购买过程自动化

      - 诊断,如运行故障查找过程或医疗诊断过程

      - 物流,如包裹跟踪和配送供应

      - 确保合规性,如验证市场交易的合法性

    51. 相关阅读:
      Nacos整合Gateway入门示例
      二叉搜索树
      【广州华锐互动】利用AR远程指导系统进行机械故障排查,实现远程虚拟信息互动
      Linux文件描述符和打开文件之间的关系
      Ajax+Axios+前后端分离+YApi+Vue-ElementUI组件+Vue路由+nginx【全详解】
      Redis核心数据结构【set】【从入门到入坟】
      CSS 实现音频loding动画
      FT2004(D2000)开发实战之在线开发OpenCV实例
      如何让数据成为企业的生产力?
      C++11智能指针之weak_ptr
    52. 原文地址:https://blog.csdn.net/xushiyu1996818/article/details/126024900