返回

Drools Map和Set踩坑记:解决类型识别问题

java

Drools 中 Map 和 Set 的使用踩坑记

最近在用 Drools,遇到一个和 Map、Set 相关的问题,搞了半天才弄明白,记录一下,免得大家踩同样的坑。

一、 问题

我想在 Drools 规则中使用一个全局的 Map 来存储一些用于校验的值。这个 Map 在 Java 里是这样定义的:

Map<String, Set<String>>

然后我把它作为全局变量传给 Drools 引擎。简化后的规则大概长这样:

import java.util.Set;
global java.util.Map lookupValues;
dialect "java"
rule "rule"
agenda-group "SYSTEM"
when
    $lookup : Set() from lookupValues["KEY"]
    // 或者这样
    //$lookup : Set() from lookupValues.get("KEY")
then
    System.out.println("fail")
end

我的目标是把 $lookup 当作一个 Set 来用,方便后续用 contains 方法来检查其他值。但当我把 $lookup 声明为 Set 时,then 部分的代码居然不执行了!

二、 问题原因分析

Drools 在处理全局变量和类型匹配时,有点“傻”。直接用 from lookupValues["KEY"] 或者 from lookupValues.get("KEY") 获取到的值,Drools 默认并不知道它是一个 Set。 即使你在java中定义的类型是Map<String, Set<String>>, Drools 在 when 部分进行模式匹配时,也只会把它当作一个普通的 Object

而当我们使用下面的方式时:

import java.util.*;
global java.util.Map lookupValues;
dialect "java"
rule "rule"
agenda-group "SYSTEM"
when

$rawLookup : Object() from lookupValues.get("KEY")

then
   Set values = (Set)lookupValues.get("KEY");
   System.out.println("Value: " + values+", "+values.getClass());

end

控制台能正常打印出:

Value: [id3, id2, id1], class java.util.HashSet

说明这个Map里面的数据类型和java代码定义是一致的, 问题出在 when 部分的模式匹配那里.

三、 解决方案

针对这个问题,我摸索出了几种解决方案。

1. 在 then 部分进行类型转换

最简单粗暴的方法,就是在 then 部分把取出来的值强转成 Set

import java.util.*;
global java.util.Map lookupValues;
dialect "java"
rule "rule"
agenda-group "SYSTEM"
when
    $rawLookup : Object() from lookupValues.get("KEY") // 先用 Object 接收
then
    Set values = (Set) $rawLookup; // 在 then 部分强转成 Set
    System.out.println("Value: " + values);
    // 后续可以用 values.contains(...) 进行判断了
end
  • 原理: 这种方法绕过了 when 部分的类型匹配,直接在 then 部分处理。
  • 代码示例: 见上。
  • 安全建议: 确保你的 lookupValues 里对应 key 的值确实是一个 Set,否则强转会抛出 ClassCastException。 你可以在强转前加个类型判断,用instanceof 运算符.

2. 使用 eval 进行类型判断 (不太推荐)

可以在 when 部分用 eval 函数来判断取出来的值是不是 Set

import java.util.*;
global java.util.Map lookupValues;
dialect "java"
rule "rule"
agenda-group "SYSTEM"
when
    eval( lookupValues.get("KEY") instanceof Set )
    $rawLookup : Object() from lookupValues.get("KEY")
then
    Set values = (Set) $rawLookup;
    System.out.println("Value: " + values);
end
  • 原理: eval 函数可以执行任意的 Java 表达式,这里用它来判断类型。
  • 代码示例: 见上。
  • 安全建议: 尽量少用 eval,因为它会让规则引擎的优化失效,影响性能。

3. 自定义一个 getter 方法

可以在你的 Java 代码里,为这个 Map 添加一个自定义的 getter 方法,专门返回 Set 类型的值。

// Java 代码
public class MyGlobals {
    private Map<String, Set<String>> lookupValues;

    public MyGlobals(Map<String, Set<String>> lookupValues) {
        this.lookupValues = lookupValues;
    }

    public Set<String> getLookupValueSet(String key) {
        return lookupValues.get(key);
    }

    // 可能会用到的其他方法...
}

然后在 Drools 规则里,用这个 getter 方法:

import java.util.Set;
global MyGlobals myGlobals; // 注意这里全局变量的类型
dialect "java"
rule "rule"
agenda-group "SYSTEM"
when
    $lookup : Set() from myGlobals.getLookupValueSet("KEY")
then
    System.out.println("Value: " + $lookup);
end
  • 原理: 通过自定义的 getter 方法,明确告诉 Drools 返回值的类型是 Set
  • 代码示例: 见上。
  • 安全建议: 无特殊安全建议。
  • 进阶技巧 : 你可以在这个get方法中实现一些数据处理的逻辑. 比如说在数据返回之前做一次过滤.

4. 将 Map 拆分成多个全局 Set 变量(数据量不大时可以用)

如果你的 Map 里的 key 不是特别多,可以考虑把每个 key 对应的 Set 单独作为一个全局变量。

// 假设原来的 Map 是这样的:
// Map<String, Set<String>> lookupValues = new HashMap<>();
// lookupValues.put("KEY1", new HashSet<>(Arrays.asList("a", "b")));
// lookupValues.put("KEY2", new HashSet<>(Arrays.asList("c", "d")));

// 现在改成这样:
Set<String> lookupValuesForKey1 = new HashSet<>(Arrays.asList("a", "b"));
Set<String> lookupValuesForKey2 = new HashSet<>(Arrays.asList("c", "d"));

然后在 Drools 规则里,直接使用这些全局 Set:

import java.util.Set;
global Set lookupValuesForKey1;
global Set lookupValuesForKey2;
dialect "java"
rule "rule_for_key1"
agenda-group "SYSTEM"
when
    $lookup : Set() from lookupValuesForKey1
then
    System.out.println("Value from KEY1: " + $lookup);
end

rule "rule_for_key2"
agenda-group "SYSTEM"
when
   $lookup: Set() from lookupValuesForKey2;
then
    System.out.println("Value from KEY2: " + $lookup);
end
  • 原理: 避免了 Map 的使用,直接操作 Set,Drools 就能正确识别类型。
  • 代码示例: 见上。
  • 安全建议: 无特殊安全建议。 这种方式更适合 key 数量比较少,而且比较固定的情况。

5. 将map转为Object数组处理(数据量小, 并且Set中的值是简单对象的时候可用)

如果set里面存的是String等基础对象, 你可以将 map 转换为 Object[] 数组再传递给Drools。 示例代码如下:

Java部分:

    Map<String, Set<String>> lookupValues = new HashMap<>();
    lookupValues.put("KEY1", new HashSet<>(Arrays.asList("a", "b")));
    lookupValues.put("KEY2", new HashSet<>(Arrays.asList("c", "d")));

     List<Object[]> globalData = new ArrayList<>();
        for (Map.Entry<String, Set<String>> entry : lookupValues.entrySet()) {
            globalData.add(new Object[]{entry.getKey(), entry.getValue().toArray()});
        }

Drools部分:

global java.util.List globalData;
dialect "java"
rule "process_map_data"
    when
      $data : Object[]( $key : (String) this[0], $values: (Object[]) this[1]) from globalData
    then
         System.out.println("key is "+ $key);
         for(Object value : $values){
             System.out.println("value: "+ value);
         }

end

原理: 数组中的Object类型是可以直接使用的. 然后再在Drools规则中逐个处理。

四、 总结

Drools 在处理复杂类型时,需要多注意一下。遇到类似问题,可以多试试不同的方法,找到最适合自己的。希望以上几种方式能够帮到遇到同样问题的小伙伴.