Drools中eval的正确打开姿势

个人博客地址:http://www.ltang.me/2020/04/23/using-eval-in-drools/

前面在做drools规则设计和测试时,发现一个很坑爹的现象,当LHS的某一行存在eval短句时,or短路就不生效了。具体情况如下例子。

我写了一个简单的例子:

import java.util.*;

global me.tangliu.drools.Test test;

rule "rule01"
when
    Map(this["A"] == 1000) || eval(test.testBoolean())
then
    System.out.println("reaching then");
end

我期望的时,当A == 1000时,直接跳转到RHS语句(then部分),而不需要执行test.testBoolean()(or短路),可是运行结果如下所示:

9:51:10.801 [main] DEBUG org.drools.compiler.kie.builder.impl.KieRepositoryImpl - Cannot load a KieRepositoryScanner, using the DummyKieScanner
19:51:11.096 [main] DEBUG org.drools.core.common.DefaultAgenda - State was INACTIVE is now FIRING_ALL_RULES
going into test boolean
reaching then
19:51:11.112 [main] DEBUG org.drools.core.common.DefaultAgenda - State was FIRING_ALL_RULES is now HALTING
19:51:11.112 [main] DEBUG org.drools.core.common.DefaultAgenda - State was HALTING is now INACTIVE
19:51:11.112 [main] DEBUG org.drools.core.common.DefaultAgenda - State was INACTIVE is now DISPOSED

going into test boolean 是在testBoolean方法中打印的日志,说明当前面this["A"] == 1000满足时,及时你写的是or,后面的判断依然会执行,or短路并未生效

这跟我预期的不一样啊!既然or短路不生效,那么and短路呢?我修改语句如下:

import java.util.*;

global me.tangliu.drools.Test test;

rule "rule01"
when
    Map(this["A"] == 1000) && eval(test.testBoolean())
then
    System.out.println("reaching then");
end

将A的值修改为99再去跑规则,发现test.testBoolean()不会执行了,说明and短路生效了

反复的做实验,发现,当同一行存在一个或多个eval()判断的时候,如果是or的关系,那么所有eval中的判断都会执行,并且,每次执行完一次判断,若满足条件,RHS都会执行。如下:

rule "rule01"
when
    Map(this["A"] == 1000) || eval(test.testBoolean()) || eval(test.testBoolean2())
then
    System.out.println("reaching then");
end

跑规则的结果是:

going into test boolean
reaching then
reaching then
going into test boolean 2
reaching then

但是,假如使用的不是or链接,而是and链接,那么短路是能生效的,这究竟是为什么呢?google了各种问答,能搜到不多的一些说明,如下:

The CE eval is essentially a catch-all which allows any semantic code (that returns a primitive boolean) to be executed. This code can refer to variables that were bound in the LHS of the rule, and functions in the rule package. Overuse of eval reduces the declarativeness of your rules and can result in a poorly performing engine. While eval can be used anywhere in the patterns, the best practice is to add it as the last conditional element in the LHS of a rule.
Evals cannot be indexed and thus are not as efficient as Field Constraints. However this makes them ideal for being used when functions return values that change over time, which is not allowed within Field Constraints.

val is very convenient as it allows us to include pretty much any condition in a rule. However, it’s considerably slower. With other conditions, Drools can cache (remember) the results because it can figure out when these results need to change. With eval, Drools doesn’t have this visibility. Therefore, all eval expressions need to be rechecked every time the rule is true. If you have to use eval, it’s best to add it as the last condition in a rule—meaning, it will be called less often. If any of the previous conditions return ‘false’, then Drools shortcuts, because there is no need to check any of the remaining conditions.

看起来都只是说了两点:

  1. eval很灵活,可以自定义方法调用来做条件判断
  2. eval效率低(因为无法缓存结果),因此尽量放在多个条件的最后

但是好像无法解答为啥短路语法不生效。再多看几篇关于drools语法规则的文档,我的理解如下:

我对drools的LHS的语义理解不对,它不像我们的java或者其他代码中的语法,它其实可以理解为:每一行是一个条件(条件间使用and链接),每一个条件是由多个约束组成的对一个对象的完整的约束,不应该在一行里面存在对多个对象的约束判断。换句话说,如果eval不是用在内联约束(inline expression),而是用在条件,那么,一行应该只有一个eval。具体来说,我们的语法应该是:

when
	eval( A() || B() )
then

而不应该是

when 
	eval( A()) || eval( B())
then

因此,最初的例子,我们可以改写为:

rule "rule01"
when
    $m: Map()
    eval( Integer.valueOf((String)$m.get("A")) == 1000  || test.testBoolean())
then
    System.out.println("reaching then");
end

这样就能达我们的期望,当A==1000时,testBoolean不需要再执行判断了。不过,这样就需要在代码里做好类型转换,很麻烦,因此改成下面这种写法,由drools自己去做转换,且可以走缓存,有可能提高效率:

import function testBoolean(){
		return me.ltang.drools.Test.testBoolean();
}
rule "rule01"
when
    Map(this["A"] == 1000 || eval(testBoolean()))
then
    System.out.println("reaching then");
end

最后达到的结果是一样的,但这种方式明显更好更合适。

### 使用 `eval` 调用自定义函数 在 Drools 中,可以通过 `eval` 函数来评估布尔表达式,并且可以在规则的左侧 (LHS) 部分调用自定义函数。为了实现这一点,首先需要确保自定义函数已经被正确定义并导入到规则文件中。 #### 定义和导入自定义函数 假设有一个名为 `isPrimeNumber` 的自定义函数,该函数接收一个整数参数并返回一个布尔值表示这个数字是否为素数: ```java // 自定义 Java 类中的 isPrimeNumber 方法 public class UtilityFunctions { public static boolean isPrimeNumber(int number) { if (number <= 1) return false; for (int i = 2; i * i <= number; ++i) { if (number % i == 0) return false; } return true; } } ``` 接着,在 `.drl` 文件顶部使用 `import` 声明引入此静态方法以便于后续使用[^1]。 ```drools package rules; import com.example.droolstest02.entity.TestNoneEntity; import function com.utility.UtilityFunctions.isPrimeNumber; ``` #### 编写带有 `eval` 和自定义函数调用的规则 下面是一个具体的例子展示如何在一个规则里利用 `eval` 来调用上述提到的 `isPrimeNumber` 函数: ```drools rule "Check Prime Number Using Eval" when $test : TestNoneEntity() eval(isPrimeNumber($test.getNumber())) then System.out.println("The provided number " + $test.getNumber() + " is a prime number."); end ``` 在这个例子中,当 `$test` 对象实例化并且其 `getNumber()` 返回的结果被传递给 `isPrimeNumber` 函数时,如果结果为真,则触发右侧的动作打印消息至控制台。 通过这种方式,可以灵活地将复杂的业务逻辑封装成独立的方法供多个规则共享,从而提高代码可读性和维护性的同时也简化了规则本身的编写过程。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值