Drools Map和Set踩坑记:解决类型识别问题
2025-02-28 16:55:02
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 在处理复杂类型时,需要多注意一下。遇到类似问题,可以多试试不同的方法,找到最适合自己的。希望以上几种方式能够帮到遇到同样问题的小伙伴.