Drools.D.4:规则语言参考

4.1. DRL (Drools Rule Language)规则

DRL(Drools Rule Language)规则是直接在.drl文本文件中定义的业务规则。这些DRL文件是Business Central中所有其他规则资产最终呈现的源文件。您可以在Business Central界面中创建和管理DRL文件,也可以使用Red Hat CodeReady Studio或其他集成开发环境(IDE)在外部创建它们,并作为Maven或Java项目的一部分。DRL文件可以包含一个或多个规则,这些规则至少定义了规则条件(when)和操作(then)。Business Central中的DRL设计器为Java、DRL和XML提供语法高亮显示。

DRL文件由以下组件组成:
DRL文件中的组件

package

import

function  // Optional

query  // Optional

declare   // Optional

global   // Optional

rule "rule name"
    // Attributes
    when
        // Conditions
    then
        // Actions
end

rule "rule2 name"

...

以下示例DRL规则确定贷款申请决策服务中的年龄限制:
贷款申请年龄限制的示例规则

rule "Underage"
  salience 15
  agenda-group "applicationGroup"
  when
    $application : LoanApplication()
    Applicant( age < 21 )
  then
    $application.setApproved( false );
    $application.setExplanation( "Underage" );
end

DRL文件可以包含单个或多个rule、query和function,可以定义资源声明,如:import、global和规则和查询分配和使用的属性。DRL package必须列在DRL文件的顶部,rule通常列在最后。所有其他DRL组件可以遵循任何顺序。

每个规则在规则包中都必须有一个唯一的名称。如果在包中的任何DRL文件中多次使用相同的规则名,则规则无法编译。总是用双引号将规则名括起来,以防止可能的编译错误,特别是在规则名中使用空格时。

与DRL规则相关的所有数据对象必须与Business Central中的DRL文件在同一个项目包中。默认情况下,导入同一个包中的资产。可以使用DRL规则导入其他包中的现有资产。


4.1.1. Packages

package是Drools中相关资产的文件夹,比如数据对象、DRL文件、决策表和其他资产类型。package还可以作为每个规则组的唯一名称空间。一个规则库可以包含多个包。您通常将包的所有规则存储在与包声明相同的文件中,以便包是自包含的。但是,您可以从您想要在规则中使用的其他包中导入对象。

以下示例是抵押应用程序决策服务中的DRL文件的包名和命名空间:

DRL文件中的包定义示例

package org.mortgages;

请注意,包必须有一个名称空间,并且使用标准Java约定来声明包名。也就是说,不允许有空格,不像规则名允许有空格。就元素的顺序而言,它们可以以任何顺序出现在规则文件中,package语句除外,它必须位于文件的顶部。在所有情况下,分号都是可选的。

请注意,任何规则属性(如section规则属性所描述的)也可以在包级别编写,取代属性的默认值。修改后的默认值仍然可以被规则中的属性设置所替换。


4.1.2. Import

与Java中的import语句类似,DRL文件中的import为您想要在规则中使用的任何对象标识完全限定的路径和类型名。您可以以packageName.objectName格式指定包和数据对象。在单独的行上有多个导入。Drools引擎自动从与DRL包同名的Java包和Java.lang包中导入类。

以下示例是抵押应用程序决策服务中的贷款应用程序对象的导入语句:

使用实例DRL文件中的import语句

import org.mortgages.LoanApplication;

4.1.3. Function

DRL文件中的Function将语义代码放在规则源文件中,而不是Java类中。如果规则的操作(then)部分被重复使用,并且每个规则只有参数不同,那么函数就特别有用。在DRL文件中的规则之上,您可以声明函数或将helper类中的静态方法作为函数导入,然后在规则的操作(then)部分中按名称使用函数。

下面的例子说明了在DRL文件中声明或导入的函数:

带有规则的函数声明示例(选项1)

function String hello(String applicantName) {
    return "Hello " + applicantName + "!";
}

rule "Using a function"
  when
    // Empty
  then
    System.out.println( hello( "James" ) );
end

带规则的函数导入示例(选项2)

import function my.package.applicant.hello;

rule "Using a function"
  when
    // Empty
  then
    System.out.println( hello( "James" ) );
end

4.1.4. Query

DRL文件中的Query在Drools引擎的工作内存中搜索与DRL文件中的规则相关的事实。在DRL文件中添加查询定义,然后在应用程序代码中获得匹配的结果。查询搜索一组已定义的条件,不需要when或then规范。查询名对于KIE库是全局的,因此在项目中的所有其他规则查询中必须是唯一的。要返回查询的结果,您可以使用ksession.getQueryResults(“name”)构造一个QueryResults定义,其中“name”是查询名称。这将返回一个查询结果列表,使您能够检索与查询匹配的对象。您可以在DRL文件中的规则之上定义查询和查询结果参数。

以下示例是一个DRL文件中的查询定义,用于抵押贷款应用程序决策服务中的未成年申请人,以及附带的应用程序代码:

查询DRL文件中的定义

query "people under the age of 21"
    $person : Person( age < 21 )
end

获取查询结果的应用示例代码

QueryResults results = ksession.getQueryResults( "people under the age of 21" );
System.out.println( "we have " + results.size() + " people under the age  of 21" );

还可以使用标准的for循环遍历返回的QueryResults。每个元素都是一个QueryResultsRow,您可以使用它来访问元组中的每个列。

获取和迭代查询结果的示例应用程序代码

QueryResults results = ksession.getQueryResults( "people under the age of 21" );
System.out.println( "we have " + results.size() + " people under the age of 21" );

System.out.println( "These people are under the age of 21:" );

for ( QueryResultsRow row : results ) {
    Person person = ( Person ) row.get( "person" );
    System.out.println( person.getName() + "\n" );
}

为了使代码更紧凑,还添加了对位置语法的支持。默认情况下,类型声明中声明的类型顺序与参数位置匹配。但是可以使用@position注释覆盖这些。这允许模式与位置参数一起使用,而不是更详细的命名参数。

declare Cheese
    name : String @position(1)
    shop : String @position(2)
    price : int @position(0)
end

org.drools.definition.type包中的@Position注释可用于注释类路径上的原始pojo。目前,只有类上的字段可以被注释。支持类继承,但不支持接口或方法。下面的isContainedIn查询演示了位置参数在模式中的使用;Location(x, y;)代替Location(thing == x, Location == y)。

查询现在可以调用其他查询,这与可选的查询参数相结合,提供了派生查询风格的反向链接。参数支持位置和命名语法。也可以混合使用位置和命名,但位置必须放在前面,用分号分隔。文字表达式可以作为查询参数传递,但是在这个阶段您不能将表达式与变量混合。下面是一个调用另一个查询的查询示例。注意,这里的’z’总是一个’out’变量。“?'符号表示查询是只拉的,一旦结果返回,您将不会收到进一步的结果,因为基础数据的变化。

declare Location
    thing : String
    location : String
end

query isContainedIn( String x, String y )
    Location(x, y;)
    or
    ( Location(z, y;) and ?isContainedIn(x, z;) )
end

4.1.5. 类型声明和元数据

为规则使用的事实类型定义新的事实类型或元数据:

  • 新的事实类型:Drools的java.lang包中的默认事实类型是Object,但您可以根据需要在DRL文件中声明其他类型。在DRL文件中声明事实类型使您能够在Drools引擎中直接定义一个新的事实模型,而不用在Java等低级语言中创建模型。当已经构建了一个域模型,并且希望用主要在推理过程中使用的额外实体来补充这个模型时,您还可以声明一个新类型。
  • 事实类型的元数据:您可以以@key(value)格式将元数据与新的或现有的事实关联起来。元数据可以是不由事实属性表示的任何类型的数据,并且在该事实类型的所有实例中是一致的。Drools引擎可以在运行时查询元数据,并在推理过程中使用。

4.1.5.1. 没有元数据的类型声明

新事实的声明不需要任何元数据,但必须包含属性或字段列表。如果类型声明不包括标识属性,Drools引擎将在类路径中搜索现有的fact类,如果缺少该类,则会引发错误。

下面的例子是一个新的事实类型Person的声明,在DRL文件中没有元数据:

带有规则的新事实类型声明示例

declare Person
  name : String
  dateOfBirth : java.util.Date
  address : Address
end

rule "Using a declared type"
  when
    $p : Person( name == "James" )
  then   // Insert Mark, who is a customer of James.
    Person mark = new Person();
    mark.setName( "Mark" );
    insert( mark );
end

在本例中,新的事实类型Person具有三个属性:name、dateOfBirth和address。每个属性都有一个类型,可以是任何有效的Java类型,包括您创建的另一个类或您之前声明的事实类型。dateOfBirth属性的类型是java.util.Date,来自Java API, address属性具有前面定义的事实类型Address。

为了避免每次声明类时都写类的全限定名,可以在import子句中定义类的全限定名:

导入中使用完全限定类名的示例类型声明

import java.util.Date

declare Person
    name : String
    dateOfBirth : Date
    address : Address
end

当声明一个新的事实类型时,Drools引擎会在编译时生成一个表示该事实类型的Java类。生成的Java类是类型定义的一对一JavaBeans 映射。

例如,下面的Java类是从示例Person类型声明中生成的:

为Person事实类型声明生成Java类

public class Person implements Serializable {
    private String name;
    private java.util.Date dateOfBirth;
    private Address address;

    // Empty constructor
    public Person() {...}

    // Constructor with all fields
    public Person( String name, Date dateOfBirth, Address address ) {...}

    // If keys are defined, constructor with keys
    public Person( ...keys... ) {...}

    // Getters and setters
    // `equals` and `hashCode`
    // `toString`
}

然后,您可以在规则中像使用其他事实一样使用生成的类,如前面的Person类型声明规则示例所示:

使用声明的Person事实类型的示例规则

rule "Using a declared type"
  when
    $p : Person( name == "James" )
  then   // Insert Mark, who is a customer of James.
    Person mark = new Person();
    mark.setName( "Mark" );
    insert( mark );
end

4.1.5.2. 枚举类型声明

DRL支持以declare enum 格式声明枚举类型,后跟以分号结尾的值列表,以逗号分隔。然后,您可以在DRL文件中的规则中使用枚举列表。

例如,下面的枚举类型声明为员工调度规则定义了星期几:

使用调度规则的枚举类型声明示例

declare enum DaysOfWeek
   SUN("Sunday"),MON("Monday"),TUE("Tuesday"),WED("Wednesday"),THU("Thursday"),FRI("Friday"),SAT("Saturday");

   fullName : String
end

rule "Using a declared Enum"
when
   $emp : Employee( dayOff == DaysOfWeek.MONDAY )
then
   ...
end

4.1.5.3. 扩展类型声明

DRL支持以declare extends 声明继承。要通过在DRL中声明的子类型来扩展在Java中声明的类型,您需要在一个不带任何字段的声明语句中重复父类型。

例如,以下类型声明从顶层Person类型扩展为Student类型,从Student子类型扩展为LongTermStudent类型:

扩展类型声明示例

import org.people.Person

declare Person end

declare Student extends Person
    school : String
end

declare LongTermStudent extends Student
    years : int
    course : String
end

4.1.5.4. 使用元数据的类型声明

您可以将@key(value)(value是可选的)格式的元数据与事实类型或事实属性相关联。元数据可以是不由事实属性表示的任何类型的数据,并且在该事实类型的所有实例中是一致的。Drools引擎可以在运行时查询元数据,并在推理过程中使用。在一个事实类型的属性之前声明的任何元数据都将被分配给该事实类型,而在一个属性之后声明的元数据则被分配给该特定属性。

在下面的示例中,为Person事实类型声明了两个元数据属性@author和@dateOfCreation,为name属性声明了两个元数据项@key和@maxLength。@key元数据属性没有必需的值,因此括号和值被省略。

事实类型和属性的元数据声明示例

import java.util.Date

declare Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )

    name : String @key @maxLength( 30 )
    dateOfBirth : Date
    address : Address
end

对于已存在类型的元数据属性的声明,可以将完全限定类名作为所有声明的import子句的一部分,或作为单独声明子句的一部分:

导入类型的元数据声明示例

import org.drools.examples.Person

declare Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )
end

已声明类型的元数据声明示例

declare org.drools.examples.Person
    @author( Bob )
    @dateOfCreation( 01-Feb-2009 )
end

4.1.5.5. 用于事实类型和属性声明的元数据标记

虽然可以在DRL声明中定义自定义元数据属性,但Drools引擎还支持以下预定义的元数据标记,用于声明事实类型或事实类型属性。

4.1.5.6. 事实类型的属性更改设置和侦听器

默认情况下,Drools引擎不会在每次触发规则时为事实类型重新评估所有事实模式,而是只对在给定模式中受到约束或绑定的修改属性作出反应。例如,如果规则调用modify()作为规则操作的一部分,但该操作没有在KIE库中生成新数据,Drools引擎不会自动重新评估所有事实模式,因为没有修改数据。这种属性反应性行为防止了KIE库中不必要的递归,并导致更有效的规则评估。这种行为还意味着您并不总是需要使用no-loop规则属性来避免无限递归。

4.1.5.7. 在应用程序代码中访问DRL声明的类型

在DRL中声明的类型通常在DRL文件中使用,而Java模型通常在规则和应用程序之间共享模型时使用。因为声明的类型是在KIE库编译时生成的,所以应用程序直到运行时才能访问它们。在某些情况下,应用程序需要直接从声明的类型中访问和处理事实,特别是当应用程序包装Drools引擎并为规则管理提供高级的、特定于领域的用户界面时。


4.1.6. DRL中的全局变量

DRL文件中的全局变量通常为规则提供数据或服务,如规则结果中使用的应用程序服务,并从规则返回数据,如日志或规则结果中添加的值。您可以通过KIE会话配置或REST操作在Drools引擎的工作内存中设置全局值,在DRL文件中的规则上面声明全局变量,然后在规则的操作部分中使用它。对于多个全局变量,在DRL文件中使用单独的行。

下面的例子演示了Drools引擎的全局变量列表配置以及DRL文件中相应的全局变量定义:

Drools引擎全局列表配置

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

带规则的全局变量定义示例

global java.util.List myGlobalList;

rule "Using a global"
  when
    // Empty
  then
    myGlobalList.add( "My global list" );
end

警告
不要使用全局变量在规则中建立条件,除非全局变量有一个不变的不可变值。全局变量没有插入到Drools引擎的工作内存中,因此Drools引擎无法跟踪变量的值变化。
不要使用全局变量在规则之间共享数据。规则总是根据工作内存状态进行推理并作出反应,因此,如果您希望将数据从一个规则传递到另一个规则,则可以将数据作为事实断言到Drools引擎的工作内存中。

全局变量的用例可能是电子邮件服务的实例。在调用Drools引擎的集成代码中,您获得了emailService对象,然后将其设置在Drools引擎的工作内存中。在DRL文件中,您声明有一个类型为emailService的全局变量,并将其命名为“email”,然后在规则结果中,您可以使用诸如email.sendSMS(number,message)的操作。

如果在多个包中声明具有相同标识符的全局变量,则必须设置所有具有相同类型的包,以便它们都引用相同的全局值。


4.1.7. DRL中的规则属性

规则属性是可以添加到业务规则以修改规则行为的附加规范。在DRL文件中,通常在规则条件和操作之上定义规则属性,多个属性在单独的行中,格式如下:

rule "rule_name"
    // Attribute
    // Attribute
    when
        // Conditions
    then
        // Actions
end

下表列出了可以分配给规则的属性的名称和支持的值:

属性
salience 定义规则优先级的整数。在激活队列中排序时,显著性值较高的规则具有更高的优先级。
enabled 一个布尔值。当该选项被选中时,该规则将被启用。当该选项未被选中时,规则将被禁用。
date-effective 包含日期和时间定义的字符串。只有当前日期和时间位于date-effective属性之后,该规则才能被激活。
date-expires 包含日期和时间定义的字符串。如果当前日期和时间在date-expires属性之后,则不能激活规则。
no-loop 一个布尔值。当选择该选项时,如果规则的结果重新触发先前满足的条件,则不能重新激活(循环)规则。当没有选择条件时,可以在这些情况下循环该规则。
agenda-group 标识要向其分配规则的议程组的字符串。议程组允许您对议程进行分区,以对规则组提供更多的执行控制。只有获得焦点的议程组中的规则才能被激活。
activation-group 标识要为其分配规则的激活组的字符串。在激活组中,只能激活一条规则。第一个触发的规则将取消激活组中所有规则的所有挂起激活。
duration 一个长整数值,定义以毫秒为单位的持续时间,在此时间之后,如果仍然满足规则条件,则可以激活规则。
timer 标识用于调度规则的int (interval)或cron计时器定义的字符串。
calendar 用于调度规则的Quartz日历定义。
auto-focus 一个布尔值,只适用于议程组中的规则。当该选项被选中时,下次规则被激活时,一个焦点将自动地分配给该规则所分配的议程组。
lock-on-active 一个布尔值,仅适用于规则流组或议程组中的规则。当选择该选项时,下次规则的规则流组变为活动状态或规则的议程组接收到焦点时,该规则不能再次激活,直到规则流组不再处于活动状态或议程组失去焦点。这是no-loop属性的一个更强版本,因为匹配规则的激活会被丢弃,而不管更新的来源是什么(不仅仅是规则本身)。对于有许多修改事实的规则且不希望任何规则重新匹配并再次触发的计算规则,此属性非常理想。
ruleflow-group 标识规则流组的字符串。在规则流组中,规则只能在关联的规则流激活组时触发。
dialect 将JAVA或MVEL标识为规则中用于代码表达式的语言的字符串。默认情况下,规则使用包级别指定的方言。这里指定的任何方言都会覆盖规则的包方言设置。

4.1.7.1. 计时器和日历规则属性

计时器和日历是DRL规则属性,允许您对DRL规则应用调度和定时约束。这些属性需要根据用例进行额外配置。
DRL规则中的timer属性是一个字符串,标识用于调度规则的int (interval)或cron计时器定义,支持以下格式:
计时器属性格式


4.1.8. 规则条件(WHEN)

DRL规则的when部分(也称为规则的(Left Hand Side,LHS))包含执行操作必须满足的条件。条件由一系列声明的模式和约束组成,具有可选的绑定和受支持的规则条件元素(关键字),这些元素基于包中可用的数据对象。例如,如果银行要求贷款申请人的年龄超过21岁,那么“未成年”规则的条件将是申请人(age < 21)。

DRL使用when而不是if,因为if通常是过程执行流的一部分,在过程执行流中,在特定的时间点检查条件。相比之下,when表示条件求值不局限于特定的求值序列或时间点,而是在任何时间连续发生。只要条件满足,就执行操作。

如果when部分为空,则认为条件为真,并且在Drools引擎中第一次调用fireAllRules()时执行then部分中的操作。如果您想使用规则设置Drools引擎状态,这是非常有用的。

下面的示例规则每次执行时都使用空条件插入一个事实:

不带条件的示例规则

rule "Always insert applicant"
  when
    // Empty
  then   // Actions to be executed once
    insert( new Applicant() );
end

// The rule is internally rewritten in the following way:

rule "Always insert applicant"
  when
    eval( true )
  then
    insert( new Applicant() );
end

如果规则条件使用多个没有定义关键字连接的模式(如and, or, not),默认连接是and:

使用实例没有关键字连接词的规则

rule "Underage"
  when
    application : LoanApplication()
    Applicant( age < 21 )
  then
    // Actions
end

// The rule is internally rewritten in the following way:

rule "Underage"
  when
    application : LoanApplication()
    and Applicant( age < 21 )
  then
    // Actions
end

4.1.8.1. 模式和约束

DRL规则条件中的模式是Drools引擎要匹配的段。模式可以潜在地匹配插入到Drools引擎工作内存中的每个事实。模式还可以包含约束,以进一步定义要匹配的事实。

在没有约束的最简单形式中,模式与给定类型的事实相匹配。在下面的例子中,类型是Person,因此模式将匹配Drools引擎工作内存中的所有Person对象:
单个事实类型的示例模式

Person()

类型不需要是某个事实对象的实际类。模式可以引用超类甚至接口,潜在地匹配来自许多不同类的事实。例如,下面的模式匹配Drools引擎工作内存中的所有对象:

所有对象的示例模式

Object() // Matches all objects in the working memory

模式的括号包含了约束条件,例如以下对人的年龄的约束条件:
带有约束的示例模式

Person( age == 50 )

约束是返回true或false的表达式。DRL中的模式约束本质上是Java表达式,有一些增强,比如属性访问,也有一些区别,比如==和!=的equals()和!equals()语义(而不是通常的相同和不同的语义)。

任何JavaBeans属性都可以直接从模式约束中访问。bean属性使用标准的JavaBeans getter在内部公开,该getter不接受参数并返回一些内容。例如,age属性在DRL中被写成age而不是getter getAge():

Person( age == 50 )

// This is the same as the following getter format:

Person( getAge() == 50 )

Drools使用标准的JDK Introspector类来实现这种映射,因此它遵循标准的JavaBeans规范。为了获得最佳的Drools引擎性能,请使用属性访问格式,如age,而不是显式地使用getter,如getAge()。

警告
不要使用属性访问器以可能影响规则的方式更改对象的状态,因为Drools引擎会缓存调用之间的匹配结果以获得更高的效率。

例如,不要以以下方式使用属性访问器:

public int getAge() {
    age++; // Do not do this.
    return age;
}
public int getAge() {
    Date now = DateUtil.now(); // Do not do this.
    return DateUtil.differenceInYears(now, birthday);
}

与其遵循第二个示例,不如插入一个事实,将当前日期包装在工作内存中,并根据需要在fireAllRules()之间更新该事实。

然而,如果找不到属性的getter,编译器将使用属性名作为回退方法名,不带参数:

Person( age == 50 )

// If `Person.getAge()` does not exist, the compiler uses the following syntax:

Person( age() == 50 )

您还可以在模式中嵌套访问属性,如下面的示例所示。嵌套属性由Drools引擎编制索引。

Person( address.houseNumber == 50 )

// This is the same as the following format:

Person( getAddress().getHouseNumber() == 50 )

警告
在有状态的KIE会话中,要小心使用嵌套访问器,因为Drools引擎的工作内存不知道任何嵌套值,也不会检测它们何时发生变化。当嵌套值的任何父引用被插入到工作内存中时,可以认为嵌套值是不可变的,或者,如果您想修改嵌套值,则将所有外部事实标记为已更新。在前面的示例中,当houseNumber属性发生变化时,任何具有该地址的人都必须被标记为updated。

您可以使用任何返回布尔值的Java表达式作为模式圆括号内的约束。Java表达式可以与其他表达式增强混合使用,比如属性访问:

Person( age == 50 )

你可以通过使用括号来改变计算优先级,就像在任何逻辑或数学表达式中一样:

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

你也可以在约束中重用Java方法,如下面的例子所示:

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

警告
不要使用约束以可能影响规则的方式更改对象的状态,因为Drools引擎会缓存调用之间的匹配结果以获得更高的效率。根据规则条件中的事实执行的任何方法都必须是只读方法。另外,事实的状态不应该在规则调用之间改变,除非这些事实在每次更改时都在工作记忆中被标记为更新。

例如,不要以以下方式使用模式约束:

Person( incrementAndGetAge() == 10 ) // Do not do this.
Person( System.currentTimeMillis() % 1000 == 0 ) // Do not do this.

标准Java操作符优先级适用于DRL中的约束操作符,除了==和!=操作符外,DRL操作符遵循标准Java语义。

==操作符使用null-safe的equals()语义,而不是通常的相同语义。例如,模式Person( firstName == “John” )类似于java.util.Objects.equals(person.getFirstName(), “John”),而且因为"John"不是null,所以模式也类似于"John".equals(person.getFirstName())。

!=操作符使用了null-safe的!equals()语义,而不是通常的不一样的语义。例如,模式Person( firstName != “John” ) 类似于!java.util.Objects.equals(person.getFirstName(), “John”)。

如果字段和约束的值是不同类型的,Drools引擎使用类型强制来解决冲突并减少编译错误。例如,如果在数值求值器中以字符串形式提供“ten”,则会发生编译错误,而“10”则被强制转换为数字10。在强制条件下,字段类型总是优先于值类型:

Person( age == "10" ) // "10" is coerced to 10

对于约束组,可以使用分隔逗号,以使用隐式和连接语义:

// Person is at least 50 years old and weighs at least 80 kilograms:
Person( age > 50, weight > 80 )

// Person is at least 50 years old, weighs at least 80 kilograms, and is taller than 2 meters:
Person( age > 50, weight > 80, height > 2 )

尽管 && 和 , 操作符具有相同的语义,但它们的优先级不同。 && 操作符在 || 操作符之前, && 和 || 操作符都在 , 操作符之前。在*约束中使用逗号操作符,以获得最佳Drools引擎性能和人类可读性。

不能在复合约束表达式中嵌入逗号操作符,例如在括号中:

// Do not use the following format:
Person( ( age > 50, weight > 80 ) || height > 2 )

// Use the following format instead:
Person( ( age > 50 && weight > 80 ) || height > 2 )

4.1.8.2. 模式和约束中的绑定变量

您可以将变量绑定到模式和约束,以引用规则其他部分中的匹配对象。绑定变量可以帮助您更有效地定义规则,或者更一致地注释数据模型中的事实。为了更容易地区分规则中的变量和字段,对变量使用标准格式$variable,特别是在复杂规则中。这个约定很有帮助,但在DRL中不是必需的。

例如,下面的DRL规则将变量$p用于带有Person事实的模式:

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

类似地,您也可以将变量绑定到模式约束中的属性,如下面的示例所示:

// Two persons of the same age:
Person( $firstAge : age ) // Binding
Person( age == $firstAge ) // Constraint expression

确保将约束绑定和约束表达式分离,以便更清晰、更有效地定义规则。尽管支持混合绑定和表达式,但它们会使模式复杂化并影响计算效率。

// Do not use the following format:
Person( $age : age * 2 < 100 )

// Use the following format instead:
Person( age * 2 < 100, $age : age )

Drools引擎不支持绑定到同一声明,但支持跨多个属性统一参数。位置参数总是被统一处理,而命名参数则使用统一符号:=。
下面的例子模式统一了年龄属性横跨两个人的事实:

Person( $age := age )
Person( $age := age )

4.1.8.3. 嵌套约束和内联类型转换

4.1.8.4. 约束中的日期文字

4.1.8.5. 自动装箱和基本类型

4.1.8.6. DRL模式约束中支持的操作符

DRL支持模式约束中的操作符的标准Java语义,但有一些例外,以及一些在DRL中唯一的附加操作符。下面的列表总结了在DRL约束中处理与标准Java语义不同的操作符,或者在DRL约束中唯一的操作符。
.(), #
使用.()操作符对嵌套对象的属性访问器进行分组,并使用#操作符将其转换为嵌套对象中的子类型。转换为子类型使得父类型的getter对子类型可用。可以使用对象名称或完全限定类名称,并且可以强制转换为一个或多个子类型。

带有嵌套对象的模式示例

// Ungrouped property accessors:
Person( name == "mark", address.city == "london", address.country == "uk" )

// Grouped property accessors:
Person( name == "mark", address.( city == "london", country == "uk") )

句点前缀 . 将嵌套对象约束与方法调用区分开来。

将内联类型转换为子类型的示例模式

// Inline casting with subtype name:
Person( name == "mark", address#LongAddress.country == "uk" )

// Inline casting with fully qualified class name:
Person( name == "mark", address#org.domain.LongAddress.country == "uk" )

// Multiple inline casts:
Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )

!.
使用此操作符以null-safe的方式解引用属性。!.操作符左边的值必须不是null(interpreted as != null) ,以便为模式匹配给出一个积极的结果。
null-safe解引用的约束示例

Person( $streetName : address!.street )

// This is internally rewritten in the following way:

Person( address != null, $streetName : address.street )

[]
使用此操作符可以通过索引访问列表值或通过键访问映射值。
使用列表和映射访问的约束示例

// The following format is the same as `childList(0).getAge() == 18`:
Person(childList[0].age == 18)

// The following format is the same as `credentialMap.get("jdoe").isValid()`:
Person(credentialMap["jdoe"].valid)

<, <=, >, >=
在具有自然排序的属性上使用这些操作符。例如,对于日期字段,< 操作符表示在前面,对于String字段,操作符表示按字母顺序在前面。这些性质只适用于可比性质。
使用before操作符的约束示例

Person( birthDate < $otherBirthDate )

Person( firstName < $otherFirstName )

==, !=
在约束中使用这些操作符equals()和!equals()方法,而不是通常的相同和不相同的语义。

null-safe等式约束的示例

Person( firstName == "John" )

// This is similar to the following formats:

java.util.Objects.equals(person.getFirstName(), "John")
"John".equals(person.getFirstName())

null-safe的不相等约束示例

Person( firstName != "John" )

// This is similar to the following format:

!java.util.Objects.equals(person.getFirstName(), "John")

&&, ||
使用这些操作符创建一个缩写的组合关系条件,该条件在字段上添加多个限制。您可以用括号()将约束分组,以创建递归语法模式。
使用缩略组合关系的约束示例

// Simple abbreviated combined relation condition using a single `&&`:
Person(age > 30 && < 40)

// Complex abbreviated combined relation using groupings:
Person(age ((> 30 && < 40) || (> 20 && < 25)))

// Mixing abbreviated combined relation with constraint connectives:
Person(age > 30 && < 40 || location == "london")

matches, not matches
使用这些操作符来指示字段匹配或不匹配指定的Java正则表达式。通常,正则表达式是字符串文字,但是也支持解析为有效正则表达式的变量。这些操作符仅适用于字符串属性。如果您对空值使用匹配,则结果计算总是false。如果您对空值使用not matches,则结果计算总是true。与Java中一样,作为字符串文字编写的正则表达式必须使用双反斜杠 \ 来转义。

匹配或不匹配正则表达式的示例约束

Person( country matches "(USA)?\\S*UK" )

Person( country not matches "(USA)?\\S*UK" )

contains, not contains
使用这些操作符验证Array或Collection的字段是否包含指定的值。这些操作符适用于数组或集合属性,但您也可以使用这些操作符来代替String.contains()和!String.contains()约束检查。

示例集合中包含和不包含的约束

// Collection with a specified field:
FamilyTree( countries contains "UK" )

FamilyTree( countries not contains "UK" )


// Collection with a variable:
FamilyTree( countries contains $var )

FamilyTree( countries not contains $var )

字符串字面值中包含和不包含的约束示例

// Sting literal with a specified field:
Person( fullName contains "Jr" )

Person( fullName not contains "Jr" )


// String literal with a variable:
Person( fullName contains $var )

Person( fullName not contains $var )

为了向后兼容,exclude操作符是not contains的同义词。

memberOf, not memberOf
使用这些操作符来验证字段是否是定义为变量的数组或集合的成员。数组或集合必须是一个变量。
使用集合的memberOf和非memberOf的约束示例

FamilyTree( person memberOf $europeanDescendants )

FamilyTree( person not memberOf $europeanDescendants )

soundslike
使用此操作符验证单词是否具有与给定值几乎相同的发音(使用英语发音)(类似于匹配操作符)。该操作符使用Soundex算法。
示例约束与声音类似

// Match firstName "Jon" or "John":
Person( firstName soundslike "John" )

str
使用此操作符验证字符串字段是以指定值开始还是以指定值结束。您还可以使用此操作符来验证字符串的长度。
使用str的约束示例

// Verify what the String starts with:
Message( routingValue str[startsWith] "R1" )

// Verify what the String ends with:
Message( routingValue str[endsWith] "R2" )

// Verify the length of the String:
Message( routingValue str[length] 17 )

4.1.8.7. 操作符在DRL模式约束中的优先级

4.1.8.8. DRL中支持的规则条件元素(关键字)

and

or

exists

not

forall
使用它来验证是否所有匹配第一个模式的事实都匹配所有剩余的模式。当forall结构满足时,该规则的计算结果为true。该元素是一个范围分隔符,因此它可以使用以前绑定的任何变量,但在它内部绑定的变量不能在它之外使用。

rule "All full-time employees have red ID badges"
  when
    forall( $emp : Employee( type == "fulltime" )
                   Employee( this == $emp, badgeColor = "red" ) )
  then
    // True, all full-time employees have red ID badges.
end

在本例中,规则选择所有类型为“fulltime”的Employee对象。对于匹配此模式的每个事实,规则计算遵循的模式(badge color),如果它们匹配,规则计算为true。

为了说明Drools引擎工作内存中给定类型的所有事实必须匹配一组约束,为了简单起见,可以使用forall和单个模式。

from
使用它来指定模式的数据源。这使得Drools引擎能够对不在工作内存中的数据进行推理。数据源可以是绑定变量的子字段,也可以是方法调用的结果。用于定义对象源的表达式是遵循常规MVEL语法的任何表达式。因此,from元素使您能够轻松地使用对象属性导航、执行方法调用以及访问映射和集合元素。

entry-point

collect

accumulate

eval

####4.1.8.9. 在DRL规则条件下,带有对象图的OOPath语法
####4.1.8.10. DRL中规则条件元素的铁路图


4.1.9. DRL中的规则动作(THEN)

规则的then部分(也称为规则的右手边(Right Hand Side,RHS))包含了当满足规则的条件部分时要执行的操作。操作由一个或多个方法组成,这些方法根据规则条件和包中的可用数据对象执行结果。例如,如果银行要求贷款申请人的年龄超过21岁(有一个规则条件Applicant(age<21))。如果一个贷款申请人未满21岁,那么“未成年”规则的行为将被设置setApproved( false ),因为申请人未满21岁而拒绝贷款。

规则操作的主要目的是在Drools引擎的工作内存中插入、删除或修改数据。有效的规则操作是小的、声明性的和可读的。如果您需要在规则操作中使用命令式或条件代码,那么可以将规则划分为多个更小、声明性更强的规则。

贷款申请年龄限制的示例规则

rule "Underage"
  when
    application : LoanApplication()
    Applicant( age < 21 )
  then
    application.setApproved( false );
    application.setExplanation( "Underage" );
end

4.1.9.1. DRL中支持的规则操作方法

DRL支持以下规则操作方法,您可以在DRL规则操作中使用。您可以使用这些方法来修改Drools引擎的工作内存,而不必首先引用工作内存实例。这些方法作为Drools分发版中KnowledgeHelper类提供的方法的快捷方式。

有关所有规则操作方法,请参阅GitHub上的Drools KnowledgeHelper.java页面。

set
使用它来设置字段的值。

set<field> ( <value> )

设置贷款申请审批值的Action

$application.setApproved ( false );
$application.setExplanation( "has been bankrupt" );

modify
使用它指定要为事实修改的字段,并将更改通知Drools引擎。这个方法为事实更新提供了一种结构化的方法。它结合了更新操作和setter调用来更改对象字段。

modify ( <fact-expression> ) {
    <expression>,
    <expression>,
    ...
}

修改贷款申请的金额和审批

modify( LoanApplication ) {
        setAmount( 100 ),
        setApproved ( true )
}

update
使用它来指定要更新的字段和整个相关事实,并将更改通知Drools引擎。在更改了一个事实之后,必须在更改另一个可能受更新值影响的事实之前调用update。为了避免这个添加的步骤,请使用modify方法。

update ( <object, <handle> )  // Informs the Drools engine that an object has changed

update ( <object> )  // Causes `KieSession` to search for a fact handle of the object

用于更新贷款申请金额和审批的规则操作

LoanApplication.setAmount( 100 );
update( LoanApplication );

如果提供了属性更改侦听器,则在对象更改时不需要调用此方法。有关属性更改侦听器的更多信息,请参见属性更改设置和事实类型的侦听器。

insert
使用它可以将一个新事实插入Drools引擎的工作内存中,并根据事实的需要定义结果字段和值。

insert( new <object> );

插入一个新的贷款申请人对象的操作

insert( new Applicant() );

insertLogical
用它在逻辑上插入一个新的事实到Drools引擎中。Drools引擎负责对事实的插入和撤销进行逻辑决策。在常规的或明确的插入之后,必须明确地收回事实。在逻辑插入之后,当插入事实的规则中的条件不再为真时,插入的事实将自动收回。

insertLogical( new <object> );

规则操作逻辑插入一个新的贷款申请人对象

insertLogical( new Applicant() );

delete
使用它从Drools引擎中删除一个对象。DRL也支持关键字retract并执行相同的操作,但为了与关键字insert保持一致,通常在DRL代码中首选delete。

delete( <object> );

删除贷款申请对象的操作

delete( Applicant );

4.1.9.2. drools和kcontext变量中的其他规则操作方法

除了标准的规则操作方法之外,Drools引擎还支持方法以及预定义的Drools和kcontext变量,您也可以在规则操作中使用这些方法。

您可以使用drools变量来调用drools分发版中的KnowledgeHelper类中的方法,标准规则操作方法也是基于这个类的。所有drools规则操作选项,请参阅GitHub上的drools KnowledgeHelper.java页面。

下面的例子是你可以使用drools变量的常用方法:

  • drools.halt()
    如果用户或应用程序之前调用了fireUntilHalt(),则终止规则执行。当用户或应用程序调用fireUntilHalt()时,Drools引擎将以活动模式启动,并持续评估规则,直到用户或应用程序显式调用halt()。否则,默认情况下,Drools引擎以被动模式运行,仅在用户或应用程序显式调用fireAllRules()时才计算规则。
  • drools.getWorkingMemory()
    返回工作内存对象。
  • drools.setFocus( “<agenda_group>” )
    将焦点设置为规则所属的指定议程组。
  • drools.getRule().getname()
    返回规则的名称。
  • drools.getTuple(), drools.getActivation()
    返回与当前执行规则匹配的元组,然后传递相应的激活。这些调用对于记录日志和调试非常有用。

您可以使用kcontext变量和getKieRuntime()方法来调用KieContext类以及Drools分发版中的RuleContext类中的其他方法。完整的知识运行时API通过kcontext变量公开,并提供广泛的规则操作方法。关于所有kcontext规则操作选项,请参阅GitHub上的Drools RuleContext.java页面。

下面的例子是你可以使用kcontext.getKieRuntime()变量-方法组合的常用方法:

  • kcontext.getKieRuntime().halt()
    如果用户或应用程序之前调用了fireUntilHalt(),则终止规则的执行。这个方法等价于drools.halt()方法。当用户或应用程序调用fireUntilHalt()时,Drools引擎将以活动模式启动,并持续评估规则,直到用户或应用程序显式调用halt()。否则,默认情况下,Drools引擎以被动模式运行,仅在用户或应用程序显式调用fireAllRules()时才计算规则。
  • kcontext.getKieRuntime().getAgenda()
    返回对KIE会话议程的引用,然后提供对规则激活组、规则议程组和规则流组的访问。
  • kcontext.getKieRuntime().getQueryResults( query)
    运行查询并返回结果。这个方法等价于drools.getKieRuntime(). getqueryresults()。
  • kcontext.getKieRuntime().getkiebase()
    返回KieBase对象。KIE基地是您规则系统中所有知识的来源,也是当前KIE会话的发起者。
  • kcontext.getKieRuntime().setGlobal(), ~.getGlobal(), ~.getGlobals()
    设置或检索全局变量。
  • kcontext.getKieRuntime().getenvironment()
    返回运行时环境,类似于操作系统环境。

4.1.9.3. 具有条件和命名结果的高级规则操作

通常,有效的规则操作都是小的、声明性的、可读的。然而,在某些情况下,每个规则只有一个结果的限制可能是具有挑战性的,并导致冗长和重复的规则语法,如下面的示例规则所示:


4.1.10. DRL文件中的注释

DRL支持以双正斜杠//为前缀的单行注释和以正斜杠和星号//为括起来的多行注释。您可以使用DRL注释来注释DRL文件中的规则或任何相关组件。当处理DRL文件时,Drools引擎会忽略DRL注释。

带注释的示例规则

rule "Underage"
  // This is a single-line comment.
  when
    $application : LoanApplication()  // This is an in-line comment.
    Applicant( age < 21 )
  then
    /* This is a multi-line comment
    in the rule actions. */
    $application.setApproved( false );
    $application.setExplanation( "Underage" );
end

4.1.11. 用于DRL故障排除的错误消息

Drools为DRL错误提供标准化消息,以帮助您排除DRL文件中的问题并解决问题。错误信息的格式如下:


4.1.12. DRL规则集中的规则单元

规则单元是一组数据源、全局变量和DRL规则,它们针对特定的目的一起发挥作用。您可以使用规则单元将规则集划分为更小的单元,将不同的数据源绑定到这些单元,然后执行单个单元。规则单元是对规则分组DRL属性(如用于执行控制的规则议程组或激活组)的增强替代方案。

####4.1.12.1. 用于规则单元的数据源
####4.1.12.2. 规则单元执行控制
####4.1.12.3. 规则单元身份冲突


4.1.13. 关于DRL的性能调优注意事项

以下关键概念或建议的实践可以帮助您优化DRL规则和Drools引擎性能。为了方便起见,本节对这些概念进行了总结,并在相互引用的文档(如果适用的话)中进行了更详细的解释。本节将根据Drools新版本的需要展开或更改。


4.2. 领域特定语言

Domain Specific Languages (DSLs) 是一种创建专门用于问题领域的规则语言的方法。一组DSL定义由从DSL“句子”到DRL构造的转换组成,这允许您使用所有底层规则语言和引擎特性。给定一个DSL,您在DSL规则(或DSLR)文件中编写规则,这些规则将被转换为DRL文件。

DSL和DSLR文件都是纯文本文件,您可以使用任何文本编辑器来创建和修改它们。但是也有DSL和DSLR编辑器,在IDE和基于web的BRMS中都有,而且您也可以使用它们,尽管它们可能不会为您提供完整的DSL功能。

上一篇:复习步骤24-28 规则引擎Drools(3)Activiti整合Drools


下一篇:Linux netstat命令