开篇:为什么你必须掌握规则引擎?
在业务开发中,你一定遇到过这些痛点:营销活动的折扣规则每周都要调整,每次改规则都要改代码、打包、上线,全量回归风险极高;风控系统的反欺诈规则每天都要迭代,硬编码的逻辑散落在各处,维护成本呈指数级增长;审批流程的条件规则频繁变动,业务人员只能排队等开发排期,业务响应效率极低。
规则引擎的核心价值,就是彻底解耦业务规则与系统代码,让频繁变动的业务规则无需开发介入,即可在线配置、动态生效,将规则迭代周期从“天级”压缩到“分钟级”。本文将从底层原理到生产级实战,用通俗语言讲透规则引擎的核心逻辑,帮你彻底掌握规则引擎的开发与落地。
一、规则引擎核心认知:基础概念与适用边界
1. 什么是Java规则引擎?
规则引擎是一种嵌入在应用程序中的组件,它将业务决策逻辑从业务代码中剥离出来,使用预定义的语义模块编写业务规则,通过接收数据输入、解释业务规则、根据规则做出业务决策,实现业务逻辑的灵活配置与快速迭代。
通俗来讲:规则引擎就是把「如果满足XX条件,就执行XX动作」的业务逻辑,从Java代码中抽离出来,交给专门的引擎管理,无需修改代码、重启服务,即可完成规则的更新与生效。
2. 核心适用场景
规则引擎并非银弹,只有在规则频繁变动的场景下才能发挥最大价值,核心适用场景包括:
- 风控反欺诈:交易风控、用户行为风控、反洗钱规则
- 营销活动:折扣规则、满减规则、会员权益规则、优惠券发放规则
- 审批流程:请假审批、报销审批、资质审核的条件判断
- 定价策略:商品定价、保费定价、服务费计算规则
- 合规校验:监管合规要求、数据校验规则、合同条款校验
3. 硬编码VS规则引擎 核心对比
| 对比维度 | 硬编码实现 | 规则引擎实现 |
| 规则与代码耦合度 | 极高,规则写死在业务代码中 | 极低,规则完全与业务代码解耦 |
| 规则迭代效率 | 极低,改规则需改代码、编译、打包、上线 | 极高,规则可在线配置、动态生效 |
| 业务人员参与度 | 完全无法参与,必须依赖开发人员 | 可通过可视化界面直接配置规则 |
| 规则可维护性 | 极差,规则散落在代码各处,难以排查 | 极好,规则统一管理、版本控制、可追溯 |
| 上线风险 | 改规则需全量回归,上线风险高 | 规则灰度发布,增量更新,风险可控 |
| 适用场景 | 规则固定不变,几乎不迭代的场景 | 规则频繁变动,需快速响应业务变化的场景 |
4. 易混淆概念明确区分
很多开发者会把规则引擎与工作流引擎、流程编排引擎混为一谈,三者的核心边界完全不同:
- 规则引擎:核心解决「WHAT」的问题,即满足什么条件,执行什么动作,聚焦于业务决策逻辑,无固定流程顺序
- 工作流引擎:核心解决「HOW」的问题,即业务按什么步骤、什么顺序流转,聚焦于流程的顺序、分支、回退、审批
- 流程编排引擎:核心解决「多个服务怎么组合执行」的问题,聚焦于分布式系统中多个服务的调用顺序、异常处理、降级熔断
二、主流Java规则引擎选型对比(2026最新稳定版)
本文所有选型均基于2026年2月最新的稳定版本,从性能、学习成本、功能完整性、社区活跃度四个维度进行权威对比,帮你快速选择适合业务场景的引擎。
| 引擎名称 | 最新稳定版 | 核心优势 | 核心劣势 | 适用场景 |
| Drools | 8.44.0.Final | 业界标杆,功能最全,支持rete/phreak算法,社区活跃,文档完善,支持动态规则热更新 | 学习成本高,有一定性能开销,轻量场景过重 | 企业级复杂规则场景,大量规则的风控、合规系统 |
| Easy Rules | 4.1.0 | 轻量级极简框架,API简单易懂,学习成本极低,零依赖,支持注解和编程式两种方式 | 功能简单,不支持复杂的模式匹配,无规则管理体系 | 简单规则场景,小型项目的规则校验、简单业务决策 |
| LiteFlow | 2.11.1 | 国内开源,组件化规则编排,支持多种脚本语言,可视化界面完善,中文文档齐全,国内社区活跃 | 核心偏向流程编排,复杂规则模式匹配能力弱于Drools | 国内企业级项目,规则编排、流程驱动的业务场景 |
| URule | 3.0.3 | 国产商业开源,全可视化规则配置,支持决策表、决策树,中文支持完善,适配国内企业需求 | 企业版收费,开源版功能有限,社区活跃度低于Drools | 国内政企项目,需要可视化规则配置、无代码开发的场景 |
选型建议:
- 简单规则场景,快速上手:选Easy Rules
- 企业级复杂规则场景,海量规则管理:选Drools
- 国内项目,规则编排为主,需要中文文档:选LiteFlow
- 政企项目,需要全可视化无代码配置:选URule
三、底层原理深度拆解:通俗讲透规则引擎的核心逻辑
1. 规则引擎核心架构
规则引擎的核心架构分为5大核心模块,各模块职责清晰,协同完成规则的全生命周期管理,架构图如下:
- 规则管理中心:负责规则的新增、修改、版本管理、灰度发布、权限控制
- 规则解析器:负责将规则脚本编译解析成引擎可识别的内部结构,构建规则网络
- 规则执行引擎:核心模块,负责事实对象与规则的模式匹配,找出满足条件的规则
- 议程调度器:负责对满足条件的规则进行优先级排序,解决规则冲突,确定执行顺序
- 事实对象:规则引擎的输入数据,即业务数据,所有规则的条件判断都基于事实对象
2. 核心算法:Rete算法通俗拆解
Rete算法是由Charles Forgy在1979年提出的,是目前绝大多数规则引擎的核心算法,核心思想是用空间换时间,通过共享规则的条件节点,避免重复计算,大幅提升大量规则下的模式匹配效率。
举个通俗的例子:如果有1000条规则,每条规则都包含「订单金额>1000」这个条件,硬编码会对每条规则都执行一次这个判断,总共执行1000次;而Rete算法只会执行1次这个判断,把结果共享给所有用到这个条件的规则,极大减少重复计算。
Rete网络核心节点与执行流程
Rete算法会将所有规则拆解成节点,构建一个有向无环图(Rete网络),核心节点与执行流程如下:
- 根节点:Rete网络的入口,所有事实对象都会进入根节点
- 类型节点:过滤事实对象的类型,只保留规则需要的对象类型,比如只处理OrderRiskFact类型的对象
- Alpha节点:单条件过滤节点,负责对事实对象的单个字段进行条件判断,比如「订单金额>1000」「用户等级==3」,每个Alpha节点对应一个独立的条件,相同条件会共享同一个Alpha节点
- Beta节点:多条件关联节点,负责对多个事实对象的条件进行关联匹配,比如「用户订单金额>1000 且 用户历史订单数>5」,实现多条件的组合判断
- 终端节点:当所有条件都满足时,会进入终端节点,触发对应的规则,将规则加入议程调度器
- 议程调度:对所有触发的规则按优先级排序,解决规则冲突,按顺序执行规则
Phreak算法:Rete算法的企业级优化
Drools 6.x之后引入了Phreak算法,针对Rete算法在事实更新时的性能瓶颈做了核心优化,也是目前Drools的默认算法,核心优化点:
- 延迟计算:只有当规则需要执行时,才进行模式匹配,避免不必要的计算
- 基于堆的议程调度:优化了冲突解决的效率,支持更灵活的优先级排序
- 节点内存优化:大幅减少了Beta节点的内存占用,降低了海量规则下的内存开销
- 并行匹配:支持多线程并行模式匹配,充分利用多核CPU的性能
3. 规则引擎完整执行生命周期
规则引擎的执行分为5个核心阶段,形成完整的闭环:
- 事实插入:将业务数据(事实对象)插入到规则会话中
- 模式匹配:规则引擎通过Rete/Phreak网络,将事实对象与所有规则进行匹配,找出所有满足条件的规则
- 冲突解决:对所有满足条件的规则,按优先级、生效时间等规则进行排序,确定执行顺序
- 规则执行:按顺序执行规则的动作逻辑,更新事实对象或执行业务操作
- 事实更新:规则执行后更新的事实对象,会重新进入模式匹配阶段,触发相关的规则(需避免死循环)
四、生产级实战:从零搭建可落地的规则引擎项目
本文所有实战代码均基于JDK 17编写。
项目基础环境说明
- JDK版本:JDK 17
- 项目管理:Maven
- 核心框架:Spring Boot 3.2.3
- 持久层框架:MyBatis Plus 3.5.6
- 数据库:MySQL 8.0
- 接口文档:Swagger3(springdoc 2.5.0)
- 规则引擎:Drools 8.44.0.Final、Easy Rules 4.1.0
- 工具类:Spring工具类、Guava 33.1.0-jre、FastJSON2 2.0.52
实战一:轻量级规则引擎Easy Rules 快速上手
Easy Rules是极简的轻量级规则引擎,零依赖,API简单易懂,适合简单规则场景,5分钟即可完成开发。
1. Maven核心依赖
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
2. 规则定义:订单折扣规则
package com.jam.demo.rule;
import lombok.extern.slf4j.Slf4j;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 订单折扣规则
* @author ken
*/
@Slf4j
@Rule(name = "order_discount_rule", description = "订单金额折扣规则", priority = 1)
public class OrderDiscountRule {
/**
* 规则条件:订单金额大于等于1000,打9折
* @param orderAmount 订单金额
* @return 是否满足条件
*/
@Condition
public boolean isDiscountAvailable(@Fact("orderAmount") BigDecimal orderAmount) {
return orderAmount.compareTo(new BigDecimal("1000")) >= 0;
}
/**
* 规则执行动作:计算折扣后金额
* @param orderAmount 订单金额
*/
@Action
public void applyDiscount(@Fact("orderAmount") BigDecimal orderAmount) {
BigDecimal discountAmount = orderAmount.multiply(new BigDecimal("0.9")).setScale(2, RoundingMode.HALF_UP);
log.info("订单金额{}元,满足折扣条件,折扣后金额:{}元", orderAmount, discountAmount);
}
}
3. 规则执行测试类
package com.jam.demo.test;
import com.jam.demo.rule.OrderDiscountRule;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import java.math.BigDecimal;
/**
* Easy Rules测试类
* @author ken
*/
public class EasyRuleTest {
public static void main(String[] args) {
// 1. 创建规则引擎
RulesEngine rulesEngine = new DefaultRulesEngine();
// 2. 创建规则集合
Rules rules = new Rules();
rules.register(new OrderDiscountRule());
// 3. 创建事实对象
Facts facts = new Facts();
facts.put("orderAmount", new BigDecimal("2000"));
// 4. 执行规则
rulesEngine.fire(rules, facts);
}
}
4. 执行结果
运行测试类,控制台输出如下,规则执行成功:
INFO com.jam.demo.rule.OrderDiscountRule - 订单金额2000元,满足折扣条件,折扣后金额:1800.00元
实战二:企业级Drools 8.x 生产级落地实战
Drools是业界标杆的企业级规则引擎,本实战将从零搭建一个电商订单风控规则引擎,支持规则持久化、动态热更新、REST接口调用,可直接用于生产环境。
1. 完整Maven pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/>
</parent>
<groupId>com.jam.demo</groupId>
<artifactId>rule-engine-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rule-engine-demo</name>
<description>Drools Rule Engine Demo</description>
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<drools.version>8.44.0.Final</drools.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<springdoc.version>2.5.0</springdoc.version>
<fastjson2.version>2.0.52</fastjson2.version>
<guava.version>33.1.0-jre</guava.version>
<lombok.version>1.18.32</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-bom</artifactId>
<version>${drools.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-mvel</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-internal</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. MySQL数据库表结构(MySQL 8.0 可直接执行)
CREATE DATABASE IF NOT EXISTS rule_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE rule_db;
DROP TABLE IF EXISTS t_rule_info;
CREATE TABLE t_rule_info (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
rule_key VARCHAR(64) NOT NULL COMMENT '规则唯一标识',
rule_name VARCHAR(128) NOT NULL COMMENT '规则名称',
rule_content TEXT NOT NULL COMMENT '规则内容(drl脚本)',
rule_type VARCHAR(32) NOT NULL COMMENT '规则类型',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-禁用 1-启用',
version INT NOT NULL DEFAULT 1 COMMENT '规则版本',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
create_by VARCHAR(64) NOT NULL DEFAULT 'system' COMMENT '创建人',
update_by VARCHAR(64) NOT NULL DEFAULT 'system' COMMENT '更新人',
remark VARCHAR(512) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (id),
UNIQUE KEY uk_rule_key_version (rule_key, version),
KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='规则信息表';
3. application.yml 配置文件
spring:
application:
name: rule-engine-demo
datasource:
url: jdbc:mysql://127.0.0.1:3306/rule_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
type-aliases-package: com.jam.demo.entity
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
swagger-ui:
enabled: true
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
server:
port: 8080
4. 核心实体类与数据层
规则信息实体类 RuleInfo
package com.jam.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 规则信息实体类
* @author ken
*/
@Data
@TableName("t_rule_info")
@Schema(description = "规则信息实体")
public class RuleInfo {
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID", example = "1")
private Long id;
@Schema(description = "规则唯一标识", example = "order_risk_rule")
private String ruleKey;
@Schema(description = "规则名称", example = "订单风控规则")
private String ruleName;
@Schema(description = "规则内容(drl脚本)", example = "rule \"xxx\" when then end")
private String ruleContent;
@Schema(description = "规则类型", example = "RISK")
private String ruleType;
@Schema(description = "状态:0-禁用 1-启用", example = "1")
private Integer status;
@Schema(description = "规则版本", example = "1")
private Integer version;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "创建人", example = "system")
private String createBy;
@Schema(description = "更新人", example = "system")
private String updateBy;
@Schema(description = "备注", example = "订单风控核心规则")
private String remark;
}
Mapper接口 RuleInfoMapper
package com.jam.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jam.demo.entity.RuleInfo;
import org.apache.ibatis.annotations.Mapper;
/**
* 规则信息Mapper接口
* @author ken
*/
@Mapper
public interface RuleInfoMapper extends BaseMapper<RuleInfo> {
}
5. 规则管理服务层
服务接口 RuleInfoService
package com.jam.demo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.jam.demo.entity.RuleInfo;
import java.util.List;
/**
* 规则信息服务接口
* @author ken
*/
public interface RuleInfoService extends IService<RuleInfo> {
/**
* 查询所有启用的规则
* @return 启用的规则列表
*/
List<RuleInfo> listEnabledRules();
/**
* 根据规则key查询最新版本的启用规则
* @param ruleKey 规则唯一标识
* @return 规则信息
*/
RuleInfo getLatestEnabledRuleByKey(String ruleKey);
/**
* 新增或更新规则
* @param ruleInfo 规则信息
* @return 是否成功
*/
boolean saveOrUpdateRule(RuleInfo ruleInfo);
}
服务实现类 RuleInfoServiceImpl
package com.jam.demo.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jam.demo.entity.RuleInfo;
import com.jam.demo.mapper.RuleInfoMapper;
import com.jam.demo.service.RuleInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* 规则信息服务实现类
* @author ken
*/
@Slf4j
@Service
public class RuleInfoServiceImpl extends ServiceImpl<RuleInfoMapper, RuleInfo> implements RuleInfoService {
private final PlatformTransactionManager transactionManager;
public RuleInfoServiceImpl(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Override
public List<RuleInfo> listEnabledRules() {
LambdaQueryWrapper<RuleInfo> queryWrapper = new LambdaQueryWrapper<RuleInfo>()
.eq(RuleInfo::getStatus, 1);
return this.list(queryWrapper);
}
@Override
public RuleInfo getLatestEnabledRuleByKey(String ruleKey) {
if (!StringUtils.hasText(ruleKey)) {
log.warn("规则key为空,无法查询规则");
return null;
}
LambdaQueryWrapper<RuleInfo> queryWrapper = new LambdaQueryWrapper<RuleInfo>()
.eq(RuleInfo::getRuleKey, ruleKey)
.eq(RuleInfo::getStatus, 1)
.orderByDesc(RuleInfo::getVersion)
.last("LIMIT 1");
return this.getOne(queryWrapper);
}
@Override
public boolean saveOrUpdateRule(RuleInfo ruleInfo) {
if (ObjectUtils.isEmpty(ruleInfo)) {
log.warn("规则信息为空,无法保存");
return false;
}
if (!StringUtils.hasText(ruleInfo.getRuleKey())) {
log.warn("规则key为空,无法保存");
return false;
}
// 编程式事务管理
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("saveOrUpdateRuleTransaction");
def.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 查询当前规则的最新版本号
RuleInfo latestRule = getLatestEnabledRuleByKey(ruleInfo.getRuleKey());
if (!ObjectUtils.isEmpty(latestRule)) {
// 版本号+1,禁用旧版本规则
ruleInfo.setVersion(latestRule.getVersion() + 1);
latestRule.setStatus(0);
this.updateById(latestRule);
} else {
ruleInfo.setVersion(1);
}
// 保存新版本规则
boolean result = this.save(ruleInfo);
transactionManager.commit(status);
log.info("规则{}保存成功,版本号:{}", ruleInfo.getRuleKey(), ruleInfo.getVersion());
return result;
} catch (Exception e) {
transactionManager.rollback(status);
log.error("规则保存失败,回滚事务", e);
return false;
}
}
}
6. Drools核心配置类(支持动态规则热更新)
package com.jam.demo.config;
import com.jam.demo.entity.RuleInfo;
import com.jam.demo.service.RuleInfoService;
import lombok.extern.slf4j.Slf4j;
import org.drools.compiler.compiler.DroolsParserException;
import org.drools.compiler.kie.builder.impl.InternalKieModule;
import org.drools.compiler.kie.builder.impl.KieContainerImpl;
import org.drools.compiler.kie.builder.impl.KieFileSystemImpl;
import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.List;
/**
* Drools规则引擎配置类
* @author ken
*/
@Slf4j
@Configuration
public class DroolsConfig {
private final RuleInfoService ruleInfoService;
public DroolsConfig(RuleInfoService ruleInfoService) {
this.ruleInfoService = ruleInfoService;
}
/**
* 初始化KieServices单例
* @return KieServices实例
*/
@Bean
public KieServices kieServices() {
return KieServices.Factory.get();
}
/**
* 初始化KieFileSystem,加载数据库中的规则
* @param kieServices KieServices实例
* @return KieFileSystem实例
* @throws DroolsParserException 规则解析异常
* @throws IOException IO异常
*/
@Bean
public KieFileSystem kieFileSystem(KieServices kieServices) throws DroolsParserException, IOException {
KieFileSystem kieFileSystem = new KieFileSystemImpl();
// 加载所有启用的规则
List<RuleInfo> ruleList = ruleInfoService.listEnabledRules();
if (CollectionUtils.isEmpty(ruleList)) {
log.warn("未查询到启用的规则,初始化空的KieFileSystem");
return kieFileSystem;
}
// 遍历规则,写入KieFileSystem
for (RuleInfo ruleInfo : ruleList) {
String ruleKey = ruleInfo.getRuleKey();
String ruleContent = ruleInfo.getRuleContent();
if (!StringUtils.hasText(ruleContent)) {
log.warn("规则{}内容为空,跳过加载", ruleKey);
continue;
}
String path = String.format("rules/%s_%d.drl", ruleKey, ruleInfo.getVersion());
kieFileSystem.write(path, ruleContent);
log.info("规则{}加载成功,路径:{}", ruleKey, path);
}
return kieFileSystem;
}
/**
* 初始化KieBuilder,构建规则模块
* @param kieServices KieServices实例
* @param kieFileSystem KieFileSystem实例
* @return KieBuilder实例
*/
@Bean
public KieBuilder kieBuilder(KieServices kieServices, KieFileSystem kieFileSystem) {
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
kieBuilder.buildAll();
// 检查规则编译结果
Results results = kieBuilder.getResults();
if (results.hasMessages(Message.Level.ERROR)) {
List<Message> errorMessages = results.getMessages(Message.Level.ERROR);
log.error("规则编译失败,错误信息:{}", errorMessages);
throw new RuntimeException("规则编译失败:" + errorMessages);
}
log.info("规则编译成功,无错误");
return kieBuilder;
}
/**
* 初始化KieModule,规则模块
* @param kieBuilder KieBuilder实例
* @return KieModule实例
*/
@Bean
public KieModule kieModule(KieBuilder kieBuilder) {
return kieBuilder.getKieModule();
}
/**
* 初始化KieContainer,规则容器
* @param kieServices KieServices实例
* @param kieModule KieModule实例
* @return KieContainer实例
*/
@Bean
public KieContainer kieContainer(KieServices kieServices, KieModule kieModule) {
return kieServices.newKieContainer(kieModule.getReleaseId());
}
/**
* 初始化KieSession,规则会话,用于执行规则
* @param kieContainer KieContainer实例
* @return KieSession实例
*/
@Bean
public KieSession kieSession(KieContainer kieContainer) {
KieSession kieSession = kieContainer.newKieSession();
log.info("KieSession初始化成功");
return kieSession;
}
/**
* 动态刷新规则,无需重启服务
* @param kieServices KieServices实例
* @param kieContainer KieContainer实例
*/
public void refreshRules(KieServices kieServices, KieContainer kieContainer) {
try {
KieFileSystem kieFileSystem = kieFileSystem(kieServices);
KieBuilder kieBuilder = kieBuilder(kieServices, kieFileSystem);
KieModule kieModule = kieModule(kieBuilder);
((KieContainerImpl) kieContainer).updateToKieModule((InternalKieModule) kieModule);
log.info("规则动态刷新成功");
} catch (Exception e) {
log.error("规则动态刷新失败", e);
throw new RuntimeException("规则动态刷新失败", e);
}
}
}
7. 订单风控核心实现
风控事实对象 OrderRiskFact
package com.jam.demo.fact;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单风控事实对象,用于规则引擎执行
* @author ken
*/
@Data
@Schema(description = "订单风控事实对象")
public class OrderRiskFact {
@Schema(description = "订单号", example = "ORD202602280001")
private String orderNo;
@Schema(description = "用户ID", example = "10001")
private Long userId;
@Schema(description = "用户等级:1-普通 2-VIP 3-超级VIP", example = "1")
private Integer userLevel;
@Schema(description = "订单金额", example = "5000.00")
private BigDecimal orderAmount;
@Schema(description = "收货地址是否为常用地址", example = "true")
private Boolean isCommonAddress;
@Schema(description = "用户近30天订单数", example = "5")
private Integer orderCount30Days;
@Schema(description = "用户历史拒付次数", example = "0")
private Integer refusePayCount;
@Schema(description = "风控结果:PASS-放行 REVIEW-人工审核 REJECT-拦截", example = "PASS")
private String riskResult;
@Schema(description = "风控描述", example = "订单正常,放行")
private String riskDesc;
@Schema(description = "订单创建时间")
private LocalDateTime orderCreateTime;
}
风控规则文件 order_risk_rule.drl(resources/rules目录下)
package com.jam.demo.rules;
dialect "mvel"
import com.jam.demo.fact.OrderRiskFact
global org.slf4j.Logger log;
/**
* 规则1:超级VIP用户,订单金额小于10000,直接放行
*/
rule "super_vip_pass_rule"
salience 100
when
$fact: OrderRiskFact(userLevel == 3, orderAmount < 10000, refusePayCount == 0)
then
$fact.setRiskResult("PASS");
$fact.setRiskDesc("超级VIP用户,订单正常,直接放行");
log.info("订单{}触发超级VIP放行规则", $fact.getOrderNo());
end
/**
* 规则2:VIP用户,订单金额小于5000,常用地址,直接放行
*/
rule "vip_pass_rule"
salience 90
when
$fact: OrderRiskFact(userLevel == 2, orderAmount < 5000, isCommonAddress == true, refusePayCount == 0)
then
$fact.setRiskResult("PASS");
$fact.setRiskDesc("VIP用户,订单正常,直接放行");
log.info("订单{}触发VIP放行规则", $fact.getOrderNo());
end
/**
* 规则3:普通用户,订单金额大于10000,直接拦截
*/
rule "normal_user_reject_rule"
salience 80
when
$fact: OrderRiskFact(userLevel == 1, orderAmount >= 10000)
then
$fact.setRiskResult("REJECT");
$fact.setRiskDesc("普通用户订单金额过高,系统自动拦截");
log.warn("订单{}触发高金额拦截规则", $fact.getOrderNo());
end
/**
* 规则4:用户有历史拒付记录,直接拦截
*/
rule "refuse_pay_reject_rule"
salience 1000
when
$fact: OrderRiskFact(refusePayCount > 0)
then
$fact.setRiskResult("REJECT");
$fact.setRiskDesc("用户有历史拒付记录,系统自动拦截");
log.error("订单{}触发拒付记录拦截规则", $fact.getOrderNo());
end
/**
* 规则5:非常用地址,近30天无订单,订单金额大于2000,人工审核
*/
rule "uncommon_address_review_rule"
salience 70
when
$fact: OrderRiskFact(isCommonAddress == false, orderCount30Days == 0, orderAmount >= 2000)
then
$fact.setRiskResult("REVIEW");
$fact.setRiskDesc("非常用地址且无近期订单,需人工审核");
log.info("订单{}触发人工审核规则", $fact.getOrderNo());
end
/**
* 规则6:默认规则,无匹配规则时放行
*/
rule "default_pass_rule"
salience 0
when
$fact: OrderRiskFact(riskResult == null)
then
$fact.setRiskResult("PASS");
$fact.setRiskDesc("无匹配风控规则,默认放行");
log.info("订单{}触发默认放行规则", $fact.getOrderNo());
end
风控服务接口与实现类
package com.jam.demo.service;
import com.jam.demo.fact.OrderRiskFact;
/**
* 订单风控服务接口
* @author ken
*/
public interface OrderRiskService {
/**
* 执行订单风控规则校验
* @param fact 订单风控事实对象
* @return 风控结果
*/
OrderRiskFact executeRiskCheck(OrderRiskFact fact);
}
package com.jam.demo.service.impl;
import com.jam.demo.config.DroolsConfig;
import com.jam.demo.fact.OrderRiskFact;
import com.jam.demo.service.OrderRiskService;
import lombok.extern.slf4j.Slf4j;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
/**
* 订单风控服务实现类
* @author ken
*/
@Slf4j
@Service
public class OrderRiskServiceImpl implements OrderRiskService {
private final KieSession kieSession;
private final KieServices kieServices;
private final KieContainer kieContainer;
private final DroolsConfig droolsConfig;
public OrderRiskServiceImpl(KieSession kieSession, KieServices kieServices, KieContainer kieContainer, DroolsConfig droolsConfig) {
this.kieSession = kieSession;
this.kieServices = kieServices;
this.kieContainer = kieContainer;
this.droolsConfig = droolsConfig;
}
@Override
public OrderRiskFact executeRiskCheck(OrderRiskFact fact) {
if (ObjectUtils.isEmpty(fact)) {
log.warn("风控事实对象为空,无法执行规则");
return null;
}
try {
// 设置全局日志对象
kieSession.setGlobal("log", log);
// 插入事实对象
kieSession.insert(fact);
// 执行所有规则
int ruleFiredCount = kieSession.fireAllRules();
log.info("订单{}风控规则执行完成,触发规则数:{}", fact.getOrderNo(), ruleFiredCount);
return fact;
} catch (Exception e) {
log.error("订单{}风控规则执行失败", fact.getOrderNo(), e);
throw new RuntimeException("风控规则执行失败", e);
} finally {
// 清空会话,避免事实对象残留
kieSession.dispose();
}
}
/**
* 刷新规则
*/
public void refreshRules() {
droolsConfig.refreshRules(kieServices, kieContainer);
}
}
8. REST接口层(Swagger3支持)
订单风控控制器
package com.jam.demo.controller;
import com.jam.demo.fact.OrderRiskFact;
import com.jam.demo.service.OrderRiskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.util.ObjectUtils;
/**
* 订单风控控制器
* @author ken
*/
@Slf4j
@RestController
@RequestMapping("/api/risk")
@Tag(name = "订单风控接口", description = "订单风控规则执行相关接口")
public class OrderRiskController {
private final OrderRiskService orderRiskService;
public OrderRiskController(OrderRiskService orderRiskService) {
this.orderRiskService = orderRiskService;
}
@PostMapping("/check")
@Operation(summary = "执行订单风控校验", description = "传入订单信息,执行风控规则,返回风控结果")
public ResponseEntity<OrderRiskFact> executeRiskCheck(@RequestBody OrderRiskFact fact) {
if (ObjectUtils.isEmpty(fact)) {
return ResponseEntity.badRequest().body(null);
}
OrderRiskFact result = orderRiskService.executeRiskCheck(fact);
return ResponseEntity.ok(result);
}
}
规则管理控制器
package com.jam.demo.controller;
import com.jam.demo.config.DroolsConfig;
import com.jam.demo.entity.RuleInfo;
import com.jam.demo.service.RuleInfoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* 规则管理控制器
* @author ken
*/
@Slf4j
@RestController
@RequestMapping("/api/rule")
@Tag(name = "规则管理接口", description = "规则信息管理、动态刷新相关接口")
public class RuleManageController {
private final RuleInfoService ruleInfoService;
private final DroolsConfig droolsConfig;
private final KieServices kieServices;
private final KieContainer kieContainer;
public RuleManageController(RuleInfoService ruleInfoService, DroolsConfig droolsConfig, KieServices kieServices, KieContainer kieContainer) {
this.ruleInfoService = ruleInfoService;
this.droolsConfig = droolsConfig;
this.kieServices = kieServices;
this.kieContainer = kieContainer;
}
@GetMapping("/list/enabled")
@Operation(summary = "查询所有启用的规则", description = "查询所有状态为启用的规则列表")
public ResponseEntity<List<RuleInfo>> listEnabledRules() {
List<RuleInfo> ruleList = ruleInfoService.listEnabledRules();
return ResponseEntity.ok(ruleList);
}
@GetMapping("/get/{ruleKey}")
@Operation(summary = "查询最新版本规则", description = "根据规则key查询最新版本的启用规则")
public ResponseEntity<RuleInfo> getLatestRule(@PathVariable String ruleKey) {
if (!StringUtils.hasText(ruleKey)) {
return ResponseEntity.badRequest().body(null);
}
RuleInfo ruleInfo = ruleInfoService.getLatestEnabledRuleByKey(ruleKey);
return ResponseEntity.ok(ruleInfo);
}
@PostMapping("/save")
@Operation(summary = "新增或更新规则", description = "新增规则或更新规则版本,自动禁用旧版本")
public ResponseEntity<Boolean> saveOrUpdateRule(@RequestBody RuleInfo ruleInfo) {
if (ObjectUtils.isEmpty(ruleInfo)) {
return ResponseEntity.badRequest().body(false);
}
boolean result = ruleInfoService.saveOrUpdateRule(ruleInfo);
return ResponseEntity.ok(result);
}
@PostMapping("/refresh")
@Operation(summary = "动态刷新规则", description = "重新加载数据库中的规则,无需重启服务")
public ResponseEntity<Boolean> refreshRules() {
try {
droolsConfig.refreshRules(kieServices, kieContainer);
return ResponseEntity.ok(true);
} catch (Exception e) {
log.error("规则刷新失败", e);
return ResponseEntity.internalServerError().body(false);
}
}
}
9. 项目启动类
package com.jam.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 规则引擎demo启动类
* @author ken
*/
@SpringBootApplication
@MapperScan("com.jam.demo.mapper")
public class RuleEngineDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RuleEngineDemoApplication.class, args);
}
}
10. 项目运行与测试
- 执行MySQL脚本,创建数据库和表
- 修改application.yml中的数据库连接信息
- 启动Spring Boot项目,项目启动成功后,访问Swagger3接口文档:http://127.0.0.1:8080/swagger-ui.html
- 调用
/api/risk/check接口,传入以下测试参数,即可执行风控规则:
{
"orderNo": "ORD202602280001",
"userId": 10001,
"userLevel": 1,
"orderAmount": 20000,
"isCommonAddress": true,
"orderCount30Days": 5,
"refusePayCount": 0,
"orderCreateTime": "2026-02-28T12:00:00"
}
- 接口返回结果如下,规则执行成功:
{
"orderNo": "ORD202602280001",
"userId": 10001,
"userLevel": 1,
"orderAmount": 20000,
"isCommonAddress": true,
"orderCount30Days": 5,
"refusePayCount": 0,
"riskResult": "REJECT",
"riskDesc": "普通用户订单金额过高,系统自动拦截",
"orderCreateTime": "2026-02-28T12:00:00"
}
五、生产环境最佳实践与踩坑指南
1. 性能优化核心最佳实践
- 事实对象设计:只包含规则需要的字段,避免冗余字段,尽量使用不可变对象,减少更新触发的全量匹配
- 规则拆分优化:大规则拆分为小规则,按业务场景分类,避免一个规则包含过多条件导致Rete网络过于复杂
- 条件顺序优化:将过滤性强的条件放在规则前面,减少后续节点的计算量;相同条件的规则尽量共享节点
- 会话管理优化:使用短会话模式,每次规则执行后通过
dispose()方法销毁会话,避免事实对象残留导致内存泄漏 - 规则预编译:静态规则启动时预编译,缓存KieBase,避免每次执行都重新编译规则
- 并行匹配开启:海量规则场景下,开启Drools的多线程并行匹配,充分利用多核CPU性能
2. 生产环境高频踩坑与解决方案
| 常见坑 | 根因分析 | 解决方案 |
| 规则死循环 | 规则执行中更新事实对象,导致规则重新触发,无限循环 | 给规则添加no-loop或lock-on-active属性,避免同一规则重复触发 |
| 规则冲突 | 多个规则同时满足条件,优先级设置不合理,执行结果不符合预期 | 明确设置规则的salience优先级,核心规则优先级更高,避免同优先级规则 |
| 内存泄漏 | KieSession未销毁,事实对象无法回收,内存持续上涨 | 使用try-finally结构,每次执行后必须调用dispose()方法销毁会话 |
| 动态规则加载线程安全问题 | 多线程同时刷新规则,导致规则执行异常 | 规则刷新添加分布式锁,保证刷新操作的原子性,刷新时暂停新的规则执行 |
| 规则语法错误导致容器崩溃 | 动态更新的规则存在语法错误,导致整个KieContainer初始化失败 | 规则保存前先做语法校验,隔离错误规则,只加载编译通过的规则 |
3. 规则治理企业级最佳实践
- 版本管理:所有规则必须有版本号,更新规则时新增版本,保留历史版本,支持一键回滚
- 灰度发布:新规则先对小流量用户生效,验证无误后再全量发布,避免全量故障
- 监控告警:监控规则执行时长、触发次数、异常率、匹配耗时,设置阈值告警,及时发现性能问题
- 权限管控:规则的新增、修改、发布必须经过审批,不同角色分配不同权限,避免误操作
- 自动化测试:每个规则必须配套对应的测试用例,规则更新前自动执行测试用例,保证规则逻辑正确
4. 高可用架构设计
- 集群部署:规则引擎服务集群部署,通过负载均衡分发请求,避免单点故障
- 规则同步:规则存储在数据库/配置中心,所有节点实时同步规则,保证集群内规则一致性
- 降级策略:规则引擎服务异常时,降级为默认规则执行,不影响核心业务流程
- 灾备方案:规则数据定期备份,支持跨机房灾备,避免规则数据丢失
六、高频问题权威答疑
1. 规则引擎什么时候该用,什么时候不该用?
该用的场景:规则频繁变动,需要快速响应业务变化;业务人员需要直接配置规则,无需开发介入;规则数量多,逻辑复杂,硬编码难以维护;需要统一管理规则,支持版本控制和审计。不该用的场景:规则固定不变,几乎不迭代;规则逻辑极其简单,只有1-2个条件;性能要求极高(纳秒级响应),规则引擎有固定的性能开销;团队无规则引擎使用经验,学习成本过高。
2. 规则太多,性能下降怎么办?
- 优化Rete网络,共享条件节点,减少重复计算;
- 拆分规则集,不同业务场景使用独立的规则会话,避免一个会话加载所有规则;
- 对规则进行优先级分类,核心规则优先执行,提前终止不必要的匹配;
- 开启多线程并行匹配,充分利用多核CPU性能;
- 对不常用的规则进行懒加载,需要时再加载到会话中。
3. 动态规则热更新的核心实现逻辑是什么?
- 规则存储在数据库/配置中心,规则更新后触发刷新事件;
- 重新构建KieFileSystem和KieModule,编译新的规则;
- 动态更新KieContainer中的规则模块,替换旧的规则;
- 刷新过程中保证线程安全,不影响正在执行的规则;
- 规则更新前必须做语法校验,避免错误规则导致容器崩溃。
4. 规则引擎和表达式引擎(Spring EL/QLExpress)的区别?
- 规则引擎:完整的规则管理、模式匹配、冲突解决、执行调度体系,适合大量复杂规则的企业级场景,支持规则可视化管理、版本控制、动态更新,学习成本较高,有一定性能开销。
- 表达式引擎:轻量级的表达式计算工具,适合简单的条件判断和数值计算,学习成本低,性能极高,但是没有规则管理、冲突解决、议程调度等能力,不适合大量复杂规则的场景。
- 核心总结:简单的条件计算用表达式引擎,复杂的业务规则管理用规则引擎。
5. 如何让业务人员无需写代码即可配置规则?
- 基于规则引擎开发可视化配置界面,提供表单化、拖拽式的规则配置能力;
- 预设通用规则模板,业务人员只需填写参数,无需编写代码;
- 提供规则语法校验和实时预览功能,配置后可立即验证规则效果;
- 结合自然语言处理,支持业务人员用自然语言描述规则,自动转换为规则脚本。
结尾
规则引擎的核心价值,不是替代开发人员写代码,而是将业务规则的控制权还给业务方,让技术团队专注于核心系统的架构设计,而不是陷入无休止的规则迭代中。本文从底层原理到生产级实战,完整覆盖了规则引擎的核心知识,所有代码均可直接编译运行,帮你快速掌握规则引擎的开发与落地。
规则引擎不是银弹,只有在合适的场景下才能发挥最大价值。在实际开发中,需要根据业务场景的复杂度、规则迭代频率、团队技术栈,选择合适的规则引擎,设计合理的规则架构,才能真正实现业务与技术的解耦,提升业务响应效率。