1. 引言

本文将带你快速上手 Evrette —— 一个全新的开源 Java 规则引擎。作为 Drools 规则引擎的轻量级替代方案,Evrette 具备以下核心特性:

  • 完全兼容 Java 规则引擎规范(JSR 94)
  • 采用经典的 RETE 前向链算法,并针对大数据处理做了优化
  • 仅需 Java 8+ 环境,零外部依赖
  • 原生支持 JSON/XML 数据处理
  • 允许使用函数式接口定义规则条件和动作
  • 通过 SPI(服务提供者接口)实现组件扩展

其中最实用的功能之一:通过 SPI 将注解 Java 类转换为可执行规则集,本文将重点演示这个特性。

2. Maven 依赖

pom.xml 中添加核心依赖:

<dependency>
    <groupId>org.evrete</groupId>
    <artifactId>evrete-core</artifactId>
    <version>3.0.01</version>
</dependency>

3. 业务场景

为避免空谈,我们模拟一个真实场景:小型企业财务年度结算,需要计算每个客户的总销售额。领域模型包含两个简单类:

客户类

public class Customer {
    private double total = 0.0;
    private final String name;

    public Customer(String name) {
        this.name = name;
    }

    public void addToTotal(double amount) {
        this.total += amount;
    }
    // getters and setters
}

发票类

public class Invoice {
    private final Customer customer;
    private final double amount;

    public Invoice(Customer customer, double amount) {
        this.customer = customer;
        this.amount = amount;
    }
    // getters and setters
}

技术贴士:引擎原生支持 Java Records,且允许将任意类属性声明为函数式接口

业务逻辑需要两条规则:

  1. 清零每个客户的销售额
  2. 匹配发票与客户,累加销售额

接下来我们分别用规则构建器 API 和注解方式实现。

4. 规则构建器 API

4.1 规则集声明

通过流式接口声明两条规则:

KnowledgeService service = new KnowledgeService();
Knowledge knowledge = service
  .newKnowledge()
  .newRule("Clear total sales")
  .forEach("$c", Customer.class)
  .execute(ctx -> {
      Customer c = ctx.get("$c");
      c.setTotal(0.0);
  })
  .newRule("Compute totals")
  .forEach(
      "$c", Customer.class,
      "$i", Invoice.class
  )
  .where("$i.customer == $c")
  .execute(ctx -> {
      Customer c = ctx.get("$c");
      Invoice i = ctx.get("$i");
      c.addToTotal(i.getAmount());
  });

关键点解析:

  • KnowledgeService 是共享执行服务,每个应用只需一个实例
  • Knowledge 是预编译规则集,提前编译可确保正确性并提升性能
  • 等效 Drools DRL 规则: ```plaintext rule "Clear total sales" when $c: Customer then $c.setTotal(0.0); end

rule "Compute totals" when $c: Customer $i: Invoice(customer == $c) then $c.addToTotal($i.getAmount()); end


### 4.2 构造测试数据

创建 3 个客户和 10 万张随机发票:

```java
List<Customer> customers = Arrays.asList(
  new Customer("Customer A"),
  new Customer("Customer B"),
  new Customer("Customer C")
);

Random random = new Random();
Collection<Object> sessionData = new LinkedList<>(customers);
for (int i = 0; i < 100_000; i++) {
    Customer randomCustomer = customers.get(random.nextInt(customers.size()));
    Invoice invoice = new Invoice(randomCustomer, 100 * random.nextDouble());
    sessionData.add(invoice);
}

4.3 执行规则

将 100,003 个对象(10 万发票 + 3 客户)注入会话并触发:

knowledge
  .newStatelessSession()
  .insert(sessionData)
  .fire();

for(Customer c : customers) {
    System.out.printf("%s:\t$%,.2f%n", c.getName(), c.getTotal());
}

输出结果示例:

Customer A:    $1,664,730.73
Customer B:    $1,666,508.11
Customer C:    $1,672,685.10

性能提示:处理 10 万级数据仅需毫秒级,RETE 算法名不虚传

5. 注解式 Java 规则

5.1 安装注解模块

添加 evrete-dsl-java 依赖:

<dependency>
    <groupId>org.evrete</groupId>
    <artifactId>evrete-dsl-java</artifactId>
    <version>3.0.01</version>
</dependency>

5.2 规则集声明

用注解重写规则集:

public class SalesRuleset {

    @Rule
    public void rule1(Customer $c) {
        $c.setTotal(0.0);
    }

    @Rule
    @Where("$i.customer == $c")
    public void rule2(Customer $c, Invoice $i) {
        $c.addToTotal($i.getAmount());
    }
}

从外部源加载规则:

KnowledgeService service = new KnowledgeService();
URL rulesetUrl = new URL("ruleset.java"); // 或 file.toURI().toURL()
Knowledge knowledge = service.newKnowledge(
  "JAVA-SOURCE",
  rulesetUrl
);

踩坑提醒:

  • 源文件需包含所有必要 import
  • 方法参数名自动作为事实标识符(如 $c
  • 规则按方法名排序,rule1 必须在 rule2 之前执行

5.3 工作原理

会话创建时,引擎会:

  1. 为规则类创建新实例
  2. 将实例与会话绑定
  3. 使类变量在所有规则方法中共享

这意味着:

  • 类变量可作为规则间共享状态
  • 规则类可继承和复用
  • 可打包成独立规则库

5.4 高级特性

未在本文覆盖的重要功能:

  • 条件方法作为类成员
  • 动态属性声明
  • 会话生命周期监听器
  • 运行时环境访问
  • 类字段在条件/动作/定义中的跨域使用

6. 结论

通过实战测试,Evrette 展现了三大核心优势:

  1. 轻量级设计:零依赖,单 jar 即可运行
  2. 开发者友好
    • 支持流式 API 构建规则
    • 注解方式贴近 Java 开发习惯
  3. 强扩展性:通过 SPI 可定制 DSL

简单粗暴总结:

  • 如果需要开箱即用的规则管理工具,Drools 可能更合适
  • 如果想深度定制规则 DSL 或嵌入 Java 逻辑,Evrette 是更优选择

未演示但值得关注的特性:

  • 任意事实属性声明
  • Java 谓词作为条件
  • 规则动态修改
  • 冲突解决策略
  • 热加载规则

官方文档:https://www.evrete.org/docs/
示例代码:GitHub 仓库


原始标题:Introduction to the Evrete Rule Engine