1. 概述

在复杂业务系统中,将业务规则从核心代码中剥离,是提升可维护性和降低变更成本的有效手段。规则引擎正是为此而生——它让业务逻辑变得可配置、可热更新,避免每次改需求都要动代码、重新打包上线。

在之前的 Java 规则引擎综述 一文中,我们提到了 JSR 94 这一规范。而 Jess Rule Engine 正是该规范的参考实现(Reference Implementation),地位特殊,值得深入研究。

本文将带你从零开始,实战集成 Jess,并通过 JSR 94 标准化接口,实现规则引擎的“热插拔”。

2. Jess 规则引擎简介

Jess 是最早一批能与 Java 无缝集成的规则引擎之一。其核心基于高效 Rete 算法的增强实现,在多数场景下,性能远超简单的 Java 循环判断。

核心特点:

  • 原生规则语言:使用类 Lisp 的语法编写 .clp 文件,简洁强大。
  • 支持 XML:也可用 XML 格式定义规则(更啰嗦,本文不采用)。
  • REPL 交互式环境:提供命令行界面,可快速验证规则逻辑,踩坑利器
  • Eclipse 插件:有配套的开发工具(兼容旧版 Eclipse)。
  • JSR 94 兼容:作为参考实现,天然支持标准 API。

⚠️ 注意:Jess 本身是商业闭源产品,虽提供免费试用,但生产环境需授权。其 JSR 94 驱动层是开源的。

2.1. JSR 94 规范简述

JSR 94 的核心价值在于解耦。它定义了一套标准 API,让你的 Java 代码无需关心底层是 Jess、Drools 还是其他引擎。

这意味着:

  • 切换引擎只需改配置:替换 Maven 依赖和驱动类名即可。
  • 规则文件仍需重写:不同引擎的规则语法差异巨大,无法直接复用。

简单粗暴地说,JSR 94 让你的应用代码规则引擎实现解耦,但规则逻辑本身还是绑定在具体引擎上的。

2.2. Jess 的 JSR 94 驱动

JSR 94 规范包中自带一个 org.jcp.jsr94.jess 包下的参考驱动,但不包含 Jess 引擎本体

推荐使用官方下载包中提供的更新、更稳定的驱动,位于 jess.jsr94 包下。集成时,需同时引入 jess.jar (引擎) 和 JSR 94 API。

我们先看原生集成,再对比 JSR 94 的用法差异。

3. 示例运行验证

动手前,先确保环境就绪。你需要:

  1. 注册并下载 Jess 7.1p2 版本(含 30 天试用)。
  2. 解压得到 Jess71p2.jar,并将其加入项目 classpath。

3.1. 运行原生 Jess 示例

进入解压目录的示例文件夹,运行定价引擎 demo:

cd Jess71p2/examples/pricing_engine
ant test

预期输出:

Buildfile: Jess71p2\examples\pricing_engine\build.xml
...
test:
[java] Items for order 123:
[java] 1 CD Writer: 199.99
...
[java] Items for order 666:
[java] 1 Incredibles DVD: 29.99
[java] Offers for order 666:
[java] BUILD SUCCESSFUL
Total time: 1 second

输出成功,说明 Jess 运行环境正常。

3.2. 运行 JSR 94 示例

  1. 下载 JSR 94 规范包:

    unzip jreng-1_0a-fr-spec-api.zip
    

    得到 jsr94-1.0 目录。

  2. 关键一步:将之前下载的 Jess71p2/lib/jess.jar 复制到 jsr94-1.0/lib/ 目录下。否则会报错:

    Error: The reference implementation Jess could not be found.
    
  3. 运行示例:

    java -jar jsr94-1.0/lib/jsr94-example.jar
    

预期输出:

Administration API Acquired RuleAdministrator: org.jcp.jsr94.jess.RuleAdministratorImpl@63947c6b
...
Runtime API Acquired RuleRuntime: org.jcp.jsr94.jess.RuleRuntimeImpl@68fb2c38
Customer credit limit result: 3000
...
Invoice 2 amount: 1750 status: paid
Released Stateful Rule Session.

成功!证明 JSR 94 与 Jess 驱动协同工作正常。

4. 原生方式集成 Jess

现在,我们用 Java 代码直接调用 Jess API 来执行规则。

4.1. Maven 依赖

由于 Jess 未发布到中央仓库,需手动安装到本地:

mvn install:install-file -Dfile=jess.jar -DgroupId=gov.sandia -DartifactId=jess -Dversion=7.1p2 -Dpackaging=jar -DgeneratePom=true

然后在 pom.xml 中添加:

<dependency>
    <groupId>gov.sandia</groupId>
    <artifactId>jess</artifactId>
    <version>7.1p2</version>
</dependency>

4.2. 第一个规则文件

创建最简单的规则文件 hellojess.clp

(printout t "Hello from Jess!" crlf)

4.3. 执行规则

Java 代码创建引擎实例并执行:

public class HelloJess {
    public static void main(String[] args) throws JessException {
        Rete engine = new Rete();
        engine.reset(); // 重置到初始状态
        engine.batch("hellojess.clp"); // 加载规则文件
        engine.run(); // 执行规则
    }
}

运行输出:

Hello from Jess!

5. 原生集成:数据交互

规则引擎的价值在于处理数据。下面我们看如何传入 Java 对象,让规则处理,并获取结果。

5.1. 数据模型

定义 QuestionAnswer 类:

public class Question {
    private String question;
    private int balance;
    // getters and setters

    public Question(String question, int balance) {
        this.question = question;
        this.balance = balance;
    }
}

public class Answer {
    private String answer;
    private int newBalance;
    // getters and setters

    public Answer(String answer, int newBalance) {
        this.answer = answer;
        this.newBalance = newBalance;
    }
}

5.2. 带数据处理的规则

创建 bonus.clp 规则文件:

(import com.baeldung.rules.jsr94.jess.model.*)
(deftemplate Question     (declare (from-class Question)))
(deftemplate Answer       (declare (from-class Answer)))

(defrule avoid-overdraft "Give $50 to anyone overdrawn"
    ?q <- (Question { balance < 0 })
    =>
    (add (new Answer "Overdrawn bonus" (+ ?q.balance 50))))

代码解析:

  • (import ...)(deftemplate ...) 声明了 Java 类,使其可在规则中使用。
  • ?q <- (Question { balance < 0 })条件(LHS)?q 是绑定变量,匹配所有余额小于 0 的 Question 对象。
  • => 后是动作(RHS),使用 (add ...) 将新创建的 Answer 对象放入工作内存。

5.3. Java 代码操作数据

Rete engine = new Rete();
engine.reset();

// 加载规则
engine.batch("bonus.clp");

// 创建数据并加入工作内存
Question question = new Question("Can I have a bonus?", -5);
engine.add("Question", question); // 或使用 engine.add() 直接添加对象

// 执行规则
engine.run();

// 从工作内存中提取结果
Iterator results = engine.getObjects(new jess.Filter.ByClass(Answer.class));
while (results.hasNext()) {
    Answer answer = (Answer) results.next();
    System.out.println("Answer: " + answer.getAnswer() + ", New Balance: " + answer.getNewBalance());
}

// 高效复用:使用标记(Marker)重置状态
WorkingMemoryMarker marker = engine.mark(); // 在加载完参考数据后打标记
// ... 处理本次请求数据 ...
engine.resetToMark(marker); // 重置,准备处理下一批数据

6. 使用 JSR 94 集成 Jess

现在,我们用标准化的 JSR 94 API 重写上述逻辑,实现引擎无关性。

6.1. Maven 依赖

<dependency>
    <groupId>jsr94</groupId>
    <artifactId>jsr94</artifactId>
    <version>1.1</version>
</dependency>

6.2. 管理 API (javax.rules.admin)

用于加载和注册规则集:

// 1. 获取服务提供者
String RULE_SERVICE_PROVIDER = "jess.jsr94";
Class.forName(RULE_SERVICE_PROVIDER + ".RuleServiceProviderImpl");
RuleServiceProvider ruleServiceProvider = RuleServiceProviderManager.getRuleServiceProvider(RULE_SERVICE_PROVIDER);

// 2. 获取管理员并注册规则集
RuleAdministrator ruleAdministrator = ruleServiceProvider.getRuleAdministrator();
InputStream ruleInput = getClass().getResourceAsStream("/bonus.clp");
HashMap vendorProperties = new HashMap(); // 供应商特定属性,Jess 可忽略

RuleExecutionSet ruleExecutionSet = ruleAdministrator
    .getLocalRuleExecutionSetProvider(vendorProperties)
    .createRuleExecutionSet(ruleInput, vendorProperties);

String rulesURI = "rules://com/baeldung/rules/bonus";
ruleAdministrator.registerRuleExecutionSet(rulesURI, ruleExecutionSet, vendorProperties);

6.3. 执行 API (javax.rules)

用于创建会话、传入数据并获取结果:

// 3. 获取运行时并创建无状态会话
RuleRuntime ruleRuntime = ruleServiceProvider.getRuleRuntime();
StatelessRuleSession statelessRuleSession = (StatelessRuleSession) 
    ruleRuntime.createRuleSession(rulesURI, new HashMap(), RuleRuntime.STATELESS_SESSION_TYPE);

try {
    // 4. 执行规则并获取结果
    List<Object> data = new ArrayList<>();
    data.add(new Question("Can I have a bonus?", -5));
    
    List<Object> results = statelessRuleSession.executeRules(data);
    
    // 5. 处理结果 (JSR 94 未使用泛型)
    for (Object obj : results) {
        if (obj instanceof Answer) {
            Answer answer = (Answer) obj;
            System.out.println("Answer: " + answer.getAnswer() + ", New Balance: " + answer.getNewBalance());
        }
    }
} finally {
    statelessRuleSession.release(); // 释放资源
}

JSR 94 优势总结:

  • 标准化:代码与具体引擎解耦。
  • 可替换:理论上可无缝切换到其他 JSR 94 兼容引擎(如早期的 Drools)。
  • 清晰分层admin 包负责规则管理,runtime 包负责规则执行。

⚠️ 缺点

  • API 较老(JDK 1.4 时代),不支持泛型,代码稍显啰嗦。
  • 实际生态中,Drools 等主流引擎已转向自己的 API,JSR 94 应用较少。

7. 结论

本文详细演示了如何通过原生 APIJSR 94 标准两种方式集成 Jess 规则引擎。

  • 原生方式:简单直接,性能最优,适合深度绑定 Jess 的项目。
  • JSR 94 方式:牺牲一点便捷性,换取未来可能的引擎迁移灵活性。

尽管 Jess 作为商业产品和 JSR 94 规范的活跃度已不如从前,但其设计思想和 Rete 算法实现,仍是理解规则引擎原理的绝佳案例。

延伸阅读

本文所有代码示例已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/rule-engines-modules/jess


原始标题:Jess Rule Engine and JSR 94