# 规则引擎

规则引擎实现了将业务决策从应用程序代码中分离出来,接受数据输入,解释业务规则,并根据业务规则做出业务决策。规则引擎其实就是一个输入输出平台。

它将复杂多变、经常需要调整的业务规则(例如:如果用户是VIP且订单金额超过500元,那么自动免运费)从固定的程序代码中剥离出来,单独进行管理。这样一来,当业务策略需要变更时(比如把免运费门槛提高到800元),业务人员无需找程序员修改代码和重新部署系统,只需在规则引擎中简单调整规则即可立即生效。

# 常见的规则引擎

通过界面配置的成熟规则引擎
# 这种规则引擎相对来说就比较重,但是功能全,也有部分业务会选择这个,比较出名的有:Drools、URule
# Drools 中文网 : http://www.drools.org.cn
# URule 官网 : http://www.bstek.com

基于JVM脚本语言
# 这种其实不是一个成熟的规则引擎,应该算是规则引擎中的核心技术,因为Drools这种相对太重了
# 很多公司会基于一些jvm的脚本语言,开发轻量级的规则引擎,如:Groovy、AviatorScript、QLExpress
# Groovy 官网 : http://www.groovy-lang.org
# AviatorScript 文档 : https://www.yuque.com/boyan-avfmj/aviatorscript
# QLExpress GitHub : https://github.com/alibaba/QLExpress

基于java代码的规则引擎
# 上面是基于jvm脚本语言去做的,会有一些语法学习的成本,所以就有基于java代码去做的规则引擎
# 比如通过一些注解实现抽象的方式去做到规则的扩展,比较出名的有: Easy Rules
# Easy Rules GitHub : https://github.com/j-easy/easy-rules
# jvs-rules : 可视化的业务规则设计器 https://gitee.com/software-minister/jvs-rules

# AviatorScript

# AviatorScript 编译和执行的入口是 AviatorEvaluatorInstance 类
# 该类的一个实例就是一个编译和执行的单元,这个单元我们称为一个 AviatorScript 引擎
# 你可以多个引擎,每个引擎可以设置不同的编译和运行选项
# AviatorEvaluator.getInstance() 返回一个全局共享的 AviatorScript 引擎

# 编译脚本文件

  • 在你的 java 项目里引用下 AviatorScript 的依赖
<dependency>
  <groupId>com.googlecode.aviator</groupId>
  <artifactId>aviator</artifactId>
  <version>5.3.1</version>
</dependency>
  • 编写你的第一个 AviatorScript 脚本,AviatorScript 脚本源码文件约定以 .av 作为文件后缀
## av/hello.av  两个 # 号引入一行注释

println("hello, AviatorScript!");
  • 编写一个类来运行测试脚本
package com.example.springbootdemo.test;

import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.AviatorEvaluatorInstance;
import com.googlecode.aviator.Expression;

public class AviatorScriptDemo {

    public static void main(String[] args) throws Exception {

        // 使用 AviatorEvaluatorInstance#compileScript 方法编译脚本到 Expression 对象
        Expression exp = AviatorEvaluator.getInstance().compileScript("av/hello.av");
        // 调用 Expression#execute() 方法执行
        exp.execute();
    }
}

# Easy Rules

项目地址 (opens new window)

# 规则条数在几条到几十条,不太复杂
# 规则数量几千条以上 + 需要高性能推理 → Drools
# 支持 MVEL、SpEL、JEXL 表达式语言,规则可外置到配置文件
# 支持规则组合
# MIT 协议完全开源
  • 引入依赖
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-core</artifactId>
    <version>4.1.0</version>
</dependency>
  • 按需加表达式语言模块
<!-- MVEL 表达式 -->
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-mvel</artifactId>
    <version>4.1.0</version>
</dependency>

<!-- SpEL(Spring 表达式) -->
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-spel</artifactId>
    <version>4.1.0</version>
</dependency>

<!-- 组合规则 -->
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-support</artifactId>
    <version>4.1.0</version>
</dependency>
  • 最快验证——判断下雨要不要带伞
@Rule(name = "weather rule", description = "if it rains then take an umbrella")
publicclass WeatherRule {

    @Condition
    public boolean itRains(@Fact("rain") boolean rain) {
        return rain;
    }

    @Action
    public void takeAnUmbrella() {
        System.out.println("It rains, take an umbrella!");
    }
}

publicclass Test {
    public static void main(String[] args) {
        Facts facts = new Facts();
        facts.put("rain", true);

        Rules rules = new Rules();
        rules.register(new WeatherRule());

        RulesEngine engine = new DefaultRulesEngine();
        engine.fire(rules, facts);
        // 输出:It rains, take an umbrella!
    }
}

# 三个核心概念

  • Rule(规则)
# 规则有名字、描述和优先级三个属性——数值越小优先级越高,默认 Integer.MAX_VALUE
# 条件(Condition)+ 动作(Action),条件返回 boolean,为 true 时执行动作
  • Facts(事实)
# key-value 容器,底层就是 HashMap<String, Object>
# 规则里通过 @Fact("key") 注入对应值
Facts facts = new Facts();
facts.put("age", 25);
facts.put("vip", true);

// 规则里这样用
@Condition
public boolean check(@Fact("age") int age, @Fact("vip") boolean vip) {
    return age >= 18 && vip;
}

注意

@Fact 引用的 key 不存在时,引擎直接抛 RuntimeException

  • RulesEngine(引擎)
# DefaultRulesEngine 按优先级跑一遍,最常用
# InferenceRulesEngine 反复跑直到没有规则被触发,适合推断场景
# 两种实现,大多数业务用 Default 就够了

# 定义规则的三种方式

  • 注解(最常用)
# @Rule 定义名称和优先级
# @Condition 标记条件方法(一个规则只能有一个)
# @Action 标记动作方法(可多个,order 控制顺序)
# @Fact 注入事实值
@Rule(name = "vip discount", priority = 1)
publicclass VipDiscountRule {

    @Condition
    public boolean isVip(@Fact("user") User user) {
        return user.isVip();
    }

    @Action(order = 1)
    public void applyDiscount(@Fact("order") Order order) {
        order.applyDiscount(0.9);
    }

    @Action(order = 2)
    public void log(@Fact("order") Order order) {
        System.out.println("已对订单 " + order.getId() + " 应用 VIP 9 折");
    }
}
  • RuleBuilder API
# 不想单独建类时用,适合动态创建或逻辑很简单的场景
Rule ageRule = new RuleBuilder()
    .name("age rule")
    .description("成年判断")
    .priority(1)
    .when(facts -> {
        int age = facts.get("age");
        return age >= 18;
    })
    .then(facts -> System.out.println("已成年,可以操作"))
    .build();
  • 表达式语言(规则外置)
# 规则写在 YAML 里,不重新部署就能改
name: "weather rule"
description: "if it rains then take an umbrella"
condition: "rain == true"
actions:
  - "System.out.println(\"It rains, take an umbrella!\");"
// 也可以用 SpEL,SpEL 功能更强(方法调用 + 集合操作),MVEL 语法更简洁
// 不少团队用 MVEL + YAML 实现规则配置化
Rule spelRule = new SpELRule()
    .name("spel rule")
    .when("#{ #facts['age'] >= 18 }")
    .then("#{ T(java.lang.System).out.println('已成年') }");

# 组合规则

easy-rules-support 提供三种组合方式:

  • UnitRuleGroup(全部满足才执行)
# 组内所有规则条件全部为 true 才执行所有动作
# 适合"VIP 且满 200"这种多条件同时满足场景
UnitRuleGroup group = new UnitRuleGroup("vip full reduction");
group.addRule(vipRule);
group.addRule(fullAmountRule);
  • ActivationRuleGroup(命中第一个就停)
# 按优先级执行,某条触发后剩下的不跑
# 适合互斥折扣——大单优先、VIP 次之、普通兜底
ActivationRuleGroup discountGroup = new ActivationRuleGroup("discount rule");
discountGroup.addRule(bigOrderRule);
discountGroup.addRule(vipRule);
discountGroup.addRule(normalRule);
  • ConditionalRuleGroup(最高优先级当门禁)
# 优先级最高的规则充当入口守卫,它通过后其他规则才继续
# 适合先校验权限再做计算

# 引擎参数

DefaultRulesEngine 支持三个常用参数:

skipOnFirstAppliedRule	
# 某条规则触发后跳过后面所有,默认false

skipOnFirstFailedRule	
# 某条规则执行失败后停,默认false

skipOnFirstNonTriggeredRule	
# 某条规则条件不满足时停,默认false
RulesEngineParameters parameters = new RulesEngineParameters()
    .skipOnFirstAppliedRule(true)
    .skipOnFirstFailedRule(true);
// 默认全部 false 引擎会把所有规则都尝试跑一遍。需要短路逻辑时再开
RulesEngine engine = new DefaultRulesEngine(parameters);

# 集成 Spring Boot

  • 配置类
@Configuration
publicclass RuleEngineConfig {

    @Bean
    public RulesEngine rulesEngine() {
        returnnew DefaultRulesEngine(
            new RulesEngineParameters().skipOnFirstNonTriggeredRule(true)
        );
    }

    @Bean
    public Rules rules() {
        Rules rules = new Rules();
        rules.register(new VipDiscountRule());
        rules.register(new FullAmountRule());
        return rules;
    }
}
  • Service 里用
@Service
publicclass OrderService {

    @Autowired
    private RulesEngine rulesEngine;
	
    @Autowired
    private Rules rules;

    public Order applyDiscount(Order order, User user) {
        Facts facts = new Facts();
        facts.put("order", order);
        facts.put("user", user);
        rulesEngine.fire(rules, facts);
        return order;
    }
}

# Drools

Drools 核心组件:

# KIE(Knowledge Is Everything):统一知识管理容器
# DRL(Drools Rule Language):规则描述语言
# Guvnor(Workbench):可视化规则设计器
# Phreak 算法:新一代高效规则匹配引擎

# Spring Boot 集成实战

  • 依赖配置
<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-spring-boot-starter</artifactId>
    <version>7.73.0.Final</version>
</dependency>
<dependency>
    <groupId>org.kie</groupId>
    <artifactId>kie-spring</artifactId>
    <version>7.73.0.Final</version>
</dependency>
  • 规则引擎配置类
@Configuration
public class DroolsConfig {

    private static final String RULES_PATH = "rules/";

    // 创建 Drools 的文件系统 Bean,用于管理规则文件
    @Bean
    public KieFileSystem kieFileSystem() throws IOException {
		// 获取 KieServices 实例并创建新的内存文件系统
        KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
        Files.list(Paths.get(RULES_PATH)).forEach(path -> {
			// 将每个规则文件写入到 KieFileSystem 中
            kieFileSystem.write(ResourceFactory.newFileResource(path.toFile()));
        });
        return kieFileSystem;
    }

    // Drools 的核心容器,包含所有编译后的规则
    @Bean
    public KieContainer kieContainer() throws IOException {
        KieServices kieServices = getKieServices();
        KieRepository kieRepository = kieServices.getRepository(); // 规则仓库
        // 使用文件系统中的规则创建构建器
        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem()).buildAll();
        // 获取编译过程中的所有消息(包括错误、警告)
        kieBuilder.getResults().getMessages().forEach(m -> 
            System.out.println("规则编译: " + m)
        );
        // 创建并返回 KieContainer,使用默认的发布 ID
        return kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
    }

    // 实际执行规则的会话
    @Bean
    public KieSession kieSession() throws IOException {
		// 从 KieContainer 创建新的规则会话并返回
        return kieContainer().newKieSession();
    }
}

// 这段配置代码完成了 Drools 规则引擎的完整初始化流程:
// 1、扫描 rules/ 目录下的规则文件
// 2、将规则文件加载到内存文件系统
// 3、编译规则
// 4、创建规则容器
// 5、提供规则会话供业务代码使用

# 可视化规则设计(Workbench)

  • 部署 Drools Workbench
# 访问 http://localhost:8080/business-central (admin/admin)
docker run -p 8080:8080 -p 8001:8001 -d --name drools-workbench jboss/drools-workbench-showcase:7.73.0.Final
  • 创建决策表
条件 动作
会员等级=VIP 折扣率=0.8
购物车金额>1000 折扣率=0.85
活动期间 && 新用户 折扣率=0.7
  • 规则版本管理
// 获取特定版本规则
KieContainer container = kieServices.newKieContainer(
    kieServices.newReleaseId("com.example", "discount-rules", "1.2.0")
);

# 核心源码解析

  • 规则匹配引擎(Phreak算法)
public class PhreakRuleEngine implements RuleEngine {
    public void fireRules(WorkingMemory wm) {
        // 1. 创建规则网络
        RuleNetwork network = buildRuleNetwork(rules);

        // 2. 事实对象传播
        for (Object fact : wm.getFacts()) {
            network.insert(fact);
        }

        // 3. 执行匹配(惰性求值)
        List<Activation> activations = network.match();

        // 4. 按优先级执行
        activations.sort(Comparator.reverseOrder());
        for (Activation act : activations) {
            act.execute();
        }
    }
}
  • Spring集成适配器
public class KieAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public KieContainer kieContainer() {
        return KieServices.Factory.get().getKieClasspathContainer();
    }

    @Bean
    @ConditionalOnMissingBean
    public KieSession kieSession(KieContainer kieContainer) {
        return kieContainer.newKieSession();
    }

    @Bean
    public DroolsRuleEngine droolsRuleEngine(KieSession kieSession) {
        return new DefaultDroolsRuleEngine(kieSession);
    }
}
  • 规则热更新机制
@RestController
public class RuleUpdateController {

    @Autowired
    private KieContainer kieContainer;

    @PostMapping("/reload-rules")
    public void reloadRules() {
        // 触发规则重新加载
        kieContainer.updateToVersion(
            kieContainer.getReleaseId().getVersion()
        );
    }
}

# 企业级最佳实践

  • 规则版本灰度发布
public class RuleVersionRouter {

    @Value("${rule.version.weights}")
    private Map<String, Double> versionWeights;

    public KieSession getVersionedSession() {
        String version = selectVersionByWeight();
        return kieContainer.newKieSession(version);
    }

    private String selectVersionByWeight() {
        double rand = Math.random();
        double cumulative = 0.0;

        for (Map.Entry<String, Double> entry : versionWeights.entrySet()) {
            cumulative += entry.getValue();
            if (rand <= cumulative) {
                return entry.getKey();
            }
        }
        return "default";
    }
}
  • 规则执行监控
public class RuleExecutionListener extends DefaultAgendaEventListener {

    @Override
    public void afterMatchFired(AfterMatchFiredEvent event) {
        Rule rule = event.getMatch().getRule();
        long duration = System.currentTimeMillis() - startTime;

        // 记录规则执行日志
        RuleLog log = new RuleLog(
            rule.getName(), 
            duration, 
            event.getMatch().getObjects()
        );

        // 发送到监控系统
        metricsService.recordRuleExecution(log);
    }
}

// 注册监听器
kieSession.addEventListener(new RuleExecutionListener());
  • 性能优化策略
// 启用无状态会话(无会话缓存)
StatelessKieSession session = kieContainer.newStatelessKieSession();

// 批量执行优化
List<Object> facts = Arrays.asList(order1, order2, order3);
session.execute(facts); // 一次引擎调用处理所有事实

// 规则分区执行
@Bean
public KieBase kieBase() {
    KieBaseConfiguration config = KieServices.Factory.get().newKieBaseConfiguration();
    config.setOption(PartitionEvaluationOption.PARTITIONED); // 启用分区
    return kieContainer.newKieBase(config);
}

# 实战:电商促销系统开发

  • 领域模型设计
public class PromotionContext {
    private User user;
    private Order order;
    private List<Product> products;
    private double finalDiscount = 1.0; // 默认无折扣
    private List<String> appliedRules = new ArrayList<>();
}

public class User {
    private String level; // VIP, NORMAL
    private int points;
    private boolean isNew;
}
  • 规则服务层
@Service
public class PromotionService {

    @Autowired
    private KieSession kieSession;

    public PromotionContext applyPromotions(PromotionContext context) {
        // 插入事实对象
        kieSession.insert(context.getUser());
        kieSession.insert(context.getOrder());
        context.getProducts().forEach(kieSession::insert);

        // 全局变量设置
        kieSession.setGlobal("logger", LoggerFactory.getLogger(getClass()));

        // 执行规则
        kieSession.insert(context);
        kieSession.fireAllRules();

        return context;
    }
}
  • 可视化规则设计(DRL示例)
rule "NewUserWelcomeDiscount"
    when
        $user : User(isNew == true)
        $ctx : PromotionContext(user == $user)
    then
        $ctx.setFinalDiscount(0.8);
        $ctx.addAppliedRule("新用户首单8折");
end

rule "VIPBirthdayDiscount"
    when
        $user : User(level == "VIP", birthday == today())
        $ctx : PromotionContext(user == $user)
    then
        $ctx.setFinalDiscount(0.7);
        $ctx.addAppliedRule("VIP生日特惠7折");
end

# 生产环境避坑指南

  • 规则冲突解决
// 显式设置规则优先级
rule "HighPriorityRule"
    salience 100 // 优先级数值越高越先执行
    when ... then ...
end
  • 循环规则防护
// 设置最大规则触发次数
KieSessionConfiguration config = KieServices.Factory.get()
    .newKieSessionConfiguration();
config.setProperty("drools.maxRuleExecutions", "10000");
  • 内存泄漏预防
try (KieSession session = kieContainer.newKieSession()) {
    // 使用try-with-resources自动释放资源
    session.insert(fact);
    session.fireAllRules();
}