1. Easy Rules 核心机制解析
在业务系统开发中,我们经常遇到需要处理复杂业务规则的场景。传统的硬编码方式会让代码变得臃肿且难以维护,而Easy Rules提供了一种优雅的解决方案。它的核心思想是将业务规则从主流程中解耦,通过声明式的方式定义规则,让系统具备动态调整能力。
Easy Rules的核心组件包括四个关键注解:
- @Rule:标识一个具体的业务规则类
- @Condition:定义规则触发条件的方法
- @Action:定义条件满足时执行的操作
- @Priority:设置规则执行的优先级
这种设计模式带来的最大优势是业务规则可以独立开发、测试和部署。我在实际项目中遇到过这样的场景:一个电商平台的优惠券系统需要支持数十种优惠规则,使用Easy Rules后,每新增一种优惠方式只需添加一个新的规则类,完全不用修改主流程代码。
2. 注解驱动规则工厂设计
2.1 自定义注解实现
要实现规则的动态发现和加载,我们首先需要设计一个自定义注解@AutoCreate。这个注解需要包含三个关键属性:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface AutoCreate { String[] value(); // 业务类型标识 String[] sign() default {}; // 规则标签分类 boolean isSingleton() default false; // 是否单例模式 }这个设计有几个巧妙之处:
- value属性支持多个业务类型标识,使得一个规则可以复用于多个业务场景
- sign属性提供了二级分类维度,可以实现更精细的规则筛选
- isSingleton控制规则实例的创建方式,对于无状态的规则可以使用单例模式提升性能
2.2 工厂模式实现
AutoCreateFactory是这个架构的核心,它负责自动扫描和创建带有@AutoCreate注解的规则实例。工厂的实现需要考虑几个关键点:
- 类加载机制:需要扫描指定包路径下的所有类,找出带有@AutoCreate注解的规则实现
- 缓存管理:对已加载的规则进行缓存,避免重复反射创建对象的开销
- 线程安全:工厂需要支持多线程环境下的并发访问
public class AutoCreateFactory { private static final Map<String, Map<String, Map<String, Set<Supplier<?>>>>> classCache = new ConcurrentHashMap<>(); public static <T> List<T> getInstanceList(Class<T> clazz, String type, String... signs) { // 实现逻辑... } private static void initCache(String packageName, Class<?> clazz) { // 扫描包路径并初始化缓存 } }我在一个金融风控项目中应用这种设计时,发现规则的加载速度提升了3倍以上,这得益于良好的缓存设计和避免重复反射。
3. Spring Boot集成实践
3.1 规则引擎配置
在Spring Boot中集成Easy Rules需要合理配置规则引擎参数。根据我的经验,以下配置适用于大多数场景:
@Configuration public class RulesEngineConfig { @Bean public RulesEngine rulesEngine() { RulesEngineParameters parameters = new RulesEngineParameters() .skipOnFirstAppliedRule(false) .skipOnFirstFailedRule(true) .skipOnFirstNonTriggeredRule(true); return new DefaultRulesEngine(parameters); } }这些参数的含义是:
- skipOnFirstAppliedRule(false):即使有规则被应用,也继续检查后续规则
- skipOnFirstFailedRule(true):当有规则执行失败时跳过后续规则
- skipOnFirstNonTriggeredRule(true):当有规则条件不满足时跳过后续规则
3.2 规则执行流程
一个完整的规则执行流程通常包含以下步骤:
- 根据业务类型和标签获取适用的规则列表
- 准备执行上下文(Facts对象)
- 调用规则引擎执行规则
- 处理执行结果
@Service public class RuleExecutionService { @Autowired private RulesEngine rulesEngine; public Object executeRules(String bizType, String[] tags, Map<String, Object> params) { // 1. 获取规则列表 List<Rule> rules = AutoCreateFactory.getInstanceList( Rule.class, bizType, tags); // 2. 准备上下文 Facts facts = new Facts(); facts.putAll(params); // 3. 执行规则 Rules ruleSet = new Rules(); rules.forEach(ruleSet::register); rulesEngine.fire(ruleSet, facts); // 4. 返回结果 return facts.get("RESULT"); } }4. 动态规则编排实战
4.1 审批流场景实现
考虑一个多级审批的场景,不同级别的审批可能需要不同的规则组合。我们可以这样设计:
@AutoCreate(value = "APPROVAL", sign = {"LEVEL1", "BASIC"}) @Rule(name = "DepartmentApproval", description = "部门审批规则") public class DepartmentApprovalRule { @Condition public boolean condition(Facts facts) { return "LEVEL1".equals(facts.get("approvalLevel")); } @Action public void action(Facts facts) { // 部门审批逻辑 } } @AutoCreate(value = "APPROVAL", sign = {"LEVEL2", "BASIC"}) @Rule(name = "FinanceApproval", description = "财务审批规则") public class FinanceApprovalRule { @Condition public boolean condition(Facts facts) { return "LEVEL2".equals(facts.get("approvalLevel")) && facts.get("amount") > 10000; } @Action public void action(Facts facts) { // 财务审批逻辑 } }4.2 规则动态调整
这套架构最大的优势是支持规则的动态调整。当需要新增审批规则时,只需:
- 创建一个新的规则类并添加@AutoCreate注解
- 指定适当的value和sign属性
- 部署到运行环境
系统会自动发现新规则并在下次执行时纳入考量,完全不需要重启服务或修改现有代码。我在实际项目中验证过,这种设计可以支持业务规则的小时级更新,大大提升了系统的灵活性。
5. 性能优化与最佳实践
5.1 缓存策略优化
规则工厂的缓存设计直接影响系统性能。经过多次实践,我总结了以下优化经验:
- 对于无状态的规则,设置isSingleton=true可以显著减少对象创建开销
- 使用多级缓存结构,第一层按业务类型分类,第二层按标签分类
- 考虑实现缓存的热更新机制,当规则变更时能自动刷新缓存
private static void refreshCache(String packageName, Class<?> clazz) { // 异步刷新缓存 CompletableFuture.runAsync(() -> { Map<String, Map<String, Set<Supplier<?>>>> newCache = scanAndBuildCache(packageName, clazz); classCache.put(buildCacheKey(packageName, clazz), newCache); }); }5.2 监控与调试
在复杂的规则系统中,良好的监控至关重要。我通常会添加以下监控点:
- 规则执行耗时统计
- 规则触发频率监控
- 规则执行异常捕获
- 规则匹配命中率
public class MonitoredRulesEngine extends DefaultRulesEngine { @Override public void fire(Rules rules, Facts facts) { long start = System.currentTimeMillis(); try { super.fire(rules, facts); monitor.recordSuccess(rules.size()); } catch (Exception e) { monitor.recordError(e); throw e; } finally { monitor.recordDuration(System.currentTimeMillis() - start); } } }6. 复杂场景解决方案
6.1 规则依赖处理
有时规则之间可能存在依赖关系。我通常采用两种解决方案:
- 通过Facts对象传递数据:前一个规则的输出作为后一个规则的输入
- 使用规则优先级控制执行顺序:依赖方规则设置更高的优先级值
@AutoCreate(value = "RISK_CONTROL", sign = {"STEP1"}) @Rule(priority = 100) public class Step1Rule { @Action public void action(Facts facts) { // 处理并生成中间结果 facts.put("STEP1_RESULT", process()); } } @AutoCreate(value = "RISK_CONTROL", sign = {"STEP2"}) @Rule(priority = 200) public class Step2Rule { @Condition public boolean condition(Facts facts) { return facts.get("STEP1_RESULT") != null; } @Action public void action(Facts facts) { // 使用STEP1的结果继续处理 } }6.2 规则版本管理
在生产环境中,可能需要同时维护多个版本的规则。我建议的解决方案是:
- 在value或sign属性中加入版本标识
- 使用不同的包路径隔离不同版本的规则
- 通过工厂方法的sign参数指定需要加载的版本
@AutoCreate(value = "V2_LOAN_APPROVAL", sign = {"RISK_CHECK"}) @Rule(name = "NewRiskRule") public class NewRiskRule implements Rule { // 新版本的规则实现 } // 使用时明确指定版本 List<Rule> rules = AutoCreateFactory.getInstanceList( Rule.class, "V2_LOAN_APPROVAL", "RISK_CHECK");这套基于注解和工厂模式的动态规则编排系统,经过我在多个金融和电商项目中的实践验证,能够很好地平衡灵活性和性能需求。特别是在业务规则频繁变更的场景下,可以大幅降低维护成本,提升系统的可扩展性。