1、介绍
此篇文章主要记录一下 drools
中的模式(patterns
)和约束(constraints
)以及when
中条件的写法。
2、语法结构
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 属性分组
.(
将这些属性访问器分组到嵌套对象,以获得更易读的规则
rule "嵌套属性的访问-03"
when
Person(age < 20 , car.(name == "宝马" || color != null)) // 属性分组访问
then
System.out.println("嵌套属性的访问-03");
end
3.4.4 强制类型转换
在嵌套模式中,我们可以使用
语法强制转换为子类型并使父类型的 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 # 类型转换
在嵌套模式中,我们可以使用
语法强制转换为子类型并使父类型的 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")
中的这个map
是Person
的一个字段是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 正则匹配
- 用来判断
给定的字段
是否匹配执行的正则表达式。 -
正则表达式
可以是一个给定的字符串,也可以是从变量中动态获取。 - 转义需要使用
\\
。
Person(name matches hobbyList[2] && car.name not matches "^奥迪") // 正则表达式可以是动态来的
5.7 contains, not contains集合或字符串是否包含什么
contains
:包含;not contains
:不包含。
1、验证一个Array
或Collection
是否包含某个指定字段的值(可以是常量
也可以是变量
)。
2、也可以是String
类型的字段是否包含某个值(可以是常量
也可以是变量
)。
Person(
hobbyList contains "打篮球" && hobbyList not contains "打橄榄球"
&&。hobbyList not contains name &&
name contains "张" && name not contains car.name
)
hobbyList
:List类型的字段。
name
或car.name
:String类型的字段。
从上方的例子中可以看到:
hobbyList contains "打篮球"
:"打篮球"是一个常量字符串。
hobbyList not contains nam
:"name"是一个动态变量,从Person中获取。
为了向后兼容,excludes运算符和not contains的作用一致。
5.8 memberOf, not memberOf字段是否是某个集合的一员
验证某个字段是否是Array
或Collection
的一员。Array
或Collection
必须是可变的。
Person("打篮球" memberOf hobbyList && "篮球" not memberOf hobbyList
&& name not memberOf hobbyList)
5.9 str验证字段是否以什么开头或结尾
- 验证指定的字符串是以什么开头
str[startsWith]
。 - 验证指定的字符串是以什么结尾
str[endsWith]
。 - 验证指定字符串的长度
str[length]
。 - 查看这个类
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 type | Operators | Notes |
---|---|---|
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
- 使用
and
可以将条件分组为逻辑组合。 -
and
支持中缀和前缀方式。 - 可以使用
()
明确的进行分组。 - 默认情况下是
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
如果Person
的hobbyList
是一个比较大的集合,那么推荐将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:
- 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. - Place the variable used in the
modify()
block as the last sentence in your rule condition. - 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、完整项目
https://gitee.com/huan1993/spring-cloud-parent/tree/master/drools/drools-drl-when
9、参考地址
1、https://docs.drools.org/7.69.0.Final/drools-docs/html_single/index.html#drl-rules-WHEN-con_drl-rules