Erlo

规则引擎Drools语法详解

2021-03-20 12:00:10 发布   499 浏览  
页面报错/反馈
收藏 点赞

1 概述

在日常的应用开发中,我们无法避免需求的频繁变化,那么我们可否通过一种方式可以灵活应对呢,接下来就是我们要介绍的主角:Drools,它是一个开源的规则引擎,它可以将不断变化的业务规则从应用的程序代码中抽离出来,交给单独的规则引擎进行处理。最终你只需要按照约定提供配置和入参,就可以达到规则的执行结果,也可以动态修改业务规则,从而快速的响应需求变化。

2 语法

2.1 规则文件

规则文件通常是带有.drl扩展名的文件,在规则文件中可以包含一个或多个rule规则,整体规则结构如下

package package-name

imports

globals

functions

queries

rules


以上元素除 package 必须在第一行申明,其他的元素顺序无关,并且也是可选的,如我们先来看下规则的基本框架

package rules

rule "name"
    attributes
    when
        LHS
    then
        RHS
end

注:(1)attributes 表示规则的属性,对规则的一种约束,如:no-loop属性

(2)LHS 是规则的条件部分

(3)RHS 是条件部分满足后需要执行的代码快

在同一个DRL的规则文件中必须具有唯一的规则名称,如果不同DRL规则中包含同一package包的相同规则名称,则后加载的规则会覆盖之前的。
规则的LHS遵循when关键字(最好另起一行),同样RHS遵循的是then关键字(也另起一行)。该规则以关键字end结束,规则之间不能嵌套。

2.1.1 LHS

LHS表示规则的条件部分,由零个或多个条件组成,如果LHS条件部分为空,那么认为条件始终为true

rule "test1"
    when
        //empty
    then
        System.out.println("test1 rule");
end

rule "test2"
    when
        eval(true)
    then
        System.out.println("test2 rule");
end

多个条件且的关系可以是隐式的方式如下第一个规则,和and连接的方式

rule "test"
    when
        Teacher()
        Student()
    then
        System.out.println("test rule");
end

rule "test2"
    when
        Teacher() and Student()
    then
        System.out.println("test2 rule");
end

注:示例表示工作内存中是否存在Teacher与Student对象

2.1.1.1 模式条件

模式包含零个或多个约束,并具有可选的模式绑定

最简单的模式,如:Person() 表示工作内存中是否存在Person对象,如果是:Object() 那么可以匹配工作内存中所有的对象。

()中表示定义需要约束的动作,如:Person( age == 100 ) 表示Person对象中的age字段值是否等与100

2.1.1.2 模式绑定

可以通过模式绑定的方式来引用对象,如下

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

注:$p 中的符号$不是必须的,但建议这么做,有利于区分变量与属性

2.1.1.3 模式约束

模式约束指返回的的true与false,如:Person(age > 18) 中的age > 18 就是模式约束,它返回true或false

Person(age > 10)
Person(getAge() > 10)

注:这两种方式是等同的,但是建议直接使用属性如age,而不是getAge(),通过字段索引可以提高性能

还支持嵌套属性访问,如

Person(address.cityName == "北京")

模式约束中可以使用java表达式,如

Person( age > 100 && ( age % 10 == 0 ) )

还可以调用Java的工具类

Person( Math.round( weight / ( height * height ) ) 

== 还具有java中的equals()语义,!= 具有 !equals() 语义

Person( firstName == "John" )  相当于 java中的 Objects.equals("John", person.getFirstName())

模式约束中多个约束之间还可以使用逗号来分隔,与使用隐式连接与and连接等同

Person( age > 50, weight > 80 )

逗号(,)运算符不能嵌入到复合约束表达式中,例如括号:

Person( ( age > 50, weight > 80 ) || height > 2 ) // Do NOT do this: compile error

// Use this instead
Person( ( age > 50 && weight > 80 ) || height > 2 )

2.1.1.4 集合访问

可以通过索引直接访问List集合

Person(addressList[0].city == "北京")

通过键值直接访问Map

Person(addressMap["0010"] == "北京")

2.1.1.5 条件连接符

  • and 相当于java中的 &&
  • or 相当于java中的 ||
  • not 检查工作内存中是否不存在指定对象
not Person()
  • exists 检查工作内存中是否存在指定对象,与not相反
  • from 可以指定任意数据源来进行模式匹配,数据源可以是绑定变量上的子字段,也可以是调用函数返回的结果
rule "test"
    when
        Group($persons : persons)
        $p : Person(age == 22) from $persons
    then
        System.out.println("person name:" + $p.getName());
end

工作内存中插入Group对象

Person person1 = new Person();
person1.setAge(22);
person1.setName("张三");

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

Person person3 = new Person();
person3.setAge(22);
person3.setName("王五");

Group group = new Group();
group.setGroupId("001");

List persons = new ArrayList();
persons.add(person1);
persons.add(person2);
persons.add(person3);
group.setPersons(persons);
kieSession.insert(group);

以上示例表示从Group对象中找到用户年龄等于22岁的用户,然后输出用户名称,以上输出内容为:

person name:王五
person name:张三

2.1.2 RHS

RHS一般包含规则要执行的动作,以及结果

  • update(object) 更新工作内存中的一个对象,改操作可能会导致规则被重新执行
  • insert(object) 插入一个对象到工作内存中
  • delete(object) 从工作内存中删除一个对象

我们举一个官方的火灾报警示例,如下

package rules
import org.example.Fire
import org.example.Room
import org.example.Sprinkler
import org.example.Alarm

rule "when there is a fire turn on the sprinkler"
when
    //判断洒水装置对应的房间与火灾发生的房间相同,并且洒水装置开关未开启,你那么就通过modify将对应的洒水装置开启
    $fire : Fire()
    $sprinkler : Sprinkler(room == $fire.room, on == false)
then
    modify($sprinkler){
        setOn(true)
    };
    System.out.println("Turn on the sprinkler for room:" + $fire.getRoom().getName());
end

rule "when thre fire is gone turn off the sprinkler"
when
    //判断洒水装置对应的房间是否开启,并且如果不存在火情,那么关闭对应的洒水装置开关
    $room : Room()
    $sprinkler : Sprinkler($room == room, on == true)
    not Fire(room == $room)
then
    modify($sprinkler){
        setOn(false)
    };
    System.out.println("Turn off the sprinkler for room:" + $room.getName());
end

rule "raise the alarm when we have one or more fires"
when
    // 如果存在火情,那么通过insert插入一个警报对象
    exists Fire()
then
    insert(new Alarm())
    System.out.println("Raise the alarm");
end

rule "cancel the alarm when all the fires have gone"
when
    //如果不存在火情,那么通过delete删除警报对象
    not Fire();
    $alarm : Alarm()
then
    delete($alarm);
    System.out.println("cancel the alarm");
end

rule "status output when things are ok"
when
    //如果既不存在警报,洒水装置也是关闭的,那么就输出一切正常
    not Alarm()
    not Sprinkler(on == true)
then
    System.out.println("Everything is ok");
end

插入内存对象

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

//未发生火灾
KieSession kieSession = kieClasspathContainer.newKieSession("mySession");
String[] names = new String[]{"kitchen", "bedroom", "office", "livingroom"};
Map name2room = new HashMap();
for( String name: names ){
    Room room = new Room( name );
    name2room.put( name, room );
    kieSession.insert( room );
    Sprinkler sprinkler = new Sprinkler( room, false );
    kieSession.insert( sprinkler );
}
kieSession.fireAllRules();

//发生火灾
Fire kitchenFire = new Fire(name2room.get("kitchen"));
Fire officeFire = new Fire(name2room.get("office"));
FactHandle kitchenFact = kieSession.insert(kitchenFire);
FactHandle officeFact = kieSession.insert(officeFire);
kieSession.fireAllRules();

//取消火灾
kieSession.delete(kitchenFact);
kieSession.delete(officeFact);
kieSession.fireAllRules();
kieSession.dispose();

2.2 举一个简单的例子:

package rules
import org.example.Person
dialect  "mvel"

rule "test"
    when
        $p : Person(age > 18)

    then
        System.out.println("成年人");
end

该规则表示:Person对象中的age字段值 大于 18 那么就打印成年人

2.3 关键字

Drools 包含软硬两种关键字,在规则文件中使用的域对象,函数和其他元素时,不能使用硬关键字,但是可以使用软关键字(通常不建议这么做)

2.3.1 硬关键字

  • true
  • false
  • null

2.3.2 软关键字

  • 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

2.4 注释

在规则文件中可以使用注释来忽略被注释的内容

2.4.1 单行注释

使用 “//” 来注释单行内容,如

package rules
dialect  "mvel"

rule "test"
    when
        //这是一个注释
        eval(true)
    then
        System.out.println("hello rule");
end

2.4.2 多行注释

使用 "/* */“ 来进行多行注释,如

package rules
dialect  "mvel"

rule "test"
    when
        /*这是一个多上注释
        多行注释*/
        eval(true)
    then
        System.out.println("hello rule");
end

2.5 错误信息

Drools提供了标准的错误信息格式,如下

错误信息共分为5大块
第一块:错误码
第二块:行列信息
第三块:错误信息描述
第四块:这块通常只具体出错的function、query等
第五块:指具体错误的位置,如when模式、then模式

2.6  package包

package包是规则与其他如(imports, globals)的集合,规则文件中的第一行为package行,语法:package namespace,包名称的命名与java的命名规则相同

2.6.1 import

类似于java中的包导入语句,在规则文件中需要到java类对象导入到规则文件中

如:import org.example.Person

2.6.2 global

全局变量,一般用作执行规则后的结果数据返回或对具体某个服务调用等,如举一个电子邮件服务的实例。

在调用规则引擎的集成代码中,获取emailService对象,然后将其设置在工作内存中。

在DRL中,声明具有EmailService类型的全局变量,并将其命名为“ email”。然后,在规则中就可以使用如email.sendSMS(number,message)来发送电子邮件了。

如果在规则文件中使用了全局变量,那么必须在规则引擎中设置全局变量值,如

global java.util.List myGlobalList;

rule "Using a global"
when
    eval( true )
then
    myGlobalList.add( "Hello World" );
end

必须在规则引擎中设置全局变量值

List list = new ArrayList();
KieSession kieSession = kiebase.newKieSession();
kieSession.setGlobal( "myGlobalList", list );

2.6.3 function

在规则中可以通过函数来做一些通用的逻辑,如:

function String format(String name) {
    return "hello " + name;
}

函数的返回类型与参数类型与java的规则一样

2.7 类型声明

类型申明有两种

2.7.1 声明一个新类型

如我们需要在规则文件中申明一个中间对象,那么就可以这么做

declare User
   userName : String
   age : int
end

以上申明了一个User对象,改对象分别包含userName与age属性,也可以在另外申明的一个对象中引用已申明的对象,如:

declare Order
   orderId : String
   user : User
end

注:规则引擎内部在编译完后其实会生出对应的java类

2.7.2 声明元数据

语法:@metadata_key( metadata_value )

如下元数据声明

declare Order
   @author(feinik)
   @dateOfCreation( 2021-03-16 )
   orderId : String @key
end

@author(feinik) 表示创建Order结构的作者是feinik

@dateOfCreation 表示创建Order结构的时间是 2021-03-16

@key 表示使用orderId属性来作为Order类的构造函数参数,和以orderId作为 生成equals()和hashCode()来判断对象的唯一标识

2.8 规则属性

规则的属性可以用来约束规则执行的某些行为

  • no-loop

类型为boolean,默认值为false,在一个规则当中如果条件满足就对 Working Memory 当中的某个 Fact 对象进行了修改, 比如使用 update 将其更新到当前的 Working Memory 当中,这时引擎会再次检查所有的规则是否满足条件,如果满足会再次执行,这样可能出现死循环。如何避免这种情况呢,这时可以引入 no-loop 属性就可以解决这个问题。no-loop 属性的作用 是用来控制已经执行过的规则在条件再次满足时是否再次执行。

  • salience

类型为整数,默认值为0,可以为负数,用来约束规则的执行顺序,数字越大,优先级越高, salience还支持数值动态绑定,如

rule "Fire in rank order 1,2,.."
        salience( -$rank )
    when
        Element( $rank : rank,... )
    then
        ...
end
  • date-effective

类型为日期型的字符串,用来设置规则的具体激活时间,日期格式为:dd-MMM-yyyy,如果用户指定了激活时间,那么该规则只有当前操作系统时间大于设定的激活时间才会被执行,否则规则被忽略不执行,如下

rule "test2"
    date-effective "17-三月-2021"
    when
        eval(true)
    then
        System.out.println("test2 rule");
end

rule "test3"
    date-effective "18-三月-2021"
    when
        eval(true)
    then
        System.out.println("test3 rule");
end

如果当前日期为17号,那么会输出test2规则被执行 ,而test3规则不会被执行,因为test3设置的激活时间还没有到

  • date-expires

类型为日期型的字符串,用来设置规则的具体失效时间,日期格式为:dd-MMM-yyyy,特性刚好与date-effective属性相反,如果当前时间大于设定的失效时间,那么就不执行,否则则执行

  • enabled

类型为boolean,默认值为true,表示是否开启该规则,false表示关闭改规则不执行

  • dialect

设置规则的语言类型,默认为java,也可以设置成mvel

  • activation-group

该属性的 作用是将若干个规则划分成一个组,用一个字符串来给这个组命名,这样在执行的时候,具有相同 activation-group 属性的规则中只要有一个被执行,其它的规则都将不再执行。也就是说,在一组具有相同 activation-group属性的规则当中,只有一个规则会被执行,其它规则都将不会被执行。当然对于具有相同activation-group属性的规则当中究竟哪一个会先执行,则可以用salience属性来实现

3 总结

本节主要介绍了Drools的一些常用的基本语法,具体还有一些复杂的功能,请查看官网文档:Drools官方文档

登录查看全部

参与评论

评论留言

还没有评论留言,赶紧来抢楼吧~~

手机查看

返回顶部

给这篇文章打个标签吧~

棒极了 糟糕透顶 好文章 PHP JAVA JS 小程序 Python SEO MySql 确认