Drools集成SpringBoot

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Drools集成SpringBoot相关的知识,希望对你有一定的参考价值。

参考技术A 为了更好的在项目中使用Drools,
需要把Drools集成到Spring Boot,
下面介绍集成的方法,
并且开发简单的Demo和测试用例。

pom.xml工程信息:

引入spring-boot-starter-web作为Web工程,对外提供Rest服务,
引入spring-boot-starter-log4j2日志框架,打印测试匹配结果,
引入spring-boot-starter-test测试框架,开发Junt5测试用例:

通过kie-spring引入Drools相关的jar包,
其依赖的spring版本都排除掉,
以上一步的spring boot依赖为准。

启动类DroolsApplication.java:

操作的对象Person,
Person.java:

对外提供的Rest服务,
可以对Person对象进行规则匹配,
提供了两个接口,
单个和批量的操作接口:
PersonRuleController.java:

在上面PersonRuleController中需要用到KieContainer,
这个必须在Spring中先初始化才能使用,
相关功能由DroolsAutoConfiguration.java提供:

在DroolsAutoConfiguration中指定了drl规则文件
所在目录rules/com/ai/prd/,
在src/main/resources/rules/com/ai/prd/目录下新建文件
ai-rules.drl:

规则1匹配名字为bob的人,并且调用工具类PersonRuleAction打印相关日志,
同时打印规则的名称和包路径到控制台。
规则2匹配年龄在25到65之间的打工人,
然后把匹配到的人直接打印到控制台。

PersonRuleAction.java在匹配到相应规则时被调用,
此处仅实现日志打印的功能:

在src/main/resources目录下,
新建日志配置文件Log4j2.xml:

日志文件配置后,
PersonRuleAction类打印的日志
不仅会输出到log/rule_result.log,
也会输出到控制台。

针对上面PersonRuleController提供的Rest接口,
开发两个Junit5的测试用例,
在src/test/java/目录下
创建PersonRuleControllerTest.java:

PersonRuleControllerTest执行后,
控制台输出:

cannot be cast to org.drools.compiler.kie.builder.impl.InternalKieModule
大概率是rule规则文件有问题,
格式,中英文字符,语法等问题,
请确保规则文件正确。
可以安装相应插件打开规则文件,
请参考:
Drools的Eclipse_IDEA插件安装

Drools规则引擎 系列教程(一)SpringBoot整合 & 快速集成上手
《Drools7.0.0.Final规则引擎教程》之Springboot集成
Drools创建Maven工程

规则引擎Drools介绍使用及SpringBoot整合Drools

一、Drools介绍

Drools 是一款由JBoss组织提供的基于Java语言开发的开源规则引擎,可以将复杂且多变的业务规则从硬编码中解放出来,以规则脚本的形式存放在文件或特定的存储介质中(例如存放在数据库中),使得业务规则的变更不需要修改项目代码、重启服务器就可以在线上环境立即生效。

Drools使用 Rete 算法对所编写的规则求值。Drools 允许使用声明方式表达业务逻辑。

可以使用非 XML 的本地语言编写规则,从而便于学习和理解。并且,还可以将 Java 代码直接嵌入到规则文件中,这令 Drools 的学习更加吸引人。

官网:http://www.drools.org/#
官方文档:http://www.drools.org/learn/documentation.html

1.1 优点

  • 非常活跃的社区支持
  • 易用
  • 快速的执行速度
  • 在 Java 开发人员中流行
  • 与 Java Rule Engine API(JSR 94)兼容

1.2 架构

Drools 是业务逻辑集成平台,被分为4个项目:

  • Drools Guvnor (BRMS/BPMS):业务规则管理系统
  • Drools Expert (rule engine):规则引擎,drools的核心部分
  • Drools Flow (process/workflow):工作流引擎
  • Drools Fusion (cep/temporal reasoning):事件处理

1.2.1 Drools引擎的核心组件

▶Rules:所有的业务规则和决策表都叫做规则。所有的规则都由规则条件校验(LHS 或conditions)和逻辑处理(RHS 或 actions)两部分组成;

▶Facts:业务数据或者称之为“事实”。所有加入到规则引擎的数据或者规则产生的数据都叫“事实”,规则引擎会根据规则或决策表过滤和加工这些数据。

▶Production memory:生产内存。当程序运行时,用于存储rules。

▶Working memory:工作内存。当程序运行时,所有facts都存储在这里。

▶Agenda:在规则引擎中注册和排序所有匹配实例(符合规则条件的数据 和 这一规则(或 一组规则)被称之为匹配实例),并激活匹配实例。

1.2.2 规则引擎工作流程简述

1、当业务数据被加入到规则引擎中后,它们会以一个或多个“事实”的形式被存储在工作内存中;

2、这些“事实”会与存储在生产内存中规则集合依次匹配,以确定哪些数据符合规则条件;

3、符合条件的数据与相应的规则组成 匹配实例被注册到agenda,然后依据规则的优先级等规则冲突策略对 匹配实例进行排序,以确定这些匹配实例的执行顺序并激活它们。

1.3 Drools语法

1.3.1 规则文件

规则文件可以使用 .drl文件,也可以是xml文件,这里我们使用drl文件

规则文件

package:对一个规则文件而言,package是必须定义的,必须放在规则文件第一行,package的名字是随意的,不必必须对应物理路径,跟java的package的概念不同,这里只是逻辑上的一种区分

如:
package com.sankuai.meituan.waimai.drools.demo

import:导入规则文件需要使用到的外部规则文件或者变量,这里的使用方法跟java相同,但是不同于java的是,这里的import导入的不仅仅可以是一个类,也可以是这个类中的某一个可访问的静态方法

import com.drools.demo.point.PointDomain;

rule:定义一个具体规则。rule “ruleName”。一个规则可以包含三个部分:

  • 属性部分: 定义当前规则执行的一些属性等,比如是否可被重复执行、过期时间、生效时间等。
  • 条件部分(LHS): 定义当前规则的条件,如 when Message(); 判断当前workingMemory中是否存在Message对象。
  • 结果部分(RHS): 即当前规则条件满足后执行的操作,可以直接调用Fact对象的方法来操作应用。这里可以写普通java代码

rule部分

rule "ruleName"
     no-loop true

 <span class="hljs-keyword">when</span>
     $message<span class="hljs-symbol">:Message</span>(status == <span class="hljs-number">0</span>)

 <span class="hljs-keyword">then</span>
     System.out.println(<span class="hljs-string">"fit"</span>);
     $message.setStatus(<span class="hljs-number">1</span>);
     update($message);

1.3.1.1. 规则详情
1. 属性详情
  • no-loop: 定义当前的规则是否不允许多次循环执行,默认是false;当前的规则只要满足条件,可以无限次执行。什么情况下会出现一条规则执行过一次又被多次重复执行呢?drools提供了一些api,可以对当前传入workingMemory中的Fact对象进行修改或者个数的增减,比如上述的update方法,就是将当前的workingMemory中的Message类型的Fact对象进行属性更新,这种操作会触发规则的重新匹配执行,可以理解为Fact对象更新了,所以规则需要重新匹配一遍,那么疑问是之前规则执行过并且修改过的那些Fact对象的属性的数据会不会被重置?结果是不会,已经修改过了就不会被重置,update之后,之前的修改都会生效。当然对Fact对象数据的修改并不是一定需要调用update才可以生效,简单的使用set方法设置就可以完成,这里类似于java的引用调用,所以何时使用update是一个需要仔细考虑的问题,一旦不慎,极有可能会造成规则的死循环。上述的no-loop true,即设置当前的规则,只执行一次,如果本身的RHS部分有update等触发规则重新执行的操作,也不要再次执行当前规则。
    但是其他的规则会被重新执行,岂不是也会有可能造成多次重复执行,数据紊乱甚至死循环?答案是使用其他的标签限制,也是可以控制的:lock-on-active true
  • lock-on-active:lock-on-active true 通过这个标签,可以控制当前的规则只会被执行一次,因为一个规则的重复执行不一定是本身触发的,也可能是其他规则触发的,所以这个是no-loop的加强版
  • date-expires:设置规则的过期时间,默认的时间格式:“日-月-年”
  • date-effective:设置规则的生效时间,时间格式同上。
  • duration:规则定时,duration 3000,3秒后执行规则
  • salience:优先级,数值越大越先执行,这个可以控制规则的执行顺序。

rule attributes

2. 条件部分- LHS
  • when:规则条件开始。条件可以单个,也可以多个,多个条件一次排列
    如:当前规则只有在这三个条件都匹配的时候才会执行RHS部分
when
      eval(true)
      $customer:Customer()
      $message:Message(status==0)
  • eval(true):是一个默认的api,true 无条件执行,类似于 while(true)
  • 操作符>>=<<===!=containsnot containsmemberOfnot memberOfmatchesnot matches

操作符

  • contains: 对比是否包含操作,操作的被包含目标可以是一个复杂对象也可以是一个简单的值
    Person( fullName not contains "Jr" )
  • not contains:与contains相反。
  • memberOf:判断某个Fact属性值是否在某个集合中,与contains不同的是他被比较的对象是一个集合,而contains被比较的对象是单个值或者对象
    CheeseCounter( cheese memberOf $matureCheeses )
  • not memberOf:与memberOf正好相反
  • matches:正则表达式匹配
    Cheese( type matches "(Buffalo)?\\\\S*Mozarella" )
    注意: 就像在Java中,写为字符串的正则表达式需要转义“\\”
  • not matches:与matches正好相反
3. 结果部分- RHS

当规则条件满足,则进入规则结果部分执行,结果部分可以是纯java代码

  • then:
then
     System.out.println("OK"); //会在控制台打印出ok
end
  • insert:往当前workingMemory中插入一个新的Fact对象,会触发规则的再次执行,除非使用no-loop限定
  • update:更新
  • modify:修改,与update语法不同,结果都是更新操作
  • retract:删除
rule "Rule 03" 
      when 
          $number : Number( ) 
          not Number( intValue < $number.intValue ) 
      then 
          System.out.println("Number found with value: " + $number.intValue() ); 
          retract( $number );
end
1.3.1.2 Drools关键词
关键词描述详情
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-
1.3.1.3 Drools方法定义
  • function
function String hello(String name) { 
      return "Hello "+name+"!";
}
1.3.1.4 Drools声明类型
  • declare:声明类型
  • 声明Class、Enum etc类型
  • 声明元数据
1. 声明类类型
declare  Address 
    number : int 
    streetName : String 
    city : String
end
2. 声明枚举类型
declare enum DaysOfWeek
    SUN("Sunday"),MON("Monday"),TUE("Tuesday"),WED("Wednesday"),THU("Thursday"),FRI("Friday"),SAT("Saturday"); 
    fullName : String
end
3. 声明元数据类型

元数据可以被分配给在Drools中几个不同的结构:

  • fact types
  • fact attributes
  • rules
定义格式:
@metadata_key(metadata_value)
例子:
@author( Bob )
import java.util.Date
declare Person
@author( Bob )
@dateOfCreation( 01-Feb-2009 )
name : String @key @maxLength( 30 )
dateOfBirth : Date address : Address
end

声明元数据类级别 关键词

  • @role( <fact | event> )
import some.package.StockTick
declare StockTick 
    @role ( event )
end
  • @typesafe( <boolean> )
  • @timestamp( <attribute name> )
declare VoiceCall 
    @role( event ) 
    @timestamp( callDateTime )
end
  • @duration( <attribute name> )
  • @expires( <time interval> )
  • @propertyChangeSupport
  • @propertyReactive

声明元数据属性级别 关键词

  • @key
两个方面影响:
  • 根据@key作为类标识符,类比较以 @key 的字段为准
  • 根据@key字段生成构造函数
declare Person 
    firstName : String @key 
    lastName : String @key 
    age : int
end
  • @position
declare Cheese 
    name : String @position(1) 
    shop : String @position(2) 
    price : int @position(0)
end

1.4 设计

1.5 Drools vs ILog vs Jess vs Mandarax

优点
Drools开源、社区非常活跃、易使用、免费、JSR94兼容(JSR94是Java Rule Engine API)、支持Java、强大的工具集只支持一种推理方式、安全性不够
ILog性能高(电信领域使用)、易使用商业产品、不开源
Jess支持2种推理方式(正向链和反向链)、很强的表示、推理能力、支持AOP不开源、无规则管理工具、不易使用
Mandarax开源、免费、支持JavaJSR94不兼容(JSR94是Java Rule Engine API)、已经不更新、社区不活跃、并且文档不全

1.5.1 推理方式

  • 正向链推理:一条由问题开始搜索,并得到其解答的链称为正向链推理。
  • 反向链推理:一条由假设回推到支持该假设的事实的链称为反向链推理。

二、Drools使用

2.1 Drools的基本使用

在项目中使用drools时,即可以单独使用也可以整合spring使用。如果单独使用只需要导入如下maven坐标即可:

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

如果我们使用IDEA开发drools应用,IDEA中已经集成了drools插件。如果使用eclipse开发drools应用还需要单独安装drools插件。

drools API开发步骤如下:

2.2 Drools基础语法

2.2.1 规则文件构成

在使用Drools时非常重要的一个工作就是编写规则文件,通常规则文件的后缀为.drl。

drl是Drools Rule Language的缩写。在规则文件中编写具体的规则内容。

一套完整的规则文件内容构成如下:

关键字描述
package包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用
import用于导入类或者静态方法
global全局变量
function自定义函数
query查询
rule end规则体

Drools支持的规则文件,除了drl形式,还有Excel文件类型的。

2.2.2 规则体语法结构

规则体是规则文件内容中的重要组成部分,是进行业务规则判断、处理业务结果的部分。

规则体语法结构如下:

rule "ruleName"
    attributes
    when
        LHS 
    then
        RHS
end

rule:关键字,表示规则开始,参数为规则的唯一名称。

attributes:规则属性,是rule与when之间的参数,为可选项。

when:关键字,后面跟规则的条件部分。

LHS(Left Hand Side):是规则的条件部分的通用名称。它由零个或多个条件元素组成。如果LHS为空,则它将被视为始终为true的条件元素。 (左手边)

then:关键字,后面跟规则的结果部分。

RHS(Right Hand Side):是规则的后果或行动部分的通用名称。 (右手边)

end:关键字,表示一个规则结束。

2.2.3 注释

在drl形式的规则文件中使用注释和Java类中使用注释一致,分为单行注释和多行注释。

单行注释用"//“进行标记,多行注释以”/“开始,以”/"结束。如下示例:

//规则rule1的注释,这是一个单行注释
rule "rule1"
    when
    then
        System.out.println("rule1触发");
end

/*
规则rule2的注释,
这是一个多行注释
*/
rule "rule2"
    when
    then
        System.out.println("rule2触发");
end

2.2.4 Pattern模式匹配

前面我们已经知道了Drools中的匹配器可以将Rule Base中的所有规则与Working Memory中的Fact对象进行模式匹配,那么我们就需要在规则体的LHS部分定义规则并进行模式匹配。LHS部分由一个或者多个条件组成,条件又称为pattern。

pattern的语法结构为:绑定变量名:Object(Field约束)

其中绑定变量名可以省略,通常绑定变量名的命名一般建议以$开始。如果定义了绑定变量名,就可以在规则体的RHS部分使用此绑定变量名来操作相应的Fact对象。Field约束部分是需要返回true或者false的0个或多个表达式。

例如我们的入门案例中:

//规则二:所购图书总价在100到200元的优惠20元
rule "book_discount_2"
    when
        //Order为类型约束,originalPrice为属性约束
        $order:Order(originalPrice < 200 && originalPrice >= 100)
    then
        $order.setRealPrice($order.getOriginalPrice() - 20);
        System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end

通过上面的例子我们可以知道,匹配的条件为:

1、工作内存中必须存在Order这种类型的Fact对象-----类型约束

2、Fact对象的originalPrice属性值必须小于200------属性约束

3、Fact对象的originalPrice属性值必须大于等于100------属性约束

以上条件必须同时满足当前规则才有可能被激活。

绑定变量既可以用在对象上,也可以用在对象的属性上。例如上面的例子可以改为:

//规则二:所购图书总价在100到200元的优惠20元
rule "book_discount_2"
    when
        $order:Order($op:originalPrice < 200 && originalPrice >= 100)
    then
        System.out.println("$op=" + $op);
        $order.setRealPrice($order.getOriginalPrice() - 20);
        System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end

LHS部分还可以定义多个pattern,多个pattern之间可以使用and或者or进行连接,也可以不写,默认连接为and。

//规则二:所购图书总价在100到200元的优惠20元
rule "book_discount_2"
    when
        $order:Order($op:originalPrice < 200 && originalPrice >= 100) and
        $customer:Customer(age > 20 && gender=='male')
    then
        System.out.println("$op=" + $op);
        $order.setRealPrice($order.getOriginalPrice() - 20);
        System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end

2.2.5 比较操作符

Drools提供的比较操作符,如下表:

符号说明
>大于
<小于
>=大于等于
<=小于等于
==等于
!=不等于
contains检查一个Fact对象的某个属性值是否包含一个指定的对象值
not contains检查一个Fact对象的某个属性值是否不包含一个指定的对象值
memberOf判断一个Fact对象的某个属性是否在一个或多个集合中
not memberOf判断一个Fact对象的某个属性是否不在一个或多个集合中
matches判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配
not matches判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配

前6个比较操作符和Java中的完全相同,下面我们重点学习后6个比较操作符。

2.2.5.1 语法

  • contains | not contains语法结构

    Object(Field[Collection/Array] contains value)

    Object(Field[Collection/Array] not contains value)

  • memberOf | not memberOf语法结构

    Object(field memberOf value[Collection/Array])

    Object(field not memberOf value[Collection/Array])

  • matches | not matches语法结构

    Object(field matches “正则表达式”)

    Object(field not matches “正则表达式”)

contain是前面包含后面,memberOf是后面包含前面。

2.2.5.2 操作步骤

第一步:创建实体类,用于测试比较操作符

package com.itheima.drools.entity;
import java.util.List;

/**
 * 实体类
 * 用于测试比较操作符
 */
public class ComparisonOperatorEntity {
    private String names;
    private List<String> list;

    public String getNames() {
        return names;
    }

    public void setNames(String names) {
        this.names = names;
    }

    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }
}

第二步:在/resources/rules下创建规则文件comparisonOperator.drl

package comparisonOperator
import com.itheima.drools.entity.ComparisonOperatorEntity
/*
 当前规则文件用于测试Drools提供的比较操作符
*/

//测试比较操作符contains
rule "rule_comparison_contains"
    when
        ComparisonOperatorEntity(names contains "张三")
        ComparisonOperatorEntity(list contains names)
    then
        System.out.println("规则rule_comparison_contains触发");
end

//测试比较操作符not contains
rule "rule_comparison_notContains"
    when
        ComparisonOperatorEntity(names not contains "张三")
        ComparisonOperatorEntity(list not contains names)
    then
        System.out.println("规则rule_comparison_notContains触发");
end

//测试比较操作符memberOf
rule "rule_comparison_memberOf"
    when
        ComparisonOperatorEntity(names memberOf list)
    then
        System.out.println("规则rule_comparison_memberOf触发");
end

//测试比较操作符not memberOf
rule "rule_comparison_notMemberOf"
    when
        ComparisonOperatorEntity(names not memberOf list)
    then
        System.out.println("规则rule_comparison_notMemberOf触发");
end

//测试比较操作符matches
rule "rule_comparison_matches"
    when
        ComparisonOperatorEntity(names matches "张.*")
    then
        System.out.println("规则rule_comparison_matches触发");
end

//测试比较操作符not matches
rule "rule_comparison_notMatches"
    when
        ComparisonOperatorEntity(names not matches "张.*")
    then
        System.out.println("规则rule_comparison_notMatches触发");
end

第三步:编写单元测试

//测试比较操作符
@Test
public void test3(){
    KieServices kieServices = KieServices.Factory.get();
    KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
    KieSession kieSession = kieClasspathContainer.newKieSession();

    ComparisonOperatorEntity comparisonOperatorEntity = new ComparisonOperatorEntity();
    comparisonOperatorEntity.setNames("张三");
    List<String> list = new ArrayList<String>();
    list.add("张三");
    list.add("李四");
    comparisonOperatorEntity.setList(list);

    //将数据提供给规则引擎,规则引擎会根据提供的数据进行规则匹配,如果规则匹配成功则执行规则
    kieSession.insert(comparisonOperatorEntity);

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

2.2.6 执行指定规则

通过前面的案例可以看到,我们在调用规则代码时,满足条件的规则都会被执行。那么如果我们只想执行其中的某个规则如何实现呢?

Drools给我们提供的方式是通过规则过滤器来实现执行指定规则。对于规则文件不用做任何修改,只需要修改Java代码即可,如下:

KieServices kieServices = KieServices.Factory.get();
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
KieSession kieSession = kieClasspathContainer.newKieSession();

ComparisonOperatorEntity comparisonOperatorEntity = new ComparisonOperatorEntity();
comparisonOperatorEntity.setNames("张三");
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
comparisonOperatorEntity.setList(list);
kieSession.insert(comparisonOperatorEntity);

//通过规则过滤器实现只执行指定规则
kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rule_comparison_memberOf"));

kieSession.dispose();

2.2.7 关键字

Drools的关键字分为:硬关键字(Hard keywords)和软关键字(Soft keywords)。

硬关键字是我们在规则文件中定义包名或者规则名时明确不能使用的,否则程序会报错。软关键字虽然可以使用,但是不建议使用。

硬关键字包括:true false null

软关键字包括: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

比如:
rule true  //不可以
rule "true"  //可以

2.2.8 Drools内置方法

规则文件的RHS部分的主要作用是通过插入,删除或修改工作内存中的Fact数据,来达到控制规则引擎执行的目的。Drools提供了一些方法可以用来操作工作内存中的数据,**操作完成后规则引擎会重新进行相关规则的匹配,**原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功了。

创建如下实体类:

package com.itheima.drools.entity;

import java.util.List;

/**
 * 学生
 */
drools6集成spring

为啥 drools 5.4 文档没有说明 JBPM FORM BUILDER 和与 Guvnor 的集成

急!!!关于drools动态加载规则文件

DROOLS相关资料

Drools笔记:初识与入门

Drools 知识库已弃用