首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

拒绝if-else!小而美的规则引擎 Easy Rules 真不错!

  • 25-04-24 15:01
  • 3915
  • 8142
blog.csdn.net

Easy Rules是一个简单而强大的Java规则引擎,提供以下功能:

  • 轻量级框架和易于学习的API

  • 基于POJO的开发与注解的编程模型

  • 定义抽象的业务规则并轻松应用它们

  • 支持从简单规则创建组合规则的能力

  • 支持使用表达式语言(如MVEL和SpEL)定义规则的能力

陈某之前也分享过其他的规则引擎,可以看之前文章:

  • 规则引擎深度对比,LiteFlow vs Drools!

  • 聊聊小而美的规则引擎 LiteFlow

  • Spring Boot + 规则引擎Drools

  • 这款轻量级 aviator 表达式引擎,真不错!

  • 干掉if..else!Spring Boot+aviator+aop 太丝滑了!

为何选择Easy Rules规则引擎

1. 传统if - else编程的困境

案例一:电商满减规则频繁变更(真实生产场景重现)

在电商业务的实际运营中,促销规则的频繁变更是一个常见的问题。假设某电商平台有如下促销规则:

  1. // 传统硬编码方式(噩梦般的代码片段)
  2. if(user.isVip()){  
  3.     if(order.getAmount() > 200){  
  4.         if(order.getItems().stream().anyMatch(i -> i.isPromotion())){  
  5.             order.applyDiscount(0.8); // 会员满200且含促销商品打8折  
  6.         }  
  7.     } else if(order.getCreateTime().isAfter(LocalDate.of(2023,11,1))){  
  8.         order.applyDiscount(0.9); // 双十一期间会员专属9折  
  9.     }  
  10. } else {  
  11.     // 普通用户规则嵌套层级更深...  
  12. }

这种传统的硬编码方式存在诸多痛点:

  • 维护困难:每当市场部调整规则时,开发者需要在大量的代码中艰难地寻找逻辑修改点,这不仅效率低下,还容易出错。

  • 发版风险高:发版频率极高,可能一个月需要进行6次规则修改和上线操作,每次上线都伴随着一定的风险,如代码冲突、功能异常等。

  • 协作问题:在多人协作开发时,由于代码结构复杂,很容易引发代码冲突,增加了开发和维护的难度。

案例二:物联网设备告警条件嵌套难题

在物联网设备监控系统中,复杂的告警条件嵌套也是一个常见的问题。某工厂设备监控系统需要进行如下判断:

  1. if(temperature > 50 || humidity > 80) {  
  2.     if(pressure < 100 && vibration > 5) {  
  3.         if(deviceStatus != Status.MAINTENANCE) {  
  4.             triggerAlarm(AlarmLevel.CRITICAL);  
  5.         }  
  6.     }  
  7. } else if (runtimeHours > 1000 && !isMaintained) {  
  8.     triggerAlarm(AlarmLevel.WARNING);  
  9. }  
  10. // 后续还有8个else if...

这种代码结构带来了以下问题:

  • 调试困难:在调试过程中,断点需要穿透10层条件判断,调试难度极大,耗费大量时间。

  • 扩展性差:当需要新增“电压波动 > 10%”这样的条件时,需要重构整个逻辑,开发成本高。

  • 知识传递困难:交接文档需要绘制3页流程图才能清晰说明规则逻辑,给知识传递带来了很大的困难。

可视化对比(代码量的显著优化)代码量对比

2. 轻量级规则引擎的优势

场景化演示:从复杂到简洁的转变
场景转变
场景转变
核心优势解析
  1. 解耦的智慧

  • 规则与业务分离:规则与业务代码实现物理隔离,可以将规则存储在独立文件或数据库中,使代码结构更加清晰。这样,业务代码专注于业务逻辑的处理,而规则代码则负责规则的定义和管理。

  • 动态加载规则:修改规则无需重新编译部署,支持动态加载规则。以下是一个动态加载规则的示例:

  1. public void refreshRules() {
  2.     List newRules = ruleLoader.loadFromDB(); // 从数据库读取最新规则
  3.     rulesEngine.fire(new Rules(newRules), facts);  
  4. }
  1. 可读性的提升

  • 自描述性规则:规则具有自描述性,每个规则都可以看作是一个独立的文档,便于理解和维护。开发者可以通过规则的名称、描述和条件等信息,快速了解规则的用途和逻辑。

  • 决策流程可视化:支持决策流程可视化,可以自动生成规则关系图。例如:

  1. [用户类型] --> [VIP规则] --> [折扣计算]  
  2.              \-> [普通用户规则] --> [满减计算]
  1. 扩展性的保障

  • 零侵入式扩展:新增规则对现有代码零侵入,只需添加新的Rule类即可。这使得系统的扩展性得到了极大的提升,开发者可以根据业务需求随时添加新的规则。

  • 多规则源支持:支持混合多种规则源,例如数据库、YAML文件和注解。以下是一个YAML规则文件的示例:

  1. # discount_rule.yml
  2. name: "老用户回馈规则"
  3. description: "注册超过3年的用户额外折扣"
  4. condition: "user.registerYears >= 3"
  5. actions:
  6.   - "order.applyAdditionalDiscount(0.95)"

定义规则

大多数业务规则可以由以下定义表示:

  • 名称:规则命名空间中的唯一规则名称

  • 说明:规则的简要说明

  • 优先级:相对于其他规则的规则优先级

  • 事实:去匹配规则时的一组已知事实

  • 条件:为了匹配该规则,在给定某些事实的情况下应满足的一组条件

  • 动作:当条件满足时要执行的一组动作(可以添加/删除/修改事实)

Easy Rules为定义业务规则的每个关键点提供了抽象。

在Easy Rules中,一个规则由Rule接口表示:

  1. public interface Rule {
  2.     /**
  3.     * 改方法封装规则的条件(conditions)
  4.     * @return 如果提供的事实适用于该规则返回true, 否则,返回false
  5.     */
  6.     boolean evaluate(Facts facts);
  7.     /**
  8.     * 改方法封装规则的操作(actions)
  9.     * @throws 如果在执行过程中发生错误将抛出Exception
  10.     */
  11.     void execute(Facts facts) throws Exception;
  12.     //Getters and setters for rule name, description and priority omitted.
  13. }

evaluate方法封装了必须求值为TRUE才能触发规则的条件。

execute方法封装了在满足规则条件时应执行的操作。条件和动作ConditionandAction接口表示。

规则可以用两种不同的方式定义:

  • 通过在POJO上添加注释,以声明方式定义

  • 通过RuleBuilder API,以编程方式定义

  1. 用注解定义规则

这些是定义规则的最常用方法,但如果需要,还可以实现Rulei接口或继承BasicRule类。

  1. @Rule(name = "my rule", description = "my rule description", priority = 1)
  2. publicclass MyRule {
  3.     @Condition
  4.     public boolean when(@Fact("fact") fact) {
  5.         //my rule conditions
  6.         returntrue;
  7.     }
  8.     @Action(order = 1)
  9.     public void then(Facts facts) throws Exception {
  10.         //my actions
  11.     }
  12.     @Action(order = 2)
  13.     public void finally() throws Exception {
  14.         //my final actions
  15.     }
  16. }

@Condition注解标记计算规则条件的方法。此方法必须是公共的,可以有一个或多个用@Fact注解的参数,并返回布尔类型。只有一个方法能用@Condition注解。

@Action注解标记要执行规则操作的方法。规则可以有多个操作。可以使用order属性按指定的顺序执行操作。默认情况下,操作的顺序为0。

2. 用RuleBuilder API定义规则

  1. Rule rule = new RuleBuilder()
  2.                 .name("myRule")
  3.                 .description("myRuleDescription")
  4.                 .priority(3)
  5.                 .when(condition)
  6.                 .then(action1)
  7.                 .then(action2)
  8.                 .build();

在这个例子中, Condition实例condition,Action实例是action1和action2。

定义事实

Facts API是一组事实的抽象,在这些事实上检查规则。在内部,Facts实例持有HashMap,这意味着:

  • 事实需要命名,应该有一个唯一的名称,且不能为空

  • 任何Java对象都可以充当事实

这里有一个实例定义事实:

  1. Facts facts = new Facts();
  2. facts.add("rain", true);

Facts 能够被注入规则条件,action 方法使用 @Fact 注解. 在下面的规则中,rain 事实被注入itRains方法的rain参数:

  1. @Rule
  2. class WeatherRule {
  3.     @Condition
  4.     public boolean itRains(@Fact("rain") boolean rain) {
  5.         return rain;
  6.     }
  7.     @Action
  8.     public void takeAnUmbrella(Facts facts) {
  9.         System.out.println("It rains, take an umbrella!");
  10.         // can add/remove/modify facts
  11.     }
  12. }

Facts类型参数 被注入已知的 facts中 (像action方法takeAnUmbrella一样).

如果缺少注入的fact, 这个引擎会抛出 RuntimeException异常.

定义规则引擎

从版本3.1开始,Easy Rules提供了RulesEngine接口的两种实现:

  • DefaultRulesEngine:根据规则的自然顺序(默认为优先级)应用规则。

  • InferenceRulesEngine:持续对已知事实应用规则,直到不再应用规则为止。

创建一个规则引擎

要创建规则引擎,可以使用每个实现的构造函数:

  1. RulesEngine rulesEngine = new DefaultRulesEngine();
  2. // or
  3. RulesEngine rulesEngine = new InferenceRulesEngine();

然后,您可以按以下方式触发注册规则:

rulesEngine.fire(rules, facts);

规则引擎参数

Easy Rules 引擎可以配置以下参数:

  • skipOnFirstAppliedRule:告诉引擎规则被触发时跳过后面的规则。

  • skipOnFirstFailedRule:告诉引擎在规则失败时跳过后面的规则。

  • skipOnFirstNonTriggeredRule:告诉引擎一个规则不会被触发跳过后面的规则。

  • rulePriorityThreshold:告诉引擎如果优先级超过定义的阈值,则跳过下一个规则。版本3.3已经不支持更改,默认MaxInt。

可以使用RulesEngineParameters API指定这些参数:

  1. RulesEngineParameters parameters = new RulesEngineParameters()
  2.     .rulePriorityThreshold(10)
  3.     .skipOnFirstAppliedRule(true)
  4.     .skipOnFirstFailedRule(true)
  5.     .skipOnFirstNonTriggeredRule(true);
  6. RulesEngine rulesEngine = new DefaultRulesEngine(parameters);

如果要从引擎获取参数,可以使用以下代码段:

RulesEngineParameters parameters = myEngine.getParameters();

这允许您在创建引擎后重置引擎参数。

5分钟极速入门(Hello World版)

1. 环境搭建(手把手教学)

为什么选择Maven依赖?Easy Rules的核心库仅有 217KB,不会造成项目臃肿。只需在pom.xml中添加:

  1.     org.jeasy
  2.     easy-rules-core
  3.     4.1.0

验证是否成功: 在IDE中新建RulesEngine engine = new DefaultRulesEngine();若无报错,则环境配置成功!

2. 第一个规则实战(带逐行解析)

场景背景: 假设我们正在开发智能家居系统,需要根据湿度传感器数据触发雨天提醒。

代码深度解读:

执行过程全解:

  1. public static void main(String[] args) {
  2.     // 模拟传感器数据(真实项目从MQTT获取)
  3.     Facts facts = new Facts();  // 事实对象(数据容器)
  4.     facts.put("humidity", 85);  // 放入湿度值
  5.     
  6.     // 创建规则引擎(核心控制器)
  7.     RulesEngine engine = new DefaultRulesEngine();
  8.     
  9.     // 装载规则并执行(点火!)
  10.     engine.fire(new Rules(new RainRule()), facts); 
  11.     
  12.     // 执行结果:
  13.     // 【智能家居】检测到湿度85%,建议关闭窗户带伞出门!
  14. }

3. 可视化规则执行流程(小白秒懂版)

完整执行链路图示:

关键点提醒:

  1. 一个Facts对象可承载多个数据:

    1. facts.put("temperature", 28);  
    2. facts.put("location", "上海");
  2. 多个规则会按优先级顺序执行(默认优先级=0)

  3. 使用

    @Priority

    注解调整执行顺序:

    @Rule(priority = 1) // 数字越大优先级越高

新手常见问题QA:规则没触发怎么办?

  • 检查@Fact名称是否与put时一致

  • 确认@Condition方法返回true

  • 添加日志打印调试:

    1. @Action
    2. public void remind() {
    3.     System.out.println("规则触发!"); // 先确认是否执行到此
    4. }

如何同时处理多个规则?

  1. // 一次性加载多个规则
  2. Rules rules = new Rules(new RainRule(), new TempRule(), new WindRule());
  3. engine.fire(rules, facts);

需要我展示如何扩展这个案例,比如增加温度规则形成组合条件吗?比如"湿度>80% 且 温度>30℃"触发高温高湿预警?

6大经典场景深度解析

场景1:电商促销系统(组合优惠精算)

案例3进阶实现:VIP折扣与满减叠加计算

避坑指南:

  • 使用@Priority控制执行顺序(数值越大越先执行)

  • 折扣计算需采用乘法叠加而非减法,避免出现0元订单

  • 在动作中增加日志记录,审计实际优惠金额

场景2:物联网报警系统(多级联动)

案例4优化版:带设备状态判断的三级报警

实战技巧:

  1. 设备维护状态作为独立Fact传递

  2. 优先处理高风险规则(priority=3)

  3. 动作中集成多种通知渠道(短信/邮件/看板)

场景3:会员等级系统(混合规则源)

案例5增强方案:YAML+注解混合使用

集成方法:

  1. // 加载YAML规则
  2. RulesLoader loader = new YamlRuleLoader();
  3. Rules yamlRules = loader.load(new File("promotion_rules.yml"));
  4. // 加载注解规则
  5. Rules annoRules = new Rules(new ShareRule());
  6. // 合并执行
  7. engine.fire(yamlRules, facts);
  8. engine.fire(annoRules, facts);

场景4:工单分配系统(动态派单)

案例6增强版:基于值班表的动态分配

  1. @Rule(name = "技术紧急工单")
  2. publicclass TechEmergencyRule {
  3.     @Condition
  4.     public boolean isTechEmergency(
  5.             @Fact("ticket") Ticket ticket,
  6.             @Fact("dutyTable") DutyTable table) {
  7.         return ticket.getType() == TECH 
  8.             && ticket.getPriority() == HIGH
  9.             && table.hasAvailableTechLead();
  10.     }
  11.     
  12.     @Action
  13.     public void assignToTechLead() {
  14.         String techLead = dutyTable.getCurrentTechLead();
  15.         ticket.setAssignee(techLead);
  16.         dutyTable.markBusy(techLead); // 标记为忙碌状态
  17.     }
  18. }

设计亮点:

  • 值班表作为独立Fact,实时反映工程师状态

  • 自动标记工程师忙碌状态,避免重复分配

  • 可扩展支持轮询、负载均衡等分配策略

场景5:风控预警系统(时序检测)

案例7优化版:时间窗口滑动检测

性能优化:

  • 使用@Fact注入预处理的时序数据

  • 采用BloomFilter快速过滤低风险设备

  • 异步执行风险处理动作

场景6:游戏战斗系统(状态管理)

案例8增强版:连招技能状态机

  1. @Rule(name = "龙卷风连击")
  2. publicclass TornadoComboRule {
  3.     @Condition
  4.     public boolean checkComboSequence(
  5.             @Fact("queue") CircularFifoQueue queue) {
  6.         return queue.size() >=3
  7.             && queue.get(0) == Skill.A
  8.             && queue.get(1) == Skill.B
  9.             && queue.get(2) == Skill.C;
  10.     }
  11.     
  12.     @Action
  13.     public void releaseSuperSkill() {
  14.         player.cast(Skill.SUPER_TORNADO);
  15.         queue.clear(); // 清空连招队列
  16.         effectPlayer.play("combo_success.wav");
  17.     }
  18. }

注意事项:

  • 使用Apache Commons的CircularFifoQueue控制队列长度

  • 动作中重置状态避免重复触发

  • 集成音效/特效等游戏元素

架构师扩展包:

  1. 规则模板技术:

    1. public abstract class BasePromotionRule implements Rule {
    2.     @Condition
    3.     public abstract boolean matchCondition(Order order);
    4.     
    5.     @Action
    6.     public void applyDiscountTemplate(@Fact("order") Order order) {
    7.         order.applyDiscount(getDiscountRate());
    8.         log.info("应用{}折扣", getRuleName());
    9.     }
    10.     
    11.     protected abstract double getDiscountRate();
    12. }
  2. 规则性能监控:

    1. engine.registerRuleListener(new RuleListener() {
    2.     public void beforeExecute(Rule rule, Facts facts) {
    3.         Monitor.startTimer(rule.getName());
    4.     }
    5.     
    6.     public void afterExecute(Rule rule, Facts facts) {
    7.         long cost = Monitor.stopTimer(rule.getName());
    8.         if(cost > 100) {
    9.             alertSlowRule(rule.getName(), cost);
    10.         }
    11.     }
    12. });

Spring Boot集成

配置自动加载:

  1. @Configuration  
  2. publicclass RuleEngineConfig {  
  3.     @Bean
  4.     public RulesEngine rulesEngine() {  
  5.         returnnew DefaultRulesEngine(  
  6.             new Parameters()  
  7.                 .skipOnFirstNonTriggeredRule(true)  
  8.                 .priorityThreshold(10)  
  9.         );  
  10.     }  
  11.     @Bean
  12.     public Rules ruleRegistry() throws IOException {  
  13.         // 自动扫描带@Rule注解的Bean  
  14.         returnnew Rules(  
  15.             new AnnotationRuleFactory().create(  
  16.                 new ClasspathRuleDefinitionReader(),  
  17.                 new ClassPathResource("rules/").getFile()  
  18.             )  
  19.         );  
  20.     }  
  21.     @Bean
  22.     public ApplicationRunner ruleInitializer(RulesEngine engine, Rules rules) {  
  23.         return args -> {  
  24.             // 启动时预加载验证规则  
  25.             engine.fire(rules, new Facts());  
  26.             logger.info("已成功加载{}条规则", rules.size());  
  27.         };  
  28.     }  
  29. }

在controller中测试:

  1. @RestController  
  2. publicclass PromotionController {  
  3.     @Autowired
  4.     private RulesEngine rulesEngine;  
  5.     @Autowired
  6.     private Rules rules;  
  7.     @PostMapping("/apply-rules")  
  8.     public Order applyRules(@RequestBody Order order) {  
  9.         Facts facts = new Facts();  
  10.         facts.put("order", order);  
  11.         rulesEngine.fire(rules, facts);  
  12.         return order;  
  13.     }  
  14. }

在生产中我们还可以将规则配置设置为热更新,以@RefreshScope + Spring Cloud Config的方式,这样在配置更新时会自动加载。

总结

Easy Rules 非常适合需要快速实现业务规则引擎的场景。对于中小型项目,Easy Rules 的简单性和灵活性是一大优势。

如果项目规则复杂或者性能要求较高,可以考虑结合 Drools 等更强大的规则引擎使用。

企业级实战总结40讲

推荐一下陈某新出的小册子总结了企业中后端的各种核心问题解决方案,包括JVM、数据库、性能调优等企业级落地40个痛点问题以及解决方案....

原价99,今日优惠价格11.9永久买断!目前已经全部更新完,大家可以扫描下方二维码在线订阅!

文章目录可以扫码进入查看

图片
注:本文转载自blog.csdn.net的不才陈某的文章"https://blog.csdn.net/qq_34162294/article/details/146968181"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

132
搜索
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top