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,且允许将任意类属性声明为函数式接口
业务逻辑需要两条规则:
- 清零每个客户的销售额
- 匹配发票与客户,累加销售额
接下来我们分别用规则构建器 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 工作原理
会话创建时,引擎会:
- 为规则类创建新实例
- 将实例与会话绑定
- 使类变量在所有规则方法中共享
这意味着:
- 类变量可作为规则间共享状态
- 规则类可继承和复用
- 可打包成独立规则库
5.4 高级特性
未在本文覆盖的重要功能:
- 条件方法作为类成员
- 动态属性声明
- 会话生命周期监听器
- 运行时环境访问
- 类字段在条件/动作/定义中的跨域使用
6. 结论
通过实战测试,Evrette 展现了三大核心优势:
- 轻量级设计:零依赖,单 jar 即可运行
- 开发者友好:
- 支持流式 API 构建规则
- 注解方式贴近 Java 开发习惯
- 强扩展性:通过 SPI 可定制 DSL
简单粗暴总结:
- 如果需要开箱即用的规则管理工具,Drools 可能更合适
- 如果想深度定制规则 DSL 或嵌入 Java 逻辑,Evrette 是更优选择
未演示但值得关注的特性:
- 任意事实属性声明
- Java 谓词作为条件
- 规则动态修改
- 冲突解决策略
- 热加载规则
官方文档:https://www.evrete.org/docs/
示例代码:GitHub 仓库