规则引擎复合变量设计实战:从Drools到Easy Rules的决策入参最佳实践

发布时间:2026/9/8 5:42:48

规则引擎复合变量设计实战:从Drools到Easy Rules的决策入参最佳实践
“这if-else怎么又炸了”上周四晚上十一点我在工位上盯着一条报错日志后台服务因为一个新增的支付渠道类型没有覆盖到硬生生抛了个空指针。根因很简单判断逻辑写死在代码里枚举一变代码就得改、发版、重启、再祈祷。同事在旁边补了一刀“早说让你上规则引擎你不听。”那一刻我确实有点后悔——如果当初把这块决策逻辑挪到规则引擎里用复合变量作为决策调用的入参根本没这么多事。这其实就是我现在特别想跟你聊的话题规则引擎的复合变量。很多人听说过Drools、Easy Rules也知道规则引擎能把业务决策从代码里抽出来但真到自己动手做的时候往往卡在同一个地方——怎么把一段复杂的、多层嵌套的、带状态的入参优雅地塞进规则里去参与决策。直接拼字符串太丑。硬编码在规则文件里又回到了原点。用复合变量很多人第一步就走错了。这篇文章我不讲虚的直接把我踩过的坑、验证过的方案、以及一套可以直接落地的复合变量设计思路完整拆开给你看。1. 硬编码的锅到底该谁来背1.1 硬编码在决策逻辑里的真实面貌先别急着骂硬编码。很多时候我们把“硬编码”等同于代码里的魔法数字、魔法字符串但真正的隐患藏得更深它藏在业务规则里。举个例子你写了一个订单折扣接口原价1000块满500打9折满1000打8折钻石会员额外再减50。你可能写得很“优雅”if (order.getAmount() 1000) { discount 0.8; } else if (order.getAmount() 500) { discount 0.9; } if (user.isVip()) { discount - 50; }过了一个月产品说“满300减30”的活动要上了你怎么办改代码。再过了半个月产品说要针对不同品类给不同折扣率你又改代码。改代码本身不是问题问题在于每改一次都可能引入新问题而且决策逻辑和业务代码彻底耦合在了一起。改的人如果是当初写这套逻辑的人还好如果换了个新人光是要搞明白“为什么满500是9折而不是8.5折”就得翻半天历史记录。这就是硬编码在决策逻辑里的真实面貌——它不是不优雅而是不稳定、不透明、不易维护。每次需求变更都是一次技术债的复利。1.2 规则引擎为什么能破这个局规则引擎的核心思路是把“怎么做决策”这件事从代码里抽出来放到一个可以独立维护、独立更新、甚至业务人员也能看懂的规则文件里。决策的“触发条件”和“执行动作”变成了一组可以动态加载的规则代码只负责提供数据、调用规则、接收结果。比较有代表性的方案有两类重量级方案DroolsKIE生态规则文件是.drl支持复杂的If-Then逻辑、评分、决策表、事件处理。轻量级方案Easy Rules、Aviator、QLExpress适合规则不算特别庞大、希望轻耦合的场景。这两种我都用过。Drools功能很强但学习曲线陡Easy Rules上手快适合中小团队快速落地。无论选哪种都会碰到同一个关键角色——入参。也就是“规则引擎到底基于什么数据去做判断”。如果入参是零散的字符串、基本类型规则也能跑但可维护性很差真正合适的方式是把一组关联的、结构化的数据封装成复合变量作为一个整体传入让规则引擎在内部自由地访问、组合、计算。1.3 复合变量在决策链路中扮演的角色我习惯把复合变量理解为“决策上下文”。它不是一个简单的字符串而是一个对象、一个集合、或者一层嵌套的结构。Drools里有Fact(事实对象)、Global(全局变量)Easy Rules里有Facts——本质上都是复合变量的不同表现形态。为什么必须是“复合”的因为业务决策几乎从来不是只看一个字段。比如风控场景既要看用户等级、又要看历史订单、还得看本次行为特征营销场景要看用户画像、库存状态、渠道来源、时间窗口。你把这一堆参数拆散了当零散入参传进去规则条件想引用都得查字典等于从一个坑跳进另一个坑。而复合变量把这些数据绑定成一个语义完整的整体规则引擎在匹配时可以直接基于对象属性做判断代码只负责构造和传入这个整体。2. 复合变量的设计决定了规则引擎好不好用2.1 一个复合变量应该包含什么设计复合变量的第一步是想清楚“一次决策调用需要什么”。我一般从三个维度去盘点主体数据决策的目标对象。比如订单决策主体就是订单对象风控决策主体就是用户本次请求。上下文数据决策发生的环境。比如当前时间、渠道、地区、活动ID这些不是主角但会影响决策分支。辅助数据决策需要参考的历史、画像、配置信息。比如用户三个月内的消费总额、库存可用量、会员等级。把这三类数据装进一个对象这个对象就是复合变量。Drools里它通常就是一个个Fact对象你可以定义Order,UserProfile,RiskContext等POJO然后通过KIE Session把它们的实例一并插入。Easy Rules里它则是Facts中的多个key-valuevalue可以是任何对象。这里有一个容易踩的坑不要为了复合而复合。如果把所有数据一股脑塞进一个大Map规则里写着map.get(user)这种代码那跟硬编码有什么区别复合变量的价值在于“结构化”而不是“打包”。每个字段都应该是规则条件里可能用到的而不是把所有能拿到的数据都塞进去。2.2 入参和规则条件的“映射关系”怎么理清设计复合变量时我心里通常揣着一张“映射表”前端传入什么 → 服务层处理成什么 → 生成什么样的复合变量 → 规则里引用什么字段。这张表不用写成正式文档但自己心里必须清楚。不然经常会出现一种尴尬情况规则文件里写的是$order.amount 500但代码里构造的复合变量属性叫orderTotalPrice怎么都对不上排查半天发现是命名不统一。我建议的命名规矩是代码层和规则层共用同一个POJO字段名保持一致注释写清楚业务含义。这样复合变量既是代码里的对象也是规则里的Fact不需要做二次映射。Drools里尤其明显你用的是Java反射直接访问Fact属性名字对不上就是编译不过或者匹配不上。2.3 用类组装而不是用Map硬凑很多第一次接触规则引擎的同事为了图省事直接把入参包装成一个MapString, Object然后往Facts里put。系统能跑但等到规则复杂起来问题就来了Map的key拼写错误编译期发现不了运行期静默失败类型不安全取出来全是Object还得强转IDE的自动补全和代码检查全部失效重构时字段改名规则里引用的key也要跟着改容易漏。相比之下定义明确的POJO作为复合变量配合Lombok、Bean Validation这些工具代码可维护性会高一个量级。事实上我在项目里还会给复合变量单独建一个包比如com.example.rule.facts里面每个类都对应一类决策场景。时间长了这些类本身就是很好的业务知识沉淀。3. 实操Drools复合变量从定义到调用3.1 先搭一个可跑的Drools基础环境Drools的引入方式不复杂Maven项目里加上依赖即可。要注意版本选择不同版本的API有差异我用的是7.73.0.Final相对稳定。dependency groupIdorg.kie/groupId artifactIdkie-api/artifactId version7.73.0.Final/version /dependency dependency groupIdorg.drools/groupId artifactIddrools-core/artifactId version7.73.0.Final/version /dependency dependency groupIdorg.drools/groupId artifactIddrools-compiler/artifactId version7.73.0.Final/version /dependency dependency groupIdorg.drools/groupId artifactIddrools-mvel/artifactId version7.73.0.Final/version /dependency通常我还会加一个drools-decisiontables依赖因为后续可能用到决策表但基础环境这一步不是必须的先不加也行。依赖配上之后最好写一个最小可运行示例验证环境没问题再继续往下做基本功要扎实。3.2 定义作为复合变量的Fact对象下面是我在订单折扣场景中验证过的一个复合变量设计。注意这里我把“订单”和“用户”分成了两个Fact因为规则里可能需要独立引用各自属性分拆开更灵活Data Builder NoArgsConstructor AllArgsConstructor public class OrderFact { private String orderId; private BigDecimal amount; private String channel; // 渠道APP、PC、H5 private String category; // 品类electronics、clothing、books private LocalDateTime createTime; }Data Builder NoArgsConstructor AllArgsConstructor public class UserFact { private String userId; private int memberLevel; // 1普通 2银卡 3金卡 4钻石 private boolean newUser; // 是否新客 private int orderCountLast90Days; // 近90天下单数 }这两个类就是决策调用的复合变量。注意字段尽量用业务上能理解的命名并且补上注释因为规则文件里会直接引用它们。注释写得好后面写DRL文件时能省很多事。3.3 DRL规则文件与复合变量的绑定在Drools里规则文件是核心。我用src/main/resources/rules/order-discount.drl作为规则文件路径通过KIE容器加载。一个能直接用的DRL长这样package com.example.order; import com.example.rule.facts.OrderFact; import com.example.rule.facts.UserFact; import com.example.rule.result.DiscountResult; rule 普通订单满500打9折 when $order: OrderFact(amount 500, amount 1000) then DiscountResult result new DiscountResult(); result.setDiscountRate(new BigDecimal(0.9)); result.setReason(满500打9折); insert(result); end rule 高额订单满1000打8折 salience 10 when $order: OrderFact(amount 1000) then DiscountResult result new DiscountResult(); result.setDiscountRate(new BigDecimal(0.8)); result.setReason(满1000打8折); insert(result); end rule 钻石会员叠加优惠50元 when $user: UserFact(memberLevel 4) $order: OrderFact(amount 100) then DiscountResult result new DiscountResult(); result.setSubtractAmount(new BigDecimal(50)); result.setReason(钻石会员满100减50); insert(result); end这里的复合变量OrderFact和UserFact作为Fact插入工作内存后规则就可以直接用属性约束去匹配。salience是优先级数字越大越先执行避免规则执行顺序不可控。钻石会员那条规则我显式加了amount 100的约束复合变量之间用“逗号”分隔表示所有条件同时满足。这里想补充一个实操心得规则之间要避免条件重叠导致的连锁干扰。比如“满500打9折”和“满1000打8折”如果订单金额为1500两条规则会先后触发但是后面那条结果会把前面那条覆盖掉。控制优先级、或者在结果对象里做合并是演练阶段就要想清楚的事。3.4 在Java代码里构造复合变量并调用决策有了Fact和DRL接下来就是通过KIE API加载规则并插入Fact触发决策。参考代码如下这段我实测过可以直接抄Slf4j Component public class OrderRuleService { private StatelessKieSession statelessKieSession; public OrderRuleService() { KieServices kieServices KieServices.Factory.get(); KieFileSystem kfs kieServices.newKieFileSystem(); // 注意生产上建议通过KieScanner或容器动态加载这里从classpath加载dependencies kfs.write(ResourceFactory.newClassPathResource(rules/order-discount.drl)); KieBuilder kieBuilder kieServices.newKieBuilder(kfs).buildAll(); KieModule kieModule kieBuilder.getKieModule(); KieContainer kieContainer kieServices.newKieContainer(kieModule.getReleaseId()); this.statelessKieSession kieContainer.newStatelessKieSession(); } public DiscountResult evaluate(OrderFact order, UserFact user) { DiscountResult result new DiscountResult(); // 结果对象也需要插入工作内存供规则写入 statelessKieSession.setGlobal(discountResult, result); statelessKieSession.execute(Arrays.asList(order, user)); return result; } }注意上面的代码我偷懒用了Global去收集结果但在更规范的设计里我会让DiscountResult本身也作为一个Fact insert进去或者直接给OrderFact增加一个可变字段去带回结果。两种方式各有优劣后面我会单独讲。Drools这里有一个非常容易踩的坑KieContainer的创建很重一定不要每次调用时候新建而是做成单例或者Spring Bean。我见过有项目因为每次请求都新建KieContainer结果压测时直接内存溢出。构造这批对象耗时很长复用一个实例就好。3.5 复合变量之间的联合条件匹配上面例子中钻石会员那条规则同时匹配了UserFact和OrderFact这就是复合变量的最大优势多个维度组合决策。实际场景更复杂一些比如rule 新客且近90天有3单以上打85折 when $user: UserFact(newUser true, orderCountLast90Days 3) $order: OrderFact(amount 200) then DiscountResult result new DiscountResult(); result.setDiscountRate(new BigDecimal(0.85)); result.setReason(新客高活跃85折); insert(result); end只要OrderFact和UserFact同时在工作内存里规则就可以跨对象做关联匹配。注意Drools的匹配是基于工作内存里所有Fact的笛卡尔积所以如果你的复合变量里嵌套了大量集合属性要用exists、not、collect这些关键字去优化避免无谓的匹配膨胀这块后面我会在性能部分详细说。3.6 有状态会话与无状态会话怎么选Drools里KieSession有状态和StatelessKieSession无状态差别很大。简单说无状态会话适合一次插入、一次执行、拿结果比如订单折扣计算有状态会话适合多次插入、持续推理、工作内存保留的状态场景比如复杂的风控流程。我个人的经验是大部分业务决策场景优先选无状态会话。它天然隔离了并发之间的数据冲突不用手动 dispose坑少。如果你的场景确实需要多次规则触发后的状态累积再考虑有状态并务必在每次请求结束时调用session.dispose()释放资源。4. 轻量级方案Easy Rules的复合变量实践4.1 为什么选Easy RulesDrools功能强但引入成本高。如果你只是几个简单的业务规则不想引入一堆KIE依赖也不想写DRL那Easy Rules值得一试。它把规则定义成Java注解或Properties文件通过RulesEngine去执行尤其适合规则数量少、团队对Drools不熟悉的项目。我的一个辅助系统就是用Easy Rules做的——需要根据用户提交的工单类型、紧急程度、当前值班组自动分配处理人。这种决策不复杂但放代码里也是一堆if-else用Easy Rules刚好。4.2 FactsEasy Rules版的复合变量Easy Rules里的Facts是一个Map但它的value可以是任何对象所以复合变量的设计思路依然适用。你可以把多个对象放进去用key区分比如Facts facts new Facts(); facts.put(order, order); facts.put(user, user); facts.put(riskContext, riskContext);然后规则里通过Fact(order)获取你定义的复合变量Rule(name 高风险订单标记) public class HighRiskOrderRule { Condition public boolean isHighRisk(Fact(user) UserFact user, Fact(order) OrderFact order) { return user.getMemberLevel() 1 order.getAmount() 10000; } Action public void markOrder(Fact(order) OrderFact order) { order.setHighRisk(true); } }你会发现虽然API风格和Drools完全不同但“复合变量”的思路是一致的用结构化对象承载决策上下文规则通过类型或key引用它。Easy Rules有两点比Drools更轻规则就是普通的Java类写起来没有新语法团队上手快引擎本身很小调试和执行路径都很清晰。4.3 复合变量在Easy Rules里的连锁判断Easy Rules也支持规则优先级、组合条件。你可以通过RulesEngineParameters设置规则跳过或终止条件多个规则也能共享同一个复合变量。例如我要实现“新用户且高金额订单进入人工审核否则自动通过”Rule(name 高金额新客人工审核, order 1) public class ManualReviewRule { Condition public boolean when(Fact(user) UserFact user, Fact(order) OrderFact order) { return user.isNewUser() order.getAmount().compareTo(new BigDecimal(5000)) 0; } Action public void then(Fact(order) OrderFact order) { order.setReviewStatus(MANUAL); } } Rule(name 默认自动通过, order 2) public class AutoPassRule { Condition public boolean when(Fact(order) OrderFact order) { return !MANUAL.equals(order.getReviewStatus()); } Action public void then(Fact(order) OrderFact order) { order.setReviewStatus(AUTO_PASS); } }这里order对象本身承担了两个职责既是决策的入参复合变量也是决策结果的承载者。这其实是一种相当实用的模式——省去了单独定义结果对象的繁琐但前提是你的字段上容忍这类“决策中间状态”写进对象。如果你的团队规范禁止这种侵入式写法可以给订单单独加一个processStatus字段来存放结果或者定义ReviewResult放到Facts里。5. 入参构造的工程化细节与性能考量5.1 外面传进来的数据先清洗再进复合变量规则引擎不是银弹入参的“脏数据”它一样处理不了。我的经验是在构造复合变量之前先做一轮数据清洗与校验必要字段是否为null比如金额、用户ID金额是否负数、日期是否合法字段是否需要默认值比如渠道为空时默认UNKNOWN敏感字段是否脱敏虽然规则引擎是内部系统但日志可能打出来。这一层我一般放在Service层而不是塞进规则里。规则专注“决策”数据准备交给代码。如果规则里到处写空值判断那规则文件就退化成代码了维护性反而下降。5.2 复合变量里的大集合怎么避免性能瓶颈Drools在处理复合变量时如果将一个大List作为Fact属性规则里用contains或者exists去匹配请谨慎。工作内存会对Fact属性和规则模式做笛卡尔积匹配当集合特别大时匹配成本不可忽视。举个例子一个OrderFact里挂了ListString tags规则写rule 包含秒杀标签的订单走特殊流程 when $order: OrderFact(tags contains seckill) then ... end如果tags只有几个元素问题不大如果tags有上千个每次匹配都会遍历。我的建议是把这类数据拆分成独立Fact或者用数据预处理做标记字段。比如提前在代码里算好order.hasSeckillTag规则只对这个布尔字段做判断性能会好很多。本质上是把“计算”和“决策”分开复合变量只存决策真正需要的最小数据。5.3 KieContainer的正确姿势这块前面提过但值得再强调一次。生产环境里我用Spring管理KieContainer和StatelessKieSession保证全局唯一Configuration public class DroolsConfig { Bean public KieContainer kieContainer() { KieServices kieServices KieServices.Factory.get(); KieFileSystem kfs kieServices.newKieFileSystem() .write(ResourceFactory.newClassPathResource(rules/order-discount.drl)); KieBuilder kb kieServices.newKieBuilder(kfs).buildAll(); if (kb.getResults().hasMessages(Level.ERROR)) { throw new IllegalStateException(规则编译失败); } KieModule km kb.getKieModule(); return kieServices.newKieContainer(km.getReleaseId()); } Bean public StatelessKieSession statelessKieSession(KieContainer kieContainer) { return kieContainer.newStatelessKieSession(); } }网上不少教程打包KieContainer时会用KieHelper其实KieHelper也是KIE API里的便捷类内部逻辑跟我们手写一样。如果规则文件的路径需要动态扩展可以把KieFileSystem的写入路径做成配置项但前提是文件存在于classpath里。5.4 规则结果和复合变量是“一体”还是“分离”这是我在实际项目里反复权衡过的一个问题。方案有两种复合变量本身携带结果字段比如OrderFact.setDiscountRate()规则只修改入参对象单独定义结果对象如DiscountResult规则创建并插入它代码从工作内存中取。第一种更简单但会污染入参对象后续想复用这个对象做别的事就得考虑状态清理第二种干净但需要额外定义类、处理规则内新建对象的逻辑。我的推荐是决策结果字段多且后续有独立消费链路时用独立结果对象结果字段少、只是简单打标直接用复合变量携带也可以。Drools下用Global收集结果有一种常见的“全局变量其实是局部变量”的误解。Global对多线程、多规则引擎实例并不安全无状态会话用它没太大问题但有状态会话和并发场景下很容易出意外。能不用Global就不用我后来都改成insert结果对象或者把结果塞回复合变量。6. 常见问题与排查技巧实录6.1 规则文件改了但没生效这是最容易碰到的问题。Drools的KieContainer默认在JVM里缓存编译结果你改了DRL文件后需要重新构建KieContainer或者在开发环境用KieScanner实现动态更新。KieScanner scanner kieServices.newKieScanner(kieContainer); scanner.start(10000L);start的入参是扫描间隔毫秒。生产环境慎用因为动态更新规则本身需要配套的版本管理。我用KieScanner最常踩的坑是Maven的SNAPSHOT版本才能被扫描到release版本更新不了。所以如果要用动态更新编程时要规划好规则版本策略避免线上环境规则错乱。6.2 复合变量属性引用报编译错误DRL文件写的时候经常出现类似 “Unable to resolve ObjectType OrderFact” 或属性找不到的错误。排查顺序我建议三步走确认DRL里import的类全路径是否正确确认Fact对象是public类属性有getterLombok的Data生成的getter可用确认DRL里写的属性名和Java字段名完全一致注意大小写。最后一步最烦人有时候看起来“一模一样”但一个是amount一个是AmountDrools编译不报错只是匹配不到这种情况很难定位。我建议在项目里约定规则里的字段名全部用小驼峰代码里也是禁用下划线。6.3 规则该触发却不触发很多时候不是规则写错而是复合变量里某个字段的值和你预期不一致。比如订单金额是字符串类型500规则里写amount 500括号里实际比较时类型不兼容匹配就静默失败了。这时候最好的工具是日志。我总结了一个调试三步法实测有效第一步在调用规则引擎之前把构造好的复合变量以JSON格式打印出来第二步在规则文件里用System.out.println或者log对象Drools支持logger把匹配到的Fact打出来第三步简化规则条件逐步增加条件定位到底哪一条约束导致触发失败。Drools的ksession.addEventListener可以监听AgendaEventListener和WorkingMemoryEventListener直接看哪些规则被激活、哪些Fact被插入/删除这是线上排查的利器。6.4 复合变量属性是null导致规则报错Drools规则里如果写$order.channel APP而channel属性为null大多数情况下等价于false不会报错。但如果你写$order.channel.startsWith(A)MVEL执行引擎会直接抛NPE而且这个异常往往在规则引擎内部被包装成奇怪的错误不容易看清原始原因。解决思路有两个一是代码构造复合变量时保证关键字段不为null二是规则里写方法调用前先判空比如$order.channel ! null $order.channel.startsWith(A)。我建议的是组合方式构造时兜底规则内重要路径判空。规则文件毕竟是给人维护的多写一个判空条件可读性会下降所以要平衡优先在代码层做好数据清洗。6.5 Easy Rules里的Facts命名冲突Easy Rules的Facts虽然看起来像Map但内部做了类型和name的双重校验。如果你往Facts里put两个相同name的变量后一个会覆盖前一个。更隐蔽的是在规则方法里通过参数类型反推Fact时如果有多个同类型对象它可能取不到正确的那一个。我踩过一次规则方法里申明了Fact(user) UserFact user但Facts里没有key为“user”的UserFact而是传成了buyer结果运行时直接报 “No fact found for name user”。这种还好容易发现最怕的是同时存在两个key里面都是UserFact某个规则引用其中一个排查起来要看半天代码。所以我在团队里反复强调Facts的key命名必须全局统一不能今天用order明天用ord。7. 复合变量在真实项目中的一个落地场景前面讲了很多理论我想再补充一个真实的落地方案完整地展示复合变量怎么在风控场景中实现“决策调用入参”的灵活化。有个内部运营活动需要给用户发优惠券但要过滤掉可疑的刷单行为。原来的代码里风控逻辑是十来个if嵌套后来我把规则抽出来。决策入参用了一个RiskEvalContext复合变量里面包含用户基本信息userId、注册时间、会员等级设备行为设备指纹、当日操作次数、切换账号频率订单维度下单金额、下单频率、收货地址数量历史黑名单近期是否有过纠纷订单。规则文件里写了十几条规则覆盖“新注册当天高频”“设备切换多个账号”“高金额且多人同地址”等场景。代码调用规则引擎时只需要构造好这个RiskEvalContext然后传入无状态会话。结果也放进同一个上下文里规则命中后给RiskEvalContext.riskLevel赋值。这轮重构之后效果非常明显运营调策略不用再发版直接改DRL规则之间的组合关系代码里看不见但DRL里一目了然新同事接手先看规则文件再补代码细节上手上亿级别。当然也有代价DRL文件本身变成了一门“业务语言”需要有专职的人维护和把关。如果团队里没人能胜任建议从Easy Rules这类轻方案起步。8. 复合变量规则引擎方案的一些“进阶玩法”复合变量的适用场景远不止“替代if-else”。我在后续项目里陆续尝试过几个方向效果都不错决策表复杂化Drools支持将复合变量属性作为决策表行条件在Excel里维护大量分支适合营销活动策略这种频繁变更的规则集规则分组分级通过agenda-group或activation-group控制规则执行域同一复合变量在不同场景下走不同规则组规则版本对比基于复合变量的结构可以在不改变代码接口的情况下加载多套规则做A/B效果验证规则可视化如果复合变量的结构定义得足够规范可以映射成一套元数据给业务做配置化的规则编辑界面这就是更高阶的玩法了。我自己另一个心得是复合变量的设计不要太贴近具体的规则而要贴近业务对象的本质。比如你做一个订单决策OrderFact里有amount、channel、category这些字段在未来好几年内大概率都是稳定的但如果你把某个规则专用的临时字段写进Fact比如isFlashSaleSuccessToday一旦规则淘汰这个字段就沦为垃圾数据还得找机会清理。所以设计复合变量时要留三分余地同时保持警觉随时删掉那些只为一两条规则服务、且语义不稳定的字段。这就是经验的体现单纯照搬网上教程是学不来的。关于规则引擎的选型我也多说两句供参考维度DroolsEasy Rules规则文件DRL功能强大学习曲线陡Java注解/Properties简单直观适合场景复杂业务规则、决策表、事件处理轻量规则、快速落地复合变量形式Fact对象支持复杂匹配Facts Mapkey-value形式性能更强机制更底层一般动态更新支持KieScanner简单直接替换规则类即可但不管选哪个复合变量的设计思路是相通的。只要你把“决策上下文”清晰地建模规则引擎的威力才能真正发挥出来。我个人的一点体会是规则引擎这套东西难点从来不是语法而是“度”的把握。什么时候该把逻辑抽出去什么时候留在代码里复合变量怎么设计才不会过度这些判断需要在实际项目里反复打磨。这篇文章敢写出来就是因为这些坑我基本都趟过一遍——希望你看到之后能少走一些弯路。如果你正准备在项目里引入规则引擎或者正在纠结复合变量怎么设计可以先用我上面给出的POJODRL模板跑一个demo卡住的时候再回到这篇文章对照检查。实践出真知规则引擎也一样。

相关新闻

OTA升级性能测试全解析:从数据采集到优化实践

OTA升级性能测试全解析:从数据采集到优化实践

2026/9/8 5:42:48

这次我们来看一个关于OTA升级后性能表现的技术分析项目。从标题"OTA之后的白幽灵果然夯!【彬彬一周数据】"来看,这应该是一个针对某款代号"白幽灵"的设备或系统在OTA更新后的性能测试和数据报告。 这个项目的核心价值在于提供了真实…

Nakamichi APOLLO 550 5.1.2声道家庭影院评测与选购指南

Nakamichi APOLLO 550 5.1.2声道家庭影院评测与选购指南

2026/9/8 5:42:48

Nakamichi APOLLO 550 家庭影院组合深度评测:5.1.2声道真实体验与选购指南最近在帮朋友搭建家庭影院系统时,发现很多人在选择音响设备时都会遇到一个难题:既要追求沉浸式音效体验,又不想投入过多预算。Nakamichi APOLLO 550作为一…

CATIA V6 2013安装部署实战:许可证问题与高频故障排查指南

CATIA V6 2013安装部署实战:许可证问题与高频故障排查指南

2026/9/8 5:32:47

简介:CATIA V6 2013是达索系统面向航空、汽车、机械及造船等领域推出的高端三维设计软件,这份安装资源包精准覆盖Catia V6 2013版本,为产品设计、结构仿真与制造规划人员提供快速部署入口。包内以rar压缩形式共收录7个文件,大小仅…

国产MCU替代STM32的5大隐藏坑:从Pin-to-Pin兼容到调试烧录全解析

国产MCU替代STM32的5大隐藏坑:从Pin-to-Pin兼容到调试烧录全解析

2026/9/8 6:32:50

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

Pajek宏命令与脚本化分析:社会网络仿真的自动化实战

Pajek宏命令与脚本化分析:社会网络仿真的自动化实战

2026/9/8 6:32:50

如果你只是偶尔用鼠标点开Pajek,画几张社交网络图,那你看到的其实只是这个软件最浅的一层。作为一款专注社会网络仿真与分析的老牌工具,Pajek真正的威力藏在两个地方:一是它对超大规模网络(几十万节点)的处…

苹果设计理念下的KVM:从硬件切换到Linux虚拟化全指南

苹果设计理念下的KVM:从硬件切换到Linux虚拟化全指南

2026/9/8 6:32:50

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

Windows下Redis配置不生效?从启动方式到配置文件全排查

Windows下Redis配置不生效?从启动方式到配置文件全排查

2026/9/8 6:32:50

上周一个朋友找我,说他在Windows上装了Redis,把配置文件里的默认端口改成了6380,保存、重启、再连接,发现还是跑在6379上。他问我的第一句话是:“Redis在Windows下是不是不读配置文件啊?”我问他第二句话是…

SpringBoot+Vue3图书管理系统:前后端分离项目从零搭建到部署

SpringBoot+Vue3图书管理系统:前后端分离项目从零搭建到部署

2026/9/8 6:32:50

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

基于RflySim的四旋翼容灾控制仿真与故障重构实践

基于RflySim的四旋翼容灾控制仿真与故障重构实践

2026/9/8 6:22:50

简介:这份MATLAB/Simulink源码包来自北航可靠飞行控制研究组,面向四旋翼容灾控制方向的学习者与开发者,重点解决故障工况下控制器重构、软件仿真与半物理验证相结合的教学科研需求。包内共142个文件,以117个XML参数配置、10个SLX模…

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

2026/9/7 20:21:46

本文首发于“生态学者”!从“湿地面积”到“土壤碳密度”:为什么需要重新认识潮汐湿地蓝碳变化?潮汐湿地位于陆地与海洋的交汇地带,包括红树林、盐沼和潮滩,是全球重要的蓝碳生态系统。其土壤能够长期储存大量有机碳&a…

adb抓包

adb抓包

2026/9/8 4:55:53

前言 本文介绍如何通过 tcpdump 在 Android 手机上抓取网络数据包,并在电脑端使用 Wireshark 进行分析。适用于需要排查 App 网络请求、分析接口调用或调试网络问题的开发与测试场景。1. 手机要有 root 权限2. 下载 tcpdump3. adb push C:\Users\zhangkuixun\Downlo…

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

2026/9/7 8:03:37

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战 在云原生基础设施中,容器镜像体积直接决定了服务的部署速度与弹性扩容敏捷度。对于传统的 Go / Java 微服务,镜像体积通常被严格控制在 50MB 到 200MB 以内,拉取镜像只…

芯片良率波动可视化:动画拆解工艺因果,重建客户信任

芯片良率波动可视化:动画拆解工艺因果,重建客户信任

2026/9/8 0:02:30

芯片这个行业有个不太被人摆到台面上、但几乎每天都在发生的场景:客户拿着一条良率曲线截图问你,这批货的良率怎么掉了三个点,是不是工艺出问题了,产生的不良会不会流到他们产线上去。你解释了半天,客户似懂非懂&#…

PyTorch DataLoader参数冲突:sampler与shuffle互斥的根源与正确写法

PyTorch DataLoader参数冲突:sampler与shuffle互斥的根源与正确写法

2026/9/8 0:02:30

ValueError: sampler option is mutually exclusive with shuffle,这个报错我在 PyTorch 的 DataLoader 上至少见过几十次了,而且很有意思的是,它经常不是新手专属——很多写了好几年模型的老手,在从单机改成自定义采样器&#xf…

中国车企再破谣言,GAC吉利零跑获欧盟安全五星

中国车企再破谣言,GAC吉利零跑获欧盟安全五星

2026/9/8 0:02:30

有人可能在网上开着皮卡拍视频,声称中国电动车不仅性能不如美国大排量车型,安全性也堪忧。然而事实恰恰相反,GAC、吉利和零跑最新推出的电动车型在极为严苛的欧盟新车安全评鉴(Euro NCAP)测试中全部斩获满分。就在特斯…

远程协作的工作台整理

远程协作的工作台整理

2026/9/8 4:23:39

远程协作的工作台整理远程协作的核心不是再加一个工具,而是让交接信息足够完整。异步任务要写明目标、输入位置、完成标准和需要决策的人。 工作台的最小配置 将日程、待办、代码和沟通入口收拢到少数固定位置;通知按紧急程度分层。工作台不需要模仿办公…

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

2026/9/8 3:19:39

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

2026/9/8 4:00:23

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…