Spring Boot整合Drools规则引擎实例

Rule Engine 同时被 2 个专栏收录
2 篇文章 0 订阅
13 篇文章 0 订阅

1、DRools介绍

官网:https://www.drools.org/

规则引擎主要完成的就是将业务规则从代码中分离出来。

DRools一款由JBoss组织提供的基于Java语言开发的开源规则引擎,目前由 Redhat 开源的规则引擎,它是 Redhat 的 KIE Group 中的组件之一,可以比较方便的跟另一个组件 JBPM 工作流配合用于管理复杂的规则流;同时 Drools 的推理策略算法在经典 Rete 算法以及其它算法的基础上做了多个版本的增强。DRools是一个具有基于前向链接和后向链接推理的规则引擎,允许快速和可靠地评估业务规则和复杂事件处理。

关于规则引擎的说明和对比可以参考另一篇文章:规则引擎

2、DRools API开发流程

Drools API开发步骤:

  • 获取KieServices

  • 获取KieContainer

  • 获取KieSession

  • 插入Fact对象

  • 触发规则(规则需事先编写,如DRL文件或规则表等)

  • 关闭KieSession

//1.获取KieServices
KieServices kieServices = KieServices.get();
//2.获取Kie容器对象
KieContainer kieContainer = kieServices.getKieClasspathContainer();
//3.从kie容器对象中获取会话对象
KieSession session = kieContainer.newKieSession();
//4.创建Fact对象(事实对象)
FactExample fact = new FactExample();
fact .setTest("test");
//5.将Fact对象插入到工作内存中去
session.insert(fact);
//6.激活规则,由drools框架自动进行规则匹配,如果匹配成功,则执行规则
session.fireAllRules();
//7.关闭session
session.dispose();

2.1、规则文件编码规范(建议)

  • 所有的规则文件(.drl)应统一放在一个规定的文件夹汇总,如:/rules文件夹

  • 书写的每一个规则应该尽量加上注释

  • 同一类型的对象尽量放在一个规则文件中,如所有的Student类型的对象尽量放在一个规则文件中

  • 每个规则最好都加上salience属性,明确执行顺序

  • Drools默认dialect为"java",尽量避免使用dialect "mvel"

3、DRools主要概念

3.1、规则引擎的构成

  • working Memory(工作内存)

  • Rule Base(规则库)

  • Inference Engine(推理引擎):Pattern Matcher(匹配器)、Agenda(议程)、Execution Engine(执行引擎)

3.2、规则引擎执行过程

  1. 将初始数据(fact)输入至工作内存(working Memory)

  2. 使用匹配器(Pattern Matcher)将规则库中的规则(rule)和数据(fact)匹配,匹配成功的放入到议程(Agenda)中

  3. 如果执行规则存在冲突,即同时激活了多个规则,将冲突的规则放入冲突集合(冲突是同时匹配了多条规则,不需要自己处理,drools会自动处理,也可以通过主动声明的方式限制只命中一条规则)

  4. 解决冲突,将激活的规则按顺序放入议程(Agenda)

  5. 执行议程(Agenda)中的规则,重复2-4,直到执行完毕议程中的所有规则

3.3、规则文件

3.3.1、规则文件的构成

  • package:包名,package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问

  • import:用于导入类或者静态方法

  • global:全局变量

  • function:自定义函数

  • query:查询

  • rule end:规则体

3.3.2、规则体(rule)的基本语法

rule "ruleName"        //rule关键字,表示规则开始,参数为规则的唯一名称
    attributes              //规则属性,是rule与when之间的参数,为可选项
    when               		//关键字,后面是规则的条件部分
        LHS            		 //Left Hand Side,是规则的条件部分
    then               		 //后面跟规则的结果部分
        RHS           		//是规则的结果或行为
end                   		 //表示一个规则的结束

3.3.3、规则体的属性

  • salience:指定规则执行的优先级,用数字表示,数字越大优先级越高。如果不设置,默认从上到下执行

  • dialect:指定规则使用的语言类型,java或mvel

  • enabled:指定规则是否启用,取值为ture或false,默认为true

  • date-effective:指定规则生效的时间

  • date-expires:指定规则失效的时间

  • timer:定时器,指定规则触发的时间

  • activation-group:激活分组,同一个组内只能有一个规则触发

  • agenda-group:议程分组,只有获取焦点的组中的规则才有可能触发。在java代码中获得焦点。

session.getAgenda().getAgendaGroup("agenda_group_name").setFocus;
  • auto-focus:自动获取焦点,一般结合agenda-group使用,取值为ture或false

  • no-loop:防止死循环,当规则使用update之类的函数修改了Fact对象时,使当前规则再次被激活从而导致死循环。

3.4、DRools的基本语法

  • 运算符:> < >= <= == !=

  • contains:检查一个Fact对象的某个属性值是否包含一个指定的对象值

  • not contains:检查一个Fact对象的某个属性值是否不包含一个指定的对象值

  • memberOf:判断一个Fact对象的某个属性是否在一个或多个集合中

  • not memberOf:判断一个Fact对象的某个属性是否不在一个或多个集合中

  • matches:判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配

  • not matcher:判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配

3.5、DRools内置方法

  • update方法:更新工作内存中的数据,并让相关的规则重新匹配。更新数据时,要注意防止陷入死循环。

  • insert方法:向工作内存中插入数据,并让相关的规则重新匹配。

  • retract方法:删除工作内存中的数据,并让相关的规则重新匹配。

3.6、DRools高级语法

3.6.1、global全局变量

global关键字用于在规则文件中定义全局变量,它可以让应用程序的对象在规则文件中能够被访问。可以用来为规则文件提供数据或服务。语法结构:global 对象类型 对象名称。

注意事项:如果对象类型为包装类时,在一个规则中改变了global的值,那么只针对当前规则有效,对其他规则中的global不会有影响。如果对象类型为集合类型或者javaBean时,在一个规则中改变了global的值,对java代码和所有规则都有效。

3.6.2、query查询

query查询提供了一种查询working memory中符合约束条件的Fact对象的简单方法。它仅包含规则文件中的LHS部分,不用指定"when"和"then"部分并且以end结束。具体语法结构如下:

query 查询的名称(可选参数)
    LHS
end 

3.6.3、function方法

function关键字用于在规则文件中定义函数,规则体中可以调用定义的function函数。使用函数的好处是可以将业务逻辑集中放置在一个地方,根据需要可以对函数进行修改。

function 返回值类型 函数名(可选参数){
  		//逻辑代码
}

3.6.4、LHS语法

1. 复合值限制in/not in

复合值限制是指超过一种匹配值的限制条件,类似于SQL语句中的in关键字。

$s:Student(name in ("张三","李四","王五"))
$s:Student(name not in ("张三","李四","王五"))   

2. 条件元素eval

eval用于规则体的LHS部分,并返回一个Boolean类型的值。语法结构:eval(表达式)

eval(1==1) 

3. 条件元素not

not用于判断working memory中是否存在某个Fact对象,如果不存在则返回true,存在返回true。

not Student(age<10)

4. 条件元素exists

exists与not相反,用于判断working memory中是否存在某个Fact对象,如果存在则返回true,不存在返回true。

exists Student(age<10) 

5. 规则继承

规则之间使用extends关键字进行规则部分的继承,类似于java类之间的继承。

rule "rule_1"
   when
       Student(age>10)
   then
      System.out.println("rule_1触发");
end

rule "rule_2" extends "rule_1"   //继承上面的规则
   when
       Student(age<20)        //此处虽然只有一个条件,但继承上面的条件,所以此处的条件是10<age<20
   then
      System.out.println("rule_2触发");
end

3.6.5、RHS语法

RHS部分需要进行业务处理。在RHS部分Drools提供了一个内置对象,名称是drools,介绍几个drools对象的方法。

1. halt

halt方法的作用是立即终止后面所有规则的执行。

rule "rule_1"
   when
       Student(age>10)
   then
      System.out.println("rule_1触发");
      drools.halt();     //后面的规则不再执行
end

rule "rule_2" 
   when
       Student(age<20)        
   then
      System.out.println("rule_2触发");
end

2. getWorkingMemory

getRule方法的作用是返回工作内存中的对象。

rule "rule_getWorkingMemory"
   when
       Student(age>10)
   then
      System.out.println(drools.getWorkingMemory());
end  

3.getRule

getRule方法的作用是返回规则对象。

rule "rule_getRule"
   when
       Student(age>10)
   then
      System.out.println(drools.getRule());
end  

4、Spring Boot整合DRools

实现一个产品佣金规则计算逻辑,满足项目的规则触发条件则按照相应公式计算佣金,并返回结果。

例子业务逻辑简介:

  1. 创建maven工程并配置pom.xml

  2. 创建resources/rules/settlement.drl文件

  3. 创建domainn/ProjectRule***.java实体类

  4. 编写配置类config/DroolsAutoConfiguration.java(例子做了简化)

  5. 创建ProjectService.java

具体代码如下:

  1. 构建Spring Boot基础项目,可通过:https://start.spring.io/

  2. Maven引入依赖

<?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>2.5.3</version>
    </parent>
    <groupId>com.xxx</groupId>
    <artifactId>drools-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>drools-demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <drools.version>7.57.0.Final</drools.version>
    </properties>
    <dependencies>
        //... Spring Boot 依赖

        <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-decisiontables</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-templates</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-api</artifactId>
            <version>${drools.version}</version>
        </dependency>
    </dependencies>
</project>
  1. 编写规则文件DRL

//package对应的不一定是真正的目录,可以任意写com.abc,同一个包下的drl文件可以相互访问
package com.xxx;

import com.xxx.droolsdemo.domain.ProjectRuleWhenCondition
import com.xxx.droolsdemo.domain.ProjectRuleWhenValue
import com.xxx.droolsdemo.domain.ProjectRuleThenCondition
import com.xxx.droolsdemo.domain.ProjectRuleThenResult
import java.math.BigDecimal
import com.sun.javafx.binding.StringFormatter
import java.util.List
import java.util.ArrayList

// TODO:function定义说明
//function 返回值类型 函数名(可选参数){
//    //逻辑代码
//}

function Boolean inList(List targetList, Object value){

    Boolean flag = false;
    for(Object obj : targetList){

    System.out.println("inList" + obj);

    if(obj.equals(value)){
        flag  = true;
        break;
    }
    }
    return flag;
}

rule "product rule"
    salience 1          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true        //防止陷入死循环
    when
        $thenCondition:ProjectRuleThenCondition();
        $thenResult:ProjectRuleThenResult();

        $whenCondition:ProjectRuleWhenCondition();
        $whenValue : ProjectRuleWhenValue(
        //inList($whenCondition.getPropertyTypes(),propertyType) && //户型
        propertyType memberOf $whenCondition.getPropertyTypes() &&
        area > 0 && area<$whenCondition.getArea() && //面积区间
        dealCount>=$whenCondition.getDealCount() ); //成交总套数
    then
        if($thenCondition.getIsServiceFee()){
          $thenResult.setServiceFeeAmount($thenCondition.getReturnAmount().multiply($thenCondition.getReturnRatio()));
        }
        else{
            $thenResult.setServiceFeeAmount(new BigDecimal(10086));
        }
        System.out.println(drools.getRule().getName() +"被执行");
end;
  1. 构建实体

  • ProjectRuleWhenCondition:规则When条件类

  • ProjectRuleWhenValue:规则When Fact值类

  • ProjectRuleThenCondition:规则Then条件类

  • ProjectRuleThenResult:规则Then结果类

/**
 * 项目规则-When条件
 */
@Data
public class ProjectRuleWhenCondition {

    /**
     * 物业类型
     */
    private List<String> propertyTypes;

    /**
     * 面积
     */
    private BigDecimal area;

    /**
     * 成交总套数
     */
    private Integer dealCount;

}
/**
 * 项目规则-When值
 */
@Data
public class ProjectRuleWhenValue {

    /**
     * 物业类型
     */
    private String propertyType;

    /**
     * 面积
     */
    private BigDecimal area;

    /**
     * 成交总套数
     */
    private Integer dealCount;

}
/**
 * 项目规则-Then条件
 */
@Data
public class ProjectRuleThenCondition {

    /**
     * 是否服务费
     */
    private Boolean isServiceFee;

    /**
     * 回款金额
     */
    private BigDecimal returnAmount;


    /**
     * 回款比率
     */
    private BigDecimal returnRatio;

}
/**
 * 项目规则-When结果
 */
@Data
public class ProjectRuleThenResult {

    /**
     * 服务费(计算得出)
     */
    private BigDecimal serviceFeeAmount;
}
  1. 构建ProjectService,实现触发逻辑

public class ProjectService {
    private KieContainer kieContainer;

    public ProjectService() {
        init();
    }

    public void reload() {
        init();
    }

    public void run() {

        //1. 构建when参数
        ProjectRuleWhenCondition whenCondition = new ProjectRuleWhenCondition();
        whenCondition.setPropertyTypes(Arrays.asList("住宅", "公寓", "别墅"));
        whenCondition.setArea(new BigDecimal(200));
        whenCondition.setDealCount(1);

        ProjectRuleWhenValue
 whenValue = new ProjectRuleWhenValue();
        whenValue.setPropertyType("住宅");
        whenValue.setArea(new BigDecimal(80));
        whenValue.setUnitType("三房");
        whenValue.setDealCount(2);
        whenValue.setDealTotalAmount(new BigDecimal(5000));

        //2. 构建then参数
        ProjectRuleThenCondition thenCondition = new ProjectRuleThenCondition();
        thenCondition.setIsServiceFee(true);
        thenCondition.setReturnAmount(new BigDecimal(100000));
        thenCondition.setReturnRatio(new BigDecimal(0.2));

        ProjectRuleThenResult thenResult = new ProjectRuleThenResult();


        //3. 初始化KIE Session
        //KIE是JBoss提供的一套集中式管理API,@link:https://docs.jboss.org/drools/release/7.58.0.Final/kie-api-javadoc/index.html

        KieSession kieSession = kieContainer.newKieSession();
        kieSession.insert(whenCondition);
        kieSession.insert(whenValue);
        kieSession.insert(thenCondition);
        kieSession.insert(thenResult);

        //只执行一个符合条件的rule,需要配合salience优先级使用
        //kieSession.fireAllRules(1);
        int i = kieSession.fireAllRules();
        System.out.println("规则执行了" + i + "次,返回计算结果为" + thenResult.getServiceFeeAmount());

        kieSession.dispose();
    }

    @SneakyThrows
    private void init() {
        KieServices kieServices = KieServices.get();
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        Resource[] resourceArray = resourcePatternResolver.getResources("rules/**/*.drl");
        for (Resource file : resourceArray) {
            kieFileSystem.write(ResourceFactory.newClassPathResource("rules/" + file.getFilename(), "UTF-8"));
        }
        KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
        kb.buildAll();
        KieModule kieModule = kb.getKieModule();
        kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
    }
}
  1. 测试验证,输出结果如下

product rule被执行
规则执行了1次,返回计算结果为20000.000000000001110223024625156540423631668090820312500000

修改ProjectRuleWhenValue的值,将输出不同的结果。

参考资料

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值