Ubuntu 22.04 集成 Apache KIE 10.2.0 指南
Ubuntu 22.04 上从零集成 Apache KIE 10.2.0 的指南,涵盖 OpenJDK 17 安装、Maven 阿里云镜像、五坐标依赖、kmodule 配置、运行验证与排障实录。
Ubuntu 22.04 集成 Apache KIE 10.2.0 指南
一、集成目标与总体路线
KIE 10.x 是纯 Java 库形态的规则引擎,"集成"的含义与 MRUnit 这类测试库一致:装好 JDK 与 Maven、建工程、引入坐标、写出第一条能真实触发的规则。本文在 Ubuntu 22.04 上以 OpenJDK 17.0.20.1 与 Maven 3.6.3 为工具链,引入 org.kie 与 org.drools 的五个 10.2.0 坐标,完成一条定价规则链(未成年人提示、成人基础折扣、VIP 叠加折扣、高价订单审计)的真实运行:四个规则、五个事实、五次触发,最终输出每个人的折扣后价格。验收标志是 exec:java 运行主类时完整打印"共触发规则数: 5"。
为什么验收标准必须是"规则真实触发"而不是"编译通过"?因为规则工程的特殊性在于:编译期 Maven 只验证 Java 代码,DRL 的解析与编译发生在运行期的 KieContainer 构建阶段——DRL 写错、包名不匹配、必备模块缺失,全都躲过 mvn compile 的检查,在 java 进程启动后的第一秒集中爆雷。本文第九节与第十节安排了三组真实失败(编码、会话空指针、约束编译异常),全部以报错原文呈现并给出修复,它们合起来构成一个完整的"为什么集成指南必须包含失败实验"的论证。读到这里仍觉得夸张的读者,看完第十节会改变看法。
集成路线八步:装 OpenJDK 17 → 装 Maven 并配阿里云镜像 → 建工程骨架 → 写 pom 引入五坐标 → 写事实类与 kmodule.xml → 写 DRL 与主程序 → 运行验证(含一次真实的编码失败实录与修复)→ 记录两个必备模块缺失的排障方法与移除方式。这八步里最有价值的是第九节与第十节的三组失败实录:默认编码下 KieBase 编译直接报 Duplicate rule name、缺 drools-xml-support 时会话静默为 null、缺 drools-mvel 时引擎编译抛 UnsupportedOperationException——它们都是编译期不报错、运行期才爆雷的类型,单看依赖树发现不了,本文全部用真实报错原文呈现。
二、集成前的选型分析
2.1 运行时:JDK 17 是正代基线
KIE 10.x 完成了 jakarta.* 命名空间迁移并抬高了运行时基线,OpenJDK 17 是当前最稳妥的选择:Ubuntu 22.04 官方源直接提供(jammy-updates/universe 的 17.0.20.1),无需第三方仓库;10.2.0 全部模块在 17 上编译运行无障碍(本文实测全程 17.0.20.1)。JDK 8 的存量工程请留在 KIE 7.x,那是它对应的代际——用 10.x 会出现 jakarta 类找不到、模块缺失等连锁问题,不值得为升级引擎单独背整个技术栈迁移的成本。
2.2 依赖面:五个坐标缺一不可
最小可用规则工程需要五个坐标:org.kie:kie-api(KieServices/KieContainer/KieSession 等核心接口)、org.drools:drools-compiler(DRL 解析与规则编译)、org.drools:drools-core(引擎运行时)、org.drools:drools-xml-support(kmodule.xml 与 XStream 序列化的 XML 能力)、org.drools:drools-mvel(MVEL 表达式引擎与动态装配路径)。前三件是传统三件套,后两件在 10.x 从可选变成了事实必备——本文第十节实测了缺它们的真实报错。版本统一用 10.2.0,五个坐标严格同版本,混版本的报错会以更隐蔽的方式出现(引擎内部接口在补丁版本间也可能调整)。
关于"为什么 10.x 会拆出这些新模块",值得放在选型分析里讲透。7.x 时代 drools-compiler 一站式承担了解析、编译、XML、表达式全部职责,模块少但每个都重;10.x 把职责拆细——drl-parser 专职 ANTLR4 解析、ecj 专职表达式编译、xml-support 专职 XML、mvel 专职动态求值——好处是依赖面可以按需裁剪(只用 KieHelper 内存编译的轻量场景可以少带桌面级组件),代价是集成者必须自己知道"哪些裁不得"。本文的立场明确:五坐标是安全最小集,裁剪属于进阶优化,先跑通再瘦身。这也解释了第十节两个报错的成因设计:引擎对缺失模块的应对是"占位实现抛异常"而不是"启动时拒绝",意味着裁剪错误的发现时机完全取决于你什么时候踩到那条代码路径。
2.3 构建工具:Maven 3.6.3 够用
KIE 10.x 是标准 Maven 工程,对构建器没有特殊版本要求,Ubuntu 源里的 Maven 3.6.3-5 实测全程可用。它不带老式的 annotation processor 钩子、不需要 shade、没有 enforcer 门槛,3.6.x 的默认生命周期完全覆盖。若团队已统一到更新的 Maven 3.9.x,同样兼容,无需为 KIE 单独降级。
三、系统准备与 OpenJDK 17 安装
3.1 确认系统并安装
grep PRETTY /etc/os-release
apt-get update
apt-get install -y openjdk-17-jdk maven
实测环境为 Ubuntu 22.04.5 LTS;已装状态重跑 apt 给出幂等回显(openjdk-17-jdk is already the newest version (17.0.20.1+1-1~22.04))。用 apt-cache 确认来源:
apt-cache policy openjdk-17-jdk | head -7
openjdk-17-jdk:
Installed: 17.0.20.1+1-1~22.04
Candidate: 17.0.20.1+1-1~22.04
Version table:
*** 17.0.20.1+1-1~22.04 500
500 http://archive.ubuntu.com/ubuntu jammy-updates/universe amd64 Packages
500 http://security.ubuntu.com/ubuntu jammy-security/universe amd64 Packages
两条 500 优先级来源都在 jammy 官方源内,无需 PPA。Maven 来自同一个源(3.6.3-5,universe)。
3.2 多 JDK 并存时的指向确认
Ubuntu 22.04 上常见 JDK 11 与 17 并存(default-jdk 元包拉 11,本文环境两者都有)。update-alternatives 查看现状:
update-alternatives --list java
/usr/lib/jvm/java-11-openjdk-amd64/bin/java
/usr/lib/jvm/java-17-openjdk-amd64/bin/java
update-alternatives --set java /usr/lib/jvm/java-17-openjdk-amd64/bin/java
java -version 2>&1
openjdk version "17.0.20.1" 2025-10-21
OpenJDK Runtime Environment (build 17.0.20.1+1-1~22.04)
OpenJDK 64-Bit Server VM (build 17.0.20.1+1-1~22.04, mixed mode, sharing)
注意 17 的 java 路径是 bin/java(不像 8 那样多一层 jre/)。set 命令在目标已是当前指向时无输出,以 java -version 回显为准。
3.3 确认 Maven 绑定与编码现状
mvn -version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 17.0.20.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: en_US, platform encoding: ANSI_X3.4-1968
runtime 指向 java-17 即绑定正确。platform encoding 显示 ANSI_X3.4-1968 在 Ubuntu 最小安装下是常态,但对 KIE 它不是无害的——本文第九节将实测:同样的环境变量缺失会让规则编译直接失败,这与纯 ASCII 数据的工程(如测试库)完全不同,原因届时展开。
四、配置 Maven 阿里云镜像
创建或编辑 /root/.m2/settings.xml(用户级配置,对所有工程生效):
mkdir -p /root/.m2
cat > /root/.m2/settings.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>central</mirrorOf>
<name>Aliyun Maven Central Mirror</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
</settings>
EOF
mirrorOf 写 central 只镜像中央仓库。KIE 的构件全在中央仓库(org.kie、org.drools 组),阿里云 public 仓完整同步,镜像配置后五个坐标一次拉齐。本文第十一节的移除实测里会给出带 Downloaded from aliyunmaven 字样的重拉输出,可作为镜像生效的复核方式。本地仓库 /root/.m2/repository 在本文实测环境中缓存了全部 KIE 依赖约 31MB(du -sh 实测),包含约六十个传递构件。
KIE 的传递依赖有三个值得认识的"重客":ANTLR4 runtime(DRL 解析器生成代码的运行时)、Eclipse JDT 的 ECJ(表达式编译成字节码用)、mvel2(动态表达式引擎)。三者合计占缓存体积的大头,也各有历史包袱(ECJ 版本与 JDK 的对应关系等)。集成排障时若遇到表达式相关的诡异行为,先核对这三者的实际解析版本(mvn dependency:tree 里的直接子节点)与 KIE 10.2.0 官方声明的版本是否一致——被其他依赖拖偏是最常见的隐性事故。
五、创建工程骨架
手工建目录(KIE 规则工程的资源目录比普通 Java 工程多两层):
mkdir -p /opt/kie-demo/src/main/java/com/example
mkdir -p /opt/kie-demo/src/main/resources/META-INF
mkdir -p /opt/kie-demo/src/main/resources/rules
find /opt/kie-demo -type d | sort
/opt/kie-demo
/opt/kie-demo/src
/opt/kie-demo/src/main
/opt/kie-demo/src/main/java
/opt/kie-demo/src/main/java/com
/opt/kie-demo/src/main/java/com/example
/opt/kie-demo/src/main/resources
/opt/kie-demo/src/main/resources/META-INF
/opt/kie-demo/src/main/resources/rules
三层资源的分工提前说清:src/main/java/com/example 放事实类与主程序;src/main/resources/META-INF 放 kmodule.xml(KIE 约定的装配声明,位置必须精确);src/main/resources/rules 放 DRL 规则文件(目录名本身不参与匹配,起匹配作用的是 DRL 里的 package 声明,见第八节)。目录建好后工程还是空的,但这个骨架已经能被 Maven 识别——先跑一次 mvn validate 确认构建体系正常再开始写代码,是隔离问题的好习惯:骨架阶段的问题(settings、镜像、JDK 绑定)与代码阶段的问题(依赖、语法、规则)分开排查,永远比混在一起快。这个习惯在多人协作的工程里还能再进一步:把骨架提交为首个提交,后续每类改动独立成提交,回滚粒度自然就有了。
六、编写 pom.xml
/opt/kie-demo/pom.xml 完整内容:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>kie-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<drools.version>10.2.0</drools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-xml-support</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-mvel</artifactId>
<version>${drools.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
</project>
五坐标同挂 ${drools.version} 属性,升级只动一处。project.build.sourceEncoding=UTF-8 是本工程的硬前提(第九节实测),exec-maven-plugin 3.1.0 用于运行主类验证。五个依赖都是 compile 范围——规则引擎是应用的能力组成,不同于测试库的 test 范围。首次 mvn compile 会拉取约六十个传递构件(含 ANTLR4、ECJ、XStream、mvel2 等),阿里云镜像下数十秒完成。
七、编写事实类与 kmodule.xml
7.1 事实类 Person
创建 src/main/java/com/example/Person.java:
package com.example;
public class Person {
private String name;
private int age;
private boolean vip;
private double price;
public Person(String name, int age, boolean vip, double price) {
this.name = name;
this.age = age;
this.vip = vip;
this.price = price;
}
public String getName() { return name; }
public int getAge() { return age; }
public boolean isVip() { return vip; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
@Override
public String toString() {
return "Person{" + name + "," + age + ",vip=" + vip + ",price=" + price + "}";
}
}
事实就是普通 POJO,引擎通过 getter 读取字段做约束匹配:DRL 里写 price 引擎调 getPrice(),写 vip 调 isVip()(布尔字段的特殊约定)。规则动作要修改价格,所以 setPrice 必须存在;其余字段保持只读,防止规则动作越权改写判断依据。
这个"字段访问走 getter"的约定有一个容易忽略的推论:DRL 约束的比较发生在引擎侧,它对字段类型的敏感度与 Java 相同——double 与 int 混用时精度语义要在规则设计阶段想清楚(价格用 double 在金额场景本就不严谨,生产系统应换 BigDecimal 并在 DRL 里用 compareTo 而不是 ==)。本文为了教学直觉保留 double,但读者迁移到真实业务时应把类型设计当作规则工程的第一道工序。另一个设计点是 setPrice 与 toString:前者供规则动作修改状态,后者在调试时把事实打印成人话——两者都不参与匹配网络,但都显著影响规则工程的可调试性。
7.2 kmodule.xml
创建 src/main/resources/META-INF/kmodule.xml:
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="pricingKBase" packages="rules">
<ksession name="pricingKS" type="stateful"/>
</kbase>
</kmodule>
kbase 的 packages="rules" 按 DRL 的 package 声明聚合资源;ksession 暴露名为 pricingKS 的有状态会话模板。位置必须在 META-INF 下且文件名精确为 kmodule.xml——KieContainer 构建时扫描 classpath 上所有 META-INF/kmodule.xml,找不到就得到一个空 KieContainer,而这在缺 drools-xml-support 时同样发生(见第十节实录)。
八、编写 DRL 规则与主程序
8.1 定价规则全文
创建 src/main/resources/rules/pricing.drl:
package rules
import com.example.Person
rule "未成年人提示"
salience 10
when
$p : Person(age < 18)
then
System.out.println("[未成年人提示] " + $p.getName() + " 年龄 " + $p.getAge() + ",需家长陪同");
end
rule "成人基础折扣"
when
$p : Person(age >= 18, price > 1000)
then
$p.setPrice($p.getPrice() * 0.9);
System.out.println("[成人基础折扣] " + $p.getName() + " 打9折,价格调整为 " + $p.getPrice());
end
rule "VIP 叠加折扣"
when
$p : Person(vip == true)
then
$p.setPrice($p.getPrice() * 0.95);
System.out.println("[VIP叠加折扣] " + $p.getName() + " 再打95折,价格调整为 " + $p.getPrice());
end
rule "高价订单审计"
when
$p : Person(price > 2000)
then
System.out.println("[高价订单审计] " + $p.getName() + " 当前价格 " + $p.getPrice() + " 超过 2000,进入人工审核");
end
package rules 与 kmodule 的 packages 过滤对应;四条规则覆盖了入门所需的全部要素:salience 优先级(提示类先于折扣类)、多条件约束(age 与 price 的与组合)、动作改状态(两条折扣)、跨规则联动(审计规则消费折扣后的价格)。注意包名是小写 rules——最初写成 com.example.rules 时规则会被 kbase 静默丢弃(没有任何报错),这是 DRL 侧最典型的坑。
8.2 主程序 RuleApp
创建 src/main/java/com/example/RuleApp.java:
package com.example;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
public class RuleApp {
public static void main(String[] args) {
KieServices ks = KieServices.Factory.get();
KieContainer kc = ks.getKieClasspathContainer();
System.out.println("KieContainer 构建完成,开始创建 KieSession...");
KieSession session = kc.newKieSession("pricingKS");
session.insert(new Person("张伟", 35, true, 2600.0));
session.insert(new Person("李娜", 16, false, 500.0));
session.insert(new Person("王芳", 28, false, 1500.0));
int fired = session.fireAllRules();
System.out.println("共触发规则数: " + fired);
System.out.println("最终价格 -> 张伟: " + 2600.0 * 0.9 * 0.95
+ " 李娜: 500.0 王芳: " + 1500.0 * 0.9);
session.dispose();
}
}
三个事实的设计各有用途:张伟(35 岁、VIP、2600 元)命中全部四条规则;李娜(16 岁、500 元)只命中未成年人提示;王芳(28 岁、1500 元)只命中成人基础折扣。最终价格行用纯 Java 按同样公式计算,与引擎输出交叉核对——这是把规则行为"对账"到常识的最朴素手段。
这套事实组合还有一层测试设计上的考量:三个对象分别对应"全命中、边界外、部分命中"三种评估形态,覆盖了规则集评估的基本分支。将来往规则集里加新规则时,这三个人是现成的回归探针——新规则若在三者之外产生了额外触发,fireAllRules 的计数会立刻暴露。把"事实组合 + 触发计数 + 最终价格"三元组做成断言(搬进 JUnit),就是规则回归测试的最小骨架,实战篇会完整展开。
九、构建与运行验证
9.1 一次真实的失败:默认编码下的 Duplicate rule name
按顺序先跑构建再运行:
cd /opt/kie-demo && mvn -q compile
mvn -q exec:java -Dexec.mainClass=com.example.RuleApp
在 Ubuntu 最小安装的默认 locale 下(mvn -version 显示 platform encoding: ANSI_X3.4-1968),运行直接失败:
[ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.1.0:java (default-cli) on project kie-demo: An exception occurred while executing the Java class. Error while creating KieBase[Message [id=1, kieBase=pricingKBase, level=ERROR, path=/opt/kie-demo/target/classes/rules/pricing.drl, line=13, column=0
[ERROR] text=Duplicate rule name: ??????????????????], Message [id=2, kieBase=pricingKBase, level=ERROR, path=/opt/kie-demo/target/classes/rules/pricing.drl, line=29, column=0
[ERROR] text=Duplicate rule name: ??????????????????]]
[ERROR] -> [Help 1]
报错解读:DRL 文件是 UTF-8 编码、规则名是中文(未成年人提示、成人基础折扣……),而引擎在默认平台编码(ASCII)下读取 DRL,中文规则名全部变成问号串——"未成年人提示""成人基础折扣""VIP 叠加折扣"这三条规则中两条的乱码形态相同,引擎判定为重名规则,KieBase 编译失败。这是规则引擎特有的编码失败形态:中文注释乱码只是难看,中文规则名乱码直接致命。
9.2 修复与验证
exec:java 在 Maven 自己的 JVM 里执行主类,因此 MAVEN_OPTS 对它有效(这一点与 surefire fork 测试 JVM 的场景相反):
MAVEN_OPTS=-Dfile.encoding=UTF-8 mvn -q exec:java -Dexec.mainClass=com.example.RuleApp
实测输出(完整逐字):
KieContainer 构建完成,开始创建 KieSession...
[未成年人提示] 李娜 年龄 16,需家长陪同
[成人基础折扣] 张伟 打9折,价格调整为 2340.0
[成人基础折扣] 王芳 打9折,价格调整为 1350.0
[VIP叠加折扣] 张伟 再打95折,价格调整为 2223.0
[高价订单审计] 张伟 当前价格 2223.0 超过 2000,进入人工审核
共触发规则数: 5
最终价格 -> 张伟: 2223.0 李娜: 500.0 王芳: 1350.0
逐条对账:未成年人提示带 salience 10 最先触发;张伟 2600 经成人基础折扣到 2340、再经 VIP 叠加到 2223(2223 > 2000 触发审计);李娜 500 无任何折扣;王芳 1500 只打九折到 1350。共触发 5 次,与最终价格行交叉核对全部吻合——集成验收达标。长期生效的做法是 export MAVEN_OPTS="-Dfile.encoding=UTF-8" 放进环境,或在 pom 的 exec 插件配置里写 <executableDependency> 配套的环境变量;最省心的根治是给系统补齐 UTF-8 locale。
把这次失败与修复沉淀成三条通用经验。其一,规则资产是"数据"不是"代码":它以字符串形式被解析,天然暴露在编码差异下,任何"中文规则名 + 非 UTF-8 运行环境"的组合都是隐患,规则命名用英文、描述性信息放注释或元数据,是成本最低的防御。其二,失败形态与损失成正比:编码问题的最坏形态不是输出乱码(可控),而是重名误判(致命)——尽量让对编码敏感的内容不承载结构性语义。其三,对照实验是最快的定位手段:本节"同一命令加不加 MAVEN_OPTS"的一对输出直接锁死根因,比任何推理都省时间。
9.3 依赖树核查
mvn dependency:tree
顶层实测输出(两级节选):
[INFO] com.example:kie-demo:jar:1.0.0
[INFO] +- org.kie:kie-api:jar:10.2.0:compile
[INFO] | \- org.slf4j:slf4j-api:jar:2.0.17:compile
[INFO] +- org.drools:drools-compiler:jar:10.2.0:compile
[INFO] | +- org.drools:drools-drl-parser:jar:10.2.0:compile
[INFO] | | +- org.drools:drools-io:jar:10.2.0:compile
[INFO] | | \- org.antlr:antlr4-runtime:jar:4.13.2:compile
[INFO] | +- org.drools:drools-base:jar:10.2.0:compile
[INFO] | +- org.drools:drools-drl-ast:jar:10.2.0:compile
[INFO] | +- org.drools:drools-kiesession:jar:10.2.0:compile
[INFO] | +- org.kie:kie-memory-compiler:jar:10.2.0:compile
[INFO] | +- org.drools:drools-ecj:jar:10.2.0:compile
[INFO] | | \- org.eclipse.jdt:ecj:jar:3.44.0:compile
[INFO] | \- org.kie:kie-internal:jar:10.2.0:compile
[INFO] +- org.drools:drools-core:jar:10.2.0:compile
[INFO] +- org.drools:drools-xml-support:jar:10.2.0:compile
[INFO] | +- com.thoughtworks.xstream:xstream:jar:1.4.21:compile
[INFO] | \- org.drools:drools-commands:jar:10.2.0:compile
[INFO] \- org.drools:drools-mvel:jar:10.2.0:compile
[INFO] +- org.mvel:mvel2:jar:2.5.2.Final:compile
[INFO] \- org.drools:drools-wiring-dynamic:jar:10.2.0:compile
核查要点:五个直接坐标全部 10.2.0 同版本;drl-parser(ANTLR4 解析)、ecj(表达式编译)、xml-support(XStream)、mvel(mvel2 表达式引擎)各就各位。首次运行时的 SLF4J(W): Defaulting to no-operation 警告说明没有日志实现,测试验证阶段可以无视,生产工程按团队规范加 slf4j 绑定即可。
十、排障实录:两个必备模块缺失
以下两组实验通过临时从 pom 摘除依赖、复现报错、再恢复验证完成,报错原文逐字保留。
10.1 缺 drools-xml-support:会话静默为 null
摘除 drools-xml-support 后编译依旧通过(编译期查不到任何异常),运行 RuleApp 在 newKieSession 之后立即抛空指针:
[ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.1.0:java (default-cli) on project kie-demo: An exception occurred while executing the Java class. Cannot invoke "org.kie.api.runtime.KieSession.insert(Object)" because "session" is null
根因链条:kmodule.xml 的解析依赖 XML 支持模块,缺失时 KieContainer 照常构建但内部 KieBase 为空,kc.newKieSession("pricingKS") 找不到会话定义返回 null,第一次 session.insert 才把问题炸出来。最阴险的运行期陷阱莫过于此——错误现场(NPE 位置)与根因位置(依赖缺失)隔了两层。修复即把坐标加回 pom 并重新编译,恢复后五次触发输出完整复现。诊断口诀:KieSession 为 null 时先查 xml-support,再查 kmodule 文件位置与 ksession 名字拼写。
10.2 缺 drools-mvel:引擎编译抛 UnsupportedOperationException
摘除 drools-mvel 后同样编译通过,运行报错:
[ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.1.0:java (default-cli) on project kie-demo: An exception occurred while executing the Java class. null: UnsupportedOperationException
message 为 null 的 UnsupportedOperationException 信息量几乎为零,加 -e 看完整堆栈后根因现形:
Caused by: java.lang.UnsupportedOperationException
at org.drools.compiler.rule.builder.ConstraintBuilder$DummyConstraintBuilder.setExprInputs (ConstraintBuilder.java:210)
DummyConstraintBuilder 是引擎在缺少 mvel 表达式模块时的占位实现——所有约束编译操作一律抛 UnsupportedOperationException。规则约束(age >= 18、vip == true)的编译正落在这条路径上。恢复依赖后运行正常。这两个实录合起来是一句集成铁律:10.x 的五坐标是"编译通过"与"运行成功"之间的鸿沟,缺谁都是运行期才暴露,新工程首次集成务必用真实触发做验收。
把三个实录(9.1 编码、10.1 会话空指针、10.2 占位实现)排成一张诊断速查表,按"报什么错"反查"缺什么":报 Duplicate rule name 且规则名是问号串,查编码;报 session is null 的 NPE,先查 xml-support 再查 kmodule 配置;报 message 为 null 的 UnsupportedOperationException,查 mvel、用 -e 看 DummyConstraintBuilder 指纹。三者共同点是根因与报错现场分离,共同解法是把"真实触发"固定为集成验收步骤——本文的定价规则链四条规则五个事实,跑一遍十秒内可完成,作为任何 KIE 工程的冒烟用例成本可忽略。
十一、移除与清理方法
11.1 从工程移除依赖
删除 pom.xml 中对应的 <dependency> 段后 mvn clean compile:主代码若还在 import 引擎类会编译失败,这个反向核查确认没有残留引用。整体放弃 KIE 时删除全部五个坐标与 kmodule.xml、DRL 资源。
11.2 清理构件缓存
du -sh /root/.m2/repository
rm -rf /root/.m2/repository/org/kie /root/.m2/repository/org/drools
ls /root/.m2/repository/org/ | grep -E "kie|drools" || echo "kie-drools-artifacts-removed"
实测输出:
31M /root/.m2/repository
kie-drools-artifacts-removed
重拉验证(同时复核镜像配置):
mvn -B dependency:get -Dartifact=org.kie:kie-api:10.2.0
实测输出(节选):
[INFO] Downloading from aliyunmaven: https://maven.aliyun.com/repository/public/org/kie/kie-api/10.2.0/kie-api-10.2.0.pom
[INFO] Downloaded from aliyunmaven: https://maven.aliyun.com/repository/public/org/kie/kie-api/10.2.0/kie-api-10.2.0.pom (6.3 kB at 15 kB/s)
[INFO] Total time: 7.385 s
dependency:get 只拉单个构件及其父 POM,完整依赖集会在下次 mvn compile 时按需补齐——这也是验证"删得干净、拉得回来"的最小实验。
11.3 删除工程与卸载工具链
rm -rf /opt/kie-demo
apt-get -s remove openjdk-17-jdk | tail -4
实测模拟输出:
The following packages will be REMOVED:
openjdk-17-jdk
0 upgraded, 0 newly installed, 1 to remove and 18 not upgraded.
Remv openjdk-17-jdk [17.0.20.1+1-1~22.04]
-s 只演算不执行,确认影响面后去掉 -s 真正卸载并 apt-get autoremove。卸载后 java -version 应提示找不到命令;/root/.m2 目录随用户数据一并清理。
与 MRUnit 那种 test 范围的测试库不同,KIE 的依赖是应用能力本体,撤除的决策成本更高——通常的撤除场景不是"整个应用不用了",而是"规则引擎被另一种实现替换"。此时的撤除边界要画清:引擎坐标与规则资源全部摘除,但规则的业务语义(折扣率、审计阈值)不会消失,它们要么沉淀成了新实现的配置,要么转移进了数据库——撤除演练时把"语义去哪了"写进交接清单,比单纯删干净更重要。这也是本文坚持三层撤除各自留验证手段的原因:依赖(反向编译核查)、缓存(重拉验证)、工具链(模拟撤除),每层都可独立回滚,任何一层发现"还需要"都能低代价回头。
十二、常见问题(FAQ)
1. 编译通过但运行报 Cannot invoke ... because "session" is null? 先查 drools-xml-support 是否在 pom 里(本文 10.1 实录:缺失时 KieContainer 空转、会话为 null),再核对 kmodule.xml 的位置(META-INF 下)与 ksession 名字拼写。
2. 运行报 UnsupportedOperationException 且 message 为 null? 缺 drools-mvel。加 -e 看堆栈,DummyConstraintBuilder.setExprInputs 是它的指纹(本文 10.2 实录)。该模块在 10.x 是规则约束编译的必需品,不是可选优化。
3. KieBase 编译报 Duplicate rule name 但规则名明明不同? 编码问题。DRL 用 UTF-8 而运行环境是 ASCII 平台编码时,中文规则名乱码后可能撞成同一串问号(本文 9.1 实录)。用 MAVEN_OPTS=-Dfile.encoding=UTF-8 修复(exec:java 场景有效),或根治于 UTF-8 locale。
4. 规则文件存在但四条规则一条都不触发? 查 DRL 的 package 声明是否命中 kmodule 的 packages 过滤——写成 com.example.rules 而 kmodule 只留 rules 时规则被静默丢弃。本文 8.1 的规则包名按 rules 组织正是为了对齐。
5. 用 JDK 8 能集成 KIE 10.2.0 吗? 不能作为正解。10.x 完成 jakarta 化且基线抬高,JDK 8 下会出现模块缺失与类不兼容的连锁问题。JDK 8 存量工程对应 KIE 7.x 线,两条线坐标相同可并存。
6. 五个坐标可以混版本吗? 不可以。org.kie 与 org.drools 的模块在同一版本线上互相咬合,混版本以运行期诡异行为呈现。统一挂一个 ${drools.version} 属性是最简单的防错设计。
7. 首次编译拉依赖很慢或超时? 确认阿里云镜像生效(下载日志域名应为 maven.aliyun.com)。KIE 全依赖约六十个构件、30MB 量级,镜像正常时数十秒完成。
8. mvn test 会跑什么?KIE 工程需要测试吗? 默认没有测试类时 Tests run 为 0 且构建成功(假绿)。规则工程的测试思路是把规则触发写成 JUnit 断言(插入事实、fireAllRules、断言价格),实战篇有完整方法。
9. exec:java 与直接 java -cp 运行有什么区别? exec:java 复用 Maven 的 JVM 与 classpath,MAVEN_OPTS 直接生效;java -cp 需要手工拼六十个 jar 的 classpath,编码参数走命令行 -Dfile.encoding。验证阶段用 exec:java 最省事。
10. 修改 DRL 后需要重启什么吗? 开发期重新 mvn compile 后新起 JVM 即生效——KieContainer 在构建时读取编译后的资源,进程内不会热加载。生产环境的规则热更新要走 KJAR 版本化或 KieHelper 动态构建路线,那是另一个维度的工程。
11. kmodule.xml 可以定义多个 ksession 吗? 可以。同一 kbase 下多个 ksession 模板(不同 type 或配置)各自独立,按名字取用。入门工程一个 stateful 足够,需要无状态批量评估时加 stateless 模板。
12. 评估结束后怎么干净退出? 三层撤除:删依赖(反向核查引用)、删构件缓存(dependency:get 验证可重拉)、卸载工具链(apt -s 先模拟)。本文第十一节全部有实测。
十三、参考链接
- Apache KIE(孵化中)官网:https://kie.apache.org/
- Maven Central 上的 kie-api 10.2.0:https://central.maven.org/artifact/org.kie/kie-api/10.2.0
- Maven Central 上的 drools-compiler 10.2.0:https://central.maven.org/artifact/org.drools/drools-compiler/10.2.0
- OpenJDK 17 发布页:https://openjdk.org/projects/jdk/17/
- exec-maven-plugin 文档:https://www.mojohaus.org/exec-maven-plugin/
- 阿里云 Maven 镜像配置说明:https://developer.aliyun.com/mvn/guide
- Apache Maven 官方文档:https://maven.apache.org/guides/