1. 从“硬编码”到“规则引擎”:为什么我们需要Aviator?
如果你写过业务系统,尤其是那些充满各种营销活动、风控策略、审批流程的系统,一定对下面这种代码不陌生:
if (user.getLevel() > 3 && order.getAmount() > 1000) { discount = order.getAmount() * 0.1; } else if (user.isNew() && product.isPromotion()) { discount = 50; } else if (date.isHoliday() && order.getAmount() > 500) { discount = order.getAmount() * 0.05; } else { discount = 0; }这段代码逻辑清晰吗?清晰。能用吗?能用。但问题在于,当业务人员跑过来跟你说:“小王,明天国庆节,我们想给所有VIP用户再加一个满2000减300的活动,并且和之前的折扣可以叠加,但新用户不参与”,你的内心是不是崩溃的?这意味着你需要重新理解这一大坨if-else的逻辑,找到合适的位置插入新的判断,然后发版、上线、测试。整个过程耗时耗力,且风险极高,一个不小心就可能改出线上Bug。
规则引擎就是为了解决这个问题而生的。它的核心思想是将业务决策逻辑从应用程序代码中剥离出来,用接近自然语言的规则脚本(Rule Script)来描述。业务人员(或产品经理)可以自行编写或修改这些规则,而无需开发人员介入编码和发布流程。Drools是Java领域一个非常重量级、功能强大的规则引擎,但它学习曲线陡峭,配置复杂,对于很多中小型项目来说有点“杀鸡用牛刀”。
而Aviator,则是一个轻量级、高性能的Java表达式求值引擎。它虽然不像Drools那样具备完整的Rete算法推理链,但它完美地解决了我们开头提到的那个核心痛点:将频繁变化的业务判断逻辑表达式化、外部化。你可以把Aviator理解为一个超级增强版的、支持自定义函数和缓存的“计算器”,专门用来解析和执行由字符串表示的表达式。当你的折扣逻辑、风控分数、运费计算规则需要变更时,你只需要修改存储在数据库、配置文件或配置中心的那个表达式字符串,然后热加载它,业务逻辑就立刻生效了。
我最初接触Aviator是在一个快速迭代的电商促销项目中,当时每天都有新的优惠券规则上线,如果每次都走开发流程,团队根本扛不住。引入Aviator后,我们将上百条规则写成了表达式,配置在后台管理页面里,运营同学自己就能搞定,研发效率提升了不止一个量级。下面,我就以一个“电商订单折扣计算”为贯穿案例,带你从零开始,彻底掌握Aviator这个利器。
2. Aviator快速上手:环境搭建与第一个表达式
Aviator的引入非常简单,它几乎没有任何外部依赖。如果你使用Maven,直接在pom.xml中添加以下依赖即可:
<dependency> <groupId>com.googlecode.aviator</groupId> <artifactId>aviator</artifactId> <version>5.4.1</version> <!-- 请使用最新稳定版本 --> </dependency>是的,就这么一行。接下来,我们写一个最简单的Java程序来验证一下。
import com.googlecode.aviator.AviatorEvaluator; public class AviatorFirstDemo { public static void main(String[] args) { // 1. 执行一个简单的算术表达式 Long result = (Long) AviatorEvaluator.execute("1 + 2 + 3"); System.out.println("1 + 2 + 3 = " + result); // 输出: 1 + 2 + 3 = 6 // 2. 执行一个带比较和逻辑的表达式 Boolean isTrue = (Boolean) AviatorEvaluator.execute("100 > 99 && 'hello' == 'hello'"); System.out.println("100 > 99 && 'hello' == 'hello' = " + isTrue); // 输出: true // 3. 使用变量 String expression = "a + b * c"; Map<String, Object> env = new HashMap<>(); env.put("a", 10); env.put("b", 20); env.put("c", 30); Long resultWithVars = (Long) AviatorEvaluator.execute(expression, env); System.out.println("a + b * c = " + resultWithVars); // 输出: a + b * c = 610 } }通过这个例子,你可以直观地感受到Aviator的基本用法:
- 核心类:
AviatorEvaluator是入口类,静态方法execute用于执行表达式。 - 表达式:就是一个字符串,比如
"1 + 2 + 3"。 - 环境变量(Env):一个
Map<String, Object>,用于向表达式注入外部变量,如例子中的a,b,c。表达式里可以直接用这些变量名。 - 返回值:
execute方法返回Object,你需要根据表达式的结果类型进行强制转换。Aviator内部有完整的类型推断。
注意:这里有一个新手极易踩的坑。
AviatorEvaluator.execute()每次调用都会编译并执行表达式。如果同一条表达式需要反复执行(比如在循环中,或作为核心规则被频繁调用),反复编译会造成巨大的性能开销。正确的做法是使用编译缓存。我们会在后面的性能优化章节详细讲解。
2.1 Aviator支持的数据类型与运算符
要让Aviator成为你得心应手的工具,必须熟悉它支持的类型系统。它和Java类型有对应关系,但并非完全一致,这是为了表达式书写的简洁和高效。
| Aviator 类型 | Java 对应类型 | 说明与示例 |
|---|---|---|
| 长整型(long) | Long | Aviator的“整数”只有long。100,-50 |
| 双精度浮点型(double) | Double | Aviator的“小数”。3.14,-0.5 |
| 高精度计算类型(BigDecimal) | BigDecimal | 涉及金钱等需要精确计算的场景,使用BigDecimal。字面量加M后缀,如10.23M |
| 字符串(string) | String | 单引号或双引号包裹。‘hello’,“world” |
| 布尔型(boolean) | Boolean | true,false |
| 正则表达式(Pattern) | java.util.regex.Pattern | 字面量,如=~‘\\d+’用于匹配 |
| nil | null | 表示空值 |
运算符方面,Aviator几乎支持所有你熟悉的运算符,并且优先级与Java一致:
- 算术:
+,-,*,/,%(取模) - 比较:
>,>=,<,<=,==,!= - 逻辑:
&&,||,! - 三元运算符:
a > b ? a : b - 正则匹配:
=~(匹配成功返回true),!~(匹配失败返回true) - 字符串连接:
+(当两边是字符串时)
这里特别提一下等于(==)运算符。在Aviator中,==的行为比Java更“智能”。它不仅比较值,还会处理类型转换。例如100 == 100.0在Aviator中是true,因为整数100和浮点数100.0在数值上是相等的。这更符合我们在业务逻辑中写判断的直觉。
3. 实战:用Aviator重构电商折扣规则引擎
现在,我们进入实战环节。假设我们有一个订单对象Order和一个用户对象User,我们需要根据一系列规则计算最终折扣。
传统硬编码方式:
public BigDecimal calculateDiscount(Order order, User user) { BigDecimal discount = BigDecimal.ZERO; // 规则1: VIP用户且订单金额大于1000,打9折 if (“VIP”.equals(user.getLevel()) && order.getAmount().compareTo(new BigDecimal(“1000”)) > 0) { discount = order.getAmount().multiply(new BigDecimal(“0.1”)); } // 规则2: 新用户且商品是促销品,立减50 else if (user.isNew() && order.getItems().stream().anyMatch(Item::isPromotion)) { discount = new BigDecimal(“50”); } // 规则3: 节假日所有用户订单满500减25 else if (isHoliday() && order.getAmount().compareTo(new BigDecimal(“500”)) > 0) { discount = new BigDecimal(“25”); } // 更多规则... return discount; }使用Aviator表达式化:首先,我们把每一条规则抽象成一个表达式字符串,可以存在数据库里:
| 规则ID | 规则名称 | 规则表达式 |
|---|---|---|
| R001 | VIP大额订单折扣 | user.level == ‘VIP’ && order.amount > 1000 ? order.amount * 0.1 : 0 |
| R002 | 新用户促销品立减 | user.isNew && order.containsPromotion ? 50 : 0 |
| R003 | 节假日满减 | isHoliday && order.amount > 500 ? 25 : 0 |
接下来,我们编写Aviator的执行引擎:
import com.googlecode.aviator.AviatorEvaluator; import com.googlecode.aviator.Expression; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; public class DiscountRuleEngine { // 假设我们从数据库或配置中心加载规则表达式 private Map<String, String> ruleExpressionMap = new HashMap<>(); public DiscountRuleEngine() { // 模拟加载规则 ruleExpressionMap.put(“R001”, “user.level == ‘VIP’ && order.amount > 1000 ? order.amount * 0.1 : 0”); ruleExpressionMap.put(“R002”, “user.isNew && order.containsPromotion ? 50 : 0”); ruleExpressionMap.put(“R003”, “isHoliday && order.amount > 500 ? 25 : 0”); } public BigDecimal executeRule(String ruleId, Order order, User user, boolean holidayFlag) { String expressionStr = ruleExpressionMap.get(ruleId); if (expressionStr == null) { throw new IllegalArgumentException(“Rule not found: “ + ruleId); } // 准备表达式执行环境 Map<String, Object> env = new HashMap<>(); env.put(“user”, user); env.put(“order”, order); env.put(“isHoliday”, holidayFlag); // 注意:order.amount 需要是Number类型(Long, Double, BigDecimal) // user.isNew 需要是Boolean类型 // user.level 需要是String类型 // order.containsPromotion 需要是Boolean类型 try { // 执行表达式 Object result = AviatorEvaluator.execute(expressionStr, env); // 处理返回值,可能是Long, Double, BigDecimal if (result instanceof Number) { return new BigDecimal(result.toString()); } return BigDecimal.ZERO; } catch (Exception e) { // 处理表达式执行异常,如语法错误、空指针等 throw new RuntimeException(“Execute rule [“ + ruleId + “] failed: “ + e.getMessage(), e); } } // 批量执行所有规则,并取最大折扣(或叠加,根据业务) public BigDecimal calculateFinalDiscount(Order order, User user, boolean holidayFlag) { BigDecimal maxDiscount = BigDecimal.ZERO; for (String ruleId : ruleExpressionMap.keySet()) { BigDecimal discount = executeRule(ruleId, order, user, holidayFlag); if (discount.compareTo(maxDiscount) > 0) { maxDiscount = discount; // 这里示例取最大,实际可能是叠加 } } return maxDiscount; } }通过这个重构,业务逻辑的变更完全变成了对数据库中ruleExpressionMap里字符串的维护。运营同学在后台修改“R001”的表达式为user.level == ‘VIP’ && order.amount > 2000 ? order.amount * 0.15 : 0,你的系统在下次查询规则时(或通过监听配置变更)就会自动生效,无需重启。
3.1 处理复杂对象与自定义函数
上面的例子直接把user和order对象放入环境变量,Aviator可以通过反射访问它们的getter方法(如user.level会调用user.getLevel())。但这存在两个问题:
- 性能开销:反射调用比直接方法调用慢。
- 安全性:暴露了整个对象,表达式可以调用任意
getter,甚至可能调用一些敏感方法。
更优雅和安全的方式是自定义函数。我们可以将业务逻辑封装成一个个函数,在表达式中调用。
例如,我们创建一个判断用户是否是VIP的函数:
public class RuleCustomFunctions { // 注册一个名为 ‘isVip’ 的函数 public static void registerFunctions() { AviatorEvaluator.addFunction(new AbstractFunction() { @Override public String getName() { return “isVip”; } @Override public AviatorObject call(Map<String, Object> env, AviatorObject arg1) { // 从环境变量中获取用户对象 User user = (User) env.get(“user”); if (user == null) { return AviatorBoolean.FALSE; } // 调用业务方法判断 return AviatorBoolean.valueOf(“VIP”.equals(user.getLevel())); } }); // 再注册一个获取订单金额的函数 AviatorEvaluator.addFunction(new AbstractFunction() { @Override public String getName() { return “getOrderAmount”; } @Override public AviatorObject call(Map<String, Object> env, AviatorObject arg1) { Order order = (Order) env.get(“order”); if (order == null || order.getAmount() == null) { return new AviatorBigDecimal(BigDecimal.ZERO); } // 返回Aviator的高精度数字类型 return new AviatorBigDecimal(order.getAmount()); } }); } }注册函数后,我们的规则表达式就可以写得更加清晰和安全:
isVip(user) && getOrderAmount(order) > 1000 ? getOrderAmount(order) * 0.1 : 0表达式里不再直接引用对象属性,而是通过我们定义的函数接口来访问,这样我们就能严格控制表达式能做什么、不能做什么。在实际项目中,我强烈建议采用这种方式,它让表达式更易读,也更容易进行单元测试。
4. 性能优化与生产级实践指南
当规则数量成百上千,且在高并发场景下被频繁执行时,性能就成为关键考量。Aviator在这方面提供了强大的支持。
4.1 表达式编译与缓存
这是最重要的优化手段,没有之一。AviatorEvaluator.execute(expression, env)内部会先编译表达式为Java字节码(如果未缓存),然后执行。编译过程相对耗时。
Aviator内置了编译缓存。默认情况下,它使用一个LRU(最近最少使用)缓存。但我们可以进行更精细的控制。
// 1. 手动编译并缓存表达式实例 Expression compiledExp = AviatorEvaluator.compile(“a + b * c”, true); // true表示缓存 Map<String, Object> env = new HashMap<>(); env.put(“a”, 10); env.put(“b”, 20); env.put(“c”, 30); Long result = (Long) compiledExp.execute(env); // 执行时无需再编译 // 2. 获取全局缓存实例并进行配置 com.googlecode.aviator.ExpressionLRUExpressionCache cache = (ExpressionLRUExpressionCache) AviatorEvaluator.getInstance().getExpressionCache(); cache.setMaxCacheSize(5000); // 设置最大缓存表达式数量,根据实际情况调整 // 3. 使用弱引用缓存(默认),防止内存泄漏 // 默认配置已足够好,除非有特殊需求,一般无需改动。最佳实践:在系统初始化时(如Spring Bean的@PostConstruct方法中),预编译所有已知的、常用的核心规则表达式,并持有这些Expression对象的引用。对于动态新增的规则,依靠Aviator的LRU缓存即可。
4.2 启用“常量和变量”优化模式
Aviator在编译时,可以分析表达式,将其中不会改变的部分(常量)提前计算好,这个特性默认是开启的。但我们可以通过选项显式控制。
// 创建一个自定义的配置选项 AviatorEvaluator.setOption(Options.OPTIMIZE_LEVEL, AviatorEvaluator.EVAL); // EVAL: 默认,常量折叠等基础优化。 // COMPILE: 更激进的优化,将表达式编译成Java字节码,性能最高,但初次编译稍慢。对于长期运行、要求极致性能的生产系统,建议使用COMPILE模式。
4.3 类型提示与避免自动转型
Aviator虽然支持灵活的自动类型转换,但转换本身有成本。在性能关键的表达式中,尽量保证运算数据类型一致。
// 不佳的写法:涉及Integer到Long的自动转换 String expr = “a > 100”; // 假设env中a是Integer // 更佳的写法:在注入环境变量时,就转为Long env.put(“a”, Long.valueOf(user.getAge()));4.4 生产环境中的异常处理与监控
规则表达式是动态的,可能包含语法错误、空指针引用、除零错误等。一个健壮的规则引擎必须能妥善处理这些异常,并具备监控能力。
public class SafeRuleExecutor { private static final Logger LOG = LoggerFactory.getLogger(SafeRuleExecutor.class); public Object safeExecute(String ruleId, String expression, Map<String, Object> env) { try { Expression compiledExp = AviatorEvaluator.compile(expression, true); return compiledExp.execute(env); } catch (ExpressionSyntaxErrorException e) { LOG.error(“规则[{}]语法错误: {}, 表达式: {}”, ruleId, e.getMessage(), expression, e); // 触发告警,通知规则配置人员 alertConfigError(ruleId, “语法错误”, e.getMessage()); return getDefaultValue(); // 返回一个安全的默认值 } catch (ArithmeticException e) { LOG.error(“规则[{}]算术异常(如除零): {}”, ruleId, e.getMessage(), e); return getDefaultValue(); } catch (Exception e) { LOG.error(“执行规则[{}]未知异常”, ruleId, e); return getDefaultValue(); } } // 监控:记录规则执行耗时 public Object executeWithMonitor(String ruleId, String expression, Map<String, Object> env) { long start = System.nanoTime(); Object result = safeExecute(ruleId, expression, env); long cost = (System.nanoTime() - start) / 1000; // 微秒 if (cost > 1000) { // 如果执行超过1毫秒,记录慢查询 LOG.warn(“规则[{}]执行缓慢,耗时: {} μs, 表达式: {}”, ruleId, cost, expression); } Metrics.counter(“rule.execute”, “ruleId”, ruleId).increment(); // 上报计数器 Metrics.timer(“rule.cost”, “ruleId”, ruleId).record(cost, TimeUnit.MICROSECONDS); // 上报耗时 return result; } }4.5 与Spring框架集成
在Spring Boot项目中,我们可以将规则引擎封装成一个Service Bean。
@Service public class RuleEngineService { private final Map<String, Expression> ruleCache = new ConcurrentHashMap<>(); @PostConstruct public void init() { // 从数据库加载所有启用的规则,并预编译 List<RuleDefinition> rules = ruleDefinitionRepository.findEnabledRules(); for (RuleDefinition rule : rules) { try { Expression exp = AviatorEvaluator.compile(rule.getExpression(), true); ruleCache.put(rule.getCode(), exp); } catch (Exception e) { LOG.error(“预编译规则[{}]失败,规则将被忽略”, rule.getCode(), e); } } } @EventListener public void handleRuleUpdateEvent(RuleUpdateEvent event) { // 监听规则更新事件(如配置中心推送),动态更新缓存 RuleDefinition newRule = event.getRule(); try { Expression newExp = AviatorEvaluator.compile(newRule.getExpression(), true); ruleCache.put(newRule.getCode(), newExp); LOG.info(“规则[{}]已热更新”, newRule.getCode()); } catch (Exception e) { LOG.error(“热更新规则[{}]失败”, newRule.getCode(), e); } } public Object executeRule(String ruleCode, Map<String, Object> env) { Expression exp = ruleCache.get(ruleCode); if (exp == null) { throw new RuleNotFoundException(“Rule not found or compiled: “ + ruleCode); } return exp.execute(env); } }这样,你的业务Service就可以轻松注入并使用RuleEngineService了。
5. 避坑指南:那些年我踩过的Aviator的“坑”
在实际项目中使用Aviator几年,我积累了不少经验教训,这里分享几个最常见的“坑”,希望能帮你绕过去。
5.1 坑一:数字类型混淆与精度丢失
这是最经典的错误。Aviator中,3 / 2的结果是1(长整型除法),而不是1.5。如果你需要小数结果,必须让至少一个操作数是浮点数,例如3.0 / 2或3 / 2.0。
更大的坑在于金融计算。0.1 + 0.2在Aviator(使用Double计算)中结果可能是0.30000000000000004。对于金额计算,必须使用BigDecimal。
// 错误示范 String expr = “price * quantity * discountRate”; // price, quantity, discountRate 如果是Double,可能有精度问题 // 正确示范 String expr = “price * quantity * discountRate”; // 确保传入的 price, quantity, discountRate 都是 BigDecimal 类型 // 或者在表达式中直接使用BigDecimal字面量 String expr = “price * quantity * 0.15M”; // 0.15M 就是 BigDecimal最佳实践:在涉及金额、利率等需要精确计算的场景,统一在Java层将数值转为BigDecimal再放入环境变量,或者在表达式中显式使用M后缀。
5.2 坑二:空指针异常(NPE)
表达式user.level == ‘VIP’,如果user本身是null,Aviator会抛出空指针异常。虽然Aviator有一些安全访问的语法糖(如后续版本的支持?.),但最稳妥的方式是在传入数据前做好判空,或者使用自定义函数进行封装。
// 在准备env时进行防御 Map<String, Object> env = new HashMap<>(); env.put(“userLevel”, user != null ? user.getLevel() : “”); env.put(“orderAmount”, order != null ? order.getAmount() : BigDecimal.ZERO); // 表达式改为 String expr = “userLevel == ‘VIP’ && orderAmount > 1000”;5.3 坑三:表达式注入安全风险
永远不要让用户直接输入并执行任意表达式!这等同于SQL注入。Aviator虽然提供了AviatorEvaluator.getInstance().setOption(Options.FEATURE_SET, Feature.asSet(Feature.Assignment, Feature.ForLoop, Feature.WhileLoop))来禁用某些危险特性(如赋值、循环),但最根本的防护是白名单机制。
- 方案一:规则表达式由后台管理员在受控的UI界面上配置,该界面可以提供下拉框、输入框(仅限数字、变量名)等组件来拼接表达式,而不是一个自由的文本编辑器。
- 方案二:如果必须接受外部输入,则需要一个强大的表达式语法解析器和白名单校验器,只允许预定义好的变量名、运算符和函数出现。
5.4 坑四:缓存污染与内存泄漏
如果你使用默认的LRU缓存,并且不断编译新的、唯一的表达式(例如,表达式里包含每次请求的ID“status_” + requestId == ‘processed’),会导致缓存不断增长,最终可能引发OOM。
解决方案:
- 避免在表达式字符串中拼接动态变化的值。应该将动态值作为变量传入。
// 错误:每次requestId不同,产生无数新表达式 String badExpr = ““status_” + requestId == ‘processed’”; // 正确:将动态部分作为变量 String goodExpr = “statusVar == ‘processed’”; env.put(“statusVar”, “status_” + requestId); - 定期监控
AviatorEvaluator.getInstance().getExpressionCache()的大小。 - 根据实际情况,调小
setMaxCacheSize。
5.5 坑五:调试困难
当表达式复杂且执行出错时,日志里只有一个简单的错误信息,很难定位问题所在。我们可以利用Aviator的调试功能。
// 在编译或执行前,开启跟踪 AviatorEvaluator.setTrace(true); try { AviatorEvaluator.execute(complexExpr, env); } catch (Exception e) { e.printStackTrace(); } finally { AviatorEvaluator.setTrace(false); } // 这会在控制台输出详细的执行步骤,对调试非常有帮助。对于生产环境,可以设计一个“规则验证”接口,在后台管理系统中,允许输入测试数据和表达式,返回详细的执行结果和中间过程,便于业务人员验证规则逻辑是否正确。
6. 进阶:Aviator高级特性与Drools的简单对比
当你熟悉了Aviator的基本用法后,可以探索一些高级特性来解决更复杂的问题。
6.1 使用“Statement”执行多条语句与赋值
Aviator不仅支持表达式(Expression,有返回值),也支持语句(Statement,无返回值)。你可以用分号分隔多条语句,最后一条语句的结果作为整个表达式的返回值。
String statement = ” let discount = 0; if (user.level == ‘VIP’) { discount = order.amount * 0.1; }; if (user.isNew) { discount = discount + 50; }; discount; // 返回最终的discount值 “; Object result = AviatorEvaluator.execute(statement, env);这让你能在单个表达式内实现更复杂的逻辑流。let用于定义局部变量。
6.2 访问数组、集合和Map
Aviator可以方便地操作Java集合。
Map<String, Object> env = new HashMap<>(); List<Integer> list = Arrays.asList(1, 2, 3); Map<String, String> map = new HashMap<>(); map.put(“key”, “value”); env.put(“list”, list); env.put(“map”, map); String expr1 = “list[0] + list[1]”; // 访问列表元素,返回 3 String expr2 = “map[‘key’]”; // 访问Map元素,返回 “value” String expr3 = “list[0] = 99”; // 修改列表元素(如果列表支持)6.3 与Drools的对比与选型建议
文章开头提到了Drools,这里简单对比一下,帮助你在技术选型时做出决策。
| 特性 | Aviator | Drools |
|---|---|---|
| 定位 | 轻量级表达式求值引擎 | 企业级规则管理系统(BRMS) |
| 核心能力 | 求值表达式、自定义函数、脚本 | 基于Rete算法的复杂事件处理、推理链、决策表 |
| 学习成本 | 极低,几分钟上手 | 高,需要理解规则语法、Rete算法、工作内存等概念 |
| 配置部署 | 引入一个Jar包即可 | 需要部署规则文件(drl),配置Kie容器,相对复杂 |
| 性能 | 极高,编译后接近原生Java代码 | 高,但规则网络(Rete网络)构建和匹配有开销 |
| 适用场景 | 1. 业务逻辑简单,主要是条件判断和计算。 2. 规则需要频繁、动态变更。 3. 希望规则由业务人员轻度参与配置。 4. 对性能有极致要求。 | 1. 业务逻辑极其复杂,规则之间有很强的关联和推理关系。 2. 规则数量庞大(成千上万),且变化相对不频繁。 3. 需要完整的规则生命周期管理(版本、测试、部署)。 4. 需要决策表等高级可视化工具。 |
| 一句话总结 | “会写if-else,就会用Aviator”,它是代码中条件逻辑的外部化工具。 | “专业的规则管家”,它是处理复杂业务决策的专家系统。 |
我的经验:在90%的中后台业务系统(如CRM、ERP、促销、风控初级规则)中,Aviator的轻量和灵活足以应对。只有当规则之间层层嵌套、互相触发,形成一张复杂的“决策网”时,才需要考虑Drools。不要因为技术“高级”而选择Drools,合适的才是最好的。
7. 真实案例:一个可运行的简易规则引擎Demo
最后,我们整合以上所有知识点,构建一个简易但功能完整的规则引擎Demo。这个Demo模拟一个电商促销场景,支持规则的热加载。
// 1. 定义规则实体(对应数据库表) @Data public class PromotionRule { private String ruleId; private String ruleName; private String status; // ENABLED, DISABLED private String expression; // Aviator表达式 private String description; } // 2. 规则引擎服务核心 @Service @Slf4j public class PromotionRuleEngine { private final Map<String, Expression> expressionCache = new ConcurrentHashMap<>(); @Autowired private RuleRepository ruleRepository; // 假设是数据访问层 @PostConstruct public void init() { loadAllRules(); } // 加载并编译所有启用规则 public void loadAllRules() { expressionCache.clear(); List<PromotionRule> enabledRules = ruleRepository.findByStatus(“ENABLED”); for (PromotionRule rule : enabledRules) { compileAndCache(rule); } log.info(“已加载 {} 条促销规则”, enabledRules.size()); } // 编译并缓存单条规则 private void compileAndCache(PromotionRule rule) { try { Expression exp = AviatorEvaluator.compile(rule.getExpression(), true); expressionCache.put(rule.getRuleId(), exp); log.debug(“规则编译缓存成功: {}”, rule.getRuleId()); } catch (Exception e) { log.error(“规则编译失败, ruleId={}, expression={}”, rule.getRuleId(), rule.getExpression(), e); } } // 执行单条规则 public BigDecimal executeRule(String ruleId, Map<String, Object> context) { Expression exp = expressionCache.get(ruleId); if (exp == null) { throw new RuntimeException(“规则未找到或未启用: “ + ruleId); } try { Object result = exp.execute(context); return convertToBigDecimal(result); } catch (Exception e) { log.error(“规则执行异常, ruleId={}”, ruleId, e); return BigDecimal.ZERO; // 执行失败返回0折扣 } } // 执行所有规则,并计算总折扣(这里假设折扣可叠加) public BigDecimal calculateTotalDiscount(Map<String, Object> context) { BigDecimal totalDiscount = BigDecimal.ZERO; for (Map.Entry<String, Expression> entry : expressionCache.entrySet()) { try { Object result = entry.getValue().execute(context); BigDecimal discount = convertToBigDecimal(result); totalDiscount = totalDiscount.add(discount); log.trace(“规则[{}]计算折扣: {}”, entry.getKey(), discount); } catch (Exception e) { log.warn(“规则[{}]执行跳过”, entry.getKey(), e); } } return totalDiscount; } private BigDecimal convertToBigDecimal(Object obj) { if (obj == null) return BigDecimal.ZERO; if (obj instanceof BigDecimal) return (BigDecimal) obj; if (obj instanceof Number) return new BigDecimal(obj.toString()); return BigDecimal.ZERO; } // 供管理后台调用的热更新接口 @Transactional public void updateRule(PromotionRule newRule) { // 1. 更新数据库 ruleRepository.save(newRule); // 2. 更新缓存 if (“ENABLED”.equals(newRule.getStatus())) { compileAndCache(newRule); } else { expressionCache.remove(newRule.getRuleId()); } log.info(“规则热更新完成: {}”, newRule.getRuleId()); } } // 3. 在业务Service中使用 @Service public class OrderService { @Autowired private PromotionRuleEngine ruleEngine; public Order createOrder(OrderRequest request, User user) { Order order = new Order(); // ... 组装订单基本信息 // 构建规则执行上下文 Map<String, Object> context = new HashMap<>(); context.put(“user”, user); context.put(“order”, order); context.put(“currentTime”, System.currentTimeMillis()); // 可以注入更多上下文,如商品列表、优惠券等 // 计算促销折扣 BigDecimal totalDiscount = ruleEngine.calculateTotalDiscount(context); order.setDiscount(totalDiscount); order.setFinalAmount(order.getTotalAmount().subtract(totalDiscount)); // ... 保存订单等其他逻辑 return order; } }这个Demo展示了一个生产可用的规则引擎雏形。它具备了核心功能:规则配置化、预编译缓存、热更新、异常处理、日志监控。你可以在此基础上,增加规则的优先级、互斥、停止后续执行(break)等更复杂的控制逻辑。
从我自己的项目经验来看,引入Aviator这类轻量级规则引擎,最大的收益不是技术上的炫技,而是极大地提升了研发响应业务变化的速度,并降低了因频繁修改逻辑代码而引入缺陷的风险。它让开发和业务的边界变得更加清晰。开始你可能觉得写表达式有点别扭,但一旦习惯,你就会发现,将易变的逻辑剥离出来,是构建高可维护性系统的重要一步。