规则引擎——Drools

Drools简介

  JBoss Rules 的前身是Codehaus的一个开源项目叫Drools。最近被纳入JBoss门下,更名为JBoss Rules,成为了JBoss应用服务器的规则引擎。

  Drools是为Java量身定制的基于Charles Forgy的RETE算法的规则引擎的实现。具有了OO接口的RETE,使得商业规则有了更自然的表达。

  Drools的用XML的<Conditons><Consequence> 节点表达If--Then句式,而里面可以嵌入上述语言的代码作为判断语句和执行语句。其中Java代码会使用Antlr进行解释,而Groovy和Python本身就是脚本语言,可以直接调用。Drools的聪明之处在于,用XML节点来规范If--Then句式和事实的定义,使引擎干起活来很舒服。而使用Java,Groovy等原生语言来做判断和执行语句,让程序员很容易过渡、移植,学习曲线很低。

Drools使用范例

  为了更好的了解Drools,我们以一个现成的例子为基础来进行详细的展示。

  我们假设我们向供应商采购某类商品,比如说我们向供应商采购的商品001为机油,现在由于业务扩张,我们需要加大采购量,因而我们下次的采购规则是这样制定的:

  假设在上次采购数量为1-100之间,我们推荐采购数量为100,当上次采购数量为100-1000之间,我们推荐采购数量为1000。

分析:

  在这里,由于一个供应商提供多种商品,即除了提供编号为001的机油之外,还提供其它的商品,比如说编号为002的车载导航等,但是由于现在机油畅销,我们只需要临时制定编号为001的机油的采购规则。

  同时在这里,一个商品对应多个供应商,因而我们需要针对不同供应商的上次采购记录制定下次不同的采购数量规划。

  在这里,由于商品和供应商之间是一种多对多的关系,同时编号为001的机油又是由于季节等原因临时畅销的,如果我们采用硬编码的方式,那么由于不同的商品有不同的畅销期,而价格以及采购策略又经常会出现变动,因而我们会遭遇频繁改动代码,这样会造成很大的开发困扰,因而在这里我们就使用规则引擎——Drools来制定相应的采购策略。

  项目结构图如下:

这里写图片描述

  源码如下:

Goods

package com.lyc.drools.entity;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@Builder
@ToString
public class Goods {

    private String goodsNo;
    private String goodsName;
    private String supplierNo;
    private String supplierName;
    private int total;

}

Rules.drl

package com.rules

import com.lyc.drools.entity.Goods

rule rule1
    when
        $goods : Goods((goodsNo == "001") && (total >= 1) && (total < 100) )
    then
    System.out.println("建议向【" + $goods.getSupplierName() + "】的" + $goods.getGoodsName() + "采购数量为100");
end

rule rule2
    when
        $goods : Goods((goodsNo == "001") && (total > 100) && (total <= 1000) )
    then
    System.out.println("建议向【" + $goods.getSupplierName() + "】的" + $goods.getGoodsName() + "采购数量为1000");
end

kmodule.xml

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <kbase name="rules" packages="com.rules">
        <ksession name="ksession-rule"/>
    </kbase>
</kmodule>

GoodsTest

package com.lyc.drools.entity;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;

import java.util.List;

@Slf4j
public class GoodsTest {

    private KieContainer kieContainer = null;
    private List<Goods> goodsList = Lists.newArrayList();

    @Before
    public void init(){
        KieServices kieServices = KieServices.Factory.get();
        kieContainer = kieServices.getKieClasspathContainer();
        Goods goods1 = Goods.builder()
                .goodsNo("001")
                .goodsName("机油001")
                .supplierNo("01")
                .supplierName("供应商01")
                .total(10)
                .build();
        Goods goods2 = Goods.builder()
                .goodsNo("002")
                .goodsName("车载导航002")
                .supplierNo("02")
                .supplierName("供应商02")
                .total(200)
                .build();
        Goods goods3 = Goods.builder()
                .goodsNo("001")
                .goodsName("机油001")
                .supplierNo("03")
                .supplierName("供应商03")
                .total(200)
                .build();
        goodsList.add(goods1);
        goodsList.add(goods2);
        goodsList.add(goods3);
    }

    @Test
    public void test(){
        KieSession kSession = kieContainer.newKieSession("ksession-rule");
        goodsList.forEach(goods -> {
            log.info(goods.toString());
            kSession.insert(goods);
        });
        kSession.fireAllRules();
        kSession.dispose();
    }
}

  项目需要引入的依赖:

        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>7.0.0.Final</version>
        </dependency>

  其它配置文件:

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
    <appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %5p %t %-5l - %m%n"/>
        </Console>
        <!--
            fileName:指定日志的所在位置以及日志名称
            filePattern:指定备份的日志所在位置以及名称
        -->
        <RollingFile name="RollingFile" fileName="F:/logs/log4j2-xml.log" filePattern="F:/logs/log4j2-xml-$${date:yyyy-MM}/log4j2-xml-%d{yyyy-MM-dd}-%i.log.gz">
            <!--输出的日志数据格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %5p %t %-5l - %m%n"/>
            <!--设置日志文件大小,当超过5M时,触发日志备份操作-->
            <SizeBasedTriggeringPolicy size="5 MB" />
        </RollingFile>
    </appenders>
    <loggers>
        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFile"/>
        </root>
    </loggers>
</configuration>

  其它依赖:

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.20</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j-impl</artifactId>
      <version>2.10.0</version>
    </dependency>
    <!--log4j2-->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.10.0</version>
    </dependency>
    <!--jackson-->
    <dependency>
      <groupId>org.codehaus.jackson</groupId>
      <artifactId>jackson-core-asl</artifactId>
      <version>1.9.13</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.codehaus.jackson/jackson-mapper-asl -->
    <dependency>
      <groupId>org.codehaus.jackson</groupId>
      <artifactId>jackson-mapper-asl</artifactId>
      <version>1.9.13</version>
    </dependency>
    <!-- Jackson -->
    <!-- 主要是用于处理json数据 -->
    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-guava</artifactId>
      <version>2.9.4</version>
    </dependency>

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.5</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
    <dependency>
      <groupId>joda-time</groupId>
      <artifactId>joda-time</artifactId>
      <version>2.9.9</version>
    </dependency>

  运行结果:

建议向【供应商01】的机油001采购数量为100
建议向【供应商03】的机油001采购数量为1000

语法介绍

  Drools的语法结构如下:

package 包名

import 引入的java类

rule 规则名
    when
        (条件)左手定则(LHS(Left Hand Side))
    then
        (动作/结果)右手定则(RHS(Right Hand Side))
end

  其中每一个规则都是以rule开头,以end结尾,中间有且只有一个唯一的when和then。而如果业务中要有多个判断条件时,就采用&&等运算符与多条规则混合使用,就如我上面所举的例子中一样。

  左手定则(LHS(Left Hand Side))是按照 DRL 语言编写的,条件eval(true)表示永远为真,即该条规则总会获得执行。也就是说如果左手定则为空时,其永远都是执行的。

  右手定则(RHS(Right Hand Side))使用 Java 语言实现,在这里直接调用java中的方法就可以了。

名词解释

Kmodule

  • 一个Kmodule中可以包含一个到多个kbase,分别对应drl的规则文件。

Kbase

  • 一个Kbase对应多个ksession。其中在同一个Kmodule中Kbase的名称是唯一的,并且有且只有唯一的一个Kbase拥有default属性。

  • KieBase就是一个知识仓库,包含了若干的规则、流程、方法等,在Drools中主要就是规则和方法,KieBase本身并不包含运行时的数据之类的,如果需要执行规则KieBase中的规则的话,就需要根据KieBase创建KieSession。

ksession

  • 一个Kbase对应多个ksession,其中在同一个Kbase中的ksession名称是唯一的。

  • KieSession就是一个跟Drools引擎打交道的会话,其基于KieBase创建,它会包含运行时数据,包含“事实Fact”,并对运行时数据实时进行规则运算。通过KieContainer创建KieSession是一种较为方便的做法,其本质上是从KieBase中创建出来的。KieSession就是应用程序跟规则引擎进行交互的会话通道。

  • 创建KieBase是一个成本非常高的事情,KieBase会建立知识(规则、流程)仓库,而创建KieSession则是一个成本非常低的事情,所以KieBase会建立缓存,而KieSession则不必。

packages

  • packages为drl文件所在resource目录下的路径。注意区分drl文件中的package与此处的package不一定相同。多个包用逗号分隔。默认情况下会扫描resources目录下所有(包含子目录)规则文件。

KieServices

  • KieServices提供了很多方法,可以通过这些方法访问KIE关于构建和运行的相关对象,比如说可以获取KieContainer,利用KieContainer来访问KBase和KSession等信息;可以获取KieRepository对象,利用KieRepository来管理KieModule等。KieServices就是一个中心,通过它来获取的各种对象来完成规则构建、管理和执行等操作。

KieContainer

  • KieContainer可以理解KieContainer就是一个KieBase的容器。提供了获取KieBase的方法和创建KieSession的方法。其中获取KieSession的方法内部依旧通过KieBase来创建KieSession。

KieRepository

  • KieRepository是一个单例对象,它是存放KieModule的仓库,KieModule由kmodule.xml文件定义。

Fact

  • 通常是一个普通的Java 的POJO,一般它们会有若干个属性,每一个属性都会对应getter 和setter 方法,用来对外提供数据的设置与访问。

  • 一般来说,在Drools 规则引擎当中,fact 所承担的作用就是将规则当中要用到的业务数据从应用当中传入进来,对于规则当中产生的数据及状态的变化通常不用fact 传出。

猜你喜欢

转载自blog.csdn.net/zzy1078689276/article/details/79892203