版本从 v7.20 更改为 v7.21 后 drools 中的无限递归
Infinite recursion in drools after version change from v7.20 to v7.21
当 drools-compiler 的版本从 7.20.0.Final
更改为 7.21.0.Final
时,某些规则会递归循环。
github中的代码:
The recursively looping version
The change between working and looping version
更多详情
当我启动一条规则时,其 then
部分修改了已在 when
部分中检查过的事实:
rule "rule 1.1"
when
$sampleDomain: SampleDomain(instanceVariable2 == "Value of instance variable")
then
System.out.println("Rule 1.1 fired");
modify($sampleDomain){
setInstanceVariable1(3)
}
end
它不会递归循环。
但是当我调用另一个从另一个调用静态函数的规则时 class:
rule "rule 1.2"
when
$sampleDomain: SampleDomain(CoreUtils.anotherFunction())
then
System.out.println("Rule 1.2 fired");
modify($sampleDomain){
setInstanceVariable1(3)
}
end
递归循环。
带静态功能的class是
import com.drool_issue.domain.SampleDomain;
public class CoreUtils {
public static boolean anotherFunction() {
System.out.println("anotherFunction() inside CoreUtils");
return true;
}
public static boolean anotherFunction(SampleDomain sampleDomain) {
System.out.println("anotherFunction(SampleDomain sampleDomain) inside CoreUtils");
return true;
}
}
我的域文件是:
public class SampleDomain {
private int instanceVariable1;
private String instanceVariable2;
private int instanceVariable3;
public int getInstanceVariable1() {
return instanceVariable1;
}
public void setInstanceVariable1(int instanceVariable1) {
this.instanceVariable1 = instanceVariable1;
}
public String getInstanceVariable2() {
return instanceVariable2;
}
public void setInstanceVariable2(String instanceVariable2) {
this.instanceVariable2 = instanceVariable2;
}
public int getInstanceVariable3() {
return instanceVariable3;
}
public void setInstanceVariable3(int instanceVariable3) {
this.instanceVariable3 = instanceVariable3;
}
}
这是版本从7.20.0.Final
更改为7.21.0.Final
后造成的。猜猜可能是什么问题?
当我进一步调查这个问题时,我也看到了这一点。
当我们在SampleDomain
class中添加两个函数即
public boolean anotherFunction() {
return true;
}
public boolean anotherFunction(SampleDomain sampleDomain) {
return true;
}
并在规则中使用它,例如:
rule "rule 1.4"
when
$sampleDomain: SampleDomain(anotherFunction())
then
System.out.println("Rule 1.4 fired");
modify($sampleDomain){
setInstanceVariable1(3)
}
end
和
rule "rule 1.5"
when
$sampleDomain: SampleDomain(anotherFunction($sampleDomain))
then
System.out.println("Rule 1.5 fired");
modify($sampleDomain){
setInstanceVariable3(4)
}
end
这些也递归循环。
github中的代码:
The recursive looping when using non static methods
The change between working and above version
此外,当任何静态方法变为非静态时,即使在规则中指定了静态方法,也会调用域 class 中的方法。
这里需要注意的代码部分是:
Rule where static method is called.
Another rule which also call the static method.
The static access modifier removed from the functions which where previously static.
github中的代码:
Weird behaviour when removing static modifier for the functions.
The change between working and above version
都是7.20.0.Final
之后的版本造成的,即7.21.0.Final
、7.22.0.Final
和7.23.0.Final
属性 基于反应性的过滤不能应用于外部函数,因为我们不知道外部函数(静态方法)在内部做了什么。
详细说明:
让我们从 属性 反应性开始;每当我们在规则的结果中使用修改或更新关键字时,我们都会通知引擎过滤相似对象类型的规则应该再次 re-evaluate 该对象。默认情况下,此 re-evaluation 发生在整个对象上。也就是说只要有一个属性的对象发生变化,规则就会认为它是一个新的对象来匹配。当我们不希望对某些更改使用规则 re-evaluated 时,这可能会导致一些问题。 loop-control 机制,例如 no-loop 和 lock-on-active,在这些情况下可能会有所帮助。但是,如果我们希望规则只控制某些属性的变化,我们需要编写非常复杂的条件。此外,如果将来模型更改为大型规则库,您可能必须修改大量规则以避免不需要的规则 re-executions。幸运的是,Drools 提供了一个允许引擎解决这个问题的功能。它允许规则编写者定义 bean 的属性,如果它们在工作内存中更新,则应该监视这些属性。此功能可以在规则中使用的数据模型(Java classes 或声明的类型)中定义,称为 property-reactive beans。
要使用此功能,我们首先需要使用 属性 Reactive 注释标记我们将在规则中使用的类型。这个注解让引擎知道,无论何时将这种类型的对象添加到工作内存中,都需要对其更改应用特殊过滤。这个注解可以添加到 Java class (在 class 级别)或声明的类型(在第一行之后,定义类型名称),如下所示,
在 Java class:
@PropertyReactive public class Customer { ... }
在声明的类型中:
declare PropertyReactiveOrder
@propertyReactive
discount: Discount
totalItems: Integer
total: Double
end
我们还可以通过将 PropertySpecificOption.ALWAYS
添加到构建器选项来使所有类型 属性 响应。
注意:属性 反应式 bean 更改将仅使用 modify 关键字通知规则引擎。 update 关键字无法区分正在更改的 bean 的属性。
属性 反应性已在 Drools 5.4 中引入,但由于从正确性和性能的角度来看,使用此功能被认为是一种很好的做法,因此在 Drools 7.0 中默认启用它。
现在再次回到我们的问题,尽管 属性 反应性已经发生了巨大的变化,它的行为就像一个细粒度的 属性 变化监听器,允许 Drools 了解更多关于我们规则的 RHS 内部发生了什么,或者至少在 modify() 操作内部发生了什么。这些功能仍然是一个黑盒子,因此产生了这个问题。
当 drools-compiler 的版本从 7.20.0.Final
更改为 7.21.0.Final
时,某些规则会递归循环。
github中的代码:
The recursively looping version
The change between working and looping version
更多详情
当我启动一条规则时,其 then
部分修改了已在 when
部分中检查过的事实:
rule "rule 1.1"
when
$sampleDomain: SampleDomain(instanceVariable2 == "Value of instance variable")
then
System.out.println("Rule 1.1 fired");
modify($sampleDomain){
setInstanceVariable1(3)
}
end
它不会递归循环。
但是当我调用另一个从另一个调用静态函数的规则时 class:
rule "rule 1.2"
when
$sampleDomain: SampleDomain(CoreUtils.anotherFunction())
then
System.out.println("Rule 1.2 fired");
modify($sampleDomain){
setInstanceVariable1(3)
}
end
递归循环。
带静态功能的class是
import com.drool_issue.domain.SampleDomain;
public class CoreUtils {
public static boolean anotherFunction() {
System.out.println("anotherFunction() inside CoreUtils");
return true;
}
public static boolean anotherFunction(SampleDomain sampleDomain) {
System.out.println("anotherFunction(SampleDomain sampleDomain) inside CoreUtils");
return true;
}
}
我的域文件是:
public class SampleDomain {
private int instanceVariable1;
private String instanceVariable2;
private int instanceVariable3;
public int getInstanceVariable1() {
return instanceVariable1;
}
public void setInstanceVariable1(int instanceVariable1) {
this.instanceVariable1 = instanceVariable1;
}
public String getInstanceVariable2() {
return instanceVariable2;
}
public void setInstanceVariable2(String instanceVariable2) {
this.instanceVariable2 = instanceVariable2;
}
public int getInstanceVariable3() {
return instanceVariable3;
}
public void setInstanceVariable3(int instanceVariable3) {
this.instanceVariable3 = instanceVariable3;
}
}
这是版本从7.20.0.Final
更改为7.21.0.Final
后造成的。猜猜可能是什么问题?
当我进一步调查这个问题时,我也看到了这一点。
当我们在SampleDomain
class中添加两个函数即
public boolean anotherFunction() {
return true;
}
public boolean anotherFunction(SampleDomain sampleDomain) {
return true;
}
并在规则中使用它,例如:
rule "rule 1.4"
when
$sampleDomain: SampleDomain(anotherFunction())
then
System.out.println("Rule 1.4 fired");
modify($sampleDomain){
setInstanceVariable1(3)
}
end
和
rule "rule 1.5"
when
$sampleDomain: SampleDomain(anotherFunction($sampleDomain))
then
System.out.println("Rule 1.5 fired");
modify($sampleDomain){
setInstanceVariable3(4)
}
end
这些也递归循环。
github中的代码:
The recursive looping when using non static methods
The change between working and above version
此外,当任何静态方法变为非静态时,即使在规则中指定了静态方法,也会调用域 class 中的方法。
这里需要注意的代码部分是:
Rule where static method is called.
Another rule which also call the static method.
The static access modifier removed from the functions which where previously static.
github中的代码:
Weird behaviour when removing static modifier for the functions.
The change between working and above version
都是7.20.0.Final
之后的版本造成的,即7.21.0.Final
、7.22.0.Final
和7.23.0.Final
属性 基于反应性的过滤不能应用于外部函数,因为我们不知道外部函数(静态方法)在内部做了什么。
详细说明: 让我们从 属性 反应性开始;每当我们在规则的结果中使用修改或更新关键字时,我们都会通知引擎过滤相似对象类型的规则应该再次 re-evaluate 该对象。默认情况下,此 re-evaluation 发生在整个对象上。也就是说只要有一个属性的对象发生变化,规则就会认为它是一个新的对象来匹配。当我们不希望对某些更改使用规则 re-evaluated 时,这可能会导致一些问题。 loop-control 机制,例如 no-loop 和 lock-on-active,在这些情况下可能会有所帮助。但是,如果我们希望规则只控制某些属性的变化,我们需要编写非常复杂的条件。此外,如果将来模型更改为大型规则库,您可能必须修改大量规则以避免不需要的规则 re-executions。幸运的是,Drools 提供了一个允许引擎解决这个问题的功能。它允许规则编写者定义 bean 的属性,如果它们在工作内存中更新,则应该监视这些属性。此功能可以在规则中使用的数据模型(Java classes 或声明的类型)中定义,称为 property-reactive beans。
要使用此功能,我们首先需要使用 属性 Reactive 注释标记我们将在规则中使用的类型。这个注解让引擎知道,无论何时将这种类型的对象添加到工作内存中,都需要对其更改应用特殊过滤。这个注解可以添加到 Java class (在 class 级别)或声明的类型(在第一行之后,定义类型名称),如下所示, 在 Java class:
@PropertyReactive public class Customer { ... }
在声明的类型中:
declare PropertyReactiveOrder
@propertyReactive
discount: Discount
totalItems: Integer
total: Double
end
我们还可以通过将 PropertySpecificOption.ALWAYS
添加到构建器选项来使所有类型 属性 响应。
注意:属性 反应式 bean 更改将仅使用 modify 关键字通知规则引擎。 update 关键字无法区分正在更改的 bean 的属性。
属性 反应性已在 Drools 5.4 中引入,但由于从正确性和性能的角度来看,使用此功能被认为是一种很好的做法,因此在 Drools 7.0 中默认启用它。
现在再次回到我们的问题,尽管 属性 反应性已经发生了巨大的变化,它的行为就像一个细粒度的 属性 变化监听器,允许 Drools 了解更多关于我们规则的 RHS 内部发生了什么,或者至少在 modify() 操作内部发生了什么。这些功能仍然是一个黑盒子,因此产生了这个问题。