前言
最近在做代码Review的时候,发现了一个非常普遍的问题——一个业务方法里,if...else叠了七八层,代码行数直接飙到300多行。
业务规则的复杂度,天然就是不断增长的。
今天加一个规则,明天改一个规则,后天删一个规则——用if...else硬编码,就是在给自己挖坑。
那有没有什么办法,能把业务规则从代码里抽离出来,让代码变得干净、可维护、可动态调整?
有。
今天我要介绍的这个工具,就是专门干这个事的——Easy Rules。
希望对你会有所帮助。
更多项目实战在我的技术网站:susan.net.cn/project
一、先看看if...else的“七宗罪”
在正式介绍Easy Rules之前,我们先看一个典型的“if...else地狱”案例:
java
代码解读
复制代码
public class DiscountService { public double calculateDiscount(User user, Order order) { double discount = 0.0; // 规则1:VIP用户打9折 if (user.isVip()) { discount = 0.9; } // 规则2:订单金额超过1000,再打95折 if (order.getAmount() > 1000) { discount = discount * 0.95; } // 规则3:新用户首单打8折 if (user.isNewUser() && user.getOrderCount() == 0) { discount = 0.8; } // 规则4:双11期间全场9折 if (isDoubleEleven()) { discount = discount * 0.9; } // 规则5:老用户且订单金额超过5000,打85折 if (!user.isNewUser() && order.getAmount() > 5000) { discount = 0.85; } // 规则6:规则7:规则8... // 越加越多,代码越来越长 return discount; } }
这段代码有什么问题?
第一,可读性差。规则越多,方法越长,谁接手谁想骂人。
第二,可维护性差。改一个规则,要找到对应的if分支,小心翼翼地改,生怕影响到别的逻辑。
第三,可测试性差。要覆盖所有规则组合的测试用例,数量呈指数级增长。
第四,扩展性差。每加一个新规则,就要修改源代码、重新编译、重新部署。
第五,业务和代码耦合。业务人员想调整规则,得求着开发改代码。
第六,重复逻辑遍地都是。同样的条件判断,可能在好几个地方出现。
第七,修改后必须重启服务。线上规则调整零活度几乎为零。
有些小伙伴可能会说:“这些规则我写个配置表,从数据库里查不就行了?”没错,配置化确实能解决一部分问题。但真正的规则引擎,不只是“把规则存到数据库里”——它还要解决规则的组合、优先级、条件评估、自动执行等一系列问题。把if...else搬到数据库里,if...else还是if...else,只是换了个地方而已。
那怎么办?
规则引擎就是干这个的。
二、Easy Rules是什么?
Easy Rules是一个简单而强大的Java规则引擎,由j-easy团队开源维护。
它的设计灵感来源于Martin Fowler提出的“规则引擎”概念。
Martin Fowler在一篇非常经典的文章里说过:
“你可以自己构建一个简单的规则引擎。你只需要创建一组具有条件和操作的对象,将它们存储在一个集合中,并运行它们来评估条件并执行操作。”
Easy Rules做的就是这件事——它提供了Rule抽象来创建带有条件和操作的规则,以及RulesEngineAPI来运行一系列规则。
2.1 一句话说清Easy Rules
Easy Rules就是一个“把if...else从代码里搬出来、用规则对象来管理”的工具。
它让你可以这样定义规则:
java
代码解读
复制代码
@Rule(name = "VIP折扣规则", priority = 1) public class VipDiscountRule { @Condition public boolean isVip(@Fact("user") User user) { return user.isVip(); } @Action public void applyDiscount(@Fact("order") Order order) { order.setDiscount(0.9); } }
然后这样执行:
java
代码解读
复制代码
RulesEngine engine = new DefaultRulesEngine(); engine.fire(rules, facts);
是不是清爽多了?
2.2 核心特点
Easy Rules有以下几个核心特点:
- 轻量级:没有复杂的依赖和配置,JAR包仅100KB左右
- POJO开发:用普通的Java类加注解就能定义规则
- 多种定义方式:支持注解、流式API、表达式语言(MVEL/SpEL/JEXL)、YAML/JSON配置
- 复合规则:支持将多个简单规则组合成复杂规则
- 易于集成:可以轻松集成到Spring Boot等框架中
三、核心概念
Easy Rules围绕四个核心抽象构建:
3.1 Rule(规则)
Rule接口是Easy Rules最核心的抽象。一个规则包含:
- name:规则的唯一名称
- description:规则的简要说明
- priority:规则的优先级(数字越小优先级越高)
- condition:条件——返回
true时触发规则 - action:动作——条件满足时执行的操作
java
代码解读
复制代码
public interface Rule { boolean evaluate(Facts facts); // 评估条件 void execute(Facts facts) throws Exception; // 执行动作 // getters for name, description, priority... }
3.2 Facts(事实)
Facts是规则执行时的数据上下文,本质是一个键值对容器。你把数据放进去,规则从中取数据:
java
代码解读
复制代码
Facts facts = new Facts(); facts.put("user", user); facts.put("order", order); facts.put("rain", true);
3.3 Rules(规则集合)
Rules是规则的有序容器,负责管理一组规则。规则按照priority自动排序:
java
代码解读
复制代码
Rules rules = new Rules(); rules.register(new VipDiscountRule()); rules.register(new NewUserDiscountRule()); rules.register(new DoubleElevenRule());
3.4 RulesEngine(规则引擎)
RulesEngine是执行规则的核心引擎。Easy Rules提供了两种实现:
| 引擎类型 | 执行策略 | 适用场景 |
|---|---|---|
| DefaultRulesEngine | 按优先级顺序执行,条件满足就执行 | 大多数常规场景 |
| InferenceRulesEngine | 前向链推理,反复执行直到没有规则可触发 | 规则之间存在依赖和连锁反应 |
java
代码解读
复制代码
// 默认引擎 RulesEngine engine = new DefaultRulesEngine(); engine.fire(rules, facts);
四、四种规则定义方式
Easy Rules提供了四种定义规则的方式,你可以根据实际场景灵活选择。
4.1 方式一:注解方式(最常用)
适用场景:规则固定、逻辑清晰、大部分常规业务场景。
用@Rule、@Condition、@Action、@Fact注解来定义规则:
java
代码解读
复制代码
@Rule(name = "天气规则", description = "如果下雨就带伞", priority = 1) public class WeatherRule { @Condition public boolean isRaining(@Fact("rain") boolean rain) { return rain; } @Action public void takeUmbrella() { System.out.println("下雨了,记得带伞!"); } }
注解说明:
@Rule:标记类为规则,可指定name、description和priority@Condition:标记条件方法,必须返回boolean,只能有一个@Action:标记动作方法,可以有多个,通过order指定执行顺序@Fact:标记参数,从Facts容器中按名称取值
执行代码:
java
代码解读
复制代码
public class Application { public static void main(String[] args) { // 1. 准备事实 Facts facts = new Facts(); facts.put("rain", true); // 2. 注册规则 Rules rules = new Rules(); rules.register(new WeatherRule()); // 3. 执行引擎 RulesEngine engine = new DefaultRulesEngine(); engine.fire(rules, facts); // 输出:下雨了,记得带伞! } }
4.2 方式二:流式API(动态规则)
适用场景:规则需要动态生成、规则条件在运行时才能确定。
使用RuleBuilder流式API构建规则:
java
代码解读
复制代码
Rule dynamicRule = new RuleBuilder() .name("高温预警规则") .description("温度超过30度时提醒防暑") .priority(2) .when(facts -> facts.get("temperature") > 30) .then(facts -> System.out.println("天气炎热,注意防暑!")) .build();
when()方法接收一个Predicate<Facts>,then()方法接收一个Consumer<Facts>。这种方式非常适合运行时动态生成规则的场景。
4.3 方式三:表达式语言(最灵活)
适用场景:规则频繁变更、希望非开发人员也能修改规则。
Easy Rules支持MVEL、SpEL、JEXL三种表达式语言:
java
代码解读
复制代码
// 使用MVEL表达式 Rule mvelRule = new MVELRule() .name("会员折扣规则") .description("会员且订单金额大于100享受9折") .when("user.vip == true && order.amount > 100") .then("order.discount = 0.9");
表达式语言最大的优势是:规则可以以字符串形式存储(数据库、配置文件、管理后台),修改规则不需要重新编译和部署。
4.4 方式四:YAML/JSON配置(配置化)
适用场景:希望把规则完全抽离到配置文件、由业务人员维护。
通过YAML或JSON文件定义规则:
yaml
代码解读
复制代码
# rules.yml - name: "新用户首单折扣" description: "新用户第一笔订单打8折" priority: 3 condition: "user.newUser == true && user.orderCount == 0" actions: - "order.discount = 0.8"
通过MVELRuleFactory或SpELRuleFactory加载配置文件:
java
代码解读
复制代码
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader()); Rule rule = ruleFactory.createRule(new FileReader("rules.yml"));
五、底层原理
5.1 整体架构
Easy Rules的架构可以分为四个层次:
5.2 DefaultRulesEngine:执行流程
DefaultRulesEngine是最常用的引擎实现,它的执行流程如下:
5.3 InferenceRulesEngine:前向链推理
InferenceRulesEngine实现了前向链推理算法。它的核心逻辑是:
- 进入推理循环
- 在当前
Facts下,找出所有条件为true的规则(候选规则) - 如果没有候选规则,退出循环
- 否则,执行所有候选规则(可能修改
Facts) - 回到步骤2,继续循环
这种模式特别适合规则之间存在依赖关系的场景——一个规则的执行结果可能成为另一个规则的触发条件。
5.4 引擎参数配置
可以通过RulesEngineParameters精细控制引擎行为:
java
代码解读
复制代码
RulesEngineParameters parameters = new RulesEngineParameters() .skipOnFirstAppliedRule(true) // 第一个规则执行后跳过后续 .skipOnFirstFailedRule(true) // 第一个规则失败后跳过后续 .skipOnFirstNonTriggeredRule(false) // 第一个规则未触发时是否跳过 .priorityThreshold(10); // 只执行优先级<=10的规则 RulesEngine engine = new DefaultRulesEngine(parameters);
5.5 监听器机制
Easy Rules提供了监听器机制,可以在规则执行的各个阶段插入自定义逻辑:
java
代码解读
复制代码
public class LoggingRuleListener implements RuleListener { @Override public boolean beforeEvaluate(Rule rule, Facts facts) { System.out.println("开始评估规则:" + rule.getName()); return true; // 返回false可跳过该规则 } @Override public void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult) { System.out.println("规则 " + rule.getName() + " 评估结果:" + evaluationResult); } @Override public void onSuccess(Rule rule, Facts facts) { System.out.println("规则 " + rule.getName() + " 执行成功"); } @Override public void onFailure(Rule rule, Facts facts, Exception exception) { System.err.println("规则 " + rule.getName() + " 执行失败:" + exception.getMessage()); } }
监听器可以用于日志记录、性能监控、统计规则命中率、调试等多种场景。
六、复合规则
有些小伙伴可能会问:单个规则只能处理一个条件,那复杂的业务逻辑怎么办?
Easy Rules提供了复合规则机制,可以把多个简单规则组合成复杂的规则。它提供了三种复合规则类型:
6.1 UnitRuleGroup(与逻辑)
“所有规则都满足,才执行”
使用场景:需要多个前置条件全部满足才能执行某个操作。比如“用户是VIP且订单金额超过1000且商品有库存”,三个条件缺一不可。
java
代码解读
复制代码
UnitRuleGroup unitGroup = new UnitRuleGroup("全部满足规则组"); unitGroup.addRule(new VipRule()); unitGroup.addRule(new AmountRule()); unitGroup.addRule(new StockRule()); // 只有三个规则全部满足,group才会执行
6.2 ActivationRuleGroup
“只要有一个满足,就执行第一个”
使用场景:多个互斥的折扣规则,只能选一个生效。比如“新用户首单8折”和“VIP用户9折”同时满足时,只执行优先级高的那个。
java
代码解读
复制代码
ActivationRuleGroup activationGroup = new ActivationRuleGroup("折扣规则组"); activationGroup.addRule(new NewUserDiscountRule()); // priority=3 activationGroup.addRule(new VipDiscountRule()); // priority=1 // 只会执行优先级最高的那个(VIP折扣)
6.3 ConditionalRuleGroup(条件逻辑)
“第一个满足的规则决定是否执行后续规则”
使用场景:根据第一个规则的结果决定是否执行后续规则链。
java
代码解读
复制代码
ConditionalRuleGroup conditionalGroup = new ConditionalRuleGroup("条件规则组"); conditionalGroup.addRule(new CheckUserTypeRule()); // 第一个规则:判断用户类型 conditionalGroup.addRule(new VipDiscountRule()); // 后续规则:根据类型执行 conditionalGroup.addRule(new NormalDiscountRule()); // 只有第一个规则满足,才会继续执行后续规则