简易规则引擎 easy-rules

文章来源声明: 原文作者:海岳云舟; 来源站点:掘金; 原文链接:https://juejin.cn/post/7688929298951323663; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

轻量、易集成、配置化驱动,适合 Spring 项目中的简单规则场景;若需要决策表、复杂流控或大规模规则治理,仍建议评估 Drools 等重型方案。

合理的使用规则引擎可以极大的减少代码复杂度,提升代码可维护性。业界知名的开源规则引擎有Drools,功能丰富,但也比较庞大。在一些简单的场景中,我们只需要简易的规则引擎就能满足要求。 本文介绍一个小巧的规则引擎 [easy-rules](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fj-easy%2Feasy-rules "https://github.com/j-easy/easy-rules"),作为一个lib库提供,支持spring的SPEL表达式,可以很好的集成在spring项目中。

具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/main/spring-easy-rule

博客原文:springcamp.cn/easy-rules

一、概述

通过将业务规则配置的配置文件中,可以精简代码,同时已于维护,当规则修改时,只需要修改配置文件即可。easy-rules是一个小巧的规则引擎,支持spring的SPEL表达式,同时还支持 Apache JEXL 表达式和 MVL 表达式。

二、项目中加入依赖

在项目的gradle中增加依赖关系。

build.gradle:

plugins {
    <span>id</span> <span>'org.springframework.boot'</span> version <span>'3.0.5'</span>
    <span>id</span> <span>'io.spring.dependency-management'</span> version <span>'1.1.0'</span>
    <span>id</span> <span>'java'</span>
}

group = <span>'cn.springcamp'</span>
version = <span>'0.0.1-SNAPSHOT'</span>
sourceCompatibility = <span>'17'</span>

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
    testCompileOnly {
        extendsFrom testAnnotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation <span>"org.springframework.boot:spring-boot-starter-json"</span>
    implementation <span>'org.springframework.boot:spring-boot-starter-validation'</span>
    implementation <span>'org.jeasy:easy-rules-core:4.1.0'</span>
    implementation <span>'org.jeasy:easy-rules-spel:4.1.0'</span>
    implementation <span>'org.jeasy:easy-rules-support:4.1.0'</span>
    annotationProcessor <span>'org.projectlombok:lombok'</span>
    testAnnotationProcessor <span>'org.projectlombok:lombok'</span>
    testImplementation <span>"org.springframework.boot:spring-boot-starter-test"</span>
    testImplementation <span>'org.junit.vintage:junit-vintage-engine'</span>
    testImplementation <span>'org.junit.vintage:junit-vintage-engine'</span>
}

dependencyManagement {
    imports {
        mavenBom <span>"org.springframework.cloud:spring-cloud-dependencies:2022.0.1"</span>
    }
}

<span>test</span> {
    useJUnitPlatform()
}

三、配置文件

示例程序将业务规则放到配置文件中,业务规则配置文件(demo-rule.yml)代码:

<span>name:</span> <span>"age rule"</span>
<span>description:</span> <span>""</span>
<span>priority:</span> <span>1</span>
<span>condition:</span> <span>"#person.getAdult() == false"</span>
<span>actions:</span>
  - <span>"T(java.lang.System).out.println("</span>Shop: Sorry, you are <span>not</span> allowed<span>")"</span>
  - <span>"#person.setAdult(true)"</span>
  - <span>"#person.setAge(18)"</span>
---
<span>name:</span> <span>"alcohol rule"</span>
<span>description:</span> <span>""</span>
<span>priority:</span> <span>1</span>
<span>condition:</span> <span>"#person.getAdult() == true"</span>
<span>actions:</span>
  - <span>"T(java.lang.System).out.println("</span>Shop: you are now allowed<span>")"</span>

配置文件中的规则通过 condition 进行配置,当满足规则时,会调用 actions 中配置的动作。 示例项目使用了spring的SPEL表达式进行规则配置,配置文件中配置了2个规则,第一个规则通过 person 这个spring bean中的getAdult()判断是否满足规则,满足规则时调用三个方法。

在spring-boot本身的配置文件中 application.yml 配置规则文件:

<span>rule:</span>
  <span>skip</span>-<span>on</span>-first-failed-rule: <span>true</span>
  <span>skip</span>-<span>on</span>-first-applied-rule: <span>false</span>
  <span>skip</span>-<span>on</span>-first-non-triggered-rule: <span>true</span>
  rules:
    - rule-id: <span>"demo"</span>
      rule-file-location: <span>"classpath:demo-rule.yml"</span>

四、代码中对规则引擎进行配置

通过 RuleEngineConfig 这个spring的配置类对规则引擎进行配置:

<span>@Slf4j</span>
<span>@EnableConfigurationProperties</span>(<span>RuleEngineConfigProperties</span>.<span>class</span>)
<span>@Configuration</span>
<span>public</span> <span>class</span> <span>RuleEngineConfig</span> <span>implements</span> <span>BeanFactoryAware</span> {
    <span>@Autowired</span>(required = <span>false</span>)
    <span>private</span> <span>List</span><<span>RuleListener</span>> ruleListeners;

    <span>@Autowired</span>(required = <span>false</span>)
    <span>private</span> <span>List</span><<span>RulesEngineListener</span>> rulesEngineListeners;

    <span>private</span> <span>BeanFactory</span> beanFactory;

    <span>@Bean</span>
    <span>public</span> <span>RulesEngineParameters</span> <span>rulesEngineParameters</span>(<span>RuleEngineConfigProperties properties</span>) {
        <span>RulesEngineParameters</span> parameters = <span>new</span> <span>RulesEngineParameters</span>();
        parameters.<span>setSkipOnFirstAppliedRule</span>(properties.<span>isSkipOnFirstAppliedRule</span>());
        parameters.<span>setSkipOnFirstFailedRule</span>(properties.<span>isSkipOnFirstFailedRule</span>());
        parameters.<span>setSkipOnFirstNonTriggeredRule</span>(properties.<span>isSkipOnFirstNonTriggeredRule</span>());
        <span>return</span> parameters;
    }

    <span>@Bean</span>
    <span>public</span> <span>RulesEngine</span> <span>rulesEngine</span>(<span>RulesEngineParameters rulesEngineParameters</span>) {
        <span>DefaultRulesEngine</span> rulesEngine = <span>new</span> <span>DefaultRulesEngine</span>(rulesEngineParameters);
        <span>if</span> (!<span>CollectionUtils</span>.<span>isEmpty</span>(ruleListeners)) {
            rulesEngine.<span>registerRuleListeners</span>(ruleListeners);
        }
        <span>if</span> (!<span>CollectionUtils</span>.<span>isEmpty</span>(rulesEngineListeners)) {
            rulesEngine.<span>registerRulesEngineListeners</span>(rulesEngineListeners);
        }
        <span>return</span> rulesEngine;
    }

    <span>@Bean</span>
    <span>public</span> <span>BeanResolver</span> <span>beanResolver</span>(<span></span>) {
        <span>return</span> <span>new</span> <span>BeanFactoryResolver</span>(beanFactory);
    }

    <span>@Bean</span>
    <span>public</span> <span>RuleEngineTemplate</span> <span>ruleEngineTemplate</span>(<span>RuleEngineConfigProperties properties, RulesEngine rulesEngine</span>) {
        <span>RuleEngineTemplate</span> ruleEngineTemplate = <span>new</span> <span>RuleEngineTemplate</span>();
        ruleEngineTemplate.<span>setBeanResolver</span>(<span>beanResolver</span>());
        ruleEngineTemplate.<span>setProperties</span>(properties);
        ruleEngineTemplate.<span>setRulesEngine</span>(rulesEngine);
        <span>return</span> ruleEngineTemplate;
    }

    <span>@Bean</span>
    <span>public</span> <span>RuleListener</span> <span>defaultRuleListener</span>(<span></span>) {
        <span>return</span> <span>new</span> <span>RuleListener</span>() {
            <span>@Override</span>
            <span>public</span> <span>boolean</span> <span>beforeEvaluate</span>(<span>Rule rule, Facts facts</span>) {
                <span>return</span> <span>true</span>;
            }

            <span>@Override</span>
            <span>public</span> <span>void</span> <span>afterEvaluate</span>(<span>Rule rule, Facts facts, <span>boolean</span> b</span>) {
                log.<span>info</span>(<span>"-----------------afterEvaluate-----------------"</span>);
                log.<span>info</span>(rule.<span>getName</span>() + rule.<span>getDescription</span>() + facts.<span>toString</span>());
            }

            <span>@Override</span>
            <span>public</span> <span>void</span> <span>beforeExecute</span>(<span>Rule rule, Facts facts</span>) {
                log.<span>info</span>(<span>"-----------------beforeExecute-----------------"</span>);
                log.<span>info</span>(rule.<span>getName</span>() + rule.<span>getDescription</span>() + facts.<span>toString</span>());
            }

            <span>@Override</span>
            <span>public</span> <span>void</span> <span>onSuccess</span>(<span>Rule rule, Facts facts</span>) {
                log.<span>info</span>(<span>"-----------------onSuccess-----------------"</span>);
                log.<span>info</span>(rule.<span>getName</span>() + rule.<span>getDescription</span>() + facts.<span>toString</span>());
            }

            <span>@Override</span>
            <span>public</span> <span>void</span> <span>onFailure</span>(<span>Rule rule, Facts facts, Exception e</span>) {
                log.<span>info</span>(<span>"-----------------onFailure-----------------"</span>);
                log.<span>info</span>(rule.<span>getName</span>() + <span>"----------"</span> + rule.<span>getDescription</span>() + facts.<span>toString</span>() + e.<span>toString</span>());
            }
        };
    }

    <span>@Override</span>
    <span>public</span> <span>void</span> <span>setBeanFactory</span>(<span>BeanFactory</span> beanFactory) throws <span>BeansException</span> {
        <span>this</span>.<span>beanFactory</span> = beanFactory;
    }
}

配置文件中配置了 ruleEngineTemplate 这个spring bean,通过ruleEngineTemplate触发规则引擎的执行。

五、执行规则引擎

ruleEngineTemplate 配置好后,我们可以在业务代码中执行规则引擎,处理配置文件中配置的业务规则:

最为演示,我们将规则引擎的执行代码放到了 Application 的 run 方法中,程序启动后立即执行规则引擎:

<span>@SpringBootApplication</span>
<span>public</span> <span>class</span> <span>Application</span> <span>implements</span> <span>CommandLineRunner</span> {

    <span>@Autowired</span>
    <span>RuleEngineTemplate</span> ruleEngineTemplate;

    <span>public</span> <span>static</span> <span>void</span> <span>main</span>(<span><span>String</span>[] args</span>) {
        <span>SpringApplication</span>.<span>run</span>(<span>Application</span>.<span>class</span>, args);
    }

    <span>@Override</span>
    <span>public</span> <span>void</span> <span>run</span>(<span><span>String</span>... args</span>) {
        <span>Person</span> person = <span>new</span> <span>Person</span>();
        <span>Facts</span> facts = <span>new</span> <span>Facts</span>();
        facts.<span>put</span>(<span>"person"</span>, person);
        ruleEngineTemplate.<span>fire</span>(<span>"demo"</span>, facts);

    }
}

程序执行后可以看到控制台里打印了 Shop: Sorry, you are not allowed,这个内容对应的是我们在规则文件中的actions中配置的 "T(java.lang.System).out.println("Shop: Sorry, you are not allowed")" ,说明规则成功执行了。