文档库 最新最全的文档下载
当前位置:文档库 › Drools应用手册

Drools应用手册

Drools应用手册

目录

1简介 (1)

1.1什么是规则引擎? (1)

1.2Drools简介 (1)

1.3何时使用Drools (1)

2Drools工作原理 (3)

2.1产生式规则引擎工作原理 (3)

2.2RETE算法 (4)

3Drools实战 (1)

3.1安装Eclipse开发插件 (1)

3.2Hello Drools (2)

3.3规则语言 (7)

3.3.1规则文件 (7)

3.3.2规则构成 (8)

3.3.3保留字 (8)

3.3.4注释 (10)

3.3.5Package (10)

3.3.6Import (11)

3.3.7Expander (11)

3.3.8Global全局变量 (11)

3.3.9Function (13)

3.3.10规则 (13)

3.3.11规则属性 (15)

3.3.12LHS (when) 条件元素 (17)

1简介

本文档是描述如何去使用Drools的文档,重点放在规则的语法和用法上,可让读者在编写规则是查阅,因此对于Drools的实现原理就不会详尽的介绍,如果读者有兴趣可以联系本文作者索取相关资料。

1.1什么是规则引擎?

在大型商业系统中,业务规则、商业逻辑等等都会比较复杂。而且在很多大型系统当中,很多业务规则、商业逻辑并不是一成不变的。甚至当系统进入生产阶段时,客户的业务规则、商业逻辑也会改变。某些系统要求甚至更高,要求能24小时不停机,并且能够实时修改商业规则。这就对商业系统提出了较大的挑战。如果将这些可变的规则直接编写到代码里面的话,业务规则一旦改变,就要修改代码。并由此带来编译、打包、发布等等问题。这对于生产系统来说是极不方便的。因此,如何考虑把一些可变的业务规则抽取到外面,使这些业务规则独立于程序代码。并最好是能够实时的修改业务规则,这样就可以做到不用打包编译发布等等。因此,规则引擎营运而生。

规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。使用规则引擎可以通过降低实现复杂业务逻辑的组件的复杂性,降低应用程序的维护和可扩展性成本。

大多数规则引擎都支持规则的次序和规则冲突检验,支持简单脚本语言的规则实现,支持通用开发语言的嵌入开发。目前,市面上应用产生了众多的规则引擎。开源规则引擎的代表是Drools;商业规则引擎的代表是ILog。

1.2D rools简介

Drools(又称JBoss Rules)是JBoss开源社区中的一个为Java量身定制的、基于RETE算法的产生式规则引擎的实现。目前(2010年8月),Drools最新的版本是5.1.0.M1,本文档是基于此版本来描述,所有例子都是基于Drools5.1.0.M1。读者可到Drools的官方网站下载Drools的相应版本(https://www.wendangku.net/doc/9310477385.html,/drools/downloads.html)。

1.3何时使用Drools

对这个问题最简短的回答就是“当没有令人满意的传统的程序设计方法能够解决这个问题时”。下面上对所谓没有传统解决方法的一个描述:

对于传统代码来说,问题需要的精确度太高。

这种问题可能并不复杂,但是你找不到一种稳定的方法去建立它。

●问题超越了任何有明显运算法则的方案。

它是一个难以解决的复杂问题,没有明显的传统解决方案或者问题没有一个准确的定论。

●业务逻辑经常发生改变

逻辑本身是简单的(但不是指过于简单),但是规则经常发生变化。在许多软件组织中正式版本的间隔是较长并且较少的,规则可以在适当的安全前提下帮助提供一定的敏捷

性。

●领域专家(或者业务分析师)是非技术人员

领域专家通常对业务规则和流程具有很好的认知。他们通常是不了解软件技术的人员,但是具有很好的逻辑性。规则能够让他们用自己的术语来描述业务逻辑。当然他们仍

然需要严密的思考和良好的逻辑思维能力(许多在非软件技术型岗位上的人没有进行过形

式逻辑的训练,因此在和他们工作时要特别小心,在将业务知识编撰成规则时,要特别注

意业务规则和流程应当是当前能够理解的)。

如果规则对于你的项目组来说是一种新的技术,那在使用前必须将学习与管理的费用成本考虑进去。规则不是一种无意义的技术,这篇文档尽量让其易于理解。

在一个面向对象的应用中,规则引擎通常被用在包含业务逻辑的关键部分(具体与应用相关),特别是在十分繁杂凌乱的部分。这是对面向对象中将所有逻辑封装在对象中的一个倒置。但这并不是说应该抛弃对象模型,相反的来说在任何一个现实应用中业务逻辑仅仅是应用的一部分。如果你曾注意到在你的代码中有很多”if””else””switch”和其它凌乱的逻辑,你总是要回过头去修改它们(可能是由于提供给你的逻辑是错误的,或是你的理解变化了),那么可以考虑使用规则。如果你所面对的问题没有算法或者模式合适解决,考虑使用规则。

规则可以被嵌入你的应用中,或者作为一个服务使用。通常规则最好被当作一个有状态的组件使用——因此它们通常在应用中是一个整体。无论怎样,在一些规则被成功定义为可重用的服务的个案中,规则是无状态的。

如果考虑在组织中使用规则,那考虑产品中将如何使用和更新规则是很重要的(选择很多,但是不同的组织间有不同的需要——通常这超出了应用者/项目团队的控制)。

2Drools工作原理

上文中曾经提到过,Drools是基于RETE算法的产生式规则引擎。那么,什么事产生式规则引擎?什么事RETE算法呢?下面为您逐一讲解。

2.1产生式规则引擎工作原理

产生式规则引擎完全关注于精确表达propositional(命题)和first order logic(一阶逻辑)的知识表示,不存在含糊不清的定义。产生式规则系统的核心是一个能够处理大量规则和事实的推理引擎。推理引擎将事实、数据与产生式规则(也可以叫做产生式,或干脆叫规则)进行匹配,以推出结论。产生式规则是一个用一阶逻辑进行知识呈现的二元结构。

when

then

将新的或已存在的事实与产生式规则进行匹配的过程被称为模式匹配,这个过程由推理机完成。推理机使用的用于模式匹配的算法有很多,包括:

Linear 线性的

Rete 网状

Treat

Leaps 叶状

Drools实现了Rete和Leaps算法;Leaps是试验性质的,因为它是个十分新的算法。Drools 目前用的主要是Rete算法,下一章会详细介绍Rete算法。

规则保存在Production Memory(规则库)中,推理机要匹配的facts(事实)保存在Working Memory(工作内存)中。事实被插入到工作内存中后,可能被修改或删除。一个有大量规则和事实的系统可能会很多规则被满足,这些规则被称为具有冲突性。Agenda通过(冲突决策策略)管理这些冲突规则的执行顺序。

2.2R ETE算法

RETE算法是一个用来实现产生式规则系统的高效模式匹配算法。该算法是由卡内基美隆大学的Charles L. Forgy在1974年发表的论文中所阐述的算法。RETE算法提供了专家系统的一个高效实现。

规则推理引擎做为产生式系统的一部分,当进行事实的断言时,包含三个阶段:匹配、选择和执行,称做match-select-act cycle。RETE算法可以对匹配阶段进行高效实现,下面从鉴别网络和模式匹配过程两个方面对该算法进行介绍。

鉴别网络(如下图所示):

由RETE算法在进行模式匹配时,是根据生成的鉴别网络来进行的。网络中非根结点的类型有1-input结点(也称为alpha结点)和2-input结点(也称为beta结点)两种。1-input结点组成了Alpha网络,2-input结点组成了Beta网络。

每个非根结点都有一个存储区。其中1-input结点有alpha存储区和一个输入口;2-input结点有left存储区和right存储区和左右两个输入口,其中left存储区是beta存储区,right存储区是alpha 存储区。存储区储存的最小单位是工作存储区元素(Working Memory Element,简称WME),WME是为事实建立的元素,是用于和非根结点代表的模式进行匹配的元素。Token是WME的列表,包含有多个WME,(在Forgy的论文中,把Token看成是WME的列表或者单个WME,为了阐述方便,本文将把Token只看成WME的列表,该列表可以包含一个WME或者多个WME),用于2-input结点的左侧输入。事实可以做为2-input结点的右侧输入,也可以做为1-input结点的输入。每个非根结点都代表着产生式左部的一个模式,从根结点到终结点的路径表示产生式的左部。

3Drools实战

3.1安装Eclipse开发插件

Drools提供了基于eclipse的IDE,读者可以到Drools官网(https://www.wendangku.net/doc/9310477385.html,/drools/downloads.html)上下载Drools Eclipse 3.5 Workbench插件到本地,然后解压该压缩包。把解压出来的features和plugins文件夹拷贝到Eclipse安装目录下dropins 文件夹,然后重启Eclipse。

Eclipse启动后,打开Windows > Preference。我们就看到Drools插件已经加载进来,看下图:

然后进入Drools > Install Drools Runtime > 点击Add 按钮。分别填入Runtime的名称和Drools的安装路径(bin)。

包存后,勾上CheckBox。让新增的Runtime成为默认的Runtime再保存设置。

到此为止,Drools的Eclipse插件安装已经成功。

3.2H ello Drools

闲话少说,下面我们利用Eclipse插件来建立第一个Drools项目。

首先,打开File > New > Other窗口打开新建项目窗口,展开Drools节点选择Drools Project 再点击Next按钮。请看下图:

然后,在New Drools Project页面输入你Project的名字,再点击Next按钮。

来到例子的生成页面,勾上头两项:生成一个Hello World的规则文件和生成一个执行规则文件的Java文件。如下图:

最后,Eclipse就会新建一个带有规则文件例子的Drools项目。接下来我们看看这个项目的结构吧。

从上图可以看到,整个项目中有两个文件。第1个是调用规则文件的Java文件,里面写有如何去加载和执行规则文件;第2个文件就是规则文件,里面定义了一些规则。

我们再看看这文件里面的内容吧。首先,是DroolsTest.java文件。

从上图我们也可以看出Drools被分为两个主要的部分:编制和运行时。

编制的过程包括为规则建立DRL或XML文件,传入一个由Antlr 3文法器定义的解析器中。解析器对文件中规则文法的正确性进行检查并为descr建立一个中间结构,在AST中的descr代表规则的描述。AST然后将descr传入Package Builder中,由其进行打包。Package Builder同时负责包括打包中用到的所有代码产生器和编译器。(请参考下图理解)

Package对象是自包含并可配置的,它是一个包含规则的序列化的对象。

RuleBase是运行时组件,包含一个或多个Package。Package在任何时候都可以向RuleBase中添加或删除。

一个RuleBase可以同时初始化多个Working Memory,在其间维护着一个弱引用,除非重新进行配置。Working Memory包含许多子组件,如Working Memory Event Support(事件支持), Truth Maintenance System(真值维护系统), Agenda 和Agenda Event Support(事件支持)。向Working Memory中设置对象的工作可能要在建立了一个或多个激活的规则后才结束。Agenda负有规划激活规则运行的责任。

然后,我们再看看规则文件里的内容:

我们可以看到。规则文件里面有两条规则,分别是“Hello World”和“GoodBye”。每条规则里面有一个LHS(Left hand side 即When语句)和RHS(Right hand side即Then语句)。当一个事实传进来时,他会先去LHS匹配,如果匹配成功就执行RHS的代码;否则,就不执行RHS。然后,该事实再去匹配下一个规则,直到匹配完成或者被retract语句从Working Memory中移除为止。

现在我们回过头来看看这个例子的执行结果吧。右击DroolsTest.Java文件,在弹出菜单中选择Run As > Java Application 来执行测试例子。我们可以在控制窗口中看到运行的结果:

我们一起来解析一下这个例子的运行步骤:

1.程序中给Working Memory插入了一个,message为[Hello World], status 为[Message.Hello]的Message对象。在Working Memory中Message被封装成一个事实(Fact)。

2.Message这个事实就会去匹配各个规则,当匹配到“Hello World”规则的时候,他就符合LHT,RHT会被执行。在RHS中是先输出了Message的message,然后把message属性改为[Goodbye cruel world], 把status属性改为[Message.GOODBYE]。实际上,直到现在Working Memory中的Message还没有被更改,当用户调用update方法时,事实才被修改,通知也会通知Working Memory,Message这个事实已经被更新,要求进行新一轮的规则匹配。

3.在新一轮的规则匹配当中,规则“GoodBye”匹配成功。然后执行其RHS,输出message。

讲到这里相信大家都已经对Drools规则引擎的运行有了初步了解。接下来我们先学习一下Drools文件里的语法吧。

3.3规则语言

3.3.1规则文件

规则文件通常是以drl扩展名结尾。在一个drl文件中可以包含多个规则,函数等等。但是你也可以将规则分开到多个规则文件中(在这种情况下建议采用.rule扩展名,但不是必需的),分散规则利于管理巨量规则的情况。DRL是简单的text文件格式。

规则文件的完整结构是:

package package-name

imports

globals

queries

rules

这些元素的声明顺序不重要,处理package的名称如果声明的话必须是规则文件的第一个元素。所有的元素都是可选的,因此你只要用你所需要的就行了。我们将在下面的内容中对它们逐一讨论。

3.3.2规则构成

规则具有如下主体结构:

rule "name"

attributes

when

LHS

then

RHS

end

规则的结构是非常简单的,许多符号都是不需要的,甚至“name”两边的引号也是可选的。ATTRIBUTES(通常是可选项)指出规则的行为表现。LHS是规则的条件部分,它遵循下面将提到的语法。RHS是允许Java语义代码(很快将支持其它语言和C#)执行的块。仅有的特别的关键字是为了设置,删除和修改facts所用。任何在LHS中绑定的变量可以在RHS中使用。

特别注意的是,空白是不重要的,除非在DSL中使用,在DSL中每一行会先于下一行处理(空白在这里可能有很重要的作用)。

3.3.3保留字

在规则语言中使用了一些保留字。避免在你的领域对象,属性,方法,函数以及规则的其它部分中使用保留字是明智的。接下来的列表是你应该在规则内容中避免使用的保留字(如果使用了,大多数时候可以正常工作,但有时会引起解析错误)。当然你可以将这也关键字用作方法等定义的一部分,例如notSomething(),这样没有问题。

下面的保留字是在编写规则时应当尽力避免使用的:

●rule

●query

●when

●end

●null

●and

●or

●not

●exists

●collect

●accumulate

●from

●forall

●true

●false

●eval

下面的列表是你在编写规则时应当尽量避免使用的,但是如果你不得不在某些地方使用它们,语法分析器也可以正常工作。

●package

●function

●global

●import

●template

●attributes

●enabled

●salience

●duration

●init

●action

●reverse

●result

●contains

●excludes

●memberOf

●matches

●in

●date-effective

●date-expires

●no-loop

●auto-focus

●activation-group

●agenda-group

●dialect

●rule-flow-group

当然,你可以将它们用作方法名称的一部分,如notSomething()——这完全没有问题。

3.3.4注释

你可以使用'#' 或者'//'建立单行注释。语法分析器会自动忽略注释的内容。例如:

//this is a single line comment.

# this is a single line comment.

多行注释使用/* */符号。

/* this is a multi-line comment

in the left hand side of a rule */

3.3.5Package

包是规则以及其它相关结构的一个集合,如import和global。包的成员应该彼此有一定联系,如人力资源的规则包。一个包通过名称空间描绘,这样很好的保持了一组规则的独立性。包名也就是名称空间名,与文件或目录名称无关。

运行时的规则包可以从多个规则源码处组装,当组装完成时,所有的规则被放入一个顶级的Package配置中。但是不可能将不同名称的Package的内容放入同样的Package资源下。在一个RuleBase上可以建立多个Package。通常情况是将所有同名Package下的规则放在一个文件中(这样它就是完全自包含的)。

下面的蓝图显示一个包中可能包含的所有组件。注意,包必须有一个名称空间,并且使用标准的java约定进行命名;例如包名不允许空格,不像规则名称可以有空格。除了package和expander

标记必须放在文件的顶部,在其它规则之前,其它关键字元素的使用没有任何顺序要求,可以使用在文件的任何地方。分号是可选用的。

3.3.6Import

Import标记就像java中的含义一样。对于任何要用在规则中的对象,你需要指定完整的路径和类型名。Drools从同名的java包中自动导入类。

3.3.7Expander

Expander标记是可选的,用来指定DSL配置(通常保存在独立文件中)。这为解析器提供了如何理解你自定义的规则语言。要注意的是在Drools4.0中(这里与Drools3.x中不同),expander 声明强制工具提供你的上下文环境并且避免错误报告,但是API允许用编程方式附加DSL模板,如果expander没有声明在源文件中。

3.3.8Global全局变量

Global是全局变量。如果多个包定义了同样名称的全局变量,它们必须使用同样的类型,并且全部指向同一个全局值。全部变量通常用来返回数据,如一个动作的记录,获得提供数据或服务给规则使用。Global不会插入到Working Memory中,因此当全局变量发生改变时,引擎不会得知;因为这个原因,全局变量不能用在条件约束上,除非这个值不会发生改变。在条件约束中错误使用

全局变量会导致意想不到的结果。Globals是全局的变量。他们常用来将应用程序的对象提供给规则使用,通常是将数据或服务提供给规则使用(指定使用在规则推论中的应用服务),从规则处返回数据(如日志或在规则推论中增加的值)或者从规则中对应用进行回调。全局变量不会被插入Working Memory,因此它们从来不会参与推论,如果全局变量是一个不变的常量,则只在LHS中使用它们。引擎不会通知和跟踪全局变量的值变更。不正确的在约束中使用全局变量会带来让人惊讶的结果——糟糕的惊讶,就像医生对你的X光片说“那里有些有趣的东西”一样。

如果多个Package声明了同样的全局变量,那么它们必须有相同的类型,并指向同一个全局变量。

为了使用全局变量,你必须:

在规则文件中声明全局变量并使用它,如:

global java.util.List myGlobalList;

rule "Using a global"

when

eval( true )

then

myGlobalList.add( "Hello World" );

end

在working memory上设置全局变量的值。最好是在将fact插入working memory之前设置完所有全局变量,如:

List list = new ArrayList();

WorkingMemory wm = rulebase.newStatefulSession();

wm.setGlobal( "myGlobalList", list );

注意,这些全局变量只是从你的应用传到Working Memory的对象的实例。这意味着你可以传入任何你想要的对象:你可以传递一个服务的位置代理,或者可能是一个服务本身。随着新的‘from’元素现在可以传递一个Hibernate Session对象作为全局变量,允许‘from’通过一个命名的Hibernate查询中将数据拉进来。

可能的一个例子是Email服务。在你的规则引擎的集成代码中,你获得你的email服务对象,并且将它引入到working memory。在DRL文件中,声明你已经有了一个EmailService类型的全局变量,并给它一个名称为email。然后在规则的推论中,你就可以使用类似email.sendSMS(number,message)这样的调用。

全局变量不是被设计用来在规则之间共享数据的,并且它们永远都不应该用于这个目的。规则经常对working memory进行推论和删除fact,因此如果你想在规则间共享数据,将数据插入working memory就可以。

从规则中设置全局变量的值是非常不合适的,我们建议你在应用程序中通过working memory 的接口设置这个值。

3.3.9Function

相对于正常的java类,函数是在你的规则代码中放置语言代码的方法。它们不可能做任何超过你可以在帮助类(在java中定义,被设置入规则的Working Memory中的类)中做到的事情(实际上,编译器为后面的场景产生帮助类,那样帮助不大)。主要使用函数的优点是可以将逻辑保存在一个地方,并且你可以在需要的时候改变函数(这样做各有优缺点)。函数最大的用处是被规则的推论(then)部分中的行为所调用,特别是当一个行为操作需要反复被调用时——如发送邮件。

典型的函数声明如下所示:

function String hello(String name) {

return "Hello "+name+"!";

}

注意function关键字的使用,虽然它并不真的是java的一部分。传入function的参数就像普通的方法一样(如果不需要参数可以为空)。返回值的概念也和普通方法相同。

作为使用函数的另一种方法,你可以在辅助类中使用一个静态方法:Foo.hello()。Drools4.0支持静态方法导入,因此你只需要做下面的事情:

import static my.package.Foo.hello

对于上面的情况,要使用函数只需要在推论或代码块中通过函数名称来调用它,例如:

rule "using a static function"

when

eval( true )

then

System.out.println( hello( "Bob" ) );

end

3.3.10规则

相关文档