学习JBoss Rules有几天了,因为这方面的中文资料较少,所以这几天都在看官网上的manual。这是一份不错的教程,我把我看的一些重要的东西翻译整理了一下,希望可以对想学习JBoss Rules的同学们提供一点帮助。
在开始这份教程之前,我先简要介绍一下JBoss Rules:
JBoss Rules 的前身是Codehaus的一个开源项目叫Drools。最近被纳入JBoss门下,更名为JBoss Rules,成为了JBoss应用服务器的规则引擎。
Drools是为Java量身定制的基于Charles Forgy的RETE算法的规则引擎的实现。具有了OO接口的RETE,使得商业规则有了更自然的表达。
既然JBoss Rules是一个商业规则引擎,那我们就要先知道到底什么是Rules,即规则。在JBoss Rules中,规则是如何被表示的
Rules
一条规则是对商业知识的编码。一条规则有 attributes ,一个 Left Hand Side ( LHS )和一个 Right Hand Side ( RHS )。 Drools 允许下列几种 attributes : salience , agenda-group , no-loop , auto-focus , duration , activation-group 。
rule “ < name > ”
< attribute > < value >
when
< LHS >
then
< RHS >
end
规则的 LHS 由一个或多个条件( Conditions )组成。当所有的条件( Conditions )都满足并为真时, RHS 将被执行。 RHS 被称为结果( Consequence )。 LHS 和 RHS 类似于:
if ( < LHS > ) {
< RHS >
}
规则可以通过 package 关键字同一个命名空间( namespace )相关联;其他的规则引擎可能称此为规则集( Rule Set )。一个 package 声明了 imports , global 变量, functions 和 rules 。
package com.sample
import java.util.List
import com.sample.Cheese
global List cheeses
function void exampleFunction(Cheese cheese) {
System.out.println( cheese );
}
rule “A Cheesy Rule”
when
cheese : Cheese( type == " stilton " )
then
exampleFunction( cheese );
cheeses.add( cheese );
end
对新的数据和被修改的数据进行规则的匹配称为模式匹配( Pattern Matching )。进行匹配的引擎称为推理机( Inference Engine )。被访问的规则称为 ProductionMemory ,被推理机进行匹配的数据称为 WorkingMemory 。 Agenda 管理被匹配规则的执行。推理机所采用的模式匹配算法有下列几种: Linear , RETE , Treat , Leaps 。
Drools 采用了 RETE 和 Leaps 的实现。 Drools 的 RETE 实现被称为 ReteOO ,表示 Drools 对 Rete 算法进行了加强和优化的实现。
一条规则的 LHS 由 Conditional Element 和域约束( Field Constraints )。下面的例子显示了对一个 Cheese Fact 使用了字面域约束( Literal Field Constraint )
rule " Cheddar Cheese "
when
Cheese( type == " cheddar " )
then
System.out.println( " cheddar " );
end
上面的这个例子类似于:
public void cheddarCheese(Cheese cheese) {
if ( cheese.getType().equals( " cheddar " ) {
System.out.println( " cheddar " );
}
}
<!--[if !vml]--> <!--[endif]-->
规则引擎实现了数据同逻辑的完全解耦。规则并不能被直接调用,因为它们不是方法或函数,规则的激发是对 WorkingMemory 中数据变化的响应。结果( Consequence ,即 RHS )作为 LHS events 完全匹配的 Listener 。
当 rules 被加入 Productioin Memory 后, rules 被规则引擎用 RETE 算法分解成一个图:
当 Facts 被 assert 进入 WorkingMemory 中后,规则引擎找到匹配的 ObjectTypeNode ,然后将此 Fact 传播到下一个节点。 ObjectTypeNode 拥有一块内存来保存所有匹配的 facts 。在我们的例子中,下一个节点是一个域约束( Field Constraint ), type = = “cheddar” 。如果某个 Cheese 对象的类型不是“ cheddar ”,这个 fact 将不会被传播到网络的下一个节点。如果是“ cheddar ”类型,它将被记录到 AlphaNode 的内存中,并传播到网络的下一个节点。 AlphaNode 是古典 RETE 术语,它是一个单输入 / 单输出的节点。最后通过 AlphaNode 的 fact 被传播到 Terminal Node 。 Terminal Node 是最终节点,到此我们说这条规则被完全匹配,并准备激发。
当一条规则被完全匹配,它并没有立刻被激发(在 RETE 中是这样,但在 Leaps 中它会立刻被激发)。这条规则和与其匹配的 facts 将激活被放入 Agenda ,由 Agenda 来负责安排激发 Activations (指的是 rule + the matched facts )。
下面的图很清楚的说明了 Drools 规则引擎的执行过程:
数据被 assert 进 WorkingMemory 后,和 RuleBase 中的 rule 进行匹配(确切的说应该是 rule 的 LHS ),如果匹配成功这条 rule 连同和它匹配的数据(此时就叫做 Activation )一起被放入 Agenda ,等待 Agenda 来负责安排激发 Activation (其实就是执行 rule 的 RHS ),上图中的菱形部分就是在 Agenda 中来执行的, Agenda 就会根据冲突解决策略来安排 Activation 的执行顺序。
<script type="text/javascript"> // </script>
# re: JBoss Rules 学习(一): 什么是Rule 2006-06-01 18:06 江南白衣
好啊,又有人开始学习和中文化总结Drools了,密切关注中。 回复 更多评论
# re: JBoss Rules 学习(一): 什么是Rule 2006-06-02 10: 19 C[ETI]O@quaffsoft
# re: JBoss Rules 学习(一): 什么是Rule 2006-06-02 10:49 guangnian
@C[ETI]O@quaffsoft
你指的是global List cheeses 吗?对于Drools rules中global属性的用法我还不太清楚,但是有一点就是,上面的Drools rules例子是写在.drl文件中的,而不是写在.java中,所有不要太用java的观点来看那段例子。上面的package也是跟java不太一样的概念,用package关键字并不会创建相应的目录层次。 回复 更多评论
# re: JBoss Rules 学习(一): 什么是Rule 2006-06-02 11:22 Water Ye
关于Globals的用法
java.util.Vector globalVector = new java.util.Vector( );
java.util.Map map = new java.util.HashMap( );
map.put( "vector", globalVector );
//Open a stateless Session StatelessRuleSession srs = (StatelessRuleSession) runtime.createRuleSession( "SistersRules", map, RuleRuntime.STATELESS_SESSION_TYPE );
...
// Persons added to List
// call executeRules( ) giving a List of Objects as parameter
// There are rules which will put Objects in the Vector
// fetch the vector from the map
v = (java.util.Vector)map.get("vector"); 回复 更多评论
在JBoss Rules 学习(一):什么是Rule中,我们介绍了JBoss Rules中对Rule的表示,其中提到了JBoss Rule中主要采用的RETE算法来进行规则匹配。下面将详细的介绍一下RETE算法在JBoss Rule中的实现,最后随便提一下JBoss Rules中也可以使用的另一种规则匹配算法Leaps。
1.Rete 算法 :
Rete 在拉丁语中是 ”net” ,有网络的意思。 RETE 算法可以分为两部分:规则编译( rule compilation )和运行时执行( runtime execution )。
编译算法描述了规则如何在 Production Memory 中产生一个有效的辨别网络。用一个非技术性的词来说,一个辨别网络就是用来过滤数据。方法是通过数据在网络中的传播来过滤数据。在顶端节点将会有很多匹配的数据。当我们顺着网络向下走,匹配的数据将会越来越少。在网络的最底部是终端节点( terminal nodes )。在 Dr Forgy 的 1982 年的论文中,他描述了 4 种基本节点: root , 1-input, 2-input and terminal 。下图是 Drools 中的 RETE 节点类型:
Figure 1. Rete Nodes
根节点( RootNode )是所有的对象进入网络的入口。然后,从根节点立即进入到 ObjectTypeNode 。 ObjectTypeNode 的作用是使引擎只做它需要做的事情。例如,我们有两个对象集: Account 和 Order 。如果规则引擎需要对每个对象都进行一个周期的评估,那会浪费很多的时间。为了提高效率,引擎将只让匹配 object type 的对象通过到达节点。通过这种方法,如果一个应用 assert 一个新的 account ,它不会将 Order 对象传递到节点中。很多现代 RETE 实现都有专门的 ObjectTypeNode 。在一些情况下, ObjectTypeNode 被用散列法进一步优化。
Figure 2 . ObjectTypeNodes
ObjectTypeNode 能够传播到 AlphaNodes, LeftInputAdapterNodes 和 BetaNodes 。
1-input 节点通常被称为 AlphaNode 。 AlphaNodes 被用来评估字面条件( literal conditions )。虽然, 1982 年的论文只提到了相等条件(指的字面上相等),很多 RETE 实现支持其他的操作。例如, Account.name = = “Mr Trout” 是一个字面条件。当一条规则对于一种 object type 有多条的字面条件,这些字面条件将被链接在一起。这是说,如果一个应用 assert 一个 account 对象,在它能到达下一个 AlphaNode 之前,它必须先满足第一个字面条件。在 Dr. Forgy 的论文中,他用 IntraElement conditions 来表述。下面的图说明了 Cheese 的 AlphaNode 组合( name = = “cheddar” , strength = = “strong” ):
Figure 3. AlphaNodes
Drools 通过散列法优化了从 ObjectTypeNode 到 AlphaNode 的传播。每次一个 AlphaNode 被加到一个 ObjectTypeNode 的时候,就以字面值( literal value )作为 key ,以 AlphaNode 作为 value 加入 HashMap 。当一个新的实例进入 ObjectTypeNode 的时候,不用传递到每一个 AlphaNode ,它可以直接从 HashMap 中获得正确的 AlphaNode ,避免了不必要的字面检查。
<!--[if !supportEmptyParas]-->
2-input 节点通常被称为 BetaNode 。 Drools 中有两种 BetaNode : JoinNode 和 NotNode 。 BetaNodes 被用来对 2 个对象进行对比。这两个对象可以是同种类型,也可以是不同类型。
我们约定 BetaNodes 的 2 个输入称为左边( left )和右边( right )。一个 BetaNode 的左边输入通常是 a list of objects 。在 Drools 中,这是一个数组。右边输入是 a single object 。两个 NotNode 可以完成‘ exists ’检查。 Drools 通过将索引应用在 BetaNodes 上扩展了 RETE 算法。下图展示了一个 JoinNode 的使用:
Figure 4 . JoinNode
注意到图中的左边输入用到了一个 LeftInputAdapterNode ,这个节点的作用是将一个 single Object 转化为一个单对象数组( single Object Tuple ),传播到 JoinNode 节点。因为我们上面提到过左边输入通常是 a list of objects 。
<!--[if !supportEmptyParas]-->
Terminal nodes 被用来表明一条规则已经匹配了它的所有条件( conditions )。 在这点,我们说这条规则有了一个完全匹配( full match )。在一些情况下,一条带有“或”条件的规则可以有超过一个的 terminal node 。
Drools 通过节点的共享来提高规则引擎的性能。因为很多的规则可能存在部分相同的模式,节点的共享允许我们对内存中的节点数量进行压缩,以提供遍历节点的过程。下面的两个规则就共享了部分节点:
rule
when
Cheese( $chedddar : name == " cheddar " )
$person : Person( favouriteCheese == $cheddar )
then
System.out.println( $person.getName() + " likes cheddar " );
end
rule
when
Cheese( $chedddar : name == " cheddar " )
$person : Person( favouriteCheese != $cheddar )
then
System.out.println( $person.getName() + " does likes cheddar " );
end
这里我们先不探讨这两条 rule 到的是什么意思,单从一个直观的感觉,这两条 rule 在它们的 LHS 中基本都是一样的,只是最后的 favouriteCheese ,一条规则是等于 $cheddar ,而另一条规则是不等于 $cheddar 。下面是这两条规则的节点图:
Figure 5 . Node Sharing
从图上可以看到,编译后的 RETE 网络中, AlphaNode 是共享的,而 BetaNode 不是共享的。上面说的相等和不相等就体现在 BetaNode 的不同。然后这两条规则有各自的 Terminal Node 。
<!--[if !supportEmptyParas]-->
RETE 算法的第二个部分是运行时( runtime )。当一个应用 assert 一个对象,引擎将数据传递到 root node 。从那里,它进入 ObjectTypeNode 并沿着网络向下传播。当数据匹配一个节点的条件,节点就将它记录到相应的内存中。这样做的原因有以下几点:主要的原因是可以带来更快的性能。虽然记住完全或部分匹配的对象需要内存,它提供了速度和可伸缩性的特点。当一条规则的所有条件都满足,这就是完全匹配。而只有部分条件满足,就是部分匹配。(我觉得引擎在每个节点都有其对应的内存来储存满足该节点条件的对象,这就造成了如果一个对象是完全匹配,那这个对象就会在每个节点的对应内存中都存有其映象。)
2. Leaps 算法:
Production systems 的 Leaps 算法使用了一种“ lazy ”方法来评估条件( conditions )。一种 Leaps 算法的修改版本的实现,作为 Drools v3 的一部分,尝试结合 Leaps 和 RETE 方法的最好的特点来处理 Working Memory 中的 facts 。
古典的 Leaps 方法将所有的 asserted 的 facts ,按照其被 asserted 在 Working Memory 中的顺序( FIFO ),放在主堆栈中。它一个个的检查 facts ,通过迭代匹配 data type 的 facts 集合来找出每一个相关规则的匹配。当一个匹配的数据被发现时,系统记住此时的迭代位置以备待会的继续迭代,并且激发规则结果( consequence )。当结果( consequence )执行完成以后,系统就会继续处理处于主堆栈顶部的 fact 。如此反复。
JBoss Rules 学习(三): Drools规则引擎 (上)
从今天开始,我们将分两期来详细的介绍Drools规则引擎的原理,和各关键类的使用方法。
Drools 规则引擎(上)
1. 概述 :
Drools 分为两个主要部分:构建( Authoring )和运行时( Runtime )。
构建的过程涉及到 .drl 或 .xml 规则文件的创建,它们被读入一个解析器,使用 ANTLR 3 语法进行解析。解析器对语法进行正确性的检查,然后产生一种中间结构“ descr ”, descr 用 AST 来描述规则。 AST 然后被传到 PackageBuilder ,由 PackagBuilder 来产生 Packaged 对象。 PackageBuilder 还承担着一些代码产生和编译的工作,这些对于产生 Package 对象都时必需的。 Package 对象是一个可以配置的,可序列化的,由一个或多个规则组成的对象。下图阐明了上述过程:
Figure 1.1 Authoring Components
RuleBase 是一个运行时组件,它包含了一个或多个 Package 对象。可以在任何时刻将一个 Package 对象加入或移出 RuleBase 对象。一个 RuleBase 对象可以在任意时刻实例化一个或多个 WorkingMemory 对象,在它的内部保持对这些 WorkingMemory 的弱引用。 WorkingMemory 由一系列子组件组成。当应用程序中的对象被 assert 进 WorkingMemory ,可能会导致一个或多个 Activation 的产生,然后由 Agenda 负责安排这些 Activation 的执行。下图说明了上述过程:
Figure 1.2 . Runtime Components
2.构建(Authoring):
主要有三个类用来完成构建过程:DrlParser, XmlParser 和 PackageBuilder。两个解析器类从传入的Reader实例产生descr AST模型。PackageBuilder提供了简便的API,使你可以忽略那两个类的存在。这两个简单的方法是:“addPackageFromDrl”和“addPackageFromXml”,两个都只要传入一个Reader实例作为参数。下面的例子说明了如何从classpath中的xml和drl文件创建一个Package对象。注意:所有传入同一个PackageBuilder实例的规则源,都必须是在相同的package 命名空间(namespace)中。
PackageBuilder builder = new PackageBuilder();
builder.addPackageFromDrl( new InputStreamReader( getClass().getResourceAsStream( "package1.drl" ) ) );
builder.addPackageFromXml( new InputStreamReader( getClass().getResourceAsStream( "package2.drl" ) ) );
Package pkg = builder.getPackage();
Figure 2.1 PackageBuilder
PackageBuilder是可以配置的,使用PackageBuilderConfiguration。通常,你可以指定另一个parent ClassLoader和用什么编译器(compiler),默认是Eclipse JDT。下面显示了如何指定JANINO编译器:
PackageBuilderConfiguration conf = new PackageBuilderConfiguration();
conf.setCompiler( PackageBuilderConfiguration.JANINO );
PackageBuilder builder = new PackageBuilder( conf );
Figure 2.2 . PackageBuilderConfiguration
3.RuleBase:
Figure 3.1 . RuleBase
一个RuleBase包含了多个将被使用的规则包(packages of rules)。一个RuleBase是可以序列化的,所以它可以被配置到JNDI或其他类似的服务。通常,第一次使用时,一个RuleBase被创建并缓存。RuleBase用RuleBaseFactory来实例化,默认返回一个ReteOO RuleBase。可以传入参数来指定采用ReteOO或Leaps。然后,用addPackage方法加入Package实例。你可以加入有相同命名空间(namespace)的多个Package。
RuleBase ruleBase = RuleBaseFactory.newRuleBase();
ruleBase.addPackage(pkg);
Figure 3.2. RuleBaseFactory
一个 rulebase instance 是线程安全的,所有你可以在你的应用中,让一个 rulebase instance 在多个线程中共享。对于一个 rulebase 的最通常的操作是产生一个新的 WorkingMemory 。
这个 rulebase 保持着到它所产生的 WorkingMemoryd 的弱引用,所以在长时间运行的 WorkingMemory 中,如果 rules 发生改变,这些 WorkingMemory 可以即使的根据最新的 rules 进行更新,而不必重启 WorkingMemory 。你也可以指定 RuleBase 不必保持一个弱引用,但是你要保证 RuleBase 不用更新。
ruleBase.newWorkingMemory(); // maintains a weak reference.
ruleBase.newWorkingMemory( false ); // do not maintain a weak reference
任何时候, Package 可以被加入或移除;所有的改变都会被反映到现存的 WorkingMemory 中。不要忘了调用 fireAllRules() 让 Activations 激发。
ruleBase.addPackage( pkg ); // Add a package instance
ruleBase.removePackage( " org.com.sample " ); // remove a package, and all its parts, by it's namespace
ruleBase.removeRule( " org.com.sample " , " my rule " ); // remove a specific rule from a namespace
虽然有删除一个单独规则的方法,但是却没有加入一个单独规则的方法(要达到这个目的只有加入一个只有一条规则的 package )。
<!--[if !supportEmptyParas]-->
RuleBaseConfigurator 可以指定 RuleBase 的附加行为。在加入 RuleBase 后, RuleBaseConfiguration 就变成不可变对象。
RuleBaseConfiguration conf = new RuleBaseConfiguration();
conf.setProperty( RuleBaseConfiguration.PROPERTY_ASSERT_BEHAVIOR,
RuleBaseConfiguration.WM_BEHAVIOR_EQUALITY );
RuleBase ruleBase = new ReteooRuleBase( conf );
两个主要的属性是: PROPERT_ASSERT_BEHAVIOR 和 PROPERTY_LOGICAL_OVERRIDE_BEHAVIOR (在以后的部分中会解释)。所有的属性值都是 RuleBaseConfiguration 类中的静态域常量。
Figure 3.3 RuleBaseConfiguration
Drools 规则引擎(下)
4 . WorkingMemory:
Figure 4.1 WorkingMemory
WorkingMemory 是运行时规则引擎的主要类。它保持了所有被 asserted 进 WorkingMemory 的数据的引用,直到取消( retracted )。 WorkingMemory 是有状态对象。它们的生命周期可长可短。如果从一个短生命周期的角度来同一个引擎进行交互,意味着你可以使用 RuleBase 对象来为每个 session 产生一个新的 WorkingMemory ,然后在结束 session 后 discard 这个 WorkingMemory (产生一个 WorkingMemory 是一个廉价的操作)。另一种形式,就是在一个相当长的时间中(例如一个 conversation ),保持一个 WorkingMemory ,并且对于新的 facts 保持持续的更新。当你希望 dispose 一个 WorkingMemory 的时候,最好的实践就是调用 dispose() 方法,此时 RuleBase 中对它的引用将会被移除(尽管这是一个弱引用)。不管怎样最后它将会被当成垃圾收集掉。术语 WorkingMemory Actions 代表了对 WorkingMemory 的 assertions , retractions 和 modifications 。
4.1 Facts
Facts 是从你的应用中,被 assert 进 WorkingMemory 中的对象( beans )。 Facts 是规则可以访问的任意的 java 对象。规则引擎中的 facts 并不是“ clone ” facts ,它只是持有到你的应用中数据的引用。 Facts 是你的应用数据。 String 和其他没有 getter 和 setter 的类不是有效的 Fact 。这样的类不能使用域约束( Field Constraints ),因为使用域约束要依靠 JavaBean 标准的 getter 和 setter 来同对象交互。
4.2 Assertion
“Assertion” 是将 facts 告诉 WorkingMemory 的动作,例如 WorkingMemory.assertObject (yourObject) 。当你 assert 一个 fact ,它将被检查是否匹配规则。这意味着所有的匹配工作将会在 assert 的过程中完成。尽管如此,当你完成 assert facts 之后,你还要调用“ fireAllRules() ”方法来执行规则。
当一个对象被 assert 后,会返回一个 FactHandle 。这个 FactHandle 是一个代表在 Working Memory 中你的 asserted Object 的令牌( token )。当你希望 retract 或者 modify 一个对象的时候,这个令牌让你用来同 WorkingMemory 进行交互。
Cheese stilton = new Cheese( " stilton " );
FactHandle stiltonHandle = workingMemory.assertObject( stilton );
WorkingMeomry 有两种 assertion 模式: Equality 和 Identity (默认是 Identity )。
Identity 模式下 WorkingMemory 使用一个 IdentityHashMap 来存储所有的 asserted Objects 。这个模式下,当 asserted 的 Object 是同一个实例时,它返回同一个 FactHandle 。
Equality 模式下 WorkingMemory 使用一个 HashMap 来存储所有的 asserted Objects 。这个模式下,当 asserted 的 Object 相等时,它返回同一个 FactHandle 。
( WorkingMemory.assertObject(yourObjcet) 只是进行 assertion 的一种 regular 方法,还存在有一种称为 logical assertion 的动作)。
4.3 Retraction
基本上就是 assert 的逆操作。当你 retract 一个 fact , WorkingMemory 将不再跟踪那个 fact 。任何被 activated 并依赖那个 fact 的规则将被取消。注意:完全有可能存在某条规则是依赖于一个 fact 的“不存在”( non existence )。在这种情况下, retract 一个 fact 将导致一条规则被激活。对一个 Fact 进行 Retraction ,必须用 assert 时返回的那个 FactHandle 做为参数。
Cheese stilton = new Cheese( " stilton " );
FactHandle stiltonHandle = workingMemory.assertObject( stilton );
.
workingMemory.retractObject( stiltonHandle );
4.4 Modification
当一个 Fact 被修改了,会通知规则引擎进行重新处理。在规则引擎内部实际上是对旧的 Fact 进行 retract ,然后对新的 Object 再进行 assert 。要使用 modifyObject() 方法来通知 Working Memory ,被改变的 Object 并不会自己通知规则引擎。注意: modifyObject() 方法总是要把被修改的 Object 做为第二参数,这就允许你把一个不可变对象替换为另一个新对象。
Cheese stilton = new Cheese( " stilton " );
FactHandle stiltonHandle = workingMemory.assertObject( stilton );
.
stilton.setPrice( 100 );
workingMemory.modifyObject( stiltonHandle, stilton );
4.5 Globals
Global 是一个能够被传进 WorkingMemory 但不需要 assert 的命名对象。大多数这些对象被用来作为静态信息或服务。这些服务被用在一条规则的 RHS ,或者可能是从规则引擎返回对象的一种方法。
List list = new ArrayList();
workingMemory.setGlobal( " list " , list);
setGlobal() 方法传进去的命名对象必须同 RuleBase 中所定义的具有相同的类型(就是要同你的规则文件中用 Global 关键字所定义的类型相同),否则会抛出一个 RuntimeException 。如果一条规则在你 setGlobal 之前调用了定义的 Global ,会抛出一个 NullPointerException 。
4.6 Property Change Listener
如果你的 fact 对象是 JavaBean ,你可以为它们实现一个 property change listener ,然后把它告诉规则引擎。这意味着,当一个 fact 改变时,规则引擎将会自动知道,并进行响应的动作(你不需要调用 modifyObject() 方法来通知 WorkingMemory )。 Proxy libraries 将会帮助实现这一切。要让 Property Change Listener 生效,还要将 fact 设置为动态( dynamic )模式,通过将 true 做为 assertObject() 方法的第二个参数来实现:
Cheese stilton = new Cheese( " stilton " );
FactHandle stiltonHandle = workingMemory.assertObject( stilton, true ); // specifies t hat this is a dynamic fact
然后要在 JavaBean 中加入一个 PropertyChangeSupport 实例,和两个方法: addPropertyChangeListener() 和 removePropertyChangeListener() 。最后要在 JavaBean 的 setter 方法中通知 PropertyChangeSupport 所发生的变化。示例代码如下:
private final PropertyChangeSupport changes = new PropertyChangeSupport( this );
public void addPropertyChangeListener( final PropertyChangeListener l) {
this .changes.addPropertyChangeListener( l );
}
public void removePropertyChangeListener( final PropertyChangeListener l) {
this .changes.removePropertyChangeListener( l );
}
public void setState( final String newState) {
String oldState = this .state;
this .state = newState;
this .changes.firePropertyChange( " state " , oldState, newState );
5. Agenda:
Figure 5.1 . Agenda
Agenda 是 RETE 的一个特点。在一个 WorkingMemory Action 发生时,可能会有多条规则发生完全匹配。当一条规则完全匹配的时候,一个 Activation 就被创建(引用了这条规则和与其匹配的 facts ),然后放进 Agenda 中。 Agenda 通过使用冲突解决策略( Conflict Resolution Strategy )来安排这些 Activations 的执行。
引擎工作在一个“ 2 阶段”模式下:
<!--[if !supportLists]--> 1) <!--[endif]--> WorkingMemory Actions : assert 新的 facts ,修改存在的 facts 和 retract facts 都是 WorkingMemory Actions 。通过在应用程序中调用 fireAllRules() 方法,会使引擎转换到 Agenda Evaluatioin 阶段。
<!--[if !supportLists]--> 2) <!--[endif]--> Agenda Evaluation :尝试选择一条规则进行激发( fire )。如果规则没有找到就退出,否则它就尝试激发这条规则,然后转换到 WorkingMemory Actions 阶段,直到 Agenda 中为空。
这个过程一直重复,直到 Agenda 是空的,此时控制权就回到应用程序中。。当 WorkingMemory Actions 发生时,没有规则正在被激发。
下图说明了这个循环的过程:
Figure 5.2 . Two Phase Execution
5 . 1 Conflict Resultion
当有多条 rules 在 agenda 中,就需要解决冲突。当激发一条规则时,会对 WorkingMemory 产生副作用。规则引擎需要知道规则要以什么顺序来激发(例如,激发 rule A 可能会引起 rule B 被从 agenda 中移除。)
Drools 采取的冲突解决策略有 2 种,按照优先级排列如下: Salience , LIFO (后进先出)。最易懂的策略是“ Salience ”,即优先级, user 可以为某个 rule 指定一个高一点的优先级(通过附给它一个比较大的数字)。高 Salience 的 rule 将会被优先激发。
5 . 2 Agenda Groups
Agenda Groups 是划分 Agenda 中 rules (其实是“ activations ”)的一种方法。在任意一个时刻,只有一个 group 拥有“ focus ”,这意味着只有在那个 group 中的 activations 才是有效的。
Agenda Groups 是在 grouped rules 之间创建一个“流”( flow )的简便的方法。你可以在规则引擎中,或是用 API 来切换具有焦点的组。如果你的规则有很明确的多“阶段”( phases )或多“序列”( sequences )的处理,可以考虑用 Agenda Groups 来达到这个目的。
每次调用 setFocus() 方法的时候,那个 Agenda Group 就会被压入一个堆栈,当这个有焦点的组为空时,它就会被弹出,然后下一个组就会被执行。一个 Agenda Group 可以出现在堆栈的多个位置。默认的 Agenda Group 是“ MAIN ”,所有没有被指定 Agenda Group 的 Activations 都被放到那个组中,这个组总是被放在堆栈的第一个组,并默认给予焦点。
5 . 3 Agenda Filters
Figure 5.3. Agenda Filter
Filter 必须实现 AgendaFilter 接口,用来允许或禁止一个 activation 能够被激发。 Drools 提供了下面几种方便的默认实现:
<!--[if !supportLists]--> · <!--[endif]--> RuleNameEndWithAgendaFilter
<!--[if !supportLists]--> · <!--[endif]--> RuleNameEqualsAgendaFilter
<!--[if !supportLists]--> · <!--[endif]--> RuleNameStartsWithAgendaFilter
要使用一个 filter 就要在调用 fireAllRules() 方法的时候指定它。下面的例子将对所有名字以“ Test ”结尾的规则进行过滤:
workingMemory.fireAllRules( new RuleNameEndsWithAgendaFilter( " Test " ) );
6.事件模型( Event Model )
Event 包里提供了规则引擎的事件机制,包括规则激发,对象被 asserted 等等。你可以使用事件机制来进行 AOP 编程。
有两种类型的 Event Listener : WorkingMemoryEventListener 和 AgendaEventListener 。
Figure 6.1. WorkingMemoryEventListener
Figure 6.2 AgendaEventListener
对两个 EventListener 接口都提供了默认实现,但在方法中并没有做任何事。你可以继承这两个默认实现来完成你自己的实现- DefaultAgendaEventListener 和 DefaultWorkingMemoryEventListener 。下面代码说明了如何扩展一个 DefaultAgendaEventListner 并把它加到 WorkingMemory 中,例子中只完成了 afterActivationFired() 方法:
workingMemory.addEventListener( new DefaultAgendaEventListener() {
public void afterActivationFired(AfterActivationFiredEvent event) {
super .afterActivationFired( event );
System.out.println( event );
}
});
Drools 也提供了 DebugWorkingMemoryEventListener 和 DebugAgendaEventListener 两个实现类,在这两个类的方法中实现了 debug 信息的输出:
workingMemory.addEventListener( new DebugWorkingMemoryEventListener() );
JBoss Rules 学习(五): JBoss Rules 3.0.1 类库介绍
下载地址:
http://labs.jboss.com/portal/index.html?ctrl:id=page.default.downloads&project=jbossrules
下载文件说明:
JBoss Rules 3.0.1 Binaries (includes javadocs) ( 13MB )― 仅仅包含 JBoss Rules 的四个核心类库:
<!--[if !supportLists]--> l <!--[endif]--> drools-core.jar - 核心引擎,运行时组件。包含了 RETE 引擎和 LEAPS 引擎;
<!--[if !supportLists]--> l <!--[endif]--> drools-compiler.jar - 规则文件的编译组件,构建可执行的 RuleBase ;
<!--[if !supportLists]--> l <!--[endif]--> drools-jsr94.jar - 提供了 JSR-94 的兼容实现,本质上是 drools- compiler 组件的包裹层。注意:由于 JSR94 规约的限制,不是所有的特点都可以通过此接口暴露。
<!--[if !supportLists]--> l <!--[endif]--> drools-decisiontables.jar - 决策表的“编译”组件(使用了 drools- compiler 组件)。支持 excel 和 CSV 输入格式。
JBoss Rules 3.0.1 Binaries with dependencies (includes javadocs) ( 23 MB )- 包含了 JBoss Rules 的核心类库和它们的 dependencies :
<!--[if !supportLists]--> l <!--[endif]--> antlr- 2.7.6 .jar
<!--[if !supportLists]--> l <!--[endif]--> antlr-3.0ea8.jar
<!--[if !supportLists]--> l <!--[endif]--> colt- 1.2.0 .jar
<!--[if !supportLists]--> l <!--[endif]--> commons-collections-3.1.jar
<!--[if !supportLists]--> l <!--[endif]--> commons-io-1.1.jar
<!--[if !supportLists]--> l <!--[endif]--> commons-jci-core-1.0-406301.jar
<!--[if !supportLists]--> l <!--[endif]--> commons-jci-eclipse- 3.2.0 .666.jar
<!--[if !supportLists]--> l <!--[endif]--> commons-jci-janino- 2.4.3 .jar
<!--[if !supportLists]--> l <!--[endif]--> commons-lang-2.1.jar
<!--[if !supportLists]--> l <!--[endif]--> commons-logging-api- 1.0.4 .jar
<!--[if !supportLists]--> l <!--[endif]--> concurrent- 1.3.4 .jar
<!--[if !supportLists]--> l <!--[endif]--> core- 3.2.0 .666.jar
<!--[if !supportLists]--> l <!--[endif]--> janino- 2.4.3 .jar
<!--[if !supportLists]--> l <!--[endif]--> jsr94-1.1.jar
<!--[if !supportLists]--> l <!--[endif]--> jung- 1.7.2 .jar
<!--[if !supportLists]--> l <!--[endif]--> junit- 3.8.1 .jar
<!--[if !supportLists]--> l <!--[endif]--> poi- 2.5.1 -final-20040804.jar
<!--[if !supportLists]--> l <!--[endif]--> stringtemplate-2.3b6.jar
<!--[if !supportLists]--> l <!--[endif]--> xercesImpl- 2.6.2 .jar
<!--[if !supportLists]--> l <!--[endif]--> xml-apis-1.0.b2.jar
<!--[if !supportLists]--> l <!--[endif]--> xpp3- 1.1.3 .4.0.jar
<!--[if !supportLists]--> l <!--[endif]--> xstream- 1.1.3 .jar
如果你运行在 Java 1.5 环境下,有一些类库,例如 XML libraries ,可以不需要。需要注意的类库有:
“ JCI ”-这是 Apache Java Compiler Interface , 提供了运行时编译能力。可以通过 PackageBuilderConfiguration 实例来设定采用 eclipse 或 janino 编译器,默认是 eclipse ;
“ POI ”-提供了解析 Excel 文件的能力;
“ antlr ”-提供了解析规则语言的能力。
JBoss Rules IDE 3.0.1 ( 13 MB )- 这是 JBoss Rules 的 Eclipse 插件,只支持 Eclipse 3.2 或以上版本。它提供了运行 JBoss Rules 的所有 dependencies 。你可以创建一个 Rule Project ,它能够为你编写规则文件提供自动完成的功能,并且它为你提供了 Agenda view , WorkingMemory view , Global Data view ,使你可以通过 eclipse 视图很清楚的看到 Agenda , WorkingMemory 和 Global Data 的情况。
你还可以通过 update site 来自动安装这个插件 ,URL 是:
http://anonsvn.labs.jboss.com/labs/jbossrules/updates/drools-ide-update/
<!--[if !supportEmptyParas]--> <!--[endif]-->
JBoss Rules 学习(六): Drools规则语言详解(上)
Drools 规则语言详解(上)
<!--[if !supportLists]--> 1. <!--[endif]--> 概述:
Drools 3 采用了原生的规则语言,那是一种非 XML 文本格式。在符号方面,这种格式是非常轻量的,并且通过“ expanders ”支持符合你问题域的 Domain Specific Language ( DSL )。这一章把焦点放在了 Drools 原生的规则格式。如果你想从技术上了解规则语言的机制,可以参考“ drl.g ”源文件,这是用 Antlr3 语法来描述规则语言。如果你使用 Rule Workbench ,内容助手将会为你完成大量的规则结构,例如输入“ ru ”,然后按 ctrl + space ,会为你建立规则结构。
<!--[if !supportLists]--> 1.1 <!--[endif]--> 规则文件
一个规则文件通常是一个以 .drl 扩展名结尾的文件。在一个 drl 文件中,你可以有多条 rules , functions 等等。尽管如此,你也可以将你的规则分布在多个文件中,这有利于管理大量的规则。一个 DRL 文件是一个简单的文本文件。
1.2 规则的结构
一个规则结构大致如下:
rule " name "
ATTRIBUTES
when
LHS
then
RHS
end
可以看到,这是非常简单的。通常的标点符号都是不需要的,甚至连“ name ”的双引号都是不需要的。 ATTRIBUTES 是简单的,也是可选的,来提示规则的行为方式。 LHS 是规则的条件部分,需要按照一定的语法来写。 RHS 基本上是一个允许执行 Java 语法的代码的块(以后将会支持 groovy 和 C #)。任何在 LHS 中使用的变量都可以在 RHS 中使用。
注意:每行开始的空格是不重要的,除非在 DSL ( Domain Specific Language )语言中有特别的指明。
<!--[if !supportLists]--> 1.3 <!--[endif]--> Domain Specific Language
Domain Specific Language 是对原生规则语言的加强。它们使用“ expander ”机制。 Expander 机制是一种可扩展的 API 。你可以使用 .dsl 文件,来提供从域或自然语言到规则语言和你的域对象的映射。你可以将 .dsl 文件看成是对你的域模型的映射。 DSL 提供了更高的规则可读性,你可以选择使用你自己创建的 DSL ,或者是原生的规则语言。
1.4 保留字
在规则语言中存在一些保留字。你应该避免使用这些保留字,来命名规则文本中的域对象,属性,方法,功能。保留字如下: when , then , rule , end , contains , matches , and , or , modify , retract , assert , salience , function , query , exists , eval , agenda-group , no-loop , duration , -> , not , auto-focus 。
<!--[if !supportLists]--> 2. <!--[endif]--> 注释
2.1 单行注释:
Figure 2.1. Single line comment
2.2 多行注释:
Figure 2.2. Multi line comment
<!--[if !supportLists]-->
3. <!--[endif]--> Package
一个包是 rule 和其他相关结构,像 import 和 global 的集合。 Package 的成员之间通常都是相关联的。一个 Package 代表了一个命名空间( namespace ),用来使给定的规则组之间保持唯一性。 Package 的名字本身就是命名空间,并且与文件或文件夹并无关联。
<!--[if !supportEmptyParas]-->
可以将来自不同规则源的规则装配在一起,前提是这些规则必须处在同一个命名空间中。尽管如此,一个通常的结构是将处于同一个命名空间中的所有规则都放在同一个相同的文件中。
下面的 rail-road 图显示了组成一个 Package 的所有组件。注意:一个 package 必须有一个命名空间,并且采用 Java 包名的约定。在一个规则文件中,各组件出现的位置是任意的,除了“ package ”和“ expander ”语句必须出现在任何一个规则之前,放在文件的顶部。在任何情况下,分号都是可选的。
Figure 3.1. package
3.1 import
Figure 3.2. import
Import 语句的使用很像 Java 中的 import 语句。你需要为你要在规则中使用的对象,指定完整的路径和类名。 Drools 自动从相同命名的 java 包中引入所需的类。
3.2 expander
Figure 3.3. expander
expander 语句是可选的,是用来指定 Domain Specific Language 的配置(通常是一个 .dsl 文件)。这使得解析器可以理解用你自己的 DSL 语言所写的规则。
3.3 global
Figure 3.4. global
Global 就是全局变量。如果多个 package 声明了具有相同标识符的 global ,那么它们必需是相同的类型,并且所有的引用都是相同的。它们通常用来返回数据,比如 actions 的日志,或者为 rules 提供所需的数据或服务。 global 并不是通过 assert 动作放入 WorkingMemory 的,所有当 global 发生改变时,引擎将不会知道。所以, global 不能作为约束条件,除非它们的值是 final 的。将 global 错误的使用在约束条件中,会产生令人惊讶的错误结果。
<!--[if !supportEmptyParas]-->
注意: global 只是从你的 application 中传入 WorkingMemory 的对象的命名实例。这意味着你可以传入任何你想要的对象。你可以传入一个 service locator ,或者是一个 service 本身。
<!--[if !supportEmptyParas]-->
下面的例子中,有一个 EmailService 的实例。在你调用规则引擎的代码中,你有一个 EmailService 对象,然后把它放入 WorkingMemory 。在 DRL 文件中,你声明了一个类型为 EmailService 的 global ,然后将它命名为“ email ”,像这样: global EmailService email ;。然后在你的规则的 RHS 中,你可以使用它,像这样: email.sendSMS(number,message) 等等。
4. Function
Figure 4.1. function
Function 是将代码放到你的规则源中的一种方法。它们只能做类似 Helper 类做的事(实际上编译器在背后帮你生成了 Helper 类)。在一个 rule 中使用 function 的主要优势是,你可以保持所有的逻辑都在一个地方,并且你可以根据需要来改变 function (这可能是好事也可能是坏事)。 Function 最有用的就是在规则的 RHS 调用 actions ,特别是当那个 action 需要反复调用的时候。
<!--[if !supportEmptyParas]-->
一个典型的 function 声明如下:
function String calcSomething(String arg) {
return " hola ! " ;
}
<!--[if !supportEmptyParas]-->
注意:“ function ”关键字的使用,它并不真正是 Java 的一部分。而 function 的参数就像是一个普通的 method (如果不需要参数就不用写)。返回类型也跟普通的 method 一样。在一条规则(在它的 RHS 中,或可能是一个 eval )中调用 function ,就像调用一个 method 一样,只需要 function 的名字,并传给它参数。
<!--[if !supportEmptyParas]-->
function 的替代品,可以使用一个 Helper 类中的静态方法: Foo.doSomething() ,或者以 global 的方式传入一个 Helper 类或服务的实例: foo.doSomething() ( foo 是一个命名的 global 变量)。
<!--[if !supportEmptyParas]-->
JBoss Rules 学习(七): Drools规则语言详解(下)
Drools规则语言详解(下)
5. Rule
Figure 5.1. rule
Rule 结构是最重要的结构。 Rule 使用了形如“ IF ” something “ THEN ” action (当然,我们的关键字是“ when ”和“ then ”)的形式。
一个规则在一个 package 中必须要有唯一的名字。如果一个名字中含有空格,那就需要将名字放在双引号中(最好总是使用双引号)。
Attribute 是可选的(最好是每行只有一个 Attribute )。
规则的 LHS 跟在“ when ”关键字的后面(最好是另起一行),同样 RHS 要跟在“ then ”关键字后面(最好也另起一行)。规则以关键字“ end ”结束。规则不能嵌套。
5.1 Left Hand Side
Left Hand Side 其实就是规则的条件部分。 LHS 对应的 rail-road 图如下,我们在后面会做进一步解释:
Figure 5.2. Left Hand Side
Figure 5.3. pattern
5.2 Right Hand Side
Right Hand Side ( RHS )就是规则的结果( consequence )或者动作( action )部分。 RHS 的目的是 retract 或 add facts 到 WorkingMemory 中,还有针对你的 application 的动作。实际上, RHS 是当规则激发( fire )时执行的代码块。
在 RHS 中,你可以使用几个方便的 method 来改变 WorkingMemory :
“ modify(obj) ”:告诉引擎一个对象已经发生变化,规则必须重新匹配( obj 对象必须是出现在 LHS 中的对象);
“ assert(new Something()) ”:将一个新的 Something 对象加入 WorkingMemory ;
“ assertLogical(new Something()) ”:与 assert 方法类似。但是,当没有 fact 支持当前激发规则的真实性的时候,这个新对象会自动被 retract ,
“ retract(obj) ”:从 WorkingMemory 中移除一个对象。
这些方法都是宏指令,提供了到 KnowledgeHelper 实例的快捷方式(参考 KnowledgeHelper 接口)。 KnowledgeHelper 接口可以在 RHS 代码块中调用,通过变量“ drools ”。如果你在 assert 进引擎的 JavaBean 中加入“ Property Change Listener ”,在对象发生变化的时候,你就不用调用“ modify ”方法。
5.3 Rule Attributes
Figure 5.4. rule attributes
5.3.1
no-loop
默认值: false
类型: boolean
当在 rule 的 RHS 中修改了一个 fact ,这可能引起这个 rule 再次被 activate ,引起递归。将 no-loop 设为 true ,就可以防止这个 rule 的 Activation 的再次被创建。
5.3.2 salience
默认值: 0
类型: int
每个 rule 都可以设置一个 salience 整数值,默认为 0 ,可以设为正整数或负整数。 Salience 是优先级的一种形式。当处于 Activation 队列中时,拥有高 salience 值的 rule 将具有更高的优先级。
5.3.3 a genda-group
默认值: MAIN
类型: String
Agenda group 允许用户对 Agenda 进行分组,以提供更多的执行控制。只有具有焦点的组中的 Activation 才会被激发( fire )。
5.3.4 a uto-focus
默认值: false
类型: boolean
当一个规则被 activate (即 Activation 被创建了),如果这个 rule 的 auto-focus 值为 true 并且这个 rule 的 agenda-group 没有焦点,此时这个 Activation 会被给予焦点,允许这个 Activation 有 fire 的潜在可能。
5.3.5 a ctivation-group
默认值: N/A
类型: String
当处于同一个 activation-group 中的第一个 Activation fire 后,这个 activation-group 中其余剩下的 Activation 都不会被 fire 。
5.3.6 duration
默认值:没有默认值
类型: long
5.4 Column
Figure 5.5. Column
Example 5.1. Column
Cheese( )
Cheese( type == " stilton " , price < 10 )
一个 Column 由一个类的一个或多个域约束构成。第一个例子没有约束,它将匹配 WorkingMemory 中所有的 Cheese 实例。第二个例子对于一个 Cheese 对象有两个字面约束( Literal Constraints ),它们被用“,”号隔开,意味着“ and ”。
Figure 5.6. Bound Column
Example 5.2. Bound Column
cheapStilton : Cheese( type == " stilton " , price < 10 )
<!--[if !supportEmptyParas]--> <!--[endif]-->
这个例子同前一个例子有点类似。但是在这个例子中,我们将一个变量绑定到匹配规则引擎的 Cheese 实例上。这意味着,你可以在另一个条件中使用 cheapStilton ,或者在 rule 的 RHS 中。
5.4.1 Field Constraints
Field Constraints 使规则引擎可以从 WorkingMemory 中挑选出合适的 Fact 对象。一个 Fact 的“ Field ”必须符合 JavaBean 规范,提供了访问 field 的 getter 方法。你可以使用 field 的名字直接访问 field ,或者使用完整的方法名(省略括号)。
例如,以我们的 Chess 类为例,下面是等价的: Cheese(type = = …) 和 Cheese(getType = = …) 。这意味着,你可以使用不太严格遵守 JavaBean 规范对象。尽管如此,你要保证 accessor 方法是不带参数的,以保证它不会改变对象的状态。
注意:如果一个 field 使用原始类型( primitive type ), Drools 将会把它们自动装箱成相应的对象(即使你使用 java 1.4 ),但是在 java 1.4 下却不能自动拆箱。总的来说,尽量在 rule 所使用的类中,使用非原始类型的域。如果是使用 java 5 ,就可以比较随意了,因为编译器会帮你执行自动装拆箱。
5.4.1 .1 Operators
Figure 5.7. Operators
有效的操作符是同域类型相关的。例如,对于日期域,“ < ”意味着“之前”。“ matches ”只适用于 String 域,“ contains ”和“ excludes ”只适用于 Collection 类型域。
5.4.1 .2 字面值约束( Literal Constraints )
最简单的域约束就是字面值约束,允许用户将一个 field 约束于一个已知值。
注意:你可以检查域是否为 null ,使用 = = 或 != 操作符和字面值‘ null ’关键字。如, Cheese(type != null) 。字面值约束,特别是“ = = ”操作符,提供了非常快的执行速度,因为可以使用散列法来提高性能。
Figure 5.8. Literal Constraints
Numeric
所有标准的 Java 数字基本类型都可以用。
有效操作符:
<!--[if !supportLists]--> · <!--[endif]--> ==
<!--[if !supportLists]--> · <!--[endif]--> !=
<!--[if !supportLists]--> · <!--[endif]--> >
<!--[if !supportLists]--> · <!--[endif]--> <
<!--[if !supportLists]--> · <!--[endif]--> >=
<!--[if !supportLists]--> · <!--[endif]--> <=
Example 5.3 . Numeric Literal Constraint
Cheese( quantity == 5 )
Date
当前只对“ dd-mm-yyyy ”的日期格式提供默认支持。你可以通过指定 drools.dateformat 系统属性,来改变默认的日期格式。如果需要更多的控制,要用谓词约束( Predicate Constraint )。
有效操作符:
<!--[if !supportLists]--> · <!--[endif]--> ==
<!--[if !supportLists]--> · <!--[endif]--> !=
<!--[if !supportLists]--> · <!--[endif]--> >
<!--[if !supportLists]--> · <!--[endif]--> <
<!--[if !supportLists]--> · <!--[endif]--> >=
<!--[if !supportLists]--> · <!--[endif]--> <=
Example 5.4. Date Literal Constraint
Cheese( bestBefore < " 27-Oct-2007 " )
String
可以使用任何有效的 Java String 。
有效操作符:
<!--[if !supportLists]--> · <!--[endif]--> ==
<!--[if !supportLists]--> · <!--[endif]--> !=
Example 5.5. String Literal Constraint
Cheese( type == " stilton " )
Boolean
只能用 “ true ”或“ false ”。 0 和 1 不能被识别,而且 Cheese(smelly) 也是不被允许的。
有效操作符:
<!--[if !supportLists]--> · <!--[endif]--> ==
<!--[if !supportLists]--> · <!--[endif]--> !=
Example 5.6 Boolean Literal Constraint
Cheese( smelly = = true )
Matches Operator
Matches 操作符后面可以跟任何有效的 Java 正则表达式。
Example 5.7. Regular Expression Constraint
Cheese( type matches " (Buffulo)?//S*Mozerella " )
Contains Operator and Excludes Operator
“ contains ”和“ excludes ”可以用来检查一个 Collection 域是否含有一个对象。
Example 5.8. Literal Cosntraints with Collections
CheeseCounter( cheeses contains " stilton " )
CheeseCounter( cheeses excludes " chedder " )
5.4.1
.3 Bound Variable Constraint
可以将 Facts 和它们的 Fields 附给一个 Bound Variable ,然后在后续的 Field Constraints 中使用这些 Bound Variable 。一个 Bound Variable 被称为声明( Declaration )。 Declaration 并不能和“ matches ”操作符合用,但却可以和“ contains ”操作符合用。
Example 5.9. Bound Field using '==' operator
Person( likes : favouriteCheese )
Cheese( type == likes )
在上面的例子中,“ likes ”就是我们的 Bound Variable ,即 Declaration 。它被绑定到了任何正在匹配的 Person 实例的 favouriteCheese 域上,并且用来在下一个 Column 中约束 Cheese 的 type 域。可以使用所有有效的 Java 变量名,包括字符“ $ ”。“ $ ”经常可以帮助你区分 Declaration 和 field 。下面的例子将一个 Declaration 绑定到匹配的实例上,并且使用了“ contains ”操作符。注意: Declaratino 的第一个字符用了“ $ ”:
Example 5.10 Bound Fact using 'contains' operator
$stilton : Cheese( type == " stilton " )
Cheesery( cheeses contains $stilton )
5.4.1
.4 Predicate Constraints
Figure 5.9. Predicate expression
Predicate 表达式可以使用任何有效的 Java 逻辑表达式。先前的 Bound Declaration 可以用在表达式中。
下面的例子将会找出所有男性比女性大 2 岁的 pairs of male/femal people :
Example 5.11. Predicate Constraints
Person( girlAge : age, sex = = " F " )
Person( boyAge : age -> ( girlAge.intValue() + 2 == boyAge.intValue() ), sex = = " M " )
5.4.1 .5 Return Value Constraints
Figure 5.10. Return Value expression
一个 Retrurn Value 表达式可以使用任何有效的 Java 表达式,只要它返回一个对象,不能返回原始数据类型。如果返回值是原始数据类型,要先进行装箱。先前的 Bound Declaration 也可以使用在表达式中。
下面的例子跟上一节的例子一样,也将会找出所有男性比女性大 2 岁的 pairs of male/femal people 。注意:这里我们不用绑定 boyAge ,增加了可读性:
Example 5.12. Return Value Constraints
Person( girlAge : age, sex = = " F " )
Person( age = = ( new Integer(girlAge.intValue() + 2 ) ), sex = = " M " )
5.5 Conditional Elements
Conditional Elements 用来连接一个或多个 Columns 。
5.5.1 “ and ”
Figure 5.11. and
Example 5.13. And
Cheese( cheeseType : type ) && Person( favouriteCheese == cheeseType )
Cheese( cheeseType : type ) and Person( favouriteCheese == cheeseType )
5.5.2
“ or ”
Figure 5.12. or
Example 5.14. or
Person( sex == " f " , age > 60 ) || Person( sex == " m " , age > 65 )
Person( sex == " f " , age > 60 ) or Person( sex == " m " , age > 65 )
Figure 5.13. or with binding
Example 5.15. or with binding
pensioner : Person( sex == " f " , age > 60 ) || pensioner : Person( sex == " m " , age > 65 )
pensioner : ( Person( sex == " f " , age > 60 ) or Person( sex == " m " , age > 65 ) )
“ or ” Conditional Element 的使用会导致多条 rule 的产生,称为 sub rules 。上面的例子将在内部产生两条规则。这两条规则会在 WorkingMemory 中各自独立的工作,也就是它们都能进行 match , activate 和 fire 。当对一个“ or ” Conditional Element 使用变量绑定时,要特别小心,错误的使用将产生完全不可预期的结果。
可以将“ OR ” Conditional Element 理解成产生两条规则的快捷方式。因此可以很容易理解,当“ OR ” Conditional Element 两边都为真时,这样的一条规则将可能产生多个 activation 。
5.5.3 “ eval ”
Figure 5.14 . eval
Eval is essentially a catch all which allows any semantic code (that returns a primitive boolean) to be executed. 在表达式中可以引用在 LHS 中出现的变量,和在 rule package 中的 Functions 。一个 Eval 应该是 LHS 中的最后一个 Conditional Element 。在一个 rule 中,你可以有多个 eval 。
Eval 不能被索引,因此不能像 Field Constraints 那样被优化。尽管如此,当 Functions 的返回值一直在变化时,应该使用 Eval ,因为这在 Field Constraints 中时不允许的。如果规则中的其他条件都匹配,一个 eval 每次都要被检查。(现在还不理解到底 eval 要怎么用?)
Example 5.16. eval
p1 : Parameter()
p2 : Parameter()
eval( p1.getList().containsKey(p2.getItem()) )
eval( isValid(p1, p2) ) // this is how you call a function in the LHS - a function called // "isValid"
5.5.4
“ not ”
Figure 5.15. not
“ not ”是一阶逻辑的存在量词( first order logic’s Existential Quantifier ) , 用来检查 WorkingMemory 中某对象的非存在性。现在,只有 Columns 可以放在 not 中,但是将来的版本会支持“ and ”和“ or ”。
Example 5.17. No Buses
not Bus()
Example 5.18. No red Buses
not Bus(color == " red " )
not ( Bus(color == " red " , number = = 42 ) ) // brackets are optional
5.5.5
“ exists ”
Figure 5.16. exists
“ exists ” 是一阶逻辑的存在量词( first order logic’s Existential Quantifier ),用来检查 WorkingMemory 中某对象的存在性。可以将“ exists ”理解为“至少有一个”( at least one… )。它的意义不同于只有 Column 本身,“ Column ”本身可以理解为“对于每一个 … ”( for each of … )。如果你对一个 Column 使用了“ exists ”,那么规则将只 activate 一次,而不管 WorkingMeomry 中有多少个数据匹配了那个条件。
现在,只有 Columns 可以放在“ exists ”中,但是将来的版本会支持“ and ”和“ or ”。
Example 5.19. At least one Bus
exists Bus()
Example 5.20. At least one red Bus
exists Bus(color == " red " )
5.5.6
“ group ”
Figure 5.17. group
Group 的作用相当于代数学中的“()”,显式的指明操作的顺序。
5.6 再谈自动装箱和原始类型
Java 5 支持在原始类型与其对应包装类之间的装拆箱。尽管如此,因为要让 drools 能够在 J2SE 1.4 下运行,我们不能依靠 J2SE 。因此, drools 自己实现了自动装箱。被引用(即被 Bound Variable 绑定)的 Field 将被自动进行装箱(如果它们本身就是 object ,就不会有任何变化)。尽管如此,必须要注意的是,他们并不会被自动拆箱。
还有一点要注意的,就是对于 ReturnValue Constraints ,返回值的代码必须返回一个对象,而不能是一个原始类型。
6 . Query
Figure 6.1 . query
一个 query 只包含了一个 rule 的 LHS 结构(你不用指定“ when ”或“ then ”)。这是查询 WorkingMemory 中匹配条件的 Facts 的简单的方法。
要得到结果,要使用 WorkingMemory.getQueryResults(“name”) 方法,其中“ name ”就是 query 的名字。 Query 的名字在 RuleBase 中是全局的,所以, do not add queries of the same name to different packages for the same RuleBase 。
下面的例子创建了一个简单的 query 来查询所有年龄大于 30 的人:
Example 6.1. Query People over the age of 30
query "people over the age of 30"
person : Person( age > 30 )
end
我们通过一个标准的循环来迭代一个返回的QueryResults对象。每一次的iterate将返回一个QueryResult对象。我们可以用QueryResult对象的get()方法来访问每个Column,通过传入Bound Declaration或index position。
Example 6.2. Query People over the age of 30
QueryResults results = workingMemory.getQueryResults( "people over the age of 30" );
System.out.println( "we have " + results.size() + " people over the age of 30" );
System.out.println( "These people are are over 30:" );
for ( Iterator it = results.iterator; it.hasNext(); ) {
QueryResult result = ( QueryResult ) it.next();
Person person = ( Person ) result.get( "person" );
System.out.println( person.getName() + "/n" );
}