drools7规则引擎

Drools7规则引擎教程

场景

  • 风控反洗钱系统
  • 商品折扣系统
  • 积分系统

概述

  • 把复杂冗余的业务规则与系统分离开来,做到架构的可复用、可移植性。

Drools5与Drools7版本变更

Drools是业务规则管理系统(BRMS)解决方案,设计一下项目:

  • Drools Workbench:业务规则管理系统
  • Drools Expert:业务规则引擎
  • Drools Fusion:事件处理
  • jBPM:工作流引擎
  • OptalPlanner:规则引擎

Drools组成部分

  • Drools规则
  • Drools规则的解释执行

API划分

  • 规则编译
  • 规则收集
  • 规则执行

Drools7依赖包

  • kie-api
  • drools-core
  • drools-compile

Drools7 核心API

  • KieServices
  • KieContainer
  • KieSession

一个简单的例子

  • 在resources/META-INF下创建kmodule.xml
<?xml version="1.0" encoding="UTF-8" ?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <kbase name="rules">
        <ksession name="all-rules"></ksession>
    </kbase>
</kmodule>
  • 在resource/rules下创建drools规则文件goods.drl
package rules
import com.drools.demo.model.Car

rule "test-drools7-older than 60"
agenda-group "test-drools7"
when
    $car: Car(person.age > 60)
then
    $car.setDiscount(80);
    System.out.println("test-drools7-older than 60:" + $car.getPerson().getAge());
 end

rule "test-drools-other"
agenda-group "test-drools7"
when
    $car: Car(person.age < 60)
then
    $car.setDiscount(60);
    System.out.println("test-drools-other:" + $car.getPerson().getAge());
 end
  • 创建Car类
package com.drools.demo.model;

import lombok.Data;

@Data
public class Car {

    private int discount = 0;

    private Person person;
}
  • 创建Person类
package com.drools.demo.model;

import lombok.Data;

@Data
public class Person {

    private int age;
}
  • 创建Drools7Test主类
package com.drools.demo.drools;

import com.drools.demo.model.Car;
import com.drools.demo.model.Person;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;

public class Drools7Test {

    private static KieSession getKiession() {
        KieServices kieServices = KieServices.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("all-rules");
        return kieSession;
    }

    /**
     * 根绝agendaGroup获取该分组下的规则,用于业务规则隔离
     * @param agendaGroup
     * @return
     */
    private static KieSession getKiesession(String agendaGroup) {
        KieSession kieSession = getKiession();
        //获取agendaGroup并设置焦点
        kieSession.getAgenda().getAgendaGroup(agendaGroup).setFocus();
        return kieSession;
    }

    public static void main(String... args) {
        KieSession kieSession = getKiesession("test-drools7");

        Person person1 = new Person();
        person1.setAge(65);

        Car car1 = new Car();
        car1.setPerson(person1);

        Person person2 = new Person();
        person2.setAge(30);

        Car car2 = new Car();
        car2.setPerson(person2);

        kieSession.insert(car1);
        kieSession.insert(car2);

        int count = kieSession.fireAllRules();
        kieSession.dispose();

        System.out.println(count);
        System.out.println("car1 discount:" + car1.getDiscount());
        System.out.println("car2 discount:" + car2.getDiscount());
    }
}

kmodule

  • kmodule默认路径放在META-INF下(见源码),drools自动解析
  • kmodule中可以包含一个到多个kbase
  • kbase的name要唯一
  • kbase的packages为drl文件所在resources下的路径,注意区分drl文件中的package与此处的packages不一定相同。多个包用逗号隔开。默认情况下扫描resources下面所有规则文件
  • kbase的default属性,标识当前KieBase是不是默认的,如果是默认的则不用名称就可以查找到该KieBase,但每个module最多有一个默认的KieBase
  • kbase下面可以有一个或多个ksession,ksession的name属性必须设置,且必须唯一

kbase属性

属性值默认值合法的值描述
namenoneanyKieBase的名称,这个属性是强制的,必须设置
includesnone逗号分隔的KieBase名称列表意味着本KieBase将会包含所有的include的KieBase的rule,process定义制品文件,非强制属性
defaulefalsetrue/false表示当前KieBase是不是默认的,如果是默认的,不用名称就可以查找该KieBase,但是每一个KieModule最多只能有一个默认的KieBase
equalsBehavioridentityidentity,equality顾名思义就是等于的行为,这个equals是针对fact(事实)的,当插入一个Fact到working memory中的时候,drools引擎会检查该fact是否已经存在,如果存在的话就使用已有的FactHandle,否则就创建新的。而判断Fact是否存在的依据是通过该属性定义的方式来进行的,设置成identity,就是判断对象是否存在,可以理解为用==判断,看是否是同一个对象,如果该属性设置成equality的话,就是通过Fact对象的equals方法来判断
eventProcessingModecloudcloud,stream当以云模式编译时,kieBase将事件视为正常事实,而在流模式下允许对其进行时间推理

ksession的属性

属性名默认值合法的值描述
namenoneanyKieSession的名称,该值必须唯一,也是强制的,必须设置
typestatefulstateful, stateless定义该session到底是有状态(statefull)还是无状态(stateless),有状态的session可以利用working memory执行多次,而无状态的则只能执行一次
defaultfalsetrue,false定义该session是否是默认的,如果是默认的话则可以不用通过session的name来创建session,在同一个module中最多只能有一个默认的session
clockTyperealtimerealtime,pseudo定义时钟类型,用在事件处理上面,在复合事件处理上会用到,其中realtime表示用的是系统时钟,而pseudo则是用在单元测试时模拟用的
belieSystemsimplesimple,defeasible,jtms定义KieSession使用belief system的类型

KIE

  • knowledge is everything

KIE的生命周期

  • 编写:编写规则文件,比如:drl、BPMN2、决策表、实体类等
  • 构建:构建一个可以发布部署的组件,对于KIE来说JAR文件
  • 测试:部署之前对规则进行测试
  • 部署:利用maven仓库将jar部署到应用程序
  • 使用:程序加载jar文件,通过KieContainer对其进行解析创建KieSession
  • 执行:通过KieSession对象的API与Drools引擎进行交互,执行规则
  • 交互:用户通过命令行或者UI与引擎进行交互
  • 管理:管理KieSession或者KieContainer对象

Fact对象

  • Fact对象简单来说就是一个特殊的JavaBean
  • 一般拥有getter/setter方法
  • 放置于WorkingMemory当中
  • 建立应用系统数据和规则的传输桥梁

Fact对象的特殊之处

  • 引用传递——影响应用层的对象
  • FactHandler
        KieSession kieSession = getKiesession(agendaName);

        Person person = new Person();
        person.setAge(90);

        FactHandle handle = kieSession.insert(person);
        System.out.println(handle.toExternalForm());


        int count = kieSession.fireAllRules();
        System.out.println("Fires:" + count);

        //更新working memory中对象的值,再执行
        person.setAge(88);
        kieSession.getAgenda().getAgendaGroup(agendaName).setFocus();
        kieSession.update(handle, person);
        count = kieSession.fireAllRules();
        System.out.println("Fires2:" + count);

        kieSession.dispose();

API

KieService

  • 提供访问KIE关于构建和运行的相关对象
    • 获取KieContainer,利用KieContainer来访问KBase和KSession等信息
    • 获取KieRepository对象,利用KieRepository来管理KieModule等
  • KieServices就是一个中心,通过它来获取的各种对象来完成规则构建、管理和执行等操作

KieContainer

  • KieContainer就是一个KieBase的容器,提供获得KieBase的方法
  • KieContainer的方法内部依旧通过KieBase来创建KieSession

KieBase

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

KieSession

  • 基于KieBase创建,与Drools引擎打交道的会话

KieRepository

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

KieProject

  • KieContainer通过KieProject来初始化、构造KieModule,并将KieModule存放在KieRepository中,然后KieContainer可以通过KieProject来查找KieModule定义的信息,并根据这些信息构造KieBase和KieSession

ClasspathKieProject

  • ClasspathKieProject实现了KieProject接口,它提供了根据类路径中的META-INF/kmodule.xml文件构造KieModule的能力,是基于maven构造Drools组件的基本保障之一。意味着只要按照前面提到过的maven工程结构组织我们的规则文件或流程文件,只用很少的代码完成模型的加载和构建。

有状态Session和无状态Session

有状态session

  • 通过KieContainer可以获取KieSession,在kmodule.xml配置文件中如果不指定ksession的type默认也是有状态的session。
  • 有状态的session的特性是:我们可以通过建立一次session完成多次与规则引擎之间的交互,在没有调用dispose的一般步骤为:获取session,insert fact对象,然后调用fireAllRules进行规则匹配,随后调用dispose方法关闭session
<?xml version="1.0" encoding="UTF-8" ?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <kbase name="rules">
        <ksession name="all-rules" type="stateful"></ksession>
    </kbase>
</kmodule>

无状态session

  • StatelessKieSession提供了一个更加便利的API,是对KieSession的封装,不再调用dispose方法进行session的关闭。
  • 它隔离了每次与规则引擎的交互,不会再去维护会话的状态。
  • 不再提供fireAllRules方法
  • 使用场景
    • 数据校验
    • 运算
    • 数据过滤
    • 消息路由
    • 任何能被描述成函数或公式的规则

一个无状态的例子

kmodule.xml

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

resources/stateless/stateless.drl

package stateless
import com.drools.demo.Person

rule "test-stateless"

when
    $p:Person()
then
    System.out.println($p.getAge());
    end

BaseKieSession.java

package com.drools.demo.kie;

import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.StatelessKieSession;

public class BaseKieSession {

    protected KieSession getKiesessionByName(String sessionName){
        KieServices kieServices = KieServices.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession(sessionName);
        return kieSession;
    }

    protected KieSession getKiesession(){
        KieServices kieServices = KieServices.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("all-rules");
        return kieSession;
    }

    protected KieSession getKiesession(String agendaName){
        KieSession kieSession = getKiesession();
        kieSession.getAgenda().getAgendaGroup(agendaName).setFocus();
        return kieSession;
    }

    protected StatelessKieSession getStatelessKiesession(){
        KieServices kieServices = KieServices.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        StatelessKieSession kieSession = kieContainer.newStatelessKieSession("stateless-rules");
        return kieSession;
    }

    protected StatelessKieSession getStatelessKiesession(String agendaName){
        StatelessKieSession kieSession = getStatelessKiesession();
//        kieSession.getAgenda().getAgendaGroup(agendaName).setFocus();
        return kieSession;
    }
}

StatelessSessionTest.java

package com.drools.demo.test.chapter5;

import com.drools.demo.Person;
import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.StatelessKieSession;

import java.util.ArrayList;
import java.util.List;

public class StatelessSessionTest extends BaseKieSession {

    @Test
    public void createTest(){
        StatelessKieSession kieSession = getStatelessKiesession();
        Person person = new Person();
        person.setAge(35);
        kieSession.execute(person);

        List<Person> personList = new ArrayList<>();
        personList.add(person);
        kieSession.execute(personList);
    }
}

规则文件

  • 标准的规则文件是以“.drl”结尾的文本文件
  • 可以存放用户自定义的函数、数据对象及自定义查询等

规则文件结构

  • package——不需要与物理路径一致,只是用来区分不同业务,package name
  • import——import static,import function
  • globals——定义全局方法函数
  • functions
  • queries
  • rule

Rule语法

  • rule “name”–规则名称
  • attributes–属性
  • when——LHS Left Hand Side,多个条件默认用and,也可以使用or
  • then——RHS Right Hand Side
  • end
    一个最简单的规则
rule "name"
when
then
end

属性

no-loop详解

  • 定义当前的规则是否允许多次循环执行,默认是false
  • 当执行update,insert,retract,modify等操作时,会循环执行规则
  • 通过设置no-loop属性为true来避免死循环
package noloop

import com.drools.demo.Person

rule "test-no-loop-rule"
//no-loop false
no-loop true
when
    $p:Person(age > 10)
then
    $p.setAge(11);
    System.out.println($p.getAge());
    update($p);
 end

ruleflow-group

  • 在使用规则流的时候要用到ruleflow-group属性,该属性的值为一个字符串,作用是将规则划分为一个个的组,然后在规则流当中通过使用ruleflow-group属性的值,从而使用对应的规则
  • 从drools6.5版本的说明文档到drools7版本的说明文档中都提到ruleflow-group和agenda-group进行合并,返回相同的底层数据结构
  • 必须设置了焦点才能执行
  • 设置焦点是一次性的,设置过焦点之后再次运行焦点就会失效,不会被执行
    kbase.xml
    <kbase name="ruleFlowGroup-rules" packages="rowflowGroup">
        <ksession name="ruleFlowGroup-rules"></ksession>
    </kbase>

ruleflowGroup.drl

package rowflowGroup

rule "test-rule-flow-group-1"
ruleflow-group "rule-flow-group-1"
when
then
 System.out.println("rule-flow-group-1 被触发了");
 end

rule "test-rule-flow-group-2"
ruleflow-group "rule-flow-group-2"
when
then
 System.out.println("rule-flow-group-2 被触发了");
 end

RowFlowGroupTest.java

package com.drools.demo.test;

import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.KieSession;

public class RowFlowGroupTest extends BaseKieSession {

    @Test
    public void createTest(){
        KieSession kieSession = getKiesessionByName("ruleFlowGroup-rules");
        //agenda-group和rowflow-group作用一样
        kieSession.getAgenda().getAgendaGroup("rule-flow-group-1").setFocus();
        kieSession.fireAllRules();

        kieSession.getAgenda().getAgendaGroup("rule-flow-group-2").setFocus();
        kieSession.fireAllRules();

        kieSession.getAgenda().getAgendaGroup("rule-flow-group-1").setFocus();
        kieSession.fireAllRules();

        kieSession.getAgenda().getAgendaGroup("rule-flow-group-2").setFocus();
        kieSession.fireAllRules();
        kieSession.dispose();
    }

}

lock-on-active

  • 避免因FACT对象被更新使得执行过的规则被再次执行
  • 触发此类规则的操作有update,retract,modify等
  • 拥有no-loop的功能,同时又能避免其他规则改变FACT对象导致规则重新执行
  • 设置为true,则不会触发执行
    kbase.xml
    <kbase name="lock-on-active" packages="lockOnActive">
        <ksession name="lock-on-active"></ksession>
    </kbase>

lockOnActive.drl

package lockOnActive

import com.drools.demo.Person

rule "test-lock-on-active-1"

when
    $p: Person(age < 20)
then
    System.out.println("test-lock-on-active-1 被触发,age=" + $p.getAge());
    $p.setAge(21);
    update($p)
 end

rule "test-lock-on-active-2"

lock-on-active true
when
    $p: Person(age >= 20)
then
    System.out.println("test-lock-on-active-2 被触发,age=" + $p.getAge());
 end

LockOnActiveTest.java

package com.drools.demo.test;

import com.drools.demo.Person;
import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.KieSession;

import java.lang.reflect.Field;

public class LockOnActiveTest extends BaseKieSession {

    @Test
    public void createTest(){
        KieSession kieSession = getKiesessionByName("lock-on-active");

        Person person = new Person();
        person.setAge(19);

        kieSession.insert(person);

        kieSession.fireAllRules();

        kieSession.dispose();
    }
}

salience

  • 用来设置规则执行的优先级
  • salience属性的值是一个数字,数字越大优先级越高,可以是负数
  • 默认是0
  • 如果不设置salience,则执行是随机的
  • salience可以被动态赋值

kmodule.xml

    <kbase name="salience-rules" packages="salienceTest">
        <ksession name="salience-rules"></ksession>
    </kbase>

salience.drl

package salienceTest

import com.drools.demo.Person

rule "salience1"
salience 0
when
then
 System.out.println("salience1被执行");
 end

rule "salience2"
salience 1
when
then
 System.out.println("salience2被执行");
 end

//动态赋值,把age复制给变量sal
rule "salience3"
salience sal
when
    $p: Person(sal: age)
then
    System.out.println("salience3执行");
 end

SalienceTest.java

package com.drools.demo.test;

import com.drools.demo.Person;
import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.KieSession;

public class SalienceTest extends BaseKieSession {

    @Test
    public void createTest(){
        KieSession kieSession = getKiesessionByName("salience-rules");

        Person person = new Person();
        person.setAge(10);
        kieSession.insert(person);

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

agenda-group

  • agenda-group基本作用
  • 对规则进行分组
  • 设置焦点
package agendaGroup

rule "test-agenda-group-1"
agenda-group "agenda-group-test"
when
then
 System.out.println("agenda-group-1被触发");
 end


rule "test-agenda-group-2"
agenda-group "agenda-group-test"
when
then
 System.out.println("agenda-group-2被触发");
 end

rule "test-agenda-group-3"
agenda-group "agenda-group-test3"
when
then
 System.out.println("agenda-group-3被触发");
 end

auto-focus

  • 对agenda-group和ruleflow-group的补充
  • 默认false
  • 设置为true,自动获取焦点

autoFocus.drl

package autoFocus

rule "test-auto-fucos-1"

agenda-group "auto-focus-1"
auto-focus false
when
then
 System.out.println("auto-focus-1 被触发");
 end

rule "test-auto-fucos-2"

agenda-group "auto-focus-2"
auto-focus true
when
then
 System.out.println("auto-focus-2 被触发");
 end

activation-group

  • 该属性将若干个规则划分成一个组,统一命名默认false
  • 具有相同activation-group属性的规则中只要又一个被执行,其他的规则都不再执行
  • 该属性以前也被成为异或(Xor)组
package activationGroup

rule "test-activation-group=1"
activation-group "activation-group"
when
then
 System.out.println("activation 1 被触发");
 end

rule "test-activation-group=2"
activation-group "activation-group"
salience 1
when
then
 System.out.println("activation 2 被触发");
 end

date-effective

  • 该属性是用来控制规则只有在到达指定时间后才会触发(生效时间)
  • 与系统当前时间进行比对,大于等于系统时间会执行
  • 默认date-effective的日期格式为"dd-MMM-yyyy"
  • 可自定义格式
  • 对照属性date-expire,用法和date-effective一样,只不过是失效时间

dateEffective.drl

package dateEffective

rule "test-date-effective-1"
//date-effective "16-Mar-2021"
date-effective "2021-03-17 21:00"
when
then
 System.out.println("test-date-effective-1 被触发了");
 end

rule "test-date-effective-2"
when
then
 System.out.println("test-date-effective-2 被触发了");
 end

DateEffectiveTest.java

package com.drools.demo.test;

import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.KieSession;

public class DateEffectiveTest extends BaseKieSession {

    @Test
    public void createTest(){
        System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm");
        KieSession kieSession = getKiesessionByName("dateEffective-rules");

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

定时器

  • 基于interval(间隔)和cron表达式
  • 间隔定时器用int来定义,它遵循java.util.Timer对象的使用方法
  • Cron定时器用cron来定义,使用标准的Unix cron表达式
定时器表达式
  • timer(int:?)
  • timer(int:30s)
  • timer(int:30s 5m)
  • timer(cron:)
  • timer(cron:* 0/15 * * * ?)

Server.java

package com.drools.demo;

import lombok.Data;

@Data
public class Server {

    private int times;
    private String result;
}

timer.drl

package timerTest
import com.drools.demo.Server
import java.util.Date

rule "test-timer"

timer (cron:* 0/1 * * * ?)

when
    $s:Server(times > 5)
then
 System.out.println("已经尝试" + $s.getTimes() + "次,超过预警次数");
 $s.setResult(new Date() + "-服务器已经尝试" + $s.getTimes() + "次,依旧失败,发出警告");
 end

TimerTest.java

package com.drools.demo.test;

import com.drools.demo.Server;
import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.FactHandle;

public class TimerTest extends BaseKieSession {

    @Test
    public void createTest() throws Exception{
        KieSession kieSession = getKiesessionByName("timerTest-rules");

        Server server = new Server();
        server.setTimes(0);

        new Thread(()->{
            //一直阻塞,直到等待到halt()
            kieSession.fireUntilHalt();
        }).start();

        FactHandle factHandle = kieSession.insert(server);

        for (int i=1;i<10;i++){
            Thread.sleep(1000);
            server.setTimes(i);
            kieSession.update(factHandle, server);
        }

        Thread.sleep(3000);

        kieSession.halt();

        System.out.println("server尝试结果:" + server.getResult());
    }
}

日历

  • 日历可以单独应用于规则中,也可以和timer结合使用在规则中使用。
  • 通过属性calendars来定义日历
  • 可以和timer配合使用
  • 适用希望在哪天触发的场景

添加quartz依赖

		<dependency>
            <groupId>org.opensymphony.quartz</groupId>
            <artifactId>quartz</artifactId>
            <version>1.6.1</version>
        </dependency>

calendar.drl

package calendarTest

rule "test-calendar-rule1"
calendars "weekday"
when
then
 System.out.println("test-calendar-rule1 被触发");
 end

rule "test-calendar-rule2"
calendars "weekday_exclude"
when
then
 System.out.println("test-calendar-rule2 被触发");
 end

CalendarTest.java

package com.drools.demo.test;

import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.KieSession;
import org.kie.api.time.Calendar;
import org.quartz.impl.calendar.WeeklyCalendar;

public class CalendarTest extends BaseKieSession {

    @Test
    public void createTest(){
        KieSession kieSession = getKiesessionByName("calendarTest-rules");
        kieSession.getCalendars().set("weekday", WEEKDAY);
        kieSession.getCalendars().set("weekday_exclude", WEEKDAY_EXCLUDE);
        kieSession.fireAllRules();
        kieSession.dispose();
    }

    private static final Calendar WEEKDAY = new Calendar(){
        @Override
        public boolean isTimeIncluded(long timestamp) {
            WeeklyCalendar weeklyCalendar = new WeeklyCalendar();
            weeklyCalendar.setDaysExcluded(new boolean[]{false, false, false, false, false, false, false});
            weeklyCalendar.setDayExcluded(java.util.Calendar.WEDNESDAY, true);//排除掉今天星期三
            return weeklyCalendar.isTimeIncluded(timestamp);
        }
    };

    private static final Calendar WEEKDAY_EXCLUDE = new Calendar(){
        @Override
        public boolean isTimeIncluded(long timestamp) {
            WeeklyCalendar weeklyCalendar = new WeeklyCalendar();
            weeklyCalendar.setDaysExcluded(new boolean[]{false, false, false, false, false, false, false});
            return weeklyCalendar.isTimeIncluded(timestamp);
        }
    };
}

其他属性规则

  • dialect:要使用的语言类型,Java,mvel
  • duration:已废弃,规则将指定的时间之后在另外一个线程里触发
  • enabled:设置规则是否可用,true:可用,false:不可用
dialect "java"
dialect "mvel"

LHS简介

  • LHS是规则条件部分的统称,由0个或多个条件元素组成
  • 如果没有条件元素那么默认就是true
  • 多个跳进默认是“和”的关系
  • and不具有优先绑定功能
package lhs
import com.drools.demo.model.Person

rule "test-lhs-rule"
when
//    eval(true)
    $p1: Person(age > 10)
//    or
//    and
    $p2: Person(age > 20)
then
    System.out.println("规则被触发");
 end
KieSession kieSession = getKieSessionByName("lhs-rules");

        Person person1 = new Person();
        person1.setAge(50);

        Person person2 = new Person();
        person2.setAge(30);

        kieSession.insert(person1);
        kieSession.insert(person2);

        kieSession.fireAllRules();
        kieSession.dispose();

Pattern模式基础用法

  • Pattern语法 patternBinding:patternType(“constraints”)
  • 任何一个JavaBean中的属性都可以访问,不过对应的属性要提供getter方法
  • 在pattern的约束条件中,可以任何返回结果为布尔类型的Java表达式
  • Java表达式也可以和增强的表达式进行结合使用,比如属性访问,可以通过使用括号来更改计算优先级,如在任一逻辑或数学表达式中
  • 逗号分隔符,逗号可以对约束条件进行分组,它的作用相当于“AND”
    $p: Person(age % 10 == 0, name == "zhangsan")
  • 变量的绑定
when
    $p: Person($age: age)
then
    System.out.println($age);
 end
  • 内部类分组访问——访问一个内部类的多个属性
    $c: Car(discount < 8, person,(age == < 10, name != ""))
  • 内部强制转换
    //subPerson强制转换成Person
    $c : Car(subPerson#Person.age == 10)
  • 日期字符——规则语法中除了支持Java标准字符,同时也支持日期字符。drools默认支持的日期格式为“dd-mmm-yyyy”,可以通过设置系统变量“drools.dateformat”的值来改变默认的日期格式
$p: Person(birthday > "3-Nov-2017")

Pattern模式之List和Map

  • Person(childList[0].age == 10)
  • Person(credetialMap[“jsmith”].valid)
package rules
import java.util.Map
import java.util.List
import com.drools.demo.model.Person

rule "map-and-list-rule"

when
//    $map: Map()
//    $map: Map(this["a"] == 1)
    $list: List()
    $p: Person(age == 20) from $list
then
//    System.out.println("a=" + $map.get("a"));
//    System.out.println("b=" + $map.get("b"));
//    System.out.println("person list:" + ((Person)$list.get(0)).getAge());
       System.out.println("person list:" + ((Person)$p).getAge());
 end
public class ListAndMapTest extends BaseKieSession {

    @Test
    public void createTest(){
        KieSession kieSession = getKieSessionByName("all-rules");

        Map<String, Integer> map = new HashMap<>();
        map.put("a", 1);
        map.put("a", 2);

        List<Person> list = new ArrayList<>();
        Person person = new Person();
        person.setAge(18);
        list.add(person);

        Person person1 = new Person();
        person1.setAge(20);
        list.add(person1);

        kieSession.insert(map);
        kieSession.insert(list);

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

Pattern模式之运算比较符

&&和||

  • Person(age > 20 && < 40)
  • Person(age((>30 && <40) || (>20 && < 25)))
  • Person(age > 30 && < 40 || location == “london”)

DRL特殊操作符

  • < > >=
  • !. 非空校验
    • Person($streetName : address!.street)——address非空的时候取street赋值给streetName
  • matches操作符(正则表达式)
    • Cheese(type matches “(Buffalo)?\S*Mozzarella”)
  • contains操作符
  • memberOf——用来检查前面对象是否符合后面集合
    • CheeseCounter(cheese memberOf $matureCheeses)

运算符优先级

操作类型操作符备注
(嵌套/空安全)属性访问.!.非标准Java语义
List/Map访问[]非标准Java语义
约束绑定:非标准Java语义
乘除*/%
加减±
移位<<>>>>>>
关系<>>=instanceof
==!=未使用标准Java语义,某些语义相当于equals
非短路AND&
非短路异或^
非短路包含OR
逻辑与&&
逻辑或||
三元运算符?:
逗号分隔, 相当于and,非标准Java语义

RHS

  • RHS是满足LHS条件之后进行后续处理部分的统称
  • 保持RHS的精简和可读性
  • 主要功能是对working memory中的数据进行insert、update、delete或modify操作

函数

insert函数

  • drl中insert函数和调用KieSession中的方法效果一样
  • 调用insert之后,规则会进行重新匹配,如果没有no-loop为true或lock-on-active为true的规则,如果条件满足则会重新执行
package rules
import com.drools.demo.model.Person

rule "test-insert-rule"

when
then
    System.out.println("规则被触发");
    Person person = new Person();
    person.setAge(27);
    insert(person);
 end

update函数

  • update函数可对Working Memory中的Fact对象进行更新变更操作,与StatefulSession中的update的作用基本相同
  • 查看KnowledgeHelper接口中的update方法可以发现,update函数由多种参数组合的使用方法
  • 在实际使用中更多的会传入FACT对象来进行更新操作

update.drl

package updates
import com.drools.demo.Person

rule "update-demo-rule-1"
salience 2
when
    $p: Person(age == 24);
then
    System.out.println("update-demo-rule-1 规则被触发");
//    $p.setAge(25);
//    update($p);
 end

rule "update-demo-rule-2"
salience 1
when
    $p: Person(age == 25)
then
    System.out.println("update-demo-rule-2 规则被触发");
    System.out.println("person.age=" + $p.getAge());
 end

UpdateTest.java

package com.drools.demo.test;

import com.drools.demo.Person;
import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.FactHandle;

public class UpdateTest extends BaseKieSession {

    @Test
    public void createTest(){
        KieSession kieSession = getKiesessionByName("updates-rules");

        Person person = new Person();
        person.setAge(24);

        FactHandle factHandle = kieSession.insert(person);

//        kieSession.fireAllRules();
        
        person.setAge(25);

        kieSession.update(factHandle, person);

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

delete函数

  • delete函数可对working memory中的FACT对象进行删除操作,与StatefulSession中的delete的作用i本想相同
  • 与retract方法效果一样,但已废弃
    delete.drl
package deletes
import com.drools.demo.Person

rule "delete-demo-rule-1"
salience 2
when
    $p: Person(age == 24);
then
    System.out.println("update-demo-rule-1 规则被触发");
    delete($p);
 end

rule "delete-demo-rule-2"
salience 1
when
    $p: Person(age == 24)
then
    System.out.println("update-demo-rule-2 规则被触发");
 end

DeleteTest.java

package com.drools.demo.test;

import com.drools.demo.Person;
import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.FactHandle;

public class DeleteTest extends BaseKieSession {

    @Test
    public void createTest(){
        KieSession kieSession = getKiesessionByName("deleteDemo-rules");

        Person person = new Person();
        person.setAge(24);

        FactHandle factHandle = kieSession.insert(person);
        kieSession.delete(factHandle);
        kieSession.delete(factHandle, FactHandle.State.LOGICAL);

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

modify函数

  • modify是基于结构化的更新操作,它将更新操作与设置属性相结合,用来更改Fact对象的属性
modify(<fact-expression>){
	<expression>[,<expression>]*
}

modify.drl

package modifyTest
import com.drools.demo.Person

rule "modify-demo-rule-1"
salience 2
when
    $p: Person(age == 21);
then
    System.out.println("modify-demo-rule-1 规则被触发");
    modify($p){
        setAge(22)
    }
 end

rule "modify-demo-rule-2"
salience 1
when
    $p: Person(age == 22)
then
    System.out.println("modify-demo-rule-2 规则被触发");
 end

ModifyTest.java

package com.drools.demo.test;

import com.drools.demo.Person;
import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.KieSession;

public class ModifyTest extends BaseKieSession {

    @Test
    public void createTest(){
        KieSession kieSession = getKiesessionByName("modify-rules");

        Person person =  new Person();
        person.setAge(21);

        kieSession.insert(person);

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

结果条件

  • 在Java中,如果有重复的代码我们会考虑进行重构,抽取公共方法或集成父类,以减少相同的代码在多处出现,达到代码的最优管理和不必要的麻烦
  • 有条件的结果
  • Drools规则的继承
  • do和标记
  • LHS中的if判断

condition.drl

package conditions
import com.drools.demo.Person
import com.drools.demo.Car

rule "condition-demo-rule-1"
no-loop true
when
    $p: Person(age > 18)
    if(age == 20) do[updatePerson1]
    else do[updatePerson2]
then
    System.out.println("condition-demo-rule-1 规则被触发");
then[updatePerson1]
    System.out.println("执行updatePerson1");
    modify($p){
        setAge(17)
    }
then[updatePerson2]
    System.out.println("执行updatePerson2");
    modify($p){
        setAge(20)
    }
 end

rule "condition-demo-rule-2"
    extends "condition-demo-rule-1"
when
//    $p: Person(age > 18)
    $c: Car(discount < 90)
then
    System.out.println("condition-demo-rule-2 规则被触发");
 end

ConditionTest.java

package com.drools.demo.test;

import com.drools.demo.Car;
import com.drools.demo.Person;
import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.KieSession;

public class ConditionTest extends BaseKieSession {

    @Test
    public void createTest(){
        KieSession kieSession = getKiesessionByName("condition-rules");

        Person person =  new Person();
        person.setAge(19);

        Car car = new Car();
        car.setDiscount(80);

        kieSession.insert(person);
        kieSession.insert(car);

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

Query查询

  • Query语法提供了一种查询working memory中符合约束条件的FACT对象的简单方法
  • 仅包含规则文件中的LHS部分,不用指定“when”和“then”部分
  • Query有一个可选参数集合,每一个参数都有可选的类型,如果没有指定类型,则默认为Object类型
  • Query名称需要全局唯一
  • 使用kieSession.getQueryResults(“name”)方法可以获得查询的结果

query.drl

package querys
import com.drools.demo.Person

rule "query-demo-rule-1"
when
    $p: Person(age == 20);
then
    System.out.println("Name is " + $p.getName());
    System.out.println("query-demo-rule-1 规则被触发");
 end

query "query-by-age"
    $p1 : Person(age == 23)
end

query "query-by-param" (Integer ageParam)
    $p2 : Person(age >= ageParam)
end

QueryTest.java

package com.drools.demo.test;

import com.drools.demo.Person;
import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.QueryResults;
import org.kie.api.runtime.rule.QueryResultsRow;

public class QueryTest extends BaseKieSession {

    @Test
    public void createTest(){
        KieSession kieSession = getKiesessionByName("query-rules");

        Person person1 =  new Person();
        person1.setAge(20);
        person1.setName("zhangsan");

        Person person2 = new Person();
        person2.setAge(23);
        person2.setName("lisi");

        kieSession.insert(person1);
        kieSession.insert(person2);

        kieSession.fireAllRules();

        QueryResults queryResults1 = kieSession.getQueryResults("query-by-age");
        System.out.println(queryResults1.size());

        for (QueryResultsRow row : queryResults1) {
            Person person = (Person) row.get("$p1");
            System.out.println("Person1 name is: " + person.getName());
        }

        System.out.println("--------------------------------");

        QueryResults queryResults2 = kieSession.getQueryResults("query-by-param", 20);
        System.out.println(queryResults2.size());

        for (QueryResultsRow row : queryResults2) {
            Person person = (Person) row.get("$p2");
            System.out.println("Person2 name is: " + person.getName());
        }

        kieSession.dispose();
    }
}

Function函数

  • 相当于Java中的方法,编译器会生成相应的辅助类
  • 封装多处都调用的业务逻辑到一个方法中
  • 函数的参数和结果数据类型与Java一致

function.drl

package functions
import com.drools.demo.Person

rule "functions-demo-rule-1"
when
then
    System.out.println("functions-demo-rule-1 规则被触发");
    helloFunction();
    System.out.println(paramFunction("Tom"));
 end

function void helloFunction(){
    System.out.println("hello drools function");
}

function String paramFunction(String name){
    return "Hello " + name + "!";
}

FunctionTest.java

package com.drools.demo.test;

import com.drools.demo.kie.BaseKieSession;
import org.junit.Test;
import org.kie.api.runtime.KieSession;

public class FunctionTest extends BaseKieSession {

    @Test
    public void createTest(){
        KieSession kieSession = getKiesessionByName("function-rules");

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

Global全局变量

  • global用来定义全局变量,它可以让应用程序的对象在规则文件中能够被访问
  • 可以用来为规则文件提供数据或服务
  • 可以用来操作规则执行结果的处理和从规则返回数据,比如执行结果的日志或值,或者与应用程序进行交互的规则的回调处理
  • 全局变量并不会被插入到working memory中,因此,除非作为常量值,否则不应该将全局变量于规则约束的判断中
  • 如果多个包中声明具有相同标识符的全局变量,则必须是相同的类型,并且他们都将引用相同的全局值

EmailService.java

package com.drools.demo.service;

public class EmailService {

//    public static void sendEmail(){
    public void sendEmail(){
        System.out.println("Email has be send.");
    }
}

global.drl

package globals
import com.drools.demo.Person

global com.drools.demo.service.EmailService emailService

rule "globals-demo-rule-1"
when
then
    System.out.println("globals-demo-rule-1 规则被触发");
    emailService.sendEmail();
 end

GlobalTest.java

package com.drools.demo.test;

import com.drools.demo.kie.BaseKieSession;
import com.drools.demo.service.EmailService;
import org.junit.Test;
import org.kie.api.runtime.KieSession;

public class GlobalTest extends BaseKieSession {

    @Test
    public void createTest(){
        KieSession kieSession = getKiesessionByName("global-rules");

        EmailService emailService = new EmailService();
        kieSession.setGlobal("emailService", emailService);

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

注释

  • drl文件中的注释与Java的注释方法一样
  • 通过//进行注释
  • 通过/*注释内容*/进行注释

异常

  • drools5引入了标准化的错误信息,可以快速的查找和解决问题
  • 异常分两类,一类为普通的异常,一类为规则特有的异常格式
    在这里插入图片描述

关键字

  • Drools5开始引入了硬关键字和软关键字

硬关键字

  • 硬关键字是保留关键字,在命名demo对象,属性,方法,函数和规则文本中使用的其他元素时,不能使用任何硬编码关键字
    • true
    • false
    • null

软关键字

  • 软关键字只在它们的上下文中被识别,可以在其他地方使用这些词,建议避免
    • 属性
    • 语法关键字(package, import, when, then等)
    • 条件判断等
    • lock-on-active, date-effective, date-expires, no-loop, auto-focus, activation-group, agenda-group, ruleflow-group, entry-point, duration, package, import, dialect, salience, enabled, attributes, rule, extend, when, then, template, query, declare, function, global, eval, not, in, or, and, exists, forall, accumulate, collect, from, action, reverse, result, end, over, init

声明类型

  • 适用于直接定义规则引擎的数据原型,不用再在Java中创建
  • 新构建扩展原型
    示例:
declare Address
	number:int
	streetName:String
	city:String
end

newType.drl

package newTypes
import java.util.Date

rule "newtype-demo-rule"
when
then
    Address address = new Address();
    address.setCity("nanjing");
    address.setNumber(111);
    insert(address);
 end

declare Address
    number: Integer
    city: String
    createTime: Date
end

声明类型之继承和枚举

  • 支持继承extends
  • 支持枚举
package rules
import java.util.Date

rule "new_type_rule0"
when
then
 Address address = new Address();
 address.setName("中国");
 address.setCity("北京");
 address.setNumber(111);
 insert(address)
 end

rule "new_type_rule"

when
    $address: Address(name == CountryName.CHINA.getFullName())
then
   System.out.println("规则被触发");
 end

declare Address extends Country
    number: Integer
    city: String
    createTime: Date
end

declare Country
    name: String
end

declare enum CountryName
    CHINA("中国");
    fullName: String
end

类型声明之API调用

  • 在drl文件中声明的类型可以通过API的方式在代码中进行调用赋值
    NewTypeTest.java
package com.drools.demo.test;

import com.drools.demo.kie.BaseKieSession;
import com.drools.demo.model.Person;
import org.junit.Test;
import org.kie.api.KieBase;
import org.kie.api.definition.type.FactType;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;

public class NewTypeTest extends BaseKieSession {

    @Test
    public void createTest() throws Exception{

        KieContainer kieContainer = getKieContainer();
        KieBase kieBase = kieContainer.getKieBase("newType-rules");
        FactType factType = kieBase.getFactType("com.newType", "Country");
        Object country = factType.newInstance();
        factType.set(country, "name", "美国");

        KieSession kieSession = getKieSessionByName("newType-rules");

        kieSession.fireAllRules();
        kieSession.dispose();

    }
}

元数据

  • 描述数据属性的数据
  • 元数据的作用:定义数据,描述数据等
  • Drools声明元数据
package rules
import com.drools.demo.model.Person
import rules.City

rule "test-meta-rule"
when
then
    City city = new City();

    city = new City("北京");
    city = new City("北京","中国");
    System.out.println("test-meta-rule 规则被触发");
 end

declare City
    @author("zzz")
    name: String @key
    address: String
end

@author是没有实际意思的元数据
@key是有限制的元数据

决策表

初识决策表

  • 决策表是一个“精确而紧凑的”表示条件逻辑的方式,非常适合商业级别的规则
  • 支持xls和csv格式的文件
  • 什么时候用决策表
    • 规则能够被表达为指定格式模板+数据的格式,考虑使用决策表
    • 很少量的规则不建议使用决策表
    • 不是遵循一组规则模板的规则也不建议是使用决策表

决策表编写及编译

  • 决策表格式

    • RuleSet 类似package
    • Squential 优先级定义
    • Functions 方法函数
    • 空行
    • RuleTable 定义规则,类似rulename
    • Condition 条件,就是LHS里的判断条件
      在这里插入图片描述
  • 决策表加载

        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-decisiontables</artifactId>
            <version>7.48.0.Final</version>
        </dependency>
  • 使用SpreadsheetCompiler类进行编译

  • 推荐使用Excel2009,xls格式

决策表实践

决策表配置

全局配置部分
  • RuleSet:定义包名
  • Import:指定导入的class,包括类方法
  • Variables:指定全局变量
  • Notes:输入任何内容
  • Functions:本地方法
RuleTable部分
  • CONDITION:指定单个规则的条件,条件不写的话默认就是==
  • ACTION:指定rule的结果
  • PRIORITY:指定rule的salience属性
  • No-loop:指定rule的no-loop属性
关键字说明是否必须
RuleSet在这个单元格内容和drl文件中的package是一样必须,只能有一个(如果为空,则使用默认值)
Sequential右边的单元可以是true或False,如果是true则确保规则按照从表格的上面到下面的顺序执行(规则是从上到下,如果是false就是乱序)可选
Import要导入规则库中的类的列表可选
Functions紧接右边的单元格可以包含函数,其可用于规则的片段中。drools支持在drl中定义函数,允许逻辑被嵌入在规则中,不用硬编码改变,小心使用,语法与标准DRL相同可选
Variables紧跟在右边的单元格包含global声明,格式是类型跟着变量名与DRL中的global一个意思可选
Queries紧接右边的单元格可以包含Drools支持的全局声明,它一个类型,紧跟着一个查询名字与drl中的query是一个意思可选
RuleTable这个的意思是表示规则名,写法是在RuleTable后直接写规则名,不用另写一列必选
CONDITION指明该列被用于规则条件CONDITION相当于drl中的when每个规则表至少一个
ACTION指明该列将被用于推论,简单理解为结果,相当于drl中r then ACTION 与CONDITION是平行的每个规则表至少一个
PRIORITY指明该列的值将被设置为该规则行的salience值可选
DURATION指明该列的值将被设置为该规则的期限duration可选
NAME指明该列的值将被设置为从那行产生的规则名字可选
NO-LOOP指明这个规则不允许循环可选
ACTIVATION-GROUP在这个列中的单元格的值,指出该规则行属于特定的XOR活动组可选
AGENDA-GROUP在这个列中的单元格的值,指出该规则行属于特定的议程组,可以理解成获取焦点可选
RULEFLOW-GROUP在这个列中的单元格的值,指出该规则行属于特定的规则流组可选

Drools与Springboot集成

依赖包

<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring.version>2.3.2.RELEASE</spring.version>
        <drools.version>7.0.0.Final</drools.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <type>pom</type>
                <version>${spring.version}</version>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>mysql</groupId>-->
<!--            <artifactId>mysql-connector-java</artifactId>-->
<!--        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-spring</artifactId>
            <version>${drools.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-tx</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-beans</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-context</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.3.2</version>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-actuator</artifactId>-->
<!--            <version>1.2.3.RELEASE</version>-->
<!--        </dependency>-->
    </dependencies>

动态加载规则

  • 第一种:KieHelper的reload方式,reloadByHelper
  • 第二种:KieFileSystem重新加载,reload

DroolsAutoConfiguration.java

package com.demo.drools.config;

import com.demo.drools.utils.KieUtils;
import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.runtime.KieContainer;
import org.kie.internal.io.ResourceFactory;
import org.kie.spring.KModuleBeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

@Configuration
public class DroolsAutoConfiguration {

    private static final String RULES_PATH = "rules/";

    @Bean
    @ConditionalOnMissingBean(KieFileSystem.class)
    public KieFileSystem kieFileSystem() throws Exception{
        KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
        for (Resource file: getRuleFiles()){
            kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
        }
        return kieFileSystem;
    }

    @Bean
    @ConditionalOnMissingBean(KieContainer.class)
    public KieContainer kieContainer() throws Exception{
        KieServices kieServices = getKieServices();
        final KieRepository kieRepository = kieServices.getRepository();

        kieRepository.addKieModule(new KieModule() {
            @Override
            public ReleaseId getReleaseId() {
                return kieRepository.getDefaultReleaseId();
            }
        });

        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem());
        Results results = kieBuilder.getResults();
        if (results.hasMessages(Message.Level.ERROR)){
            System.out.println(results.getMessages());
            throw new IllegalStateException("### errors ###");
        }
        kieBuilder.buildAll();

        KieContainer kieContainer = kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
        KieUtils.setKieContainer(kieContainer);
        return kieContainer;
    }

    @Bean
    @ConditionalOnMissingBean(KieBase.class)
    public KieBase kieBase() throws Exception{
        return kieContainer().getKieBase();
    }

    @Bean
    @ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
    public KModuleBeanFactoryPostProcessor kiePostProcessor(){
        return new KModuleBeanFactoryPostProcessor();
    }


    private Resource[] getRuleFiles() throws Exception{
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
    }

    private KieServices getKieServices(){
        return KieServices.Factory.get();
    }
}

ReloadDroolsRules.java

package com.demo.drools.component;

import com.demo.drools.utils.KieUtils;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.Message;
import org.kie.api.builder.Results;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieContainer;
import org.kie.internal.utils.KieHelper;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;

@Component
public class ReloadDroolsRules {

    private String loadRules(){
        //从数据库加载规则
        return "package plausibcheck.adress\n\n rule \"Postcode 6 numbers\"\n\n    when\n  then\n        System.out.println(\"规则2中打印日志:校验通过!\");\n end";
    }

    private KieServices getKieServices(){
        return KieServices.Factory.get();
    }

    public void reload() throws UnsupportedEncodingException {
        KieServices kieServices = getKieServices();
        KieFileSystem kfs = kieServices.newKieFileSystem();
        kfs.write("src/main/resources/rules/temp.drl", loadRules());
        KieBuilder kieBuilder = kieServices.newKieBuilder(kfs).buildAll();
        Results results = kieBuilder.getResults();
        if (results.hasMessages(Message.Level.ERROR)){
            System.out.println(results.getMessages());
            throw new IllegalStateException("### errors ###");
        }

        KieUtils.setKieContainer(kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId()));
        System.out.println("reload新规则重载成功");
    }

    public void reloadByHelper() throws UnsupportedEncodingException{
        KieHelper kieHelper = new KieHelper();
        kieHelper.addContent(loadRules(), ResourceType.DRL);

        Results results = kieHelper.verify();
        if (results.hasMessages(Message.Level.ERROR)){
            throw new IllegalStateException("### errors ###");
        }

        KieContainer kieContainer = kieHelper.getKieContainer();

        KieUtils.setKieContainer(kieContainer);
        System.out.println("新规则重载成功");
    }
}

KieUtils.java

package com.demo.drools.utils;

import org.kie.api.runtime.KieContainer;

public class KieUtils {

    private static KieContainer kieContainer;

    public static KieContainer getKieContainer(){
        return kieContainer;
    }

    public static void setKieContainer(KieContainer kieContainer){
        KieUtils.kieContainer = kieContainer;
    }
}

TestController.java

package com.demo.drools.controller;

import com.demo.drools.component.ReloadDroolsRules;
import com.demo.drools.model.Address;
import com.demo.drools.model.fact.AddressCheckResult;
import com.demo.drools.utils.KieUtils;
import org.kie.api.runtime.KieSession;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.io.IOException;

@RestController
@RequestMapping("/test")
public class TestController {

    @Resource
    private ReloadDroolsRules reloadDroolsRules;

    @ResponseBody
    @RequestMapping("/address")
    public void test(){
        KieSession kieSession = KieUtils.getKieContainer().newKieSession();

        Address address = new Address();
        address.setPostcode("994251");

        AddressCheckResult result = new AddressCheckResult();

        kieSession.insert(address);
        kieSession.insert(result);

        int ruleFiredCount = kieSession.fireAllRules();
        System.out.println("触发了" + ruleFiredCount + "条规则");

        if (result.isPostCodeResult()){
            System.out.println("规则校验通过");
        }

        kieSession.dispose();
    }

    @ResponseBody
    @RequestMapping("/reload")
    public String reload() throws IOException{
        reloadDroolsRules.reload();
        return "OK";
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值