在你的企业级java应用中使用Drools

规则引擎(DROOLS)培训

DROOLS培训(5天)

目的:

重点讲解DROOLS是什么、能做什么、工作流程、怎么用到系统中,让学习人员可以对DROOLS有个全面初步的了解,并能够用DROOLS进行简单的系统开发。

 

参加人员:

    需要使用DROOLS进行系统设计和开发或者对DROOLS感兴趣的同事。

 

主讲人员:

 

DROOLS

内容

主讲

时间

第一天

DROOLS 入门

什么是DROOLS

 

小时

DROOLS能做什么

 

 

为什么用DROOLS

 

 

什么样的系统适合用DROOLS

 

 

DROOLS的模块构成

 

小时

DROOLS的工作流程

分钟

第二天

DROOLS规则

规则语言

 

小时

第三天

开发流程

用DROOLS开发的系统流程

 

小时

性能告警系统实例

 

 

第四天

 

DROOLS练习

Hello Word (静态规则)

 

小时

Hello Word (动态规则)

 

 

关键提点

 

 

第五天

DROOLS进阶

DROOLS在spring和jboss的中集成与运用

 

 

 

 

 

 

 

 

 

 

 

第一章 DROOLS 入门

第 1 节 什么是DROOLS

 如何组织企业应用中的业务逻辑,如果靠手工的代码来解决。随着大量业务规则的变化,导致应用程序不停的变更,如何能找到一种解决商业逻辑的架构,来解决当商务规则不停的变化时,可以保证我们的应用系统具有较好的柔韧性,可以适应特定的商务规则的变化,而无需修改我们的应用系统。Drools就是这样的一个应用在商务逻辑层的架构。 

 

CODEHAUS的一个开源项目叫Drools,Drools是为Java量身定制的基于RETE算法的规则引擎的实现。具有了OO接口的RETE,使得商业规则有了更自然的表达。Drools是用Java写的,但能同时运行在Java和.Net上。最近被纳入JBOSS门下,更名为JBOSS Rules,成为了JBOSS应用服务器的规则引擎。

 

RETE算法是CHARLES FORGY在1979年发明的。RETE是唯一的,效率与执行规则数目无关的决策支持算法。For the uninitiated, that means it can scale to incorporate and execute hundreds of thousands of rules in a manner which is an order of magnitude more efficient then the next best algorithm。RETE应用于生产系统已经有很多年了,但在Java开源软件中并没有得到广泛应用,直到DROOLS的出现。

第 2 节 DROOLS能做什么

大多数web和企业Java应用可以分成三个部分:一个和用户交互的前台, 一个和后台系统,例如数据库交互的服务层,以及他们中间的业务逻辑。 现在使用框架构建前台和后台系统已经成为普遍共识(例如, Struts, Cocoon, Spring, Hibernate, JDO, 和 Entity Beans), 但却没有一个标准的方法来构建业务逻辑。一些框架,例如 EJB 和 Spring 只在一个高层实现业务逻辑,但对于我们组织逻辑代码没有任何帮助,所以,为什么没有一个框架来替换冗繁,易错的if...then语句呢,这个框架应该和其它前台或后台框架一样,易于配置,具有可读性和重用性。Drools 规则引擎,这个就是来解决我们上述问题的框架。

 

第 3 节 为什么使用DROOLS

软件应用领域充满了变化。在技术领域里,IT从业人员也在不断探索各种软件方法学,来适应用的变化,如快速软件开发,极限编程,敏捷软件开发等,它们无一例外地强调灵活和变化的重要性。

虽然IT团队反应迅速,但他们通常带来"电话效应"―IT给商业计划的执行带来的阻力和它带来的利益一样多。不幸的是,在开发团队完全理解商业决策规则并实现之前,规则已经改变了。在软件进入市场前,它已经过时了,需要进行重构以满足新的业务需求。如果你是一个开发人员,你会知道我在说什么。再也没有比在需求变动的情况下构造软件让开发人员更沮丧的事情了。作为软件开发人员,你必须象业务人员一样了解业务,有时还要了解更多。

试想一下你是一位商业决策者。假如公司的成功依赖于你对于市场趋势敏锐的洞察力,它常常帮助你领先于竞争者利用变化的市场环境获利。每天你都会得到更多更好的市场信息,但并不要紧。完成新产品开发可能需要6-9个月,在此期间,对于市场大胆和敏锐的洞察和信息优势可能已经浪费了。而且,当产品发布时,有这样几种可能:产品没有什么吸引人的特性,预算超支,过了产品的最佳发布期限,或三者兼而有之。

情况可能还会更糟,在完成产品开发时,市场环境和规划产品开发时相比,已经发生了根本变化。现在你必须要遵守新的规则,你已经丧失了你的边际优势,而且设计软件的五人中的三人已经离开了公司。你必须给接手的新人重新讲解复杂的业务。如果事情不顺利,你可能发现自己要对付一个缺少文档,并且你完全不了解的遗留应用。

你的战略在哪出现了问题?你在哪里应该可以做到更好?最近的轻量级软件过程,如极限编程,敏捷软件开发等都在强调自动单元测试和软件功能优先级的重要性。除此之外,还有其他的原则,你的开发团队可能也很熟悉,这些原则可以帮助他们对需求的变动做出迅速反应并缩短项目的开发周期。这些原则的大多数,如系统分解,多年前就已经出现,并得到了Java平台的支持(如JMX等),还有如面向对象和角色建模,已经内建在Java语言中。

为什么你的开发团队不得不像商业经理人一样,在代码中包含复杂微妙的商业决策逻辑呢?你怎样才能向他们解释决策推理的微妙之处呢?你这样做是否谨慎呢?可能不是。为什么要冒这样的风险,让应用代码或测试代码错误地表达你的商业决策逻辑呢?如果这样做的话,你怎样检查它们的正确性呢---难道你自己想学习如何编程和编写测试代码,或者你的客户会为你测试软件?你一方面要应付市场,一方面要应付软件代码,这实在太困难了。 

如果使用DROOLS开发系统就能将这些商业决策规则集中地放在一个地方,以一种你可以理解的格式定义,让你可以直接管理,而不是散落在代码的各个角落。这样你就能把商业决策规则独立于你的软件代码,让开发团队做出技术决策,你将会获得更多好处。你的项目开发周期会更短,软件对于变动的需求更灵活。

 

第 4 节 什么样的系统适合用DROOLS

既然DROOLS可以使程序更灵活,适应变化的能力更强,是否所有的系统都适合用DROOLS开发呢?回答是否定的!

是否需要用DROOLS,需要考虑一下问题:

第一:我的应用程序有多复杂?

对于那些只是把数据从数据库中传入传出,并不做更多事情的应用程序,最好不要使用规则引擎。但是,当在Java中有一定量的商业逻辑处理的话,可以考虑Drools的使用。这是因为很多应用随着时间的推移越来越复杂,而Drools可以让你轻松应对这一切。
  第二:我的应用的生命周期有多久?这个问题的正确答案往往是“令人惊讶的长”――还记得那些认为他们的程序不会苟活到2000年的大型机的程序员吗?使用规则引擎将会在中长期得到好处。像这篇文章所展示的那样,甚至原型都能从Drools与灵活方法的组合中获益,让“原型系统”转化成生产系统。
  第三:我的应用需要改变吗?唯一能确定的是你的需求将会改变,无论是在开发过程中或是在开发完成以后。Drools使用一个或多个简单的DRL文件帮你来应对这一切。

 

第 5 节 规则引擎的框架结构

Drools 分为两个主要部分:构建( Authoring )和运行时( Runtime )。构建首先会使用 DRL 文件进行创建规则。运行时要构造出fact,把fact放入working memory 进行规则匹配。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1 Production memory

 

规则加载流程:

首先创建xml或drl 规则文件à 将规则文件加载到Parser(解析器)中à产生中间结构descrà 使用Package Builder对中间结构descr 进行 执行和编码à并且返回package对象。(package对象中包含1个或者多个规则)。

RuleBase是一个运行时组件,它包含了一个或多个 Package 对象。

  

RuleBase中可以包含一个或者多个package对象,并且可以在任何时刻将一个 Package 对象加入或移出 RuleBase 对象,RuleBase可以在任何时刻创建一个或者多个Working Memory对象。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2 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 。

 

2.1 Facts

Facts 是从你的应用中,被 assert 进 WorkingMemory 中的对象( beans )。 Facts 是规则可以访问的任意的 java 对象。规则引擎中的 facts 并不是“ clone ” facts ,它只是持有到你的应用中数据的引用。 Facts 是你的应用数据。 String 和其他没有 getter 和 setter 的类不是有效的 Fact 。这样的类不能使用域约束( Field Constraints ),因为使用域约束要依靠 JavaBean 标准的 getter 和 setter 来同对象交互。 

 

2.2 Assertion 

“Assertion”是将 facts 加入事实库的动作,例如 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 的动作。

 

2.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 );

 

2.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 );

 

2.5 Globals 

Global 是一个能够被传进 WorkingMemory 但不需要 assert 的命名对象。大多数这些对象被用来作为静态信息或服务。这些服务被用在一条规则的 RHS ,或者可能是从规则引擎返回对象的一种方法。 

List list  =   new  ArrayList();
workingMemory.setGlobal( " list " , list);

 

setGlobal() 方法传进去的命名对象必须同 RuleBase 中所定义的具有相同的类型(就是要同你的规则文件中用 Global 关键字所定义的类型相同),否则会抛出一个 RuntimeException 。如果一条规则在你 setGlobal 之前调用了定义的 Global ,会抛出一个 NullPointerException 。

 

2.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 );

 

2.7 Agenda

 

 

基本流程:首先 通过fire Rule –〉将相应的 细节对象加载到 事实库(Working Memory) 和 相应的规则 à 确定单个细节对象对应的规则(规则可以为多个)à将规则和对象进行匹配并且加载,相应匹配成功和不成功所触发的事件。

1、 匹配成功后 àFire Rule查找是否有相应 rule进行匹配,有将继续执行rule流程,没有à exit

2、 匹配不成功后 àexit

jBoss Rule 中提供了两种算法 

1、 RETE算法  通过节点之间的共享提供性能

2、 Leaps算法 迭代匹配

 

第 6 节 规则引擎的工作流程

数据被 assert 进 WorkingMemory 后,和 RuleBase 中的 rule 进行匹配(确切的说应该是 rule 的 LHS ),如果匹配成功这条 rule 连同和它匹配的数据(此时就叫做 Activation )一起被放入 Agenda ,等待 Agenda 来负责安排激发 Activation (其实就是执行 rule 的 RHS ),上图中的菱形部分就是在 Agenda 中来执行的, Agenda 就会根据冲突解决策略来安排 Activation 的执行顺序。 菱形部分执行完后会查找一下是否有新的规则需要匹配,如果有进入下一个匹配循环,如果没有结束退出。

 

 

第二章 DROOLS规则

第 1 节 规则简介

既然JBoss Rules是一个商业规则引擎,那我们就要先知道到底什么是Rules,即规则。在JBoss Rules中,规则是如何被表示的。

 

Drools 3 的rule采用了原生的规则语言,那是一种非 XML 文本格式。在符号方面,这种格式是非常轻量的,并且通过“ expanders ”支持符合你问题域的 Domain Specific Language ( DSL )。下面重点介绍 Drools 规则格式。

 

1.1 规则

一条规则是对商业知识的编码。一条规则有 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 > 
}

 

 

 

1.1 规则文件 

同一个package的规则通常被组织为一个规则文件,规则文件通常是一个以 .drl 扩展名结尾的文件。一个 DRL 文件是一个简单的文本文件。

1.1 规则的结构 

一个规则结构大致如下: 

可以看到,这是非常简单的。通常的标点符号都是不需要的,甚至连“ name ”的双引号都是不需要的。 ATTRIBUTES 是简单的,也是可选的,来提示规则的行为方式。 LHS 是规则的条件部分,需要按照一定的语法来写。 RHS 基本上是一个允许执行 Java 语法的代码的块(以后将会支持 groovy 和 C #)。任何在 LHS 中使用的变量都可以在 RHS 中使用。 

注意:每行开始的空格是不重要的,除非在 DSL ( Domain Specific Language )语言中有特别的指明。

 

1.1 Domain Specific Language 

Domain Specific Language 是对原生规则语言的加强。它们使用“ expander ”机制。 Expander 机制是一种可扩展的 API 。你可以使用 .dsl 文件,来提供从域或自然语言到规则语言和你的域对象的映射。你可以将 .dsl 文件看成是对你的域模型的映射。 DSL 提供了更高的规则可读性,你可以选择使用你自己创建的 DSL ,或者是原生的规则语言。

 

1.1 保留字 

在规则语言中存在一些保留字。你应该避免使用这些保留字,来命名规则文本中的域对象,属性,方法,功能。保留字如下: when , then , rule , end , contains , matches , and , or , modify , retract , assert , salience , function , query , exists , eval , agenda-group , no-loop , duration , -> , not , auto-focus 。

第 2 节 规则语法 

2.1 注释

单行注释

 

Figure 2.1.1 import

示例:

    // 下面是一条规则

    # 规则名称

rule  " name " 
    ATTRIBUTES
    when
        LHS
    then
        RHS
   end

 

多行注释

Figure 2.1.2 import

示例:

    /* 下面是一条规则

        规则名称

*/

rule  " name " 
    ATTRIBUTES
    when
        LHS
    then
        RHS
   end

 


2.2 Package

代表了一个命名空间(具体感觉和java中的package相似)用来使给定的规则组之间保持唯一性

 

Figure 2.2. import

2.3 Import


Figure 2.3. import

Import 语句的使用很像 Java 中的 import 语句。你需要为你要在规则中使用的对象,指定完整的路径和类名。 Drools 自动从相同命名的 java 包中引入所需的类。 

2.4 Expander

Figure 2.4. expander 

expander 语句是可选的,是用来指定 Domain Specific Language 的配置(通常是一个 .dsl 文件)。这使得解析器可以理解用你自己的 DSL 语言所写的规则

2.5 Global

Figure 2.5. global 

Global 就是全局变量。如果多个 package 声明了具有相同标识符的 global ,那么它们必需是相同的类型,并且所有的引用都是相同的

注意: global 只是从你的 application 中传入 WorkingMemory 的对象的命名实例。这意味着你可以传入任何你想要的对象。你可以传入一个 service locator ,或者是一个 service 本身

2.6 Function


Figure 4.1. function

Function 最有用的就是在规则的 RHS 调用 actions ,特别是当那个 action 需要反复调用的时候。

一个典型的 function 声明如下: 

function String calcSomething(String arg) {
return   " hola ! " ;

}

注意:“ function ”关键字的使用,它并不真正是 Java 的一部分。而 function 的参数就像是一个普通的 method (如果不需要参数就不用写)。返回类型也跟普通的 method 一样。在一条规则(在它的 RHS 中,或可能是一个 eval )中调用 function ,就像调用一个 method 一样,只需要 function 的名字,并传给它参数。 

function 的替代品,可以使用一个 Helper 类中的静态方法: Foo.doSomething() ,或者以 global 的方式传入一个 Helper 类或服务的实例: foo.doSomething() ( foo 是一个命名的 global 变量)。

2.7 Rule

规则的 LHS 跟在“ when ”关键字的后面(最好是另起一行),同样 RHS 要跟在“ then ”关键字后面(最好也另起一行)。规则以关键字“ end ”结束。规则不能嵌套。

2.7.1 Left Hand Side 

判断的条件

Figure 5.2. Left Hand Side

Figure 5.3. pattern

 

2.7.2 Right Hand Side 

执行的操作可以使用java代码

modify(obj) :告诉引擎一个对象已经发生变化,规则必须重新匹配( obj 对象必须是出现在 LHS 中的对象); 

assert(new Something()):将一个新的 Something 对象加入 WorkingMemory ; 

assertLogical(new Something()):与 assert 方法类似。但是,当没有 fact 支持当前激发规则的真实性的时候,这个新对象会自动被 retract , 

retract(obj):从 WorkingMemory 中移除一个对象。 

2.7.3  Rule Attributes

Figure 5.4. rule attributes 

2.7.3.1  no-loop:

默认值: false 

类型: boolean 

当在 rule 的 RHS 中修改了一个 fact ,这可能引起这个 rule 再次被 activate ,引起递归。将 no-loop 设为 true ,就可以防止这个 rule 的 Activation 的再次被创建。 

2.7.3.2  Salience

默认值: 0 

类型: int 

每个 rule 都可以设置一个 salience 整数值,默认为 0 ,可以设为正整数或负整数。 Salience 是优先级的一种形式。当处于 Activation 队列中时,拥有高 salience 值的 rule 将具有更高的优先级。 

2.7.3.3  agenda-group

默认值: MAIN 

类型: String 

Agenda group 允许用户对 Agenda 进行分组,以提供更多的执行控制。只有具有焦点的组中的 Activation 才会被激发( fire )。 

2.7.3.4  auto-focus

默认值: false 

类型: boolean 

当一个规则被 activate (即 Activation 被创建了),如果这个 rule 的 auto-focus 值为 true 并且这个 rule 的 agenda-group 没有焦点,此时这个 Activation 会被给予焦点,允许这个 Activation 有 fire 的潜在可能。 

 

2.7.3.5  activation-group

默认值: N/A 

类型: String 

当处于同一个 activation-group 中的第一个 Activation fire 后,这个 activation-group 中其余剩下的 Activation 都不会被 fire 。 

 

2.7.3.6  Duration

默认值:没有默认值 

类型: long 

设置运行时间的

 

2.7.3.7  Column

Figure 5.5. Column

  

2.7.3.8  Field Constraints

使规则引擎可以从 WorkingMemory 中挑选出合适的 Fact 对象。一个 Fact 的“ Field ”必须符合 JavaBean 规范,提供了访问 field 的 getter 方法。你可以使用 field 的名字直接访问 field ,或者使用完整的方法名(省略括号)。 

2.7.3.9  Operators

Figure 5.7. Operators 

有效的操作符是同域类型相关的。例如,对于日期域,“ < ”意味着“之前”。“ matches ”只适用于 String 域,“ contains ”和“ excludes ”只适用于 Collection 类型域。 

2.7.3.10  字面值约束( Literal Constraints )

最简单的域约束就是字面值约束,允许用户将一个 field 约束于一个已知值。 

注意:你可以检查域是否为 null ,使用 = = 或 != 操作符和字面值‘ null ’关键字。如, Cheese(type != null) 。字面值约束,特别是“ = = ”操作符,提供了非常快的执行速度,因为可以使用散列法来提高性能。 

Figure 5.8. Literal Constraints 

 

Example 5.3 . Numeric Literal Constraint

Cheese( quantity  ==   5  ) 

 

Example 5.4. Date Literal Constraint

你可以通过指定 drools.dateformat 系统属性,来改变默认的日期格式

Cheese( bestBefore  <   " 27-Oct-2007 "  ) 

 

Example 5.5. String Literal Constraint

Cheese( type  ==   " stilton "  ) 

 

Example 5.6 Boolean Literal Constraint

Cheese( smelly  =   =   true  ) 

 

Example 5.7. Regular Expression Constraint

Cheese( type matches  " (Buffulo)?\\S*Mozerella "  ) 

 

Example 5.8. Literal Cosntraints with Collections 

CheeseCounter( cheeses contains  " stilton "  )
CheeseCounter( cheeses excludes  " chedder "  )

 

2.7.3.11  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 )

 

Example 5.10 Bound Fact using 'contains' operator

$stilton : Cheese( type  ==   " stilton "  )
Cheesery( cheeses contains $stilton )

 

  

2.7.3.12  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 "  )

 

2.7.3.13  Return Value Constraints

Figure 5.10. Return Value expression

 

Example 5.12. Return Value Constraints 

Person( girlAge : age, sex  =   =   " F "  )
Person( age  =   =  (  new  Integer(girlAge.intValue()  +   2 ) ), sex  =   =   " M "  )

 

2.7.3.14  Conditional Elements

and 

Figure 5.11. and 


Example 5.13.  And 

Cheese( cheeseType : type )  &&  Person( favouriteCheese  ==  cheeseType )
Cheese( cheeseType : type ) and Person( favouriteCheese  ==  cheeseType )

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  )

eval

Figure 5.14 . 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" 

 

not

Figure 5.15. not 

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 

 

exists

 

 

如果你对一个 Column 使用了“ exists ”,那么规则将只 activate 一次,而不管WorkingMeomry 中有多少个数据匹配了那个条件。

 

Example 5.19. At least one Bus 

exists Bus()


Example 5.20. At least one red Bus 

exists Bus(color  ==   " red " )

 

group

Figure 5.17. group 

Group 的作用相当于代数学中的“()”,显式的指明操作的顺序。 

 

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 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 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" );
}

 

 

第三章 开发流程

运用DROOLS开发应用系统的流程:

1、 整理系统的商业逻辑

2、 从逻辑中抽象出规则依赖的事实(FACTS)

3、 根据商业逻辑和事实编写(定制)规则

4、 加载事实到事实库(Workingmemory)

5、 加载规则到规则库(rulebase)

6、 激活规则引擎匹配规则(fire)

第 1 节 商业逻辑

DROOLS是用来处理系统中的商业逻辑的,在决定一个项目使用DROOLS开发后,就要着手整理这个系统中所包含的商业逻辑,可以借助excel建立系统商业逻辑的决策表,决策表包括系统逻辑中所有条件,及满足特定逻辑规则时采取的动作集合。

决策表的左边是逻辑的条件(可以有多个),决策表的最右边是当全部的左边条件成立时执行的动作(可以有多个)。决策表的每一行都是一条逻辑规则。

 

 

 

 

 

第 2 节 抽象事实

从第一节中整理的商业逻辑的条件中抽象出DROOLS的事实类,所谓的事实类就是以条件中的变量为属性的标准的javabean类(每个属性都有getset方法),事实必须包括全部的条件中的变量。可以根据需要抽象出一个或多个事实。

从上图给出的决策表可以抽象出事实类:

package drools.test;

public class Fact {

int a;

int b;

int c;

public int getA() {

return A;

}

public void setA(int a) {

A = a;

}

public int getB() {

return b;

}

public void setB(int b) {

this.b = b;

}

public int getC() {

return c;

}

public void setC(int c) {

this.c = c;

}

}

事实对象可以批量插入事实库,再激活规则,执行规则匹配动作。DROOLS会把同类的事实存入一个缓冲队列中,执行的时候会从这个队列中依次取出匹配激活的规则。事实库中的事实都是保存在内存中,如果事实太多会影响DROOLS的执行性能,drools中的事实最好控制的 10000以下,并且当workingmemory中的事实不会再被使用时,要及时清除,以释放资源。根据测试每秒钟可以加载5000个事实并且对每个事实匹配 20 条规则(加载 20条规则  392ms, 加载 5000事实  336ms, 执行fire 8ms)。

 

第 3 节 编写规则

DROOLS的规则可以静态编写在drl文件中,也可以有用户在前台定制逻辑表达式,由后台服务动态生成drools规则字符串加载到规则库中。

 

由第一节的商业逻辑生成的规则实例:

package org.drools.examples

 

import drools.test;

 

// 规则名称, fire的时候 可以指定匹配的规则名称或者以规则的开头字符串

// workingMem.fireAllRules(new RuleNameStartsWithAgendaFilter("r120_"));

rule "test_rule_1"

 

// 对议事表(agenda)分组, 如果有分组fire前必须先激活要分组

// 激活举例: workingMem.setFocus("312851");

// agenda-group "312851"

 

// 当没有下面的true时 rsh中有assetobj), 

// 并且asser进去的 obj 可以匹配该规则,会构成无限次循环匹配

no-loop true

 

// 先匹配优先级高的规则

salience 1

 

when

factInstance : test(a > 10)

then

    执行活动A JAVA代码;

end

 

rule "test_rule_2"

 

no-loop true

salience 0

 

when

factInstance : test(b > 10 && c > 10)

then

    执行活动A JAVA代码;

    执行活动B JAVA代码;

end

 

rule "test_rule_3"

no-loop true

salience -1

when

factInstance : test(a < 10 && (b < 10 || c < 10))

then

    执行活动B JAVA代码;

    执行活动C JAVA代码;

end

如果由用户再前台配置商业的逻辑,系统需要在后台将用户配置的逻辑表达式解析为上面格式的字符串对象,然后加载到规则库。

规则可以按照类型和用途的不同放在不同的规则文件中,以便于规则的维护,在不同文件中的规则加载到规则库以后DROOLS会自动合并相同包中的规则。

规则也可以按照激活的批次不同来划分成不同的规则组,相同批次需要激活的规则放在同一个规则group中,这个执行的时候可以有选择的激活规则组,只激活需要的规则组,可以提高规则引擎的执行效率。

默认情况下,规则的执行是按照加载进规则库中的顺序,依次匹配规则并激活相应的活动的,但是我们也可以指定规则的优先级别来改变规则的匹配的顺序,规则优先级别的属性是 salience, salience的取值为整数,可以为正整数、负整数、或者零,取值越大规则的优先级别越高,在执行的时候就会优先被匹配。

 

第 4 节 加载事实

事实是规则引擎推理的依据,在java程序中构造事实,并对事实对象的属性赋值:

fact = new Fact();

fact.setA(15);

 

将上面构造的事实装载到事实库。加载事实需要注意,如果要匹配规则A,那么规则A中 LSH中声明的全部对象,必须都装载到事实库,否则规则A就不会被匹配。在规则引擎中事实库对象为:org.drools.WorkingMemory 装载事实的方法为:

workingMemory.assertObject(fact);

 

第 5 节 加载规则

规则是系统逻辑的DROOLS规则语法描述,是DROOLS的推理依据,在前期工作的基础上加载的工作变的很简单,规则库对象为:org.drools.RuleBase  装载事实的方法为:

// 规则编译器

final PackageBuilder builder = new PackageBuilder();

builder.addPackageFromDrl(stringReader);

final Package pkg = builder.getPackage();

 

// 把规则加载到规则库

ruleBase.addPackage(pkg);

 

stringReader对象是从规则文件或者后台服务构造的规则String对象,读入的标准DROOLS规则字符流。  

 

第 6 节 启动引擎

1.1 激活规则

如果你的规则分组的话,即有定义 agenda-group 属性,那么在启动引擎前必须先激活需要的指标组:

workingMemory.setFocus(“指标组名称”);

 

1.2 执行fire

执行时可以选择几种模式:

n 匹配全部激活的规则

workingMemory.fireAllRules();

执行fire不带任何参数规则引擎将会尝试匹配全部活动状态的规则,这是最简单的一种fire方式,但是通常也是效率最低下的一种方式。

 

n 匹配以指定规则名开头的规则

ruleNameStartsWithAgendaFilter  = new RuleNameStartsWithAgendaFilter("rule")

workingMemory.fireAllRules(ruleNameStartsWithAgendaFilter);

这样将匹配规则名以 “rule” 开头的规则比如rule_1rule_2

 

n 匹配以指定规则名开头的规则

ruleNameStartsWithAgendaFilter  = new RuleNameStartsWithAgendaFilter("rule")

workingMemory.fireAllRules(ruleNameStartsWithAgendaFilter);

这样将匹配规则名以 “rule” 开头的规则比如rule_1rule_2

 

 

第 7 节 实例

 

上面介绍了一般采用DROOLS开发系统的流程,下面给出性能告警系统各个步骤下的中间结果,以供实践的时候参考。

 

开发系统前先要整理系统的逻辑决策表。性能告警系统的逻辑决策表:

 

 

由系统逻辑决策表抽象出事实对象,性能告警系统的事实对象: 

 

 

由系统逻辑决策表编写DROOLS规则,性能告警系统的规则:

 

 

 

 

 

第四章 DROOLS练习

第 1 节 环境搭建 

3.1 资源准备

DROOLS开发系统首先要从DROOLS网站下载相关的开发Jar文件。

下载文件名称:

jbossrules-3.0.4-bin-withdeps.zip

drools-4.0.1-eclipse.zip(使用eclipse开发drools的插件,只有使用eclipse才需要下载)

下载网址:http://labs.jboss.com/drools/downloads.html

需要的jar文件: 

antlr-2.7.6.jar

antlr-3.0ea8.jar

commons-jci-core-1.0-406301.jar

commons-jci-eclipse-3.2.0.666.jar

commons-jci-janino-2.4.3.jar

commons-lang-2.1.jar

commons-logging-1.0.4.jar

core-3.2.0.666.jar

drools-compiler-3.0.4.jar

drools-core-3.0.4.jar

drools-decisiontables-3.0.4.jar

drools-jsr94-3.0.4.jar

stringtemplate-2.3b6.jar

3.2 drools eclipse插件的安装

将E:\work\myWork\demos解压到eclipse下面的plugins目录,重起eclipse,如果在eclipse的开发页面的工具栏看到图标说明插件安装成功,否则请查看eclipse的安装帮助。

 

3.3 创建工程

java开发工具(比如eclipse)新建工程,将上面的jar文件,加载到工程中,即可进行相关应用的开发了。使用eclipse建一个实例程序非常简单 选择 下拉选择 就可以创建helloworld程序。使用其他开发工具需要自己创建并编写drl文件。

使用eclipsedrools插件创建的test工程:

     

第 2 节 Helloword (从drl静态加载规则)

下面的示例工程是在 DROOLS 3.0.4版本下编译测试的

Drl规则文件

java文件:

第 3 节 Helloword (程序动态加载规则)

下面的示例工程是在 DROOLS 3.0.4版本下编译测试的

 

第 4 节 关键提点

3.1 关于事实

在规则when中使用的对象必须在workingmem assert进去,否则不会匹配该条规则

 

3.2 注意无限循环

规则中如果不设置 no-loop true 可能会构成无限循环

注:当rule中的 no-loop false或者默认时, 在规则的rhs中使用modify更新事实,并且更新后的事实会重新符合rule的rhs就会形成无限循环

 

3.3 效率优化

3.3.1 条件顺序

Condition 的排放顺序。如果你的规则中Condition 不只一个的话,那么把哪个Condition 放在前面是很有讲究的,这直接关系到规则引擎的执行效率。

例如下面,有上万个如下类型的facts 将同时assert 进入规则引擎中参与计算。而这些facts 中绝大部分facts 的type 为1,name 却各不相同:

/* * fact class */

public class FactObject {    

private int type;    

private String name;    

// 省去getter/setter Methods

}

规则1(低效):

when fact : FactObject(type == 1, name == "test")

 

规则2(高效):

when fact : FactObject(name == "test", type == 1)

 

后面这种定义相对于前面,单单是换了一个顺序,在执行效率方面规则2比规则1约高出25% (实际测试:1000000个事实一条规则,规则1插入事实和fire用时 13187毫秒,规则2用时 10234毫秒)

 

 

 

第五章 DROOLS的集成应用

第 1 节 Spring集成DROOLS

Spring是一款优秀的开源框架,怎么使用Spring集成drools使springdrools在系统中能够相得益彰呢?这里使用Springsidedrools的集成做一个介绍:

 

1.  BaseLoader.java

此类是是一个接口,其实现类将从数据库或drl文件中读取RuleBase,代码如下:

public interface BaseLoader {

    public RuleBase buildRuleBase(String RulesetName, Date date) throws Exception;

}

 

2.  FileRuleBaseLoader.java

此类是BaseLoader的实现类,负责从drl文件中读取并组装成Drools RuleBase

代码如下:

public class FileRuleBaseLoader implements BaseLoader {

    private Map ruleSetFileMap;

    public void setRuleSetFileMap(Map ruleSetFileMap) {

        this.ruleSetFileMap = ruleSetFileMap;

    }

 

    public RuleBase buildRuleBase(String ruleSetName, Date date) throws Exception {

        String filePath = (String) ruleSetFileMap.get(ruleSetName);

        if (filePath == null)

            throw new Exception("No RuleSet found by Name:" + ruleSetName);

        InputStream is = this.getClass().getResourceAsStream(filePath);

        if (is == null)

            throw new Exception("No RuleSet found by Name:" + ruleSetName);

        return RuleBaseLoader.loadFromInputStream(is);

    }

}

 

3.  RuleService.java

public interface RuleService {

    /**

     * 执行规则

     * @param ruleSetName 执行的规则集名

     * @param facts       参与规则运算的事实列表

     * @param date        运算日期,用于选择有效期内的规则

     */

    public List executeRules(String ruleSetName, List facts, Date date) throws Exception;

 

    /**

     * 参见 excueteRules(String ruleSetName, List facts, Date date)

     */

    public List executeRules(String ruleSetName, Object fact, Date date) throws Exception;

}

 

4.  DroolsTemplate.java

public class DroolsTemplate implements RuleService {

    private BaseLoader ruleBaseLoader;

    private Cache ruleCache;

 

    /**

     * 执行规则,见接口定义

     */

    public List executeRules(String ruleSetName, Object fact, Date date) throws Exception {

        List facts = new ArrayList();

        facts.add(fact);

        return executeRules(ruleSetName, facts, date);

    }

 

    /**

     * 执行规则,见接口定义

     */

    public List executeRules(String ruleSetName, List facts, Date date) throws Exception {

        RuleBase rb = loadRuleBase(ruleSetName, date);

        WorkingMemory session = rb.newWorkingMemory();

        for (Iterator objectIter = facts.iterator(); objectIter.hasNext();) {

            session.assertObject(objectIter.next());

        }

        session.fireAllRules();

        return session.getObjects();

    }

 

 

 

    /**

     * 会先尝试从Cache中获取,如果为空则从数据库或drl文件中获取规则并重新编译RuleSet

     */

    private RuleBase loadRuleBase(String ruleSetName, Date date) throws Exception {

        RuleBase ruleBase = (RuleBase) EhCacheUtil.getCache(ruleCache, ruleSetName + date);

        if (ruleBase == null) {

            ruleBase = ruleBaseLoader.buildRuleBase(ruleSetName, date);

            EhCacheUtil.setCache(ruleCache, ruleSetName + date, ruleBase);

        }

        return ruleBase;

    } 

 

    public void setRuleBaseLoader(BaseLoader ruleBaseLoader) {

        this.ruleBaseLoader = ruleBaseLoader;

    }

 

    public void setRuleCache(Cache ruleCache) {

        this.ruleCache = ruleCache;

    }

}

 

5.  ApplicationContext-rule.xml

 

    

    

        

    

    

        

            

                

            

        

    

 

    

        

    

 

6.  OrderPricing.xml

          xmlns="http://drools.org/rules"

          xmlns:groovy="http://drools.org/semantics/groovy"

          xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"

          xs:schemaLocation="http://drools.org/rules rules.xsd

                             http://drools.org/semantics/groovy groovy.xsd">

    

        

            org.springside.bookstore.domain.Order

        

        order.getTotalPrice() >=100

        

            Double price = order.getTotalPrice() *0.9d;

            order.setDiscountPrice(price);

            order.addApplyRule(drools.getRuleName());

        

    

 

7.  需要说明的是:上述代码采用了Ehcache来缓存rulebase。具体细节见上面代码。


你可能感兴趣的:(java)