drools中的条件 when

488 阅读3分钟

1、介绍

此篇文章主要记录一下 drools 中的模式(patterns)和约束(constraints)以及when中条件的写法。

2、语法结构

Pattern.png

3、模式例子

3.1 单个对象匹配

rule "工作内存中只要有Person对象就执行,存在多个执行多次"
    when Person()
    then
        System.out.println("工作内存中存在Person对象");
end

3.2 匹配任何对象

rule "只要工作内存中有对象,都会匹配到"
    when Object()
    then
        System.out.println("只要工作内存中有对象,都会匹配到");
end

3.3 带条件匹配

rule "匹配年龄小于20岁的"
    when
        Person(age < 20) // 等驾与getAge() < 20,推荐使用属性的写法
    then
        System.out.println("匹配年龄小于20岁的");
end

3.3.1 注意事项

1、匹配的条件结果需要是 true或者false
2、Person(age < 20)Person(getAge() < 20) 是等价的,但是推荐第一种写法。 3、Person(age < 20)默认会调用getAge()方法,如果该方法不存在则会调用age()方法,如果还不存在,则抛出异常。
4、Drools engine 会缓存调用期间的匹配结果以提高效率,因此我们的getter方法,不要有状态。

3.4 嵌套属性的匹配

3.4.1 访问单个嵌套属性

rule "嵌套属性的访问"
    when
        Person(car.name == "宝马")
    then
        System.out.println("嵌套属性的访问");
end

3.4.2 访问多个嵌套属性

rule "嵌套属性的访问-02"
    when
        Person( age < 20 && car.name == "宝马" && car.color == null)
    then
        System.out.println("嵌套属性的访问-02");
end

3.4.3 属性分组

.( <constraints> ) 将这些属性访问器分组到嵌套对象,以获得更易读的规则

rule "嵌套属性的访问-03"
    when
        Person(age < 20 , car.(name == "宝马" || color != null)) // 属性分组访问
    then
        System.out.println("嵌套属性的访问-03");
end

3.4.4 强制类型转换

在嵌套模式中,我们可以使用 <type>#<subtype>语法强制转换为子类型并使父类型的 getter 用于子类型。

rule "嵌套属性的访问-强制类型转换"
    when
        Person(age < 20 , car#BMWCar.name == "宝马") // 强制类型转换
    then
        System.out.println("嵌套属性的访问-强制类型转换");
end

注意看上方的car#BMWCar,这个是将car转换成BMWCar类型来使用。

3.4.5 注意事项

在有状态的kie session中,需要谨慎的使用嵌套属性。因为 Drools engine 的工作内存不知道任何嵌套值,也不会检测它们何时更改。

3.5 调用java方法约束

rule "调用java方法约束"
    when
        Person(!isChild())
    then
        System.out.println("调用java方法约束");
end

3.5.1 注意实现

isChild()方法不应该修改fact的状态,因为drools引擎为了提高工作效率,会将调用期间的结果进行缓存,如果修改了状态,可能将会导致匹配的结果不准。

3.6 多个字段约束

rule "多个字段约束"
    when
        Person((name != null && age < 20) && car != null) // isChild 方法中需要有状态的更改
    then
        System.out.println("多个字段约束");
end

3.7 顶级字段约束

Person(name != null , age < 20 , car != null)
Person((name != null && age < 20) && car != null)

上面2种写法是一样的。

3.7.1 注意事项

1、在顶级字段约束中,的性能是要高于&&的。
2、&&优先于||&&||两者都优先,
3、不可在复合表达式中嵌入,,比如:Person((name != null , age < 20) , car != null)这是错误的写法,需要将,换成&&符号。

3.8 日期类型的使用

drools中默认的日期格式为dd-mmm-yyyy,此处我们通过设置系统变量drools.dateformat修改成yyyy-MM-dd HH:mm:ss格式。

rule "日期类型的使用"
    when
        $p: Person(registerDate < '2022-05-24 12:12:12' ) // 日期格式比较,System.setProperty("drools.dateformat","yyyy-MM-dd HH:mm:ss");
    then
        System.err.println("日期类型的使用 注册时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format($p.getRegisterDate()) );
end

4、在模式和约束中使用绑定变量

rule "使用绑定变量"
    when
        $p: Person($age: age)
    then
        System.err.println("使用绑定变量 " + $p.getName() + ": " + $age);
end

后期我们可以使用$p$age$p表示当前规则运行时,工作内存中匹配到的Person对象,$age表示匹配到这个对象的age属性,一般绑定变量以$开头,和fact的属性区分开。

4.1 字段约束中绑定变量不好的写法

rule "使用绑定变量-不好的写法"
    when
        Person($age: age * 2 < 100)
    then
        System.err.println("使用绑定变量-不好的写法 " + ": " + $age);
end

这样写不清晰,而且执行效率不高。

4.2 字段约束中绑定变量好的写法

rule "使用绑定变量-推荐的写法"
    when
        Person( age * 2 < 100, $age: age) // 这样写更清晰,运行效率更高
    then
        System.err.println("使用绑定变量-推荐的写法 " + ": " + $age);
end

4.3 约束绑定只考虑后面的第一个原子表达式

Person( $age1: (age * 2)) 

Person( $age2: age * 2)

的结果是不一样的,$age1的结果是$age2的结果的2倍。

5、支持的操作符

5.1 .() 分组属性

使用 .() 运算符将属性访问器分组到嵌套对象

Person(age < 20 , car.(name == "宝马" , color == null ))
Person(age < 20 , car.name == "宝马" , car.color == null )

以上2种写法是同一个意思

5.2 # 类型转换

在嵌套模式中,我们可以使用 <type>#<subtype>语法强制转换为子类型并使父类型的 getter 用于子类型。

Person(car#BMWCar.brand == "BMW")

car#BMWCar指的是将car转换成BMWCar类型。

5.3 !. 嵌套属性null安全

Person(car!.name == "宝马") // 如果此时 car 为null,则使用 car!.name 是不会报错的

当我们的属性存在嵌套的时候,使用!.可以避免空指针异常。

5.4 [] 操作List或Map

1、List操作-按照索引访问

Person(hobbyList[0] == "打篮球")

2、map操作-按照键操作

Person(map["key1"] == "value1")

Person(map["key1"] == "value1")中的这个mapPerson的一个字段是map

5.5 <, <=, >, >=,==, !=,&&,||

这些操作符和Java中的用法一致。 <, <=, >, >= 这些操作符,如果是用在Date类型的字段,则<表示before,对于String类型的字段,则按照自然顺序排序比较

Person(age ((> 30 && < 40) || (>= 18 && <= 25)) 
&& car != null && registerDate < '2022-12-12 12:12:12')

Person(age >= 18 && age <= 25)Person(age (>= 18 && <= 25))是相等的。

5.6 matches, not matches 正则匹配

  1. 用来判断给定的字段是否匹配执行的正则表达式。
  2. 正则表达式可以是一个给定的字符串,也可以是从变量中动态获取。
  3. 转义需要使用\\
 Person(name matches hobbyList[2] && car.name not matches "^奥迪") // 正则表达式可以是动态来的

5.7 contains, not contains集合或字符串是否包含什么

contains:包含;not contains:不包含。

1、验证一个ArrayCollection是否包含某个指定字段的值(可以是常量也可以是变量)。
2、也可以是String类型的字段是否包含某个值(可以是常量也可以是变量)。

Person(
    hobbyList contains "打篮球" && hobbyList not contains "打橄榄球" 
    &&。hobbyList not contains name &&
    name contains "张" && name not contains car.name
)

hobbyList:List类型的字段。
namecar.name:String类型的字段。

从上方的例子中可以看到: hobbyList contains "打篮球":"打篮球"是一个常量字符串。
hobbyList not contains nam:"name"是一个动态变量,从Person中获取。

为了向后兼容,excludes运算符和not contains的作用一致。

5.8 memberOf, not memberOf字段是否是某个集合的一员

验证某个字段是否是ArrayCollection的一员。ArrayCollection必须是可变的。

Person("打篮球" memberOf hobbyList && "篮球" not memberOf hobbyList 
&& name not memberOf hobbyList)

5.9 str验证字段是否以什么开头或结尾

  1. 验证指定的字符串是以什么开头str[startsWith]
  2. 验证指定的字符串是以什么结尾str[endsWith]
  3. 验证指定字符串的长度str[length]
  4. 查看这个类org.drools.core.base.evaluators.StrEvaluatorDefinition
Person(
   name str[startsWith] "张" && name str[endsWith] "三" &&
   name str[length] 2 && "张三" str[startsWith] "张"
)

5.10 in not in

判断某个值是否在某一组值中

Person(
    $name: name &&
    name in ($name, "李四") &&
    "打篮球"  in ("打篮球", "踢足球") &&
    car.name not in ("打篮球", $name)
)

6、运算符的优先级

下表列出了 DRL 运算符从高到低的优先级。

Operator typeOperatorsNotes
Nested or null-safe property access., .(), !.Not standard Java semantics
List or Map access[]Not standard Java semantics
Constraint binding:Not standard Java semantics
Multiplicative*, /%
Additive+, -
Shift>>, >>>, <<
Relational<, <=, >, >=, instanceof
Equality== !=Uses equals() and !equals() semantics, not standard Java same and not same semantics
Non-short-circuiting AND&
Non-short-circuiting exclusive OR^
Non-short-circuiting inclusive OR|
Logical AND&&
Logical OR|
Ternary? :
Comma-separated AND,Not standard Java semantics

7、DRL支持的规则条件元素(关键字)

drl中支持的规则条件元素比较多,此处讲解部分关键字字的用法。

7.1 and

  1. 使用and可以将条件分组为逻辑组合。
  2. and支持中缀和前缀方式。
  3. 可以使用()明确的进行分组。
  4. 默认情况下是and
// 规则 and-01 and-02 and-03 是同一个意思,工作内存中需要同时存在Person和Order对象
rule "and-01"
    when
        Person() and Order()
    then
        System.out.println("and-01");
end

rule "and-02"
    when
        (and Person() Order())
    then
        System.out.println("and-02");
end

rule "and-03"
    when
        Person()
        Order()
    then
        System.out.println("and-03");
end

7.2 or

or也支持好几种写法,此处列出一种写法。和java中的or用法一致

rule "or-01"
    when
        $p: Person() or Order() // 规则内存中只要存在Pereson或Order对象就会执行,如果都存在,那么可能会执行多次。如果只想执行一次,可以看下exists的用法
    then
        System.out.println("or-01");
end

7.3 exists

与工作内存中的Fact进行匹配,只会在第一次匹配时触发,不会触发多次,如果和多个模式一起使用,则需要使用()

简单理解: 假设我工作内存中一次插入了5个Person对象,如果exists匹配到了,那么只会执行一次,不会执行5次。

rule "exists"
    when
        exists (Person() or Order()) // 单个: exists Person() 多个:需要()分割
    then
        System.out.println("exists 工作内存中同时存在多个Person()对象和Order()对象,该规则也只执行一次");
end

7.4 not

规则内存中不存在这个对象时,触发规则。

比如: not Person() 表示规则内存中没有Person这个Fact对象时触发。

rule "not-02"
    when
        not (Person(name == "李四") or Order(orderId == 1000))
    then
        System.out.println("not-02,规则内存中不存在Person#name==李四或Order#orderId=1000 时触发");
end

7.5 from

使用它来指定模式的数据源。 这使 Drools 引擎能够对不在工作内存中的数据进行推理。 数据源可以是绑定变量的子字段,也可以是方法调用的结果。 用于定义对象源的表达式是任何遵循常规 MVEL 语法的表达式。 因此,from 元素使您能够轻松地使用对象属性导航、执行方法调用以及访问映射和集合元素。

基本用法:

rule "from"
    when
        $p: Person($hobbyList: hobbyList)
        $hobby: String() from $hobbyList
    then
        System.out.println("如果$hobby有多个,那么此处可能执行多次");
        System.out.println("from: person: " + $p.getName() + " 的 hobby is: " +$hobby);
end

如果PersonhobbyList是一个比较大的集合,那么推荐将hobbyList这个插入到kie session中,来提高性能。

和lock-on-active一起使用的解决办法

Using from with lock-on-active rule attribute can result in rules not being executed. You can address this issue in one of the following ways:

  1. Avoid using the from element when you can insert all facts into the working memory of the Drools engine or use nested object references in your constraint expressions.
  2. Place the variable used in the modify() block as the last sentence in your rule condition.
  3. Avoid using the lock-on-active rule attribute when you can explicitly manage how rules within the same ruleflow group place activations on one another.

form子句后在跟一个模式的解决办法

包含 from 子句的模式后面不能跟以括号开头的另一个模式。 此限制的原因是 DRL 解析器将 from 表达式读取为“来自 $l (String() or Number())”,它无法将此表达式与函数调用区分开来。 最简单的解决方法是将 from 子句括在括号中,如以下示例所示:

// Do not use `from` in this way:
rule R
  when
    $l : List()
    String() from $l
    (String() or Number())
  then
    // Actions
end

// Use `from` in this way instead:
rule R
  when
    $l : List()
    (String() from $l)
    (String() or Number())
  then
    // Actions
end

7.6 entry-point

使用它来定义与模式的数据源相对应的入口点或事件流。 此元素通常与 from 条件元素一起使用。 您可以为事件声明一个入口点,以便 Drools 引擎仅使用来自该入口点的数据来评估规则。 您可以通过在 DRL 规则中引用它来隐式声明一个入口点,或者在您的 Java 应用程序中显式声明它。

drl文件

rule "entry-point"
    when
        $o: Order() from entry-point "order-entry-point" // 这个地方的数据是从 order-entry-point 中来的,kieSession.getEntryPoint("order-entry-point");
        $p: Person() // 这个地方的数据是通过kieSession.insert 来的
    then
        System.err.println("entry-point" + $p.getName() + ": " + $o.getOrderId());
end

Order()从上方的规则文件中可以,这个Order()对象是从order-entry-point这个地方来的。而不是别的地方来的。

Java文件

// order-entry-point 这个是 drl 文件中定义的
EntryPoint entryPoint = kieSession.getEntryPoint("order-entry-point");
entryPoint.insert(new Order(2001L, 10000L));

8、完整项目

gitee.com/huan1993/sp…

9、参考地址

1、docs.drools.org/7.69.0.Fina…