[Drools]JAVA规则引擎 -- Drools

Drools是一个基于java的规则引擎,开源的,可以将复杂多变的规则从硬编码中解放出来,以规则脚本的形式存放在文件中,使得规则的变更不需要修正代码重启机器就可以立即在线上环境生效。

 本文所使用的demo已上传 http://download.csdn.net/source/3002213

1、Drools语法

开始语法之前首先要了解一下drools的基本工作过程,通常而言我们使用一个接口来做事情,首先要穿进去参数,其次要获取到接口的实现执行完毕后的结果,而drools也是一样的,我们需要传递进去数据,用于规则的检查,调用外部接口,同时还可能需要获取到规则执行完毕后得到的结果。在drools中,这个传递数据进去的对象,术语叫 Fact对象。Fact对象是一个普通的java bean,规则中可以对当前的对象进行任何的读写操作,调用该对象提供的方法,当一个java bean插入到workingMemory中,规则使用的是原有对象的引用,规则通过对fact对象的读写,实现对应用数据的读写,对于其中的属性,需要提供getter setter访问器,规则中,可以动态的往当前workingMemory中插入删除新的fact对象。

规则文件可以使用 .drl文件,也可以是xml文件,这里我们使用drl文件。

规则语法:

package:对一个规则文件而言,package是必须定义的,必须放在规则文件第一行。特别的是,package的名字是随意的,不必必须对应物理路径,跟java的package的概念不同,这里只是逻辑上的一种区分。同样的package下定义的function和query等可以直接使用。

比如:package com.drools.demo.point

import:导入规则文件需要使用到的外部变量,这里的使用方法跟java相同,但是不同于java的是,这里的import导入的不仅仅可以是一个类,也可以是这个类中的某一个可访问的静态方法。

比如:

import com.drools.demo.point.PointDomain;

import com.drools.demo.point.PointDomain.getById;

rule:定义一个规则。rule "ruleName"。一个规则可以包含三个部分:

属性部分:定义当前规则执行的一些属性等,比如是否可被重复执行、过期时间、生效时间等。

条件部分,即LHS,定义当前规则的条件,如  when Message(); 判断当前workingMemory中是否存在Message对象。

结果部分,即RHS,这里可以写普通java代码,即当前规则条件满足后执行的操作,可以直接调用Fact对象的方法来操作应用。

规则事例:

rule "name"

       no-loop true

       when

               $message:Message(status == 0)

       then

               System.out.println("fit");

               $message.setStatus(1);

               update($message);

end

上述的属性中:

no-loop : 定义当前的规则是否不允许多次循环执行,默认是false,也就是当前的规则只要满足条件,可以无限次执行。什么情况下会出现一条规则执行过一次又被多次重复执行呢?drools提供了一些api,可以对当前传入workingMemory中的Fact对象进行修改或者个数的增减,比如上述的update方法,就是将当前的workingMemory中的Message类型的Fact对象进行属性更新,这种操作会触发规则的重新匹配执行,可以理解为Fact对象更新了,所以规则需要重新匹配一遍,那么疑问是之前规则执行过并且修改过的那些Fact对象的属性的数据会不会被重置?结果是不会,已经修改过了就不会被重置,update之后,之前的修改都会生效。当然对Fact对象数据的修改并不是一定需要调用update才可以生效,简单的使用set方法设置就可以完成,这里类似于java的引用调用,所以何时使用update是一个需要仔细考虑的问题,一旦不慎,极有可能会造成规则的死循环。上述的no-loop true,即设置当前的规则,只执行一次,如果本身的RHS部分有update等触发规则重新执行的操作,也不要再次执行当前规则。

但是其他的规则会被重新执行,岂不是也会有可能造成多次重复执行,数据紊乱甚至死循环?答案是使用其他的标签限制,也是可以控制的:lock-on-active true

lock-on-active true:通过这个标签,可以控制当前的规则只会被执行一次,因为一个规则的重复执行不一定是本身触发的,也可能是其他规则触发的,所以这个是no-loop的加强版。当然该标签正规的用法会有其他的标签的配合,后续提及。

date-expires:设置规则的过期时间,默认的时间格式:“日-月-年”,中英文格式相同,但是写法要用各自对应的语言,比如中文:"29-七月-2010",但是还是推荐使用更为精确和习惯的格式,这需要手动在java代码中设置当前系统的时间格式,后续提及。属性用法举例:date-expires "2011-01-31 23:59:59" // 这里我们使用了更为习惯的时间格式

date-effective:设置规则的生效时间,时间格式同上。

duration:规则定时,duration 3000   3秒后执行规则

salience:优先级,数值越大越先执行,这个可以控制规则的执行顺序。

其他的属性可以参照相关的api文档查看具体用法,此处略。

 

规则的条件部分,即LHS部分:

when:规则条件开始。条件可以单个,也可以多个,多个条件一次排列,比如

 when

         eval(true)

         $customer:Customer()

         $message:Message(status==0)

上述罗列了三个条件,当前规则只有在这三个条件都匹配的时候才会执行RHS部分,三个条件中第一个

eval(true):是一个默认的api,true 无条件执行,类似于 while(true)

$message:Message(status==0) 这句话标示的:当前的workingMemory存在Message类型并且status属性的值为0的Fact对象,这个对象通常是通过外部java代码插入或者自己在前面已经执行的规则的RHS部分中insert进去的。

前面的$message代表着当前条件的引用变量,在后续的条件部分和RHS部分中,可以使用当前的变量去引用符合条件的FACT对象,修改属性或者调用方法等。可选,如果不需要使用,则可以不写。

条件可以有组合,比如:

Message(status==0 || (status > 1 && status <=100))

RHS中对Fact对象private属性的操作必须使用getter和setter方法,而RHS中则必须要直接用.的方法去使用,比如

  $order:Order(name=="qu")
  $message:Message(status==0 && orders contains $order && $order.name=="qu")

特别的是,如果条件全部是 &&关系,可以使用“,”来替代,但是两者不能混用

如果现在Fact对象中有一个List,需要判断条件,如何判断呢?

看一个例子:

Message {

        int status;

        List names;

}

$message:Message(status==0 && names contains "网易" && names.size >= 1)

上述的条件中,status必须是0,并且names列表中含有“网易”并且列表长度大于等于1

contains:对比是否包含操作,操作的被包含目标可以是一个复杂对象也可以是一个简单的值。 

Drools提供了十二中类型比较操作符:

>  >=  <  <=  ==  !=  contains / not contains / memberOf / not memberOf /matches/ not matches

not contains:与contains相反。

memberOf:判断某个Fact属性值是否在某个集合中,与contains不同的是他被比较的对象是一个集合,而contains被比较的对象是单个值或者对象。

not memberOf:正好相反。

matches:正则表达式匹配,与java不同的是,不用考虑'/'的转义问题

not matches:正好相反。

 

规则的结果部分

当规则条件满足,则进入规则结果部分执行,结果部分可以是纯java代码,比如:

then

       System.out.println("OK"); //会在控制台打印出ok

end

当然也可以调用Fact的方法,比如  $message.execute();操作数据库等等一切操作。

结果部分也有drools提供的方法:

insert:往当前workingMemory中插入一个新的Fact对象,会触发规则的再次执行,除非使用no-loop限定;

update:更新

modify:修改,与update语法不同,结果都是更新操作

retract:删除

RHS部分除了调用Drools提供的api和Fact对象的方法,也可以调用规则文件中定义的方法,方法的定义使用 function 关键字

function void console {

   System.out.println();

   StringUtils.getId();// 调用外部静态方法,StringUtils必须使用import导入,getId()必须是静态方法

}

Drools还有一个可以定义类的关键字:

declare 可以再规则文件中定义一个class,使用起来跟普通java对象相似,你可以在RHS部分中new一个并且使用getter和setter方法去操作其属性。

declare Address
 @author(quzishen) // 元数据,仅用于描述信息

 @createTime(2011-1-24)
 city : String @maxLengh(100)
 postno : int
end

上述的'@'是什么呢?是元数据定义,用于描述数据的数据~,没什么执行含义

你可以在RHS部分中使用Address address = new Address()的方法来定义一个对象。

 

更多的规则语法,可以参考其他互联网资料,推荐:

http://wenku.baidu.com/view/a6516373f242336c1eb95e7c.html

(写的很基础,但是部分语法写的有些简单,含糊不好理解)

2、Drools应用实例:

现在我们模拟一个应用场景:网站伴随业务产生而进行的积分发放操作。比如支付宝信用卡还款奖励积分等。

发放积分可能伴随不同的运营策略和季节性调整,发放数目和规则完全不同,如果使用硬编码的方式去伴随业务调整而修改,代码的修改、管理、优化、测试、上线将是一件非常麻烦的事情,所以,将发放规则部分提取出来,交给Drools管理,可以极大程度的解决这个问题。

(注意一点的是,并非所有的规则相关内容都建议使用Drools,这其中要考虑系统会运行多久,规则变更频率等一系列条件,如果你的系统只会在线上运行一周,那根本没必要选择Drools来加重你的开发成本,java硬编码的方式则将是首选)

我们定义一下发放规则:

积分的发放参考因素有:交易笔数、交易金额数目、信用卡还款次数、生日特别优惠等。

定义规则:

// 过生日,则加10分,并且将当月交易比数翻倍后再计算积分

// 2011-01-08 - 2011-08-08每月信用卡还款3次以上,每满3笔赠送30分

// 当月购物总金额100以上,每100元赠送10分

// 当月购物次数5次以上,每五次赠送50分

// 特别的,如果全部满足了要求,则额外奖励100分

// 发生退货,扣减10分

// 退货金额大于100,扣减100分

在事先分析过程中,我们需要全面的考虑对于积分所需要的因素,以此整理抽象Fact对象,通过上述的假设条件,我们假设积分计算对象如下:

[java] view plain copy
  1. /** 
  2.  * 积分计算对象 
  3.  * @author quzishen 
  4.  */  
  5. public class PointDomain {  
  6.     // 用户名  
  7.     private String userName;  
  8.     // 是否当日生日  
  9.     private boolean birthDay;  
  10.     // 增加积分数目  
  11.     private long point;  
  12.     // 当月购物次数  
  13.     private int buyNums;  
  14.     // 当月退货次数  
  15.     private int backNums;  
  16.     // 当月购物总金额  
  17.     private double buyMoney;  
  18.     // 当月退货总金额  
  19.     private double backMondy;  
  20.     // 当月信用卡还款次数  
  21.     private int billThisMonth;  
  22.       
  23.     /** 
  24.      * 记录积分发送流水,防止重复发放 
  25.      * @param userName 用户名 
  26.      * @param type 积分发放类型 
  27.      */  
  28.     public void recordPointLog(String userName, String type){  
  29.         System.out.println("增加对"+userName+"的类型为"+type+"的积分操作记录.");  
  30.     }  
  31.   
  32.     public String getUserName() {  
  33.         return userName;  
  34.     }  
  35. // 其他getter setter方法省略  
  36. }  

定义积分规则接口

[java] view plain copy
  1. /** 
  2.  * 规则接口 
  3.  * @author quzishen 
  4.  */  
  5. public interface PointRuleEngine {  
  6.       
  7.     /** 
  8.      * 初始化规则引擎 
  9.      */  
  10.     public void initEngine();  
  11.       
  12.     /** 
  13.      * 刷新规则引擎中的规则 
  14.      */  
  15.     public void refreshEnginRule();  
  16.       
  17.     /** 
  18.      * 执行规则引擎 
  19.      * @param pointDomain 积分Fact 
  20.      */  
  21.     public void executeRuleEngine(final PointDomain pointDomain);  
  22. }  

规则接口实现,Drools的API很简单,可以参考相关API文档查看具体用法:

[java] view plain copy
  1. import java.io.File;  
  2. import java.io.FileNotFoundException;  
  3. import java.io.FileReader;  
  4. import java.io.IOException;  
  5. import java.io.Reader;  
  6. import java.util.ArrayList;  
  7. import java.util.List;  
  8.   
  9. import org.drools.RuleBase;  
  10. import org.drools.StatefulSession;  
  11. import org.drools.compiler.DroolsParserException;  
  12. import org.drools.compiler.PackageBuilder;  
  13. import org.drools.spi.Activation;  
  14.   
  15. /** 
  16.  * 规则接口实现类 
  17.  * @author quzishen 
  18.  */  
  19. public class PointRuleEngineImpl implements PointRuleEngine {  
  20.     private RuleBase ruleBase;  
  21.   
  22.     /* (non-Javadoc) 
  23.      * @see com.drools.demo.point.PointRuleEngine#initEngine() 
  24.      */  
  25.     public void initEngine() {  
  26.         // 设置时间格式  
  27.         System.setProperty("drools.dateformat""yyyy-MM-dd HH:mm:ss");  
  28.         ruleBase = RuleBaseFacatory.getRuleBase();  
  29.         try {  
  30.             PackageBuilder backageBuilder = getPackageBuilderFromDrlFile();  
  31.             ruleBase.addPackages(backageBuilder.getPackages());  
  32.         } catch (DroolsParserException e) {  
  33.             e.printStackTrace();  
  34.         } catch (IOException e) {  
  35.             e.printStackTrace();  
  36.         } catch (Exception e) {  
  37.             e.printStackTrace();  
  38.         }  
  39.     }  
  40.       
  41.     /* (non-Javadoc) 
  42.      * @see com.drools.demo.point.PointRuleEngine#refreshEnginRule() 
  43.      */  
  44.     public void refreshEnginRule() {  
  45.         ruleBase = RuleBaseFacatory.getRuleBase();  
  46.         org.drools.rule.Package[] packages = ruleBase.getPackages();  
  47.         for(org.drools.rule.Package pg : packages) {  
  48.             ruleBase.removePackage(pg.getName());  
  49.         }  
  50.           
  51.         initEngine();  
  52.     }  
  53.   
  54.     /* (non-Javadoc) 
  55.      * @see com.drools.demo.point.PointRuleEngine#executeRuleEngine(com.drools.demo.point.PointDomain) 
  56.      */  
  57.     public void executeRuleEngine(final PointDomain pointDomain) {  
  58.         if(null == ruleBase.getPackages() || 0 == ruleBase.getPackages().length) {  
  59.             return;  
  60.         }  
  61.           
  62.         StatefulSession statefulSession = ruleBase.newStatefulSession();  
  63.         statefulSession.insert(pointDomain);  
  64.           
  65.         // fire  
  66.         statefulSession.fireAllRules(new org.drools.spi.AgendaFilter() {  
  67.             public boolean accept(Activation activation) {  
  68.                 return !activation.getRule().getName().contains("_test");  
  69.             }  
  70.         });  
  71.           
  72.         statefulSession.dispose();  
  73.     }  
  74.   
  75.     /** 
  76.      * 从Drl规则文件中读取规则 
  77.      * @return 
  78.      * @throws Exception 
  79.      */  
  80.     private PackageBuilder getPackageBuilderFromDrlFile() throws Exception {  
  81.         // 获取测试脚本文件  
  82.         List drlFilePath = getTestDrlFile();  
  83.         // 装载测试脚本文件  
  84.         List readers = readRuleFromDrlFile(drlFilePath);  
  85.   
  86.         PackageBuilder backageBuilder = new PackageBuilder();  
  87.         for (Reader r : readers) {  
  88.             backageBuilder.addPackageFromDrl(r);  
  89.         }  
  90.           
  91.         // 检查脚本是否有问题  
  92.         if(backageBuilder.hasErrors()) {  
  93.             throw new Exception(backageBuilder.getErrors().toString());  
  94.         }  
  95.           
  96.         return backageBuilder;  
  97.     }  
  98.   
  99.     /** 
  100.      * @param drlFilePath 脚本文件路径 
  101.      * @return 
  102.      * @throws FileNotFoundException 
  103.      */  
  104.     private List readRuleFromDrlFile(List drlFilePath) throws FileNotFoundException {  
  105.         if (null == drlFilePath || 0 == drlFilePath.size()) {  
  106.             return null;  
  107.         }  
  108.   
  109.         List readers = new ArrayList();  
  110.   
  111.         for (String ruleFilePath : drlFilePath) {  
  112.             readers.add(new FileReader(new File(ruleFilePath)));  
  113.         }  
  114.   
  115.         return readers;  
  116.     }  
  117.   
  118.     /** 
  119.      * 获取测试规则文件 
  120.      *  
  121.      * @return 
  122.      */  
  123.     private List getTestDrlFile() {  
  124.         List drlFilePath = new ArrayList();  
  125.         drlFilePath  
  126.                 .add("D:/workspace2/DroolsDemo/src/com/drools/demo/point/addpoint.drl");  
  127.         drlFilePath  
  128.                 .add("D:/workspace2/DroolsDemo/src/com/drools/demo/point/subpoint.drl");  
  129.   
  130.         return drlFilePath;  
  131.     }  
  132. }  

为了获取单实例的RuleBase,我们定义一个工厂类

[java] view plain copy
  1. import org.drools.RuleBase;  
  2. import org.drools.RuleBaseFactory;  
  3.   
  4. /** 
  5.  * RuleBaseFacatory 单实例RuleBase生成工具 
  6.  * @author quzishen 
  7.  */  
  8. public class RuleBaseFacatory {  
  9.     private static RuleBase ruleBase;  
  10.       
  11.     public static RuleBase getRuleBase(){  
  12.         return null != ruleBase ? ruleBase : RuleBaseFactory.newRuleBase();  
  13.     }  
  14. }  

剩下的就是定义两个规则文件,分别用于积分发放和积分扣减

addpoint.drl

[java] view plain copy
  1. package com.drools.demo.point  
  2.   
  3. import com.drools.demo.point.PointDomain;  
  4.   
  5. rule birthdayPoint  
  6.     // 过生日,则加10分,并且将当月交易比数翻倍后再计算积分  
  7.     salience 100  
  8.     lock-on-active true  
  9.     when  
  10.         $pointDomain : PointDomain(birthDay == true)  
  11.     then  
  12.         $pointDomain.setPoint($pointDomain.getPoint()+10);  
  13.         $pointDomain.setBuyNums($pointDomain.getBuyNums()*2);  
  14.         $pointDomain.setBuyMoney($pointDomain.getBuyMoney()*2);  
  15.         $pointDomain.setBillThisMonth($pointDomain.getBillThisMonth()*2);  
  16.           
  17.         $pointDomain.recordPointLog($pointDomain.getUserName(),"birthdayPoint");  
  18. end  
  19.   
  20. rule billThisMonthPoint  
  21.     // 2011-01-08 - 2011-08-08每月信用卡还款3次以上,每满3笔赠送30分  
  22.     salience 99  
  23.     lock-on-active true  
  24.     date-effective "2011-01-08 23:59:59"  
  25.     date-expires "2011-08-08 23:59:59"  
  26.     when  
  27.         $pointDomain : PointDomain(billThisMonth >= 3)  
  28.     then  
  29.         $pointDomain.setPoint($pointDomain.getPoint()+$pointDomain.getBillThisMonth()/3*30);  
  30.         $pointDomain.recordPointLog($pointDomain.getUserName(),"billThisMonthPoint");  
  31. end  
  32.   
  33. rule buyMoneyPoint  
  34.     // 当月购物总金额100以上,每100元赠送10分  
  35.     salience 98  
  36.     lock-on-active true  
  37.     when  
  38.         $pointDomain : PointDomain(buyMoney >= 100)  
  39.     then  
  40.         $pointDomain.setPoint($pointDomain.getPoint()+ (int)$pointDomain.getBuyMoney()/100 * 10);  
  41.         $pointDomain.recordPointLog($pointDomain.getUserName(),"buyMoneyPoint");  
  42. end  
  43.   
  44. rule buyNumsPoint  
  45.     // 当月购物次数5次以上,每五次赠送50分  
  46.     salience 97  
  47.     lock-on-active true  
  48.     when  
  49.         $pointDomain : PointDomain(buyNums >= 5)  
  50.     then  
  51.         $pointDomain.setPoint($pointDomain.getPoint()+$pointDomain.getBuyNums()/5 * 50);  
  52.         $pointDomain.recordPointLog($pointDomain.getUserName(),"buyNumsPoint");  
  53. end  
  54.   
  55. rule allFitPoint  
  56.     // 特别的,如果全部满足了要求,则额外奖励100分  
  57.     salience 96  
  58.     lock-on-active true  
  59.     when  
  60.         $pointDomain:PointDomain(buyNums >= 5 && billThisMonth >= 3 && buyMoney >= 100)  
  61.     then  
  62.         $pointDomain.setPoint($pointDomain.getPoint()+ 100);  
  63.         $pointDomain.recordPointLog($pointDomain.getUserName(),"allFitPoint");  
  64. end  

subpoint.drl

[java] view plain copy
  1. package com.drools.demo.point  
  2.   
  3. import com.drools.demo.point.PointDomain;  
  4.   
  5. rule subBackNumsPoint  
  6.     // 发生退货,扣减10分  
  7.     salience 10  
  8.     lock-on-active true  
  9.     when  
  10.         $pointDomain : PointDomain(backNums >= 1)  
  11.     then  
  12.         $pointDomain.setPoint($pointDomain.getPoint()-10);  
  13.         $pointDomain.recordPointLog($pointDomain.getUserName(),"subBackNumsPoint");  
  14. end  
  15.   
  16. rule subBackMondyPoint  
  17.     // 退货金额大于100,扣减100分  
  18.     salience 9  
  19.     lock-on-active true  
  20.     when  
  21.         $pointDomain : PointDomain(backMondy >= 100)  
  22.     then  
  23.         $pointDomain.setPoint($pointDomain.getPoint()-10);  
  24.         $pointDomain.recordPointLog($pointDomain.getUserName(),"subBackMondyPoint");  
  25. end  

 

测试方法:

[java] view plain copy
  1. public static void main(String[] args) throws IOException {  
  2.         PointRuleEngine pointRuleEngine = new PointRuleEngineImpl();  
  3.         while(true){  
  4.             InputStream is = System.in;  
  5.             BufferedReader br = new BufferedReader(new InputStreamReader(is));  
  6.             String input = br.readLine();  
  7.               
  8.             if(null != input && "s".equals(input)){  
  9.                 System.out.println("初始化规则引擎...");  
  10.                 pointRuleEngine.initEngine();  
  11.                 System.out.println("初始化规则引擎结束.");  
  12.             }else if("e".equals(input)){  
  13.                 final PointDomain pointDomain = new PointDomain();  
  14.                 pointDomain.setUserName("hello kity");  
  15.                 pointDomain.setBackMondy(100d);  
  16.                 pointDomain.setBuyMoney(500d);  
  17.                 pointDomain.setBackNums(1);  
  18.                 pointDomain.setBuyNums(5);  
  19.                 pointDomain.setBillThisMonth(5);  
  20.                 pointDomain.setBirthDay(true);  
  21.                 pointDomain.setPoint(0l);  
  22.                   
  23.                 pointRuleEngine.executeRuleEngine(pointDomain);  
  24.                   
  25.                 System.out.println("执行完毕BillThisMonth:"+pointDomain.getBillThisMonth());  
  26.                 System.out.println("执行完毕BuyMoney:"+pointDomain.getBuyMoney());  
  27.                 System.out.println("执行完毕BuyNums:"+pointDomain.getBuyNums());  
  28.                   
  29.                 System.out.println("执行完毕规则引擎决定发送积分:"+pointDomain.getPoint());  
  30.             } else if("r".equals(input)){  
  31.                 System.out.println("刷新规则文件...");  
  32.                 pointRuleEngine.refreshEnginRule();  
  33.                 System.out.println("刷新规则文件结束.");  
  34.             }  
  35.         }  
  36.     }  

执行结果:

-----------------

增加对hello kity的类型为birthdayPoint的积分操作记录.
增加对hello kity的类型为billThisMonthPoint的积分操作记录.
增加对hello kity的类型为buyMoneyPoint的积分操作记录.
增加对hello kity的类型为buyNumsPoint的积分操作记录.
增加对hello kity的类型为allFitPoint的积分操作记录.
增加对hello kity的类型为subBackNumsPoint的积分操作记录.
增加对hello kity的类型为subBackMondyPoint的积分操作记录.
执行完毕BillThisMonth:10
执行完毕BuyMoney:1000.0
执行完毕BuyNums:10
执行完毕规则引擎决定发送积分:380

==========================================================关于rete算法转载的资料=============================================

【转】rete算法简介

Rete算法是Charles Forgy在1979年的论文中首次提出的,针对基于规则知识表现的模式匹配算法。目前来说,大部分规则引擎还是基于rete算法作为核心,但都有所改进,比如drool,jess等等,下面介绍rete算法的概念,一些术语,以及使用规则引擎需要注意的问题。

先来看看如下的表达式:

     (name-of-this-production
        LHS /* one or more conditions */
       -->
        RHS /* one or more actions */
       )

    name-of-this-production就是规则,LHS(left hand side)一系列条件,RHS(right hand side)这个是我们满足条件后应该执行的动作。

    

     

[Drools]JAVA规则引擎 -- Drools_第1张图片


     

   结合该图介绍几个概念:

    production memory(PM)是由所有的production形成。

    working memory(WM)由外界输入根据匹配算法形成的,反映了运行中的规则引擎的状态,记录各种数据, WM中每一个item称为working memory element(WME) ,由外界的输入产生。

     agenda负责匹配,冲突解决,执行动作。

   

    rete是网络的意思(拉丁语),它将所有的规则最终解释(或编译)生成一个识别网络,其中包括了alpha网络,beta网络。alpha网络是根据LHS生成的网络,它根据外界的输入快速识别是否存在condition成立,并且跟其beta网络交互更新整个网络的状态,如下图:

[Drools]JAVA规则引擎 -- Drools_第2张图片


     

    最基本的alpha网络就如上图所示,类似于这样,所有的condition被parse到这样的网络,当外界输入wme时,该wme会进入这样一个网络进行辨识,如果到达最底端,证明一个condition成立了,当然,如图这个网络算是最简单的实现了,实际规则引擎需要提供更快速的算法去辨识输入的wme,比如将图中color的各种值存入hashtable,或者是jumptable,又或者是trie tree。整个alpha network是一个巨大的字符串匹配和过滤网络,需要将各种数据结构组合在一起去实现海量condition情况下的快速匹配。各种规则引擎的实现又是不一致的,比如jess,如下图:

    (defrule done
       (TESTING)
       (number ?number)
       (TEST IS DONE)
      (INIT CREDIT 5)
      (CUSTOMER AGE ?age)
       (has ?type "PP"))
=>
      (assert (TEST COMPLETED)))

      [Drools]JAVA规则引擎 -- Drools_第3张图片


这个production的解释后生成的网络,这里我们先注意红色的节点,这些节点就是alpha网络的节点,这个图只是描述了大致的过程,以第一列为例,第一个红色node表示输入是否匹配TESTING这个字符串,第二个node匹配在TESTING后面的参数数量(slot)是否匹配0,如果我们assert TESTING进入WM,那么这个fact是可以匹配到done这个rule的第一个condition的,其他可以依次类推,值得注意的是最后一个condition,has是我们自定义的function,类似这样的function,jess没有单独生成一列,只是将它作为CUSTOMER AGE ?age这一列的最后一个node,这样的condition有个特点就是需要执行一段代码去判断某个事实是否成立(不仅仅只是做字符串的操作),这段代码不仅仅是字符串的匹配,同时还具有实时性,类似这样的condition开发中需要注意,因为alpha network在运行期会不止一次去执行这个condition是否成立,这个是匹配算法的特性决定,所以,我们需要用cache或者规则语言的特性去避免不必要的执行code以提高性能。

下面贴个比较复杂的例子:

   

[Drools]JAVA规则引擎 -- Drools_第4张图片   [Drools]JAVA规则引擎 -- Drools_第5张图片


   图太大,一个截不下来。。。。。。

   下面我们结合两个例子说说beta网络,当alpha网络过滤后condition成立,WME被传递到beta网络时,绿色的node就要发挥作用了,这个node就是join node,它有两个input,一个join node ,一个alpha node(红色),join node是由多个WME组成的,对于初始的join node 我们称为left input adapter 如图中黄色的node,该node是空的,那么第一个把这个node作为left input的join node就只包含了一个WME,下一个join node则包含了两个WME,以此类推。图中天蓝色的node上方的join node 完全匹配了production执行需要的condition,所以这个rule就被激活等待执行了。

    假设我们需要编辑业务逻辑,那么最好的描述载体就是流程图,简单的流程图包含以下一些基本单元:起始节点,逻辑判断,执行动作,结束节点。这些节点可以完成最简单的业务逻辑描述,那么我们把这些流程parse到规则的时候,我们会怎么去做,第一个逻辑判断单元返回true,于是我们执行某个动作,第二,三个逻辑判断单元返回true我们执行某个动作,相当于会parse到两个规则,符合condition1,production1触发,符合condition2,3,production2触发,有了beta网络,我们在触发production2时只需要判断condition2,3是否成立,对于更复杂的情况,beta网络提高了速度,避免了重复匹配。

   

    开发中使用规则引擎也遇到些问题,总结如下:

    1)规则引擎中对于特殊condition的处理,由于condition会在部分production中重复出现,所以会造成condition的重复匹配问题,影响了程序的性能,这个要结合项目去优化rule脚本的parse或者使用cache去提升性能。

    2)内存消耗问题,rete算法是空间换时间,所以对于内存的消耗是比较大的,特别是加载rule的时候(生成网络),在运行期内存会缓慢增长,所以gc效率需要注意,同时单个服务器所能承受的压力(多个WM)也跟规则引擎息息相关。

    3)测试,对于使用规则去表达业务的系统,如何测试是必须解决的问题,对于这个问题,也只能保证基本的流程分支覆盖测试,对于复杂情况下的defect很难发现,不过有些原则需要注意,如果要使用规则引擎,我们必须完全以规则引擎为核心,对于业务逻辑必须尽可能的抽取到规则引擎去实现,对于扩展实现的function粒度必须小且简单,不要再代码中去实现业务逻辑。

    4)大部分的condition需要是不变的,也就是说基本信息需要保持稳定不变。比如某客户公司上属集团信用额度大于100w这样的condition,这个额度变化的频度不会很高,不需要去实时匹配。

    5),remove WME production是较复杂的操作,规则较复杂时,应该尽量少去做这样的操作。


====================================================关于rete算法转载的资料================================================

最近面试的时候,经常被问及自己参加的项目中rete算法的原理,但是RETE算法是一个比较复杂的算法,在短时间内不能阐述的足够清晰,在这里做个简单的介绍。转载请注明来自 http://chillwarmoon.iteye.com
RETE
算法是一个用来实现产生式规则系统的高效模式匹配算法。该算法是由卡内基美隆大学的Charles L. Forgy1974年发表的论文中所阐述的算法。RETE算法提供了专家系统的一个高效实现。
规则推理引擎做为产生式系统的一部分,当进行事实的断言时,包含三个阶段:匹配、选择和执行,称做match-select-act cycleRETE算法可以对匹配阶段进行高效实现,下面从鉴别网络和模式匹配过程两个方面对该算法进行介绍。

鉴别网络(如下图所示):
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是为事实建立的元素,是用于和非根结点代表的模式进行匹配的元素。TokenWME的列表,包含有多个WME,(在Forgy的论文中,把Token看成是WME的列表或者单个WME,为了阐述方便,本文将把Token只看成WME的列表,该列表可以包含一个WME或者多个WME),用于2-input结点的左侧输入。事实可以做为2-input结点的右侧输入,也可以做为1-input结点的输入。
每个非根结点都代表着产生式左部的一个模式,从根结点到终结点的路径表示产生式的左部。

[Drools]JAVA规则引擎 -- Drools_第6张图片


规则匹配:
推理引擎在进行模式匹配时,先对事实进行断言,为每一个事实建立 WME ,然后将 WME RETE 鉴别网络的根结点开始匹配,因为 WME 传递到的结点类型不同采取的算法也不同,下面对 alpha 结点和 beta 结点处理 WME 的不同情况分开讨论。

1)如果WME的类型和根节点的后继结点TypeNodealpha结点的一种)所指定的类型相同,则会将该事实保存在该TypeNode结点对应的alpha存储区中,该WME被传到后继结点继续匹配,否则会放弃该WME的后续匹配;

2)如果WME被传递到alpha结点,则会检测WME是否和该结点对应的模式相匹配,若匹配,则会将该事实保存在该alpha结点对应的存储区中,该WME被传递到后继结点继续匹配,否则会放弃该WME的后续匹配;

3)如果WME被传递到beta结点的右端,则会加入到该beta结点的right存储区,并和left存储区中的Token进行匹配(匹配动作根据beta结点的类型进行,例如:joinprojectionselection),匹配成功,则会将该WME加入到Token中,然后将Token传递到下一个结点,否则会放弃该WME的后续匹配;

4)如果Token被传递到beta结点的左端,则会加入到该beta结点的left存储区,并和right存储区中的WME进行匹配(匹配动作根据beta结点的类型进行,例如:joinprojectionselection),匹配成功,则该Token会封装匹配到的WME形成新的Token,传递到下一个结点,否则会放弃该Token的后续匹配;

5)如果WME被传递到beta结点的左端,将WME封装成仅有一个WME元素的WME列表做为Token,然后按照(4)所示的方法进行匹配;

6)如果Token传递到终结点,则和该根结点对应的规则被激活,建立相应的Activation,并存储到Agenda当中,等待激发。

7)如果WME被传递到终结点,将WME封装成仅有一个WME元素的WME列表做为Token,然后按照(6)所示的方法进行匹配;

以上是RETE算法对于不同的结点,来进行WME或者token和结点对应模式的匹配的过程。


============================================上文提及资料的转载===============================================

http://www.bstek.com
第 1  页  共 74  页
Drools5 Drools5 规则引擎 规则引擎
开发教程
高杰
上海锐道信息技术有限公司
2009-8-20
http://www.bstek.com
第 2  页  共 74  页
1.  学习前的准备
Drools 是一款基于 Java 的开源规则引擎,所以在使用 Drools 之前需要在开发机器上安装好
JDK 环境,Drools5 要求的 JDK 版本要在 1.5 或以上。
1.1.  开发环境搭建
大多数软件学习的第一步就是搭建这个软件的开发环境, Drools 也不例外。本小节的内
容就是介绍如何搭建一个 Drools5 的开发、运行、调试环境。
1.1.1.  下载开发工具
 Drools5 提供了一个基于 Eclipse3.4 的一个 IDE 开发工具,所以在使用之前需要到
http://eclipse.org 网 站 下 载 一 个 3.4.x 版 本 的 Eclipse , 下 载 完 成 之 后 , 再 到
http://jboss.org/drools/downloads.html  网站,下载 Drools5 的 Eclipse 插件版 IDE 及 Drools5
的开发工具包,如图 1-1 所示。
http://www.bstek.com
第 3  页  共 74  页
图 1-1
除这两个下载包以外,还可以把 Drools5 的相关文档、源码和示例的包下载下来参考学
习使用。
将下载的开发工具包及 IDE 包解压到一个非中文目录下,解压完成后就可以在
Eclipse3.4 上安装 Drools5 提供的开发工具 IDE 了。
1.1.2.  安装 Drools IDE
打开 Eclipse3.4 所在目录下的 links 目录(如果该目录不存在可以手工在其目录下创建
一个 links 目录), 在 links 目录下创建一个文本文件,并改名为 drools5-ide.link,用记事本打
开该文件,按照下面的版本输入 Drools5 Eclipse Plugin 文件所在目录:
path=D:\\eclipse\\drools-5.0-eclipse-all
这个值表示 Drools5  Eclipse  Plugin 文件位于 D 盘 eclipse 目录下的 drools-5.0-eclipse-all
下面,这里有一点需要注意,那就是 drools-5.0-eclipse-all 文件夹下必须再包含一个 eclipse
目录,所有的插件文件都应该位于该 eclipse 目录之下,接下来要在 win dos 下重启 Eclipse 3.4,
检验 Drools5 IDE 是否安装成功。
进入 win dos,进入 Eclipes3.4 所在目录,输入 eclipse –clean 启动 Eclipse3.4。启动完成
http://www.bstek.com
第 4  页  共 74  页
后打开菜单 WindowPreferences,在弹出的窗口当中如果能在左边导航树中发现 Drools 节
点就表示 Drools5 IDE 安装成功了,如图 1-2 所示。
图 1-2
 IDE 安装完成后,接下来需要对 Drools5 的 IDE 环境进行简单的配置,打开菜单
WindowPreferences ,在弹出的窗口当中选择左边导航树菜单 DroolsInstalled  Drools
Runtimes 设置 Drools5 IDE 运行时依赖的开发工具包,点击“Add…”按钮添加一个开发工
具包,如图 1-3 所示。
http://www.bstek.com
第 5  页  共 74  页
图 1-3
图 1-3 当中添加了一个开发工具包,名称为“drools-5.0.0”,对应的工具包目录为 D 盘
doc\about rule\drools5.x\drools-5.0-bin 目录。添加完成后这样 Drools5 的开发环境就搭好了。
下面我们就来利用这个环境编写一个规则看看运行效果。
1.2.  编写第一个规则
1.3.  规则的编译与运行
在 Drools 当中,规则的编译与运行要通过 Drools 提供的各种 API 来实现,这些 API 总
体来讲可以分为三类:规则编译、规则收集和规则的执行。完成这些工作的 API 主要有
http://www.bstek.com
第 6  页  共 74  页
KnowledgeBuilder、 KnowledgeBase、 StatefulKnowledgeSession、 StatelessKnowledgeSession、、
等,它们起到了对规则文件进行收集、编译、查错、插入 fact、设置 global、执行规则或规
则流等作用,在正式接触各种类型的规则文件编写方式及语法讲解之前,我们有必要先熟悉
一下这些 API 的基本含义及使用方法。
1.3.1.  KnowledgeBuilder
规则编写完成之后,接下来的工作就是在应用的代码当中调用这些规则,利用这些编写
好的规则帮助我们处理业务问题。 KnowledgeBuilder 的作用就是用来在业务代码当中收集已
经编写好的规则 ,然后对这些规则文件进行编译 ,最终产生一 批编译好的规则包
(KnowledgePackage)给其它的应用程序使用。 KnowledgeBuilder 在编译规则的时候可以通
过其提供的 hasErrors()方法得到编译规则过程中发现规则是否有错误,如果有的话通过其提
供的 getErrors()方法将错误打印出来,以帮助我们找到规则当中的错误信息。
创建 KnowledgeBuilder 对象使用的是 KnowledgeBuilderFactory 的 newKnowledgeBuilder
方法。代码清单 1-1 就演示了 KnowledgeBuilder 的用法。
代码清单 1-1
packagetest;
importjava.util.Collection;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.definition.KnowledgePackage;
importorg.drools.io.ResourceFactory;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder
kbuilder=KnowledgeBuilderFactory.newKnowledgeBuilder();
  kbuilder.add(ResourceFactory.newClassPathResource("test.drl",
Test.class),ResourceType.DRL);
  Collection
kpackage=kbuilder.getKnowledgePackages();//产生规则包的集合
 }
}
http://www.bstek.com
第 7  页  共 74  页
通过 KnowledgeBuilder 编译的规则文件的类型可以有很多种,如.drl 文件、.dslr 文件或
一个 xls 文件等。产生的规则包可以是具体的规则文件形成的,也可以是规则流(rule flow)
文件形成的,在添加规则文件时,需要通过使用 ResourceType 的枚举值来指定规则文件的
类型;同时在指定规则文件的时候 drools 还提供了一个名为 ResourceFactory 的对象,通过
该对象可以实现从 Classpath、URL、File、ByteArray、Reader 或诸如 XLS 的二进制文件里
添加载规则。
在规则文件添加完成后,可以通过使用 hasErrors()方法来检测已添加进去的规则当中有
没有错误,如果不通过该方法检测错误,那么如果规则当中存在错误,最终在使用的时候也
会将错误抛出。代码清单 1-2 就演示了通过 KnowledgeBuilder 来检测规则当中有没有错误。
代码清单 1-2
packagetest;
importjava.util.Collection;
importjava.util.Iterator;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderErrors;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.definition.KnowledgePackage;
importorg.drools.io.ResourceFactory;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder
kbuilder=KnowledgeBuilderFactory.newKnowledgeBuilder();
  kbuilder.add(ResourceFactory.newClassPathResource("test.drl",
Test.class),ResourceType.DRL);
if(kbuilder.hasErrors()){
   System.out.println("规则中存在错误,错误消息如下:");
   KnowledgeBuilderErrors kbuidlerErrors=kbuilder.getErrors();
for(Iterator
iter=kbuidlerErrors.iterator();iter.hasNext();){
    System.out.println(iter.next());
   }
  }
  Collection
kpackage=kbuilder.getKnowledgePackages();//产生规则包的集合
 }
http://www.bstek.com
第 8  页  共 74  页
}
后面随着介绍的深入我们还会看到 KnowledgeBuilder 的一些其它用法。
1.3.2.  KnowledgeBase
 KnowledgeBase 是 Drools 提供的用来收集应用当中知识(knowledge)定义的知识库对
象,在一个 KnowledgeBase 当中可以包含普通的规则(rule)、规则流(rule  flow)、函数定义
(function)、用户自定义对象(type  model)等。KnowledgeBase 本身不包含任何业务数据对
象(fact 对象,后面有相应章节着重介绍 fact 对象), 业务对象都是插入到由 KnowledgeBase
产生的两种类型的 session 对象当中 (StatefulKnowledgeSession 和 StatelessKnowledgeSession,
后面会有对应的章节对这两种类型的对象进行介绍), 通过 session 对象可以触发规则执行或
开始一个规则流执行。
创建一个 KnowledgeBase 要通过 KnowledgeBaseFactory 对象提供的 newKnowledgeBase
() 方法来实现,这其中创建的时候还可以为其指定一个 KnowledgeBaseConfiguration 对象,
KnowledgeBaseConfiguration 对象是一个用来存放规则引擎运行时相关环境参数定义的配置
对象,代码清单 1-3 演示了一个简单的 KnowledgeBase 对象的创建过程。
代码清单 1-3
packagetest;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBase kbase=KnowledgeBaseFactory.newKnowledgeBase();
 }
}
代 码 清 单 1-4 演 示 了 创 建 KnowledgeBase 过 程 当 中 , 使 用 一 个
KnowledgeBaseConfiguration 对象来设置环境参数。
代码清单 1-4
packagetest;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseConfiguration;
importorg.drools.KnowledgeBaseFactory;
http://www.bstek.com
第 9  页  共 74  页
public classTest {
public static voidmain(String[] args) {
  KnowledgeBaseConfiguration kbConf =
KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
  kbConf.setProperty( "org.drools.sequential", "true");
  KnowledgeBase kbase =
KnowledgeBaseFactory.newKnowledgeBase(kbConf);
 }
}
从代码清单 1-4 中可以看到,创建一个 KnowledgeBaseConfiguration 对象的方法也是使
用 KnowldegeBaseFactory,使用的是其提供的 newKnowledgeBaseConfiguration()方法,该方
法创建好的 KnowledgeBaseConfiguration 对象默认情况下会加载 drools-core-5.0.1.jar 包下
META-INF/drools.default.rulebase.conf 文件里的规则运行环境配置信息,加载完成后,我们
可以在代码中对这些默认的信息重新赋值,以覆盖加载的默认值,比如这里我们就把
org.drools.sequential 的值修改为 true,它的默认值为 false。
除了这种方式创建 KnowledgeBaseConfiguration 方法之外,我们还可以为其显示的指定
一个 Properties 对象,在该对象中设置好需要覆盖默认值的相关属性的值,然后再通过
newKnowledgeBaseConfiguration(Properties  prop , ClassLoader  loader) 方 法 创 建 一 个
KnowledgeBaseConfiguration 对象。该方法方法当中第一个参数就是我们要设置的 Properties
对象,第二个参数用来设置加载 META-INF/drools.default.rulebase.conf 文件的 ClassLoader,
因为该文件在 ClassPath 下,所以采用的是 ClassLoader 方法进行加载,如果不指定这个参数,
那 么 就 取 默 认 的 ClassLoader 对 象 , 如 果 两 个 参 数 都 为 null , 那 么 就 和
newKnowledgeBaseConfiguration()方法的作用相同了,代码清单代码清单 1-5 演示了这种用
法。
代码清单 1-5
packagetest;
importjava.util.Properties;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseConfiguration;
importorg.drools.KnowledgeBaseFactory;
public classTest {
public static voidmain(String[] args) {
http://www.bstek.com
第 10  页  共 74  页
  Properties properties = newProperties();
  properties.setProperty( "org.drools.sequential", "true");
  KnowledgeBaseConfiguration kbConf =
KnowledgeBaseFactory.newKnowledgeBaseConfiguration(properties, null);
  KnowledgeBase kbase =
KnowledgeBaseFactory.newKnowledgeBase(kbConf);
 }
}
用来设置默认规则运行环境文件 drools.default.rulebase.conf 里面所涉及到的具体项内容
如代码清单 1-6 所示:
代码清单 1-6
drools.maintainTms =
drools.assertBehaviour =
drools.logicalOverride =
drools.sequential =
drools.sequential.agenda =
drools.removeIdentities =
drools.shareAlphaNodes =
drools.shareBetaNodes =
drools.alphaNodeHashingThreshold = <1...n>
drools.compositeKeyDepth = <1..3>
drools.indexLeftBetaMemory =
drools.indexRightBetaMemory =
drools.consequenceExceptionHandler =
drools.maxThreads = <-1|1..n>
drools.multithreadEvaluation =
在后面的内容讲解过程当中,对于代码清单 1-6 里列出的属性会逐个涉及到,这里就不
再多讲了。
 KnowledgeBase 创建完成之后,接下来就可以将我们前面使用 KnowledgeBuilder 生成的
KnowledgePackage 的集合添加到 KnowledgeBase 当中,以备使用,如代码清单 1-7 所示。
代码清单 1-7
packagetest;
importjava.util.Collection;
http://www.bstek.com
第 11  页  共 74  页
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseConfiguration;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.definition.KnowledgePackage;
importorg.drools.io.ResourceFactory;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder kbuilder = KnowledgeBuilderFactory
    .newKnowledgeBuilder();
  kbuilder.add(ResourceFactory.newClassPathResource("test.drl",
    Test.class), ResourceType.DRL);
  Collection kpackage =
kbuilder.getKnowledgePackages();
  KnowledgeBaseConfiguration kbConf = KnowledgeBaseFactory
    .newKnowledgeBaseConfiguration();
  kbConf.setProperty("org.drools.sequential", "true");
  KnowledgeBase kbase =
KnowledgeBaseFactory.newKnowledgeBase(kbConf);
  kbase.addKnowledgePackages(kpackage);//将KnowledgePackage集合添
加到KnowledgeBase当中
 }
}
1.3.3.  StatefulKnowledgeSession
规则编译完成之后,接下来就需要使用一个 API 使编译好的规则包文件在规则引擎当
中运行起来。在 Drools5 当中提供了两个对象与规则引擎进行交互: StatefulKnowledgeSession
和 StatelessKnowledgeSession,本小节当中要介绍的是 StatefulKnowledgeSession 对象,下面
的一节将对 StatelessKnowledgeSession 对象进行讨论。
 StatefulKnowledgeSession 对象是一种最常用的与规则引擎进行交互的方式,它可以与规
则引擎建立一个持续的交互通道,在推理计算的过程当中可能会多次触发同一数据集。在用
户的代码当中,最后使用完 StatefulKnowledgeSession 对象之后,一定要调用其 dispose()方
http://www.bstek.com
第 12  页  共 74  页
法以释放相关内存资源。
 StatefulKnowledgeSession 可以接受外部插入(insert)的业务数据——也叫 fact,一个
fact 对象通常是一个普通的 Java 的 POJO,一般它们会有若干个属性,每一个属性都会对应
getter 和 setter 方法,用来对外提供数据的设置与访问。一般来说,在 Drools 规则引擎当中,
fact 所承担的作用就是将规则当中要用到的业务数据从应用当中传入进来,对于规则当中产
生的数据及状态的变化通常不用 fact 传出。如果在规则当中需要有数据传出,那么可以通过
在 StatefulKnowledgeSession 当中设置 global 对象来实现,一个 global 对象也是一个普通的
Java 对象,在向 StatefulKnowledgeSession 当中设置 global 对象时不用 insert 方法而用
setGlobal 方法实现。
创建一个 StatefulKnowledgeSession 要通过 KnowledgeBase 对象来实现,下面的代码清
单 1-8 就演示了 StatefulKnowledgeSession 的用法:
代码清单 1-8
packagetest;
importjava.util.Collection;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseConfiguration;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.definition.KnowledgePackage;
importorg.drools.io.ResourceFactory;
importorg.drools.runtime.StatefulKnowledgeSession;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder kbuilder = KnowledgeBuilderFactory
    .newKnowledgeBuilder();
  kbuilder.add(ResourceFactory.newClassPathResource("test.drl",
    Test.class), ResourceType.DRL);
  Collection kpackage =
kbuilder.getKnowledgePackages();
  KnowledgeBaseConfiguration kbConf = KnowledgeBaseFactory
    .newKnowledgeBaseConfiguration();
  kbConf.setProperty("org.drools.sequential", "true");
http://www.bstek.com
第 13  页  共 74  页
  KnowledgeBase kbase =
KnowledgeBaseFactory.newKnowledgeBase(kbConf);
  kbase.addKnowledgePackages(kpackage);//将KnowledgePackage集合添
加到KnowledgeBase当中
  StatefulKnowledgeSession
statefulKSession=kbase.newStatefulKnowledgeSession();
  statefulKSession.setGlobal("globalTest", newObject());//设置一
个global对象
  statefulKSession.insert(newObject());//插入一个fact对象
  statefulKSession.fireAllRules();
  statefulKSession.dispose();
 }
}
代码清单 1-8 当中同时也演示了规则完整的运行处理过程,可以看到,它的过程是首先
需要通过使用 KnowledgeBuilder 将相关的规则文件进行编译,产生对应的 KnowledgePackage
集合,接下来再通过 KnowledgeBase 把产生的 KnowledgePackage 集合收集起来,最后再产
生 StatefulKnowledgeSession 将规则当中需要使用的 fact 对象插入进去、将规则当中需要用
到的 global 设置进去,然后调用 fireAllRules()方法触发所有的规则执行,最后调用 dispose()
方法将内存资源释放。
1.3.4.  StatelessKnowledgeSession
 StatelessKnowledgeSession 的作用与 StatefulKnowledgeSession 相仿,它们都是用来接收
业务数据、执行规则的。事实上,StatelessKnowledgeSession 对 StatefulKnowledgeSession 做
了包装,使得在使用 StatelessKnowledgeSession 对象时不需要再调用 dispose()方法释放内存
资源了。
因为 StatelessKnowledgeSession 本身所具有的一些特性,决定了它的使用有一定的局限
性。在使用 StatelessKnowledgeSession 时不能进行重复插入 fact 的操作、也不能重复的调用
fireAllRules()方法来执行所有的规则,对应这些要完成的工作在 StatelessKnowledgeSession
当中只有 execute(…)方法,通过这个方法可以实现插入所有的 fact 并且可以同时执行所有的
规则或规则流,事实上也就是在执行 execute(…)方法的时候就在 StatelessKnowledgeSession
内部执行了 insert()方法、fireAllRules()方法和 dispose()方法。
代码清单 1-9 演示了 StatelessKnowledgeSession 对象的用法。
http://www.bstek.com
第 14  页  共 74  页
代码清单 1-9
packagetest;
importjava.util.ArrayList;
importjava.util.Collection;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseConfiguration;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.definition.KnowledgePackage;
importorg.drools.io.ResourceFactory;
importorg.drools.runtime.StatelessKnowledgeSession;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder kbuilder = KnowledgeBuilderFactory
    .newKnowledgeBuilder();
  kbuilder.add(ResourceFactory.newClassPathResource("test.drl",
    Test.class), ResourceType.DRL);
  Collection kpackage =
kbuilder.getKnowledgePackages();
  KnowledgeBaseConfiguration kbConf = KnowledgeBaseFactory
    .newKnowledgeBaseConfiguration();
  kbConf.setProperty("org.drools.sequential", "true");
  KnowledgeBase kbase =
KnowledgeBaseFactory.newKnowledgeBase(kbConf);
  kbase.addKnowledgePackages(kpackage);//将KnowledgePackage集合添
加到KnowledgeBase当中
  StatelessKnowledgeSession
statelessKSession=kbase.newStatelessKnowledgeSession();
  ArrayList list=newArrayList();
  list.add(newObject());
  list.add(newObject());
  statelessKSession.execute(list);
 }
}
代 码 清 单 1-9 当 中 , 通 过 新 建 了 一 个 ArrayList 对 象 , 将 需 要 插 入 到
http://www.bstek.com
第 15  页  共 74  页
StatelessKnowledgeSession 当中的对象放到这个 ArrayList 当中,将这个 ArrayList 作为参数
传给 execute(…)方法,这样在 StatelessKnowledgeSession 内部会对这个 ArrayList 进行迭代,
取出其中的每一个 Element,将其作为 fact,调用 StatelessKnowledgeSession 对象内部的
StatefulKnowledgeSession 对 象 的 insert() 方 法 将 产 生 的 fact 逐 个 插 入 到
StatefulKnowledgeSession 当中,然后调用 StatefulKnowledgeSession 的 fireAllRules()方法,
最后执行 dispose()方法释放内存资源。
在代码清单 1-9 当中,如果我们要插入的 fact 就是这个 ArrayList 而不是它内部的 Element
那该怎么做呢?在 StatelessKnowledgeSession 当中,还提供了 execute(Command cmd)的方法,
在该方法中通过 CommandFactory 可以创建各种类型的 Command,比如前面的需求要直接
将这个 ArrayList 作为一个 fact 插入,那么就可以采用 CommandFactory.newInsert(Object obj)
来实现,代码清单 1-9 当中 execute 方法可做如代码清单 1-10 所示的修改。
代码清单 1-10
statelessKSession.execute(CommandFactory.newInsert(list));
如 果 需 要 通 过 StatelessKnowledgeSession 设 置 global 的 话 , 可 以 使 用
CommandFactory.newSetGlobal(“key”,Object  obj)来实现;如果即要插入若干个 fact,又要设
置相关的 global,那么可以将 CommandFactory 产生的 Command 对象放在一个 Collection 当
中,然后再通过 CommandFactory.newBatchExecution(Collection collection)方法实现。代码清
单 1-11 演示了这种做法。
代码清单 1-11
packagetest;
importjava.util.ArrayList;
importjava.util.Collection;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseConfiguration;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.command.Command;
importorg.drools.command.CommandFactory;
importorg.drools.definition.KnowledgePackage;
importorg.drools.io.ResourceFactory;
importorg.drools.runtime.StatelessKnowledgeSession;
http://www.bstek.com
第 16  页  共 74  页
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder kbuilder = KnowledgeBuilderFactory
    .newKnowledgeBuilder();
  kbuilder.add(ResourceFactory.newClassPathResource("test.drl",
    Test.class), ResourceType.DRL);
  Collection kpackage =
kbuilder.getKnowledgePackages();
  KnowledgeBaseConfiguration kbConf = KnowledgeBaseFactory
    .newKnowledgeBaseConfiguration();
  kbConf.setProperty("org.drools.sequential", "true");
  KnowledgeBase kbase =
KnowledgeBaseFactory.newKnowledgeBase(kbConf);
  kbase.addKnowledgePackages(kpackage);//将KnowledgePackage集合添
加到KnowledgeBase当中
  StatelessKnowledgeSession
statelessKSession=kbase.newStatelessKnowledgeSession();
  ArrayList list=newArrayList();
  list.add(CommandFactory.newInsert(newObject()));
  list.add(CommandFactory.newInsert(newObject()));
  list.add(CommandFactory.newSetGlobal("key1", newObject()));
  list.add(CommandFactory.newSetGlobal("key2", newObject()));
 statelessKSession.execute(CommandFactory.newBatchExecution(list))
;
 }
}
1.4. Fact 对象
Fact 是指在 Drools 规则应用当中,将一个普通的 JavaBean 插入到规则的 WorkingMemory
当中后的对象。规则可以对 Fact 对象进行任意的读写操作,当一个 JavaBean 插入到
WorkingMemory 当中变成 Fact 之后, Fact 对象不是对原来的 JavaBean 对象进行 Clon,而是
原来 JavaBean 对象的引用。规则在进行计算的时候需要用到应用系统当中的数据,这些数
据设置在 Fact 对象当中,然后将其插入到规则的 WorkingMemory 当中,这样在规则当中就
可以通过对 Fact 对象数据的读写,从而实现对应用数据的读写操作。一个 Fact 对象通常是
http://www.bstek.com
第 17  页  共 74  页
一个具有 getter 和 setter 方法的 POJO 对象,通过这些 getter 和 setter 方法可以方便的实现对
Fact 对象的读写操作,所以我们可以简单的把 Fact 对象理解为规则与应用系统数据交互的
桥梁或通道。
当 Fact 对象插入到 WorkingMemory 当中后,会与当前 WorkingMemory 当中所有的规
则进行匹配,同时返回一个 FactHandler 对象。FactHandler 对象是插入到 WorkingMemory
当中 Fact 对象的引用句柄,通过 FactHandler 对象可以实现对对应的 Fact 对象的删除及修改
等操作。
在前面介绍 StatefulKnowledgeSession 和 StatelessKnowledgeSession 两个对象的时候也提
到了插入 Fact 对象的方法,在 StatefulKnowledgeSession 当中直接使用 insert 方法就可以将
一个 Java 对象插入到 WokingMemory 当中,如果有多个 Fact 需要插入,那么多个调用 insert
方法即可;对于 StatelessKnowledgeSession 对象可利用 CommandFactory 实现单个 Fact 对象
或多个 Fact 对象的插入。
http://www.bstek.com
第 18  页  共 74  页
2.  规则
学习 Drools 规则语法的目的是为了在应用当中帮助我们解决实际的问题,所以学会并
灵活的在规则当中使用就显的尤为重要。本章的内容包括规则的基本的约束部分语法讲解
(LHS)、规则动作执行部分语法讲解及规则的各种属性介绍。
2.1.  规则文件
在 Drools 当中,一个标准的规则文件就是一个以“.drl”结尾的文本文件,由于它是一
个标准的文本文件,所以可以通过一些记事本工具对其进行打开、查看和编辑。规则是放在
规则文件当中的,一个规则文件可以存放多个规则,除此之外,在规则文件当中还可以存放
用户自定义的函数、数据对象及自定义查询等相关在规则当中可能会用到的一些对象。
一个标准的规则文件的结构如代码清单 2-1 所示。
代码清单 2-1
package  package package-name
imports
globals
functions
queries
rules
对于一个规则文件而言,首先声明 package 是必须的,除 package 之外,其它对象在规
则文件中的顺序是任意的,也就是说在规则文件当中必须要有一个 package 声明,同时
package 声明必须要放在规则文件的第一行。
规则文件当中的 package 和 Java 语言当中的 package 有相似之处,也有不同之处。在 Java
当中 package 的作用是用来对功能相似或相关的文件放在同一个 package 下进行管理,这种
package 管理既有物理上 Java 文件位置的管理也有逻辑上的文件位置的管理,在 Java 当中
这种通过 package 管理文件要求在文件位置在逻辑上与物理上要保持一致;在 Drools 的规则
http://www.bstek.com
第 19  页  共 74  页
文件当中 package 对于规则文件中规则的管理只限于逻辑上的管理,而不管其在物理上的位
置如何,这点是规则与 Java 文件的 package 的区别。
对于同一 package 下的用户自定义函数、自定义的查询等,不管这些函数与查询是否在
同一个规则文件里面,在规则里面是可以直接使用的,这点和 Java 的同一 package 里的 Java
类调用是一样的。
2.2.  规则语言
规则是在规则文件当中编写,所以要编写一个规则首先需要先创建一个存放规则的规则
文件。一个规则文件可以存放若干个规则,每一个规则通过规则名称来进行标识。代码清单
2-2 说明了一个标准规则的结构。
代码清单 2-2
rule rule "name"
attributes
when when
LHS
then then
RHS
end endend end
从代码清单 2-2 中可以看到,一个规则通常包括三个部分:属性部分(attribute)、条件
部分(LHS)和结果部分(RHS)。对于一个完整的规则来说,这三个部分都是可选的,也
就是说如代码清单 2-3 所示的规则是合法的。
代码清单 2-3
rule rule "name"
when when
then then
end
对于代码清单 2-3 所示的这种规则,因为其没有条件部分,默认它的条件部分是满足的,
因为其结果执行部分为空,所以即使条件部分满足该规则也什么都不做。接下来我们就分别
来对规则的条件部分、结果部分和属性部分进行介绍。
2.2.1.  条件部分
条件部分又被称之为 Left Hand Side,简称为 LHS,下文当中,如果没有特别指出,那
么所说的 LHS 均指规则的条件部分,在一个规则当中 when 与 then 中间的部分就是 LHS 部
http://www.bstek.com
第 20  页  共 74  页
分。在 LHS 当中,可以包含 0~n 个条件,如果 LHS 部分没空的话,那么引擎会自动添加一
个 eval(true)的条件,由于该条件总是返回 true,所以 LHS 为空的规则总是返回 true。所以
代码清单 2-3 所示的规则在执行的时候引擎会修改成如代码清单 2-4 所示的内容。
代码清单 2-4
rule rule "name"
when when
eval(true)
then then
end
 LHS 部分是由一个或多个条件组成,条件又称之为 pattern(匹配模式),多个 pattern
之间用可以使用 and 或 or 来进行连接,同时还可以使用小括号来确定 pattern 的优先级。
一个 pattern 的语法如代码清单 2-5 所示:
代码清单 2-5
[绑定变量名:]Object([field 约束])
对于一个 pattern 来说“绑定变量名”是可选的,如果在当前规则的 LHS 部分的其它的
pattern 要用到这个对象,那么可以通过为该对象设定一个绑定变量名来实现对其引用,对于
绑定变量的命名,通常的作法是为其添加一个“$”符号作为前缀,这样可以很好的与 Fact
的属性区别开来;绑定变量不仅可以用在对象上,也可以用在对象的属性上面,命名方法与
对象的命名方法相同;“field 约束”是指当前对象里相关字段的条件限制,代码清单 2-6 规
则中 LHS 部分单个 pattern 的情形。
代码清单 2-6
rule rule "rule1"
when when
$customer:Customer()
then then

end
代码清单 2-6 规则中“$customer”是就是一个绑定到 Customer 对象的“绑定变量名”,
该规则的 LHS 部分表示,要求 Fact 对象必须是 Customer 类型,该条件满足了那么它的 LHS
会返回 true。
代码清单 2-7 演示了多个约束构成的 LHS 部分。
代码清单 2-7
rule rule "rule1"
when when
http://www.bstek.com
第 21  页  共 74  页
$customer:Customer(age>20,gender==’male’)
Order(customer==$customer,price>1000)
then then

end
代码清单 2-7 中的规则就包含两个 pattern,第一个 pattern 有三个约束,分别是:对象
类型必须是 Cutomer;同时 Cutomer 的 age 要大于 20 且 gender 要是 male;第二个 pattern 也
有三个约束,分别是:对象类型必须是 Order,同时 Order 对应的 Cutomer 必须是前面的那
个 Customer 且当前这个 Order 的 price 要大于 1000。在这两个 pattern 没有符号连接,在 Drools
当中在 pattern 中没有连接符号,那么就用 and 来作为默认连接,所以在该规则的 LHS 部分
中两个 pattern 只有都满足了才会返回 true。默认情况下,每行可以用 “;”来作为结束符 (和
Java 的结束一样),当然行尾也可以不加“;”结尾。
2.2.1.1.  约束连接
对于对象内部的多个约束的连接,可以采用 “&&”(and)、“||”(or)和“,”(and)来实现,
代码清单 2-7 中规则的 LHS 部分的两个 pattern 就里对象内部约束就采用 “,”来实现,“&&”
(and)、 “||”(or)和“,”这三个连接符号如果没有用小括号来显示的定义优先级的话,那么
它们的执行顺序是:“&&”(and)、 “||”(or)和“,”。代码清单 2-8 演示了使用“&&”(and)、
“||”(or)来连接约束的情形。
代码清单 2-8
rule rule "rule1"
when when
Customer(age>20 || gender==’male’&& city==’sh’)
then then

End
代码清单 2-8 中规则的 LHS 部分只有一个 pattern,在这个 pattern 中有四个约束,首先
必须是一个 Customer 对象,然后要么该对象 gender=’male’且 city=’sh’,要么 age>20,在
Customer 对象的字段约束当中,age>20 和 gender=’male’且 city=’sh’这两个有一个满足就可
以了,这是因为“&&”连接符的优先级要高于“||”,所以代码清单 2-8 中规则的 LHS 部分
pattern 也可以写成代码清单 2-9 的样子,用一小括号括起来,这样看起来就更加直观了。
代码清单 2-9
rule rule "rule1"
http://www.bstek.com
第 22  页  共 74  页
when when
Customer(age>20 || (gender==’male’&& city==’sh’))
then then

End
表面上看“,”与“&&”具有相同的含义,但是有一点需要注意,“,”与“&&”和“||”
不能混合使用,也就是说在有“&&”或“||”出现的 LHS 当中,是不可以有“,”连接符出
现的,反之亦然。
2.2.1.2.  比较操作符
在前面规则例子当中,我们已经接触到了诸如 “>”、“= =”之类的比较操作符,在 Drools5
当中共提供了十二种类型的比较操作符,分别是: >、 >=、 <、 <=、 = =、 !=、 contains、 not contains、
memberof、not memberof、matches、not matches;在这十二种类型的比较操作符当中,前六
个是比较常见也是用的比较多的比较操作符,本小节当中将着重对后六种类型的比较操作符
进行介绍。
2.2.1.2.1.  contains
比较操作符 contains 是用来检查一个 Fact 对象的某个字段(该字段要是一个 Collection
或是一个 Array 类型的对象)是否包含一个指定的对象。代码清单 2-10 演示了 contains 比较
操作符的用法。
代码清单 2-10
#created on: 2009-8-26
packagetest
rule"rule1"
when
  $order:Order();
  $customer:Customer(age >20, orders contains$order);
then
  System.out.println($customer.getName());
end
 contains 操作符的语法如下:
http://www.bstek.com
第 23  页  共 74  页
Object(field[Collection/Array] contains value)
 contains 只能用于对象的某个 Collection/Array 类型的字段与另外一个值进行比较,作为
比较的值可以是一个静态的值,也可以是一个变量(绑定变量或者是一个 global 对象),在代
码清单 2-10 当中比较值$order 就是一个绑定变量。
2.2.1.2.2.  not contains
not  contains 作用与 contains 作用相反,not  contains 是用来判断一个 Fact 对象的某个字
段(Collection/Array 类型)是不是包含一个指定的对象,和 contains 比较符相同,它也只能
用在对象的 field 当中,代码清单 2-11 演示了 not contains 用法。
代码清单 2-11
#created on: 2009-8-26
packagetest
rule"rule1"
when
  $order:Order(items not contains "手机");
then
  System.out.println($order.getName());
end
代码清单 2-11 的规则当中,在判断订单 (Order)的时候,要求订单当中不能包含有 “手
机”的货物,这里的规则对于比较的项就是一个表态固定字符串。
2.2.1.2.3.  memberOf
memberOf 是用来判断某个 Fact 对象的某个字段是否在一个集合(Collection/Array)当
中,用法与 contains 有些类似,但也有不同,memberOf 的语法如下:
Object(fieldName memberOf value[Collection/Array])
可以看到 memberOf 中集合类型的数据是作为被比较项的,集合类型的数据对象位于
memberOf 操作符后面,同时在用 memberOf 比较操作符时被比较项一定要是一个变量(绑定
变量或者是一个 global 对象),而不能是一个静态值。代码清单 2-12 是一个演示 memberOf
使用的规则示例。
http://www.bstek.com
第 24  页  共 74  页
代码清单 2-12
#created on: 2009-8-26
packagetest
globalString[] orderNames;
rule"rule1"
when
  $order:Order(name memberOforderNames);
then
  System.out.println($order.getName());
end
代码清单 2-12 中被比较对象是一个 String Array 类型的 global 对象。
2.2.1.2.4.  not memberOf
该操作符与 memberOf 作用洽洽相反,是用来判断 Fact 对象当中某个字段值是不是中
某个集合 (Collection/Array)当中,同时被比较的集合对象只能是一个变量 (绑定变量或 global
对象),代码清单 2-13 演示了 not memberOf 的用法。
代码清单 2-13
#created on: 2009-8-26
packagetest
importjava.util.List;
rule"rule1"
when
  $orderList:String[]();
  $order:Order(name not memberOf$orderList);
then
  System.out.println($order.getName());
end
代码清单 2-13 中表示只有订单(Order)的名字(name)在订单集合($orderList)时
LHS 才能返回 true,该例子当中被比较的集合$orderList 是一个字符串数组的类型的绑定
变量对象。
2.2.1.2.5.  matches
matches 是用来对某个 Fact 的字段与标准的 Java 正则表达式进行相似匹配,被比较的字
http://www.bstek.com
第 25  页  共 74  页
符串可以是一个标准的 Java 正则表达式,但有一点需要注意,那就是正则表达式字符串当
中不用考虑“\”的转义问题。matches 使用语法如下:
代码清单 2-14 演示了 matches 的用法
代码清单 2-14
#created on: 2009-8-26
packagetest
importjava.util.List;
rule "rule1"
when
  $customer:Customer(name matches "李.*");
then
  System.out.println($customer.getName());
end
在清单代码清单 2-14 中示例的规则就像我们展示了 matches 的用法,该规则是用来查
找所有 Customer 对象的 name 属性是不是以“李”字开头,如果满足这一条件那么就将该
Customer 对象的 name 属性打印出来,代码清单 2-15 是对该规则的测试类源码。
代码清单 2-15
packagetest;
importjava.util.Collection;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder
kb=KnowledgeBuilderFactory.newKnowledgeBuilder();
  kb.add(newClassPathResource("test/test.drl"),
ResourceType.DRL);
  Collection collection=kb.getKnowledgePackages();
  KnowledgeBase
knowledgeBase=KnowledgeBaseFactory.newKnowledgeBase();
Object(fieldName matches “正则表达式”)
http://www.bstek.com
第 26  页  共 74  页
  knowledgeBase.addKnowledgePackages(collection);
  StatefulKnowledgeSession
statefulSession=knowledgeBase.newStatefulKnowledgeSession();
  Customer cus1=newCustomer();
  cus1.setName("张三");
  Customer cus2=newCustomer();
  cus2.setName("李四");
  Customer cus3=newCustomer();
  cus3.setName("王二");
  Customer cus4=newCustomer();
  cus4.setName("李小龙");
  statefulSession.insert(cus1);
  statefulSession.insert(cus2);
  statefulSession.insert(cus3);
  statefulSession.insert(cus4);
  statefulSession.fireAllRules();
  statefulSession.dispose();
  System.out.println("end.....");
 }
}
测试类运行结果如图 2-1 所示。
图 2-1
从测试结果中可以看到,该规则运用 matches 操作符已经将所有的以“李”字开头的
Customer 对象找到并打印出来。
2.2.1.2.6.  not matches
与 matches 作用相反,是用来将某个 Fact 的字段与一个 Java 标准正则表达式进行匹配,
看是不是能与正则表达式匹配。not matches 使用语法如下:
因为 not  matches 用法与 matches 一样,只是作用相反,所以这里就不再举例了,有兴
Object(fieldname not matches “正则表达式”)
http://www.bstek.com
第 27  页  共 74  页
趣的读者可以用 not matches 写个例子测试一下。
2.2.2.  结果部分
条件部分又被称之为 Right Hand Side,简称为 RHS,在一个规则当中 then 后面部分就
是 RHS,只有在 LHS 的所有条件都满足时 RHS 部分才会执行。
 RHS 部分是规则真正要做事情的部分,可以将因条件满足而要触发的动作写在该部分
当中,在 RHS 当中可以使用 LHS 部分当中定义的绑定变量名、设置的全局变量、或者是直
接编写 Java 代码(对于要用到的 Java 类,需要在规则文件当中用 import 将类导入后方能使
用,这点和 Java 文件的编写规则相同)。
我们知道,在规则当中 LHS 就是用来放置条件的,所以在 RHS 当中虽然可以直接编写
Java 代码,但不建议在代码当中有条件判断,如果需要条件判断,那么请重新考虑将其放在
LHS 当中,否则就违背了使用规则的初衷。
在 Drools 当中,在 RHS 里面,提供了一些对当前 Working  Memory 实现快速操作的宏
宏函数或对象,比如 insert/insertLogical、 update 和 retract 就可以实现对当前 Working Memory
中的 Fact 对象进行新增、删除或者是修改;如果您觉得还要使用 Drools 当中提供的其它方
法,那么您还可以使用另一外宏对象 drools,通过该对象可以使用更多的操作当前 Working
Memory 的方法;同时 Drools 还提供了一个名为 kcontext 的宏对象,使我们可以通过该对象
直接访问当前 Working Memory 的 KnowledgeRuntime。下面我们就来详细讨论一下这些宏函
数的用法。
2.2.2.1.  insert
函数 insert 的作用与我们在 Java 类当中调用 StatefulKnowledgeSession 对象的 insert 方法
的作用相同,都是用来将一个 Fact 对象插入到当前的 Working Memory 当中。它的基本用法
格式如下:
insert(new Object());
一旦调用 insert 宏函数,那么 Drools 会重新与所有的规则再重新匹配一次,对于没有设
置 no-loop 属性为 true 的规则,如果条件满足,不管其之前是否执行过都会再执行一次,这
个特性不仅存在于 insert 宏函数上,后面介绍的 update、retract 宏函数同样具有该特性,所
http://www.bstek.com
第 28  页  共 74  页
以在某些情况下因考虑不周调用 insert、update 或 retract 容易发生死循环,这点大家需要注
意。
代码清单 2-16 演示了 insert 函数的用法。
代码清单 2-16
#created on: 2009-8-26
packagetest
importjava.util.List;
rule "rule1"
salience1
when
eval(true);
then
  Customer cus=newCustomer();
  cus.setName("张三");
insert(cus);
end
rule "rule2"
salience2
when
  $customer:Customer(name =="张三");
then
  System.out.println("rule2----"+$customer.getName());
end
代码清单 2-16 中有两个规则 rule1 和 rule2,在 rule1 这个规则当中,LHS 是 eval(true),
表示没有任何条件限制(关于 eval 的用法,在后续的内容当中会有详细的介绍), RHS 当中
创建了一个 Customer 对象,并设置它的 name 属性为“张三”,完成后调用 insert 宏函数将
其插入到当前的 Working Memory 当中;在名为 rule2 这个规则当中,首先判断当前的 Working
Memory 当中没有一个 name 属性的值为“张三”的 Customer 对象,如果有的话,那么就在
其 RHS 部分将这个 Customer 对象的 name 属性打印出来。
很明显我们希望 rule1 这个规则先执行,rule2 这个规则后执行,为了达成这个希望,我
们为这两个规则添加了一个名为“salience”的属性,该属性的作用是通过一个数字来确认
规则执行的优先级,数字越大,执行越靠前。这里为 rule1 规则设置它的 salience 属性值为 2,
rule2 为 1 那么就表示 rule1 会先执行,rule2 会后执行。实际上,我们前面讲到,因为一旦
调用 insert、update 或 retract 函数,Drools 会重新与所有的规则再重新匹配一次,对于没有
设置 no-loop 属性为 true 的规则,如果条件满足,不管其之前是否执行过都会再执行一次。
所以这里的优先级如果设置也可以达到相同的效果。
http://www.bstek.com
第 29  页  共 74  页
编写测试类,测试这两个规则的执行情况,测试类源码如代码清单 2-17 所示。
代码清单 2-17
packagetest;
importjava.util.Collection;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder
kb=KnowledgeBuilderFactory.newKnowledgeBuilder();
  kb.add(newClassPathResource("test/test.drl"),
ResourceType.DRL);
  Collection collection=kb.getKnowledgePackages();
  KnowledgeBase
knowledgeBase=KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(collection);
  StatefulKnowledgeSession
statefulSession=knowledgeBase.newStatefulKnowledgeSession();
  statefulSession.fireAllRules();
  statefulSession.dispose();
  System.out.println("end.....");
 }
}
运行之后,结果如图 2-3 所示。
从执行的结果可以看出,正如我们前面论述的那样,rule1 先执行,向 Working Memory
当中插入一个 name 属性为 “张三”的 Customer 对象,然后 rule2 再执行,将该对象的 name
属性在控制台打印出来。
http://www.bstek.com
第 30  页  共 74  页
2.2.2.2.  insertLogical
insertLogical 作用与 insert 类似,它的作用也是将一个 Fact 对象插入到当前的 Working
Memroy 当中,
2.2.2.3.  update
update 函数意义与其名称一样,用来实现对当前 Working Memory 当中的 Fact 进行更新,
update 宏函数的作用与 StatefulSession 对象的 update 方法的作用基本相同,都是用来告诉当
前的 Working Memory 该 Fact 对象已经发生了变化。它的用法有两种形式,一种是直接更新
一个 Fact 对象,另一种为通过指定 FactHandle 来更新与指定 FactHandle 对应的 Fact 对象,
下面我们就来通过两个实例来说明 update 的这两种用法。
先来看第一种用法,直接更新一个 Fact 对象。第一种用法的格式如下:
update(new Object());
示例规则如代码清单 2-18 所示。
代码清单 2-18
#created on: 2009-8-26
packagetest
importjava.util.List;
query "query fact count"
 Customer();
end
rule "rule1"
http://www.bstek.com
第 31  页  共 74  页
salience2
when
eval(true);
then
  Customer cus=newCustomer();
  cus.setName("张三");
  cus.setAge(1);
insert(cus);
end
rule "rule2"
salience1
when
  $customer:Customer(name=="张三",age<10);
then
  $customer.setAge($customer.getAge()+1);
update($customer);
  System.out.println("----------"+$customer.getName());
end
在代码清单 2-18 当中,有两个规则:rule1 和 rule2,在 rule1 当中我们通过使用 insert
宏函数向当前 Working  Memory 当中插入了一个 name 属性为“张三”、age 属性为“1”的
Customer 对象; rule2 的规则当中首先判断当前的 Working Memory 当中有没有一个 name 属
性为“张三”同时 age 属性值小于“10”的 Customer,如果有的话,那么就重新设置当前这
个 Customer 对象的 age 属性的值:将当前 age 属性的值加 1,然后调用 update 宏函数,对
这个修改后的 Customer 对象进行更新。
因为 rule1 的优先级为 2,所以它会先执行; rule2 的优先级为 1,所以它会在 rule1 执行
完成后执行。在第一次 rule1 规则执行完成后,Working  Memory 当中就会有一个一个 name
属性为“张三”、 age 属性为“1”的 Customer 对象,正好满足 rule2 规则的条件,所以 rule2
的 RHS 部分会执行,在 rule2 当中一旦使用 update 宏函数对 Customer 对象进行了更新, Drools
会重新检查所有的规则,看看有没有条件满足,这时只要 Customer 对象的 age 属性值在没
有更新的 10 之前 rule2 都会再次触发,直到 Customer 对象的 age 属性值更新到大于等于 10,
这时所有的规则执行才算完成。
为了测试在多次调用 update 宏函数更新 Customer 对象后 Working  Memory 当中还只存
在一个 Customer 对象,所以我们还添加了一个名为“query fact count”的 query 查询(关于
query 查询后面的章节会有详细介绍), 该查询的作用是用来检索当中 Working Memory 当中
有多少个 Working Memory 对象,在该示例当中,Customer 对象应该只有一个。
http://www.bstek.com
第 32  页  共 74  页
编写测试类,测试这两个规则执行是不是符合我们的期望。测试类代码如代码清单 2-19
所示。
代码清单 2-19
packagetest;
importjava.util.Collection;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
importorg.drools.runtime.rule.QueryResults;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder
kb=KnowledgeBuilderFactory.newKnowledgeBuilder();
  kb.add(newClassPathResource("test/test.drl"),
ResourceType.DRL);
  Collection collection=kb.getKnowledgePackages();
  KnowledgeBase
knowledgeBase=KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(collection);
  StatefulKnowledgeSession
statefulSession=knowledgeBase.newStatefulKnowledgeSession();
  statefulSession.fireAllRules();
  statefulSession.dispose();
  QueryResults qr=statefulSession.getQueryResults("query fact
count");
  System.out.println("customer  对象数目:"+qr.size());
  System.out.println("end.....");
 }
}
运行测试类,结果如图 2-2 所示。
http://www.bstek.com
第 33  页  共 74  页
图 2-2
从运行结果图中可以看出, Customer 对象的 name 属性共输出了 9 次,同时最后检索出
来的 Customer 对象的结果也只是一个,符合我们的预期。
在上面的这个例子当中,一旦使用 update 宏函数,那么符合条件的规则又会重新触发,
不管该规则是否执行过,如果您希望规则只执行一次,那么可以通过设置规则的 no-loop 属
性为 true 来实现,如上面示例当中的 rule2 规则,如果添加 no-loop 属性为 true,那么 Customer
的 name 属性将只会输出一次。添加 no-loop 属性后的 rule2 规则如代码清单 2-20 所示。
代码清单 2-20
rule "rule2"
salience1
no-loop true
when
  $customer:Customer(name=="张三",age<10);
then
  $customer.setAge($customer.getAge()+1);
update($customer);
  System.out.println("----------"+$customer.getName());
end
再次运行测试类,可以得到如图 2-3 所示的结果。
图 2-3
可以看到,由于添加了 no-loop 属性为 true,rule2 规则只执行了一次。下面我们就来看
http://www.bstek.com
第 34  页  共 74  页
一下 update 宏函数的第二种用法。
第二种用法的格式如下:
update(new FactHandle(),new Object());
从第二种用法格式上可以看出,它可以支持创建一个新的 Fact 对象,从而把 FactHandle
对象指定的 Fact 对象替换掉,从而实现对象的全新更新,代码清单 2-21 里的规则演示这种
用法。
代码清单 2-21
#created on: 2009-8-26
packagetest
importjava.util.List;
query "query fact count"
 Customer();
end
rule "rule1"
salience2
when
eval(true);
then
  Customer cus=newCustomer();
  cus.setName("张三");
  cus.setAge(1);
insert(cus);
end
rule "rule2"
salience1
when
  $customer:Customer(name=="张三",age<10);
then
  Customer customer=newCustomer();
  customer.setName("张三");
  customer.setAge($customer.getAge()+1);
update(drools.getWorkingMemory().getFactHandleByIdentity($custome
r),customer);
  System.out.println("----------"+$customer.getName());
end
和前面的规则相比,更改了 rule2 的 RHS 部分,在这个部分当中,创建了一个新的
Customer 对象,并设置了它的 name 属性为“张三”、age 属性为当前 Working Memory 当中
http://www.bstek.com
第 35  页  共 74  页
的 Customer 对象的 age 属性值加 1,设置完成后将这个新的 Customer 对象通过使用 update
方法替换了原来的在 Working  Memory 当中的 Customer 对象。编写测试类,可以发现运行
结果同前面的结果相同。
在这个规则当中我们使用了一个名为 drools 的宏对象,通过该对象获取当前的 Working
Memory 对象,再通过 WorkingMemory 得到指定的 Fact 对象的 FactHandle。关于 drools 宏
函数后面会有详细介绍。
2.2.2.4.  retract
和 StatefulSession 的 retract 方法一样,宏函数 retract 也是用来将 Working Memory 当中
某个 Fact 对象从 Working Memory 当中删除,下面就通过一个例子来说明 retract 宏函数的用
法。代码清单 2-22 为示例规则。
代码清单 2-22
#created on: 2009-8-26
packagetest
importjava.util.List;
query "query fact count"
 Customer();
end
rule "rule1"
salience2
when
eval(true);
then
  Customer cus=newCustomer();
  cus.setName("张三");
  cus.setAge(1);
insert(cus);
end
rule "rule2"
salience1
when
  $customer:Customer(name=="张三");
then
retract($customer);
end
代码清单 2-22 中有两个规则和前面的示例基本相同,在 rule2 当中 RHS 部分,通过使
http://www.bstek.com
第 36  页  共 74  页
用 retract 宏函数对符合条件的 Customer 对象进行了删除,将其从当前的 Working  Memory
当中清除,这样在执行完所有的规则之后,调用名为“query  fact  count”的 query 查询,查
询到的结果数量应该是 0,编写测试类,验证我们推理的结果。测试类代码如代码清单 2-23
所示。
代码清单 2-23
packagetest;
importjava.util.Collection;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
importorg.drools.runtime.rule.QueryResults;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder
kb=KnowledgeBuilderFactory.newKnowledgeBuilder();
  kb.add(newClassPathResource("test/test.drl"),
ResourceType.DRL);
  Collection collection=kb.getKnowledgePackages();
  KnowledgeBase
knowledgeBase=KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(collection);
  StatefulKnowledgeSession
statefulSession=knowledgeBase.newStatefulKnowledgeSession();
  statefulSession.fireAllRules();
  statefulSession.dispose();
  QueryResults qr=statefulSession.getQueryResults("query fact
count");
  System.out.println("customer  对象数目:"+qr.size());
  System.out.println("end.....");
 }
}
运行测试类,可以看到如图 2-4 所示的结果。
http://www.bstek.com
第 37  页  共 74  页
正如推理的那样,因为使用了 retract 将 Customer 对象从当前的 Working  Memory 当中
删除,所以运行结果中看到的 Customer 对象的数目为 0。
2.2.2.5.  drools
如果您希望在规则文件里更多的实现对当前的 Working  Memory 控制,那么可以使用
drools 宏对象实现,通过使用 drools 宏对象可以实现在规则文件里直接访问 Working
Memory。在前面介绍 update 宏函数的时候我们就使用 drools 宏对象来访问当前的 Working
Memory,得到一个指定的 Fact 对象的 FactHandle。同时前面介绍的 insert、insertLogical、
update 和 retract 宏函数的功能皆可以通过使用 drools 宏对象来实现。代码清单 2-24 和代码
清单 2-25 所示的两个规则在功能上是完全一样的。
代码清单 2-24
rule "rule1"
salience11
when
eval(true);
then
  Customer cus=newCustomer();
  cus.setName("张三");
insert(cus);
end
代码清单 2-25
rule "rule1"
salience11
when
eval(true);
then
  Customer cus=newCustomer();
  cus.setName("张三");
  drools.insert(cus)
http://www.bstek.com
第 38  页  共 74  页
end
代码清单 2-24 中向当前的 Working  Memory 当中插入一个 Customer 对象采用的是宏函
数 insert 实现的,代码清单 2-25 则是采用宏对象 drools 的 insert 方法来实现,两个规则向当
前 Working Memory 当中插入对象的方法不同,但最终实现的功能是一样的。
使用 drools 宏对象,可以得到很多操纵 Working Memory 的方法,如果想查看 drools 宏
对象具有哪些方法可以使用,可以通过按“ALT”+“/”这两个功能键实现。具体做法是先
输入“drools.”然后按“ALT”+“/”这两个功能键就可以看到 drools 宏函数的方法列表,
如果在操作过程中,您没有看到方法列表,那应该是您操作系统里的  “ALT”+“/”这两
个功能键组合被其它软件占用。
表 2-1 中罗列了 drools 宏对象的常用方法。
表格 2-1
方法名称  用法格式  含义
getWorkingMemory()  drools.getWorkingMemory()  获取当前的 WorkingMemory 对象
halt()  drools.halt()  在当前规则执行完成后,不再执行
其它未执行的规则。
getRule()  drools.getRule()  得到当前的规则对象
insert(new Object)  drools.insert(new Object)  向当前的 WorkingMemory 当中插入
指定的对象,功能与宏函数 insert
相同。
update(new Object)  drools.update(new Object)  更新当前的 WorkingMemory 中指定
的对象,功能与宏函数 update 相同。
update(FactHandle
Object)
drools.update(FactHandle
Object)
更新当前的 WorkingMemory 中指定
的对象,功能与宏函数 update 相同。
retract(new Object)  drools.retract(new Object)  从当前的 WorkingMemory 中删除指
定的对象,功能与宏函数 retract 相
同。
http://www.bstek.com
第 39  页  共 74  页
2.2.2.6.  kcontext
kcontext 也 是 Drools 提 供 的 一 个 宏 对 象 , 它 的 作 用 主 要 是 用 来 得 到 当 前 的
KnowledgeRuntime 对象, KnowledgeRuntime 对象可以实现与引擎的各种交互,关于
KnowledgeRuntime 对象的介绍,您可以参考后面的章节内容。
2.2.2.7.  modify
modify 是一个表达式块,它可以快速实现对 Fact 对象多个属性进行修改,修改完成后
会自动更新到当前的 Working Memory 当中。它的基本语法格式如下:
modify(fact-expression){
   <修改 Fact 属性的表达式>[,<修改 Fact 属性的表达式>*]
}
下面我们通过一个实例来说明 modify 表达式块的用法。
代码清单 2-26 中规则 rule1 里用使用 modify 表达式块来对 Customer 对象的属性进行了
修改。
代码清单 2-26
#created on: 2009-8-26
packagetest
importjava.util.List;
rule "rule1"
salience2
when
  $customer:Customer(name=="张三",age==20);
then
  System.out.println("modify before customer
id:"+$customer.getId()+";age:"+$customer.getAge());
modify($customer){
   setId("super man"),
   setAge(30)
  }
end
rule "rule2"
salience1
when
http://www.bstek.com
第 40  页  共 74  页
  $customer:Customer(name=="张三");
then
  System.out.println("modify after customer
id:"+$customer.getId()+";age:"+$customer.getAge());
end
在这两个规则当中,我们通过使用 salience 属性来控制他们的执行顺序,让 rule1 先执
行,执行时先打印出当前 Customer 对象的 id 与 age 属性的值,然后利用 modify 块对 Customer
对象的这两个属性进行修改,接下来再执行 rule2,再次打印 Customer 对象的 id 与 age 属性
的值,这时的值应该是 modify 块修改后的值。编写测试类,测试类代码如代码清单 2-27 所
示。
代码清单 2-27
packagetest;
importjava.util.Collection;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder
kb=KnowledgeBuilderFactory.newKnowledgeBuilder();
  kb.add(newClassPathResource("test/test.drl"),
ResourceType.DRL);
  Collection collection=kb.getKnowledgePackages();
  KnowledgeBase
knowledgeBase=KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(collection);
  StatefulKnowledgeSession
statefulSession=knowledgeBase.newStatefulKnowledgeSession();
  Customer cus=newCustomer();
  cus.setAge(20);
  cus.setId("ZhangeShan");
  cus.setName("张三");
  statefulSession.insert(cus);
http://www.bstek.com
第 41  页  共 74  页
  statefulSession.fireAllRules();
  statefulSession.dispose();
  System.out.println("end.....");
 }
}
运行测试类,可以看到如图 2-所示的结果。
图 2-4
这里有一点需要注意,那就是和 insert、 update、 retract 对 Working Memory 的操作一样,
一旦使用了 modify 块对某个 Fact 的属性进行了修改,那么会导致引擎重新检查所有规则是
否匹配条件,而不管其之前是否执行过,所以这个例子当中,在 rule1 这个规则当中,LHS
条件约束部分我们添加了两个条件 name = = “张三”和 age = = 20,如果去掉 age = =20 那么
就会形成死循环,至于原因应该是比较容易理解的。
2.2.3.  属性部分
规则属性是用来控制规则执行的重要工具,在前面举出的关于规则的例子当中,已经
接触了如控制规则执行优先级的 salience,和是否允许规则执行一次的 no-loop 等。在目前
的 Drools5 当中,规则的属性共有 13 个,它们分别是:activation-group、agenda-group、
auto-focus、date-effective、date-expires、dialect、duration、enabled、lock-on-active、no-loop、
ruleflow-group、salience、when,这些属性分别适用于不同的场景,下面我们就来分别介绍
这些属性的含义及用法。
2.2.3.1.  salience
该属性的作用我们在前面的内容也有涉及,它的作用是用来设置规则执行的优先级,
salience 属性的值是一个数字,数字越大执行优先级越高,同时它的值可以是一个负数。默
认情况下,规则的 salience 默认值为 0,所以如果我们不手动设置规则的 salience 属性,那
http://www.bstek.com
第 42  页  共 74  页
么它的执行顺序是随机的。
代码清单 2-28
packagetest
rule"rule1"
salience1
when
eval(true)
then
  System.out.println("rule1");
end
rule "rule2"
salience2
when
eval(true)
then
  System.out.println("rule2");
end
在代码清单 2-28 中两个规则,虽然 rule1 位于前面,但因为它的 salience 为 1,而 rule2
的 salience 属性为 2,所以 rule2 会先执行,然后 rule1 才会执行。
2.2.3.2.  no-loop
在一个规则当中如果条件满足就对 Working Memory 当中的某个 Fact 对象进行了修改,
比如使用 update 将其更新到当前的 Working Memory 当中,这时引擎会再次检查所有的规则
是否满足条件,如果满足会再次执行,代码清单 2-29 中的规则演示了这种用法。
代码清单 2-29
packagetest
rule"rule1"
salience1
when
  $customer:Customer(name=="张三")
then
update($customer);
  System.out.println("customer name:"+$customer.getName());
end
编写测试类,测试类代码如代码清单 2-30 所示。
http://www.bstek.com
第 43  页  共 74  页
代码清单 2-30
packagetest;
importjava.util.Collection;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder
kb=KnowledgeBuilderFactory.newKnowledgeBuilder();
  kb.add(newClassPathResource("test/test.drl"),
ResourceType.DRL);
  Collection collection=kb.getKnowledgePackages();
  KnowledgeBase
knowledgeBase=KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(collection);
  StatefulKnowledgeSession
statefulSession=knowledgeBase.newStatefulKnowledgeSession();
  Customer cus=newCustomer();
  cus.setName("张三");
  statefulSession.insert(cus);
  statefulSession.fireAllRules();
  statefulSession.dispose();
  System.out.println("end.....");
 }
}
运行测试代码,可以看到,控制台会不断输出打印 Custom 的 name 属性值的信息,而
且永无止境,很明显这是一个死循环。造成这个死循环的原因就是在代码清单 2-29 中规则
的 RHS 中使用了 update 宏函数,对当前的 Customer 的 Fact 对象进行更新操作,这样就导
致引擎再次重新检查所有的规则是否符合条件,如果符合条件,那么就继续执行,那么再次
对 Customer 进行更新……。
http://www.bstek.com
第 44  页  共 74  页
如何避免这种情况呢,这时可以引入 no-loop 属性来解决这个问题。 no-loop 属性的作用
是用来控制已经执行过的规则在条件再次满足时是否再次执行,前面的示例当中也用到该属
性帮我们解决应该当中的问题。no-loop 属性的值是一个布尔型,默认情况下规则的 no-loop
属性的值为 false,如果 no-loop 属性值为 true,那么就表示该规则只会被引擎检查一次,如
果满足条件就执行规则的 RHS 部分,如果引擎内部因为对 Fact 更新引起引擎再次启动检查
规则,那么它会忽略掉所有的 no-loop 属性设置为 true 的规则。
现在对规则进行修改,为其添加一个 no-loop 属性,属性的值为 true,修改后的规则如
代码清单 2-31 所示。
代码清单 2-31
packagetest
rule"rule1"
salience1
no-loop true
when
  $customer:Customer(name=="张三")
then
update($customer);
  System.out.println("customer name:"+$customer.getName());
End
再次运行测试类,可以看到控制台只会打印一次 Custom 的 name 属性值的信息。
2.2.3.3.  date-effective
该属性是用来控制规则只有在到达后才会触发,在规则运行时,引擎会自动拿当前操作
系统的时候与 date-effective 设置的时间值进行比对,只有当系统时间>=date-effective 设置的
时间值时,规则才会触发执行,否则执行将不执行。在没有设置该属性的情况下,规则随时
可以触发,没有这种限制。
date-effective 的值为一个日期型的字符串,默认情况下,date-effective 可接受的日期格
式为“dd-MMM-yyyy”,例如 2009 年 9 月 25 日在设置为 date-effective 的值时,如果您的操
作系统为英文的,那么应该写成“25-Sep-2009”;如果是英文操作系统“25-九月-2009”,那
么就是,代码清单 2-32 就演示了 date-effective 属性的用法。
代码清单 2-32
packagetest
http://www.bstek.com
第 45  页  共 74  页
rule "rule1"
date-effective " 25-九月-2009"
when
eval(true);
then
  System.out.println("rule1 is execution!");
end
该规则里的 date-effective 的属性值为“09-九月-2009”,因为我的操作系统是中文,所
以这里直接用的汉字来表示九月。
在实际使用的过程当中,如果您不想用这种时间的格式,那么可以在调用的 Java 代码
中通过使用 System.setProperty(String key,String value)方法来修改默认的时间格式,如代码清
单 2-33 的规则就是采用格式化后的日期。
代码清单 2-33
packagetest
rule "rule1"
date-effective "2009-09-25"
when
eval(true);
then
  System.out.println("rule1 is execution!");
end
在编写测试代码时,需要调用 System.setProperty(String key,String value)方法来修改默认
的时间格式,调用的测试类如代码清单 2-34 所示。
代码清单 2-34
packagetest;
importjava.util.Collection;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
public classTest {
public static voidmain(String[] args) {
 KnowledgeBuilder kb=KnowledgeBuilderFactory.newKnowledgeBuilder();
 System.setProperty("drools.dateformat","yyyy-MM-dd");
  kb.add(newClassPathResource("test/test.drl"),
http://www.bstek.com
第 46  页  共 74  页
ResourceType.DRL);
  Collection collection=kb.getKnowledgePackages();
  KnowledgeBase
knowledgeBase=KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(collection);
  StatefulKnowledgeSession
statefulSession=knowledgeBase.newStatefulKnowledgeSession();
  statefulSession.fireAllRules();
  statefulSession.dispose();
  System.out.println("end.....");
 }
}
测试代码中System.setProperty("drools.dateformat","yyyy-MM-dd");这句就是用来修改当
前系统默认的时间格式的。在进行这个操作的时候有两个地方需要注意:一是设置的key必
须是“drools.dateformat”,值的话遵循标准的Java日期格式;二是修改当前系统默认的时间
格式的这句代码必须放在向KnowledgeBuilder里添加规则文件之前,否则将不起作用。
2.2.3.4.  date-expires
该属性的作用与 date-effective 属性恰恰相反, date-expires 的作用是用来设置规则的有
效期,引擎在执行规则的时候,会检查规则有没有 date-expires 属性,如果有的话,那么会
将这个属性的值与当前系统时间进行比对,如果大于系统时间,那么规则就执行,否则就不
执行。该属性的值同样也是一个日期类型,默认格式也是“dd-MMM-yyyy”,具体用法与
date-effective 属性相同。
代码清单 2-35 演示了 date-expires 属性的用法。
代码清单 2-35
packagetest
rule "rule1"
date-expires "2009-09-27"
when
eval(true);
then
  System.out.println("rule1 is execution!");
end
在代码清单 2-35 中,名为 rule1 的规则的有效期为“2009-09-27”,当然该值遵循的是
“yyyy-MM-dd”格式,所以在执行该规则的时候需要设置 drools.dateformat 环境变量的值。  
http://www.bstek.com
第 47  页  共 74  页
2.2.3.5.  enabled
enabled 属性比较简单,它是用来定义一个规则是否可用的。该属性的值是一个布尔值,
默认该属性的值为 true,表示规则是可用的,如果手工为一个规则添加一个 enabled 属性,
并且设置其 enabled 属性值为 false,那么引擎就不会执行该规则。
2.2.3.6.  dialect
该属性用来定义规则当中要使用的语言类型,目前 Drools5 版本当中支持两种类型的语
言:mvel 和 java,默认情况下,如果没有手工设置规则的 dialect,那么使用的 java 语言。
代码清单 2-36 演示了如何查看当前规则的 dialect 的值。
代码清单 2-36
packagetest
rule "rule1"
when
eval(true)
then
  System.out.println("dialect:"+drools.getRule().getDialect());
end
代码清单 2-36 中名为 rule1 的规则 RHS 当中利用规则当中内置的宏对象 drools 得到当
前的 Rule 对象,再通过这个 Rule 对象得到其 dialect 属性的值。
执行该规则我们可以看如图 2-5 所示的结果。
图 2-5
从执行输出的结果当中可以看出,规则默认情况下使用的 dialect 值为“java”。
我们知道,规则是存放于规则文件当中,在规则文件当中也可以定义一个 dialect 属性,
如果一个规则没有定义 dialect 属性,那么它就采用规则文件里设置的 dialect 属性的值。规
则文件里 dialect 属性默认值为“java”,所以规则中默认 dialect 属性的值也为“java”。java
语言的语法大家都比较清楚,这里就简单的介绍一下 mvel 的语法。
 mvel 在本书当中有专门的章节加一介绍,
http://www.bstek.com
第 48  页  共 74  页
2.2.3.7.  duration
对于一个规则来说,如果设置了该属性,那么规则将在该属性指定的值之后在另外一个
线程里触发。该属性对应的值为一个长整型,单位是毫秒,代码清单 2-37 里的规则 rule1 添
加了 duration 属性,它的值为 3000,表示该规则将在 3000 毫秒之后在另外一个线程里触发。
代码清单 2-37
packagetest
rule "rule1"
duration3000
when
eval(true)
then
  System.out.println("rule thread
id:"+Thread.currentThread().getId());
end
对应的测试 Java 类源码如代码清单 2-38 所示。
代码清单 2-38
packagetest;
importjava.util.Collection;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
public classTest {
public static voidmain(String[] args) {
 KnowledgeBuilder kb=KnowledgeBuilderFactory.newKnowledgeBuilder();
 kb.add(newClassPathResource("test/test.drl"), ResourceType.DRL);
  Collection collection=kb.getKnowledgePackages();
  KnowledgeBase
knowledgeBase=KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(collection);
  StatefulKnowledgeSession
statefulSession=knowledgeBase.newStatefulKnowledgeSession();
  statefulSession.fireAllRules();
http://www.bstek.com
第 49  页  共 74  页
  statefulSession.dispose();
  System.out.println("current thread
id:"+Thread.currentThread().getId());
 }
}
运行测试类,可以得到如图 2-6 所示的结果。
图 2-6
从运行结果可以看到,执行规则和线程与测试类运行的线程的 ID 不同,表示规则不是
在与测试类的线程里运行的,同时,又因为规则执行产生了一个新的线程没有结束,所以可
以看到测试类的执行时一直处于未结束状态,需要手动结束主线程才行。
2.2.3.8.  lock-on-active
当在规则上使用 ruleflow-group 属性或 agenda-group 属性的时候,将 lock-on-action 属性
的值设置为 true,可能避免因某些 Fact 对象被修改而使已经执行过的规则再次被激活执行。
可以看出该属性与 no-loop 属性有相似之处,no-loop 属性是为了避免 Fact 修改或调用
了 insert、retract、update 之类而导致规则再次激活执行,这里的 lock-on-action 属性也是起
这个作用,lock-on-active 是 no-loop 的增强版属性,它主要作用在使用 ruleflow-group 属性
或 agenda-group 属性的时候。lock-on-active 属性默认值为 false。关于该属性的介绍,我们
在后面讲解规则流的时候还涉及到。
2.2.3.9.  activation-group
该属性的作用是将若干个规则划分成一个组,用一个字符串来给这个组命名,这样在执
行的时候,具有相同 activation-group 属性的规则中只要有一个会被执行,其它的规则都将
不再执行。也就是说,在一组具有相同 activation-group 属性的规则当中,只有一个规则会
被执行,其它规则都将不会被执行。当然对于具有相同 activation-group 属性的规则当中究
竟哪一个会先执行,则可以用类似 salience 之类属性来实现。代码清单 2-39 就演示了
http://www.bstek.com
第 50  页  共 74  页
activation-group 属性的用法。
代码清单 2-39
packagetest
rule "rule1"
activation-group "test"
when
eval(true)
then
  System.out.println("rule1 execute");
end
rule "rule 2"
activation-group "test"
when
eval(true)
then
  System.out.println("rule2 execute");
end
代码清单 2-39 中有两个规则:rule1 和 rule2,这两个规则具有相同的 activation-group
属性,这个属性的值为 “test”,前面我们讲过,具有相同 activation-group 属性的规则只会有
一个被执行,其它规则将会被忽略掉,所以这里的 rule1 和 rule2 这两个规则因为具体相同
名称的 activation-group 属性,所以它们只有一个会被执行。
2.2.3.10.  agenda-group
规则的调用与执行是通过 StatelessSession 或 StatefulSession 来实现的,一般的顺序是创
建一个 StatelessSession 或 StatefulSession,将各种经过编译的规则的 package 添加到 session
当中,接下来将规则当中可能用到的 Global 对象和 Fact 对象插入到 Session 当中,最后调用
fireAllRules 方法来触发、执行规则。在没有调用最后一步 fireAllRules 方法之前,所有的规
则及插入的 Fact 对象都存放在一个名叫 Agenda 表的对象当中,这个 Agenda 表中每一个规
则及与其匹配相关业务数据叫做 Activation,在调用 fireAllRules 方法后,这些 Activation 会
依次执行,这些位于 Agenda 表中的 Activation 的执行顺序在没有设置相关用来控制顺序的
属性时(比如 salience 属性),它的执行顺序是随机的,不确定的。
 Agenda Group 是用来在 Agenda 的基础之上,对现在的规则进行再次分组,具体的分组
方法可以采用为规则添加 agenda-group 属性来实现。
http://www.bstek.com
第 51  页  共 74  页
 agenda-group 属性的值也是一个字符串,通过这个字符串,可以将规则分为若干个
Agenda Group,默认情况下,引擎在调用这些设置了 agenda-group 属性的规则的时候需要显
示的指定某个 Agenda Group 得到 Focus (焦点), 这样位于该 Agenda Group 当中的规则才会
触发执行,否则将不执行。
代码清单 2-40 当中有两个规则 rule1 和 rule2, rule1 的 agenda-group 属性值为 001; rule2
的 agenda-group 属性值为 002,这就表示 rule1 和 rule2 这两个规则分别被划分到名为 001 和
002 的两个 Agenda  Group 当中,这样引擎执行这两个规则的时候必须显示的设置名为 001
或 002 的 Agenda Group 得到焦点,这样位于 001 或 002 的 Agenda Group 里的规则才会触发
执行。
代码清单 2-40
packagetest
rule "rule1"
agenda-group "001"
when
eval(true)
then
  System.out.println("rule1 execute");
end
rule "rule 2"
agenda-group "002"
when
eval(true)
then
  System.out.println("rule2 execute");
end
为了测试这两个规则的执行情况,编写测试类,测试类代码如代码清单 2-41 所示。
代码清单 2-41
packagetest;
importjava.util.Collection;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.base.RuleNameStartsWithAgendaFilter;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
http://www.bstek.com
第 52  页  共 74  页
importorg.drools.runtime.StatefulKnowledgeSession;
importorg.drools.runtime.rule.AgendaFilter;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder kb =
KnowledgeBuilderFactory.newKnowledgeBuilder();
  kb.add(newClassPathResource("test/test.drl"),
ResourceType.DRL);
  Collection collection = kb.getKnowledgePackages();
  KnowledgeBase knowledgeBase =
KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(collection);
  StatefulKnowledgeSession statefulSession = knowledgeBase
    .newStatefulKnowledgeSession();
  statefulSession.getAgenda().getAgendaGroup("002").setFocus();
  statefulSession.fireAllRules();
  statefulSession.dispose();
 }
}
在代码清单 2-41 当中,注意如下这句:
statefulSession.getAgenda().getAgendaGroup("002").setFocus();
这句代码的作用是先得到当前的 Agenda,再通过 Agenda 得到名为 002 的 Agenda Group
对象,最后把 Focus 设置到名为 002 的 Agenda Group 当中,这个位于名为 002 的 Agenda Group
中的 rule2 规则会执行,而位于名为 001 的 Agenda Group 当中的 rule1 则不会被执行,因为
名为 001 的 Agenda Group 没有得到 Focus。
运行测试点,可以看到如图 2-7 所示。
图 2-7
实际应用当中 agenda-group 可以和 auto-focus 属性一起使用,这样就不会在代码当中显
示的为某个 Agenda Group 设置 Focus 了。一旦将某个规则的 auto-focus 属性设置为 true,那
么即使该规则设置了 agenda-group 属性,我们也不需要在代码当中显示的设置 Agenda Group
的 Focus 了。
http://www.bstek.com
第 53  页  共 74  页
2.2.3.11.  auto-focus
前面我们也提到 auto-focus 属性,它的作用是用来在已设置了 agenda-group 的规则上设
置该规则是否可以自动独取 Focus,如果该属性设置为 true,那么在引擎执行时,就不需要
显示的为某个 Agenda Group 设置 Focus,否则需要。
对于规则的执行的控制,还可以使用 Agenda  Filter 来实现。在 Drools 当中,提供了一
个名为 org.drools.runtime.rule.AgendaFilter 的 Agenda  Filter 接口,用户可以实现该接口,通
过规则当中的某些属性来控制规则要不要执行。 org.drools.runtime.rule.AgendaFilter 接口只有
一个方法需要实现,方法体如下:
public booleanaccept(Activation activation);
在该方法当中提供了一个 Activation 参数,通过该参数我们可以得到当前正在执行的规
则对象或其它一些属性,该方法要返回一个布尔值,该布尔值就决定了要不要执行当前这个
规则,返回 true 就执行规则,否则就不执行。
代码清单 2-42 中的两个规则设置了 agenda-group 属性,设置了 auto-focus 属性为 true,
这样在引擎在执行这两个规则的时候就不需要显示的为 Agenda Group 设置 Focus。
代码清单 2-42
packagetest
rule "rule1"
agenda-group "001"
auto-focus true
when
eval(true)
then
  System.out.println("rule1 execute");
end
rule "rule2"
agenda-group "002"
auto-focus true
when
eval(true)
then
  System.out.println("rule2 execute");
end
在引擎执行规则的时候,我们希望使用规则名来对要执行的规则做一个过滤,此时就可
以通过 AgendaFilter 来实现,代码清单 2-43 既为我们实现的一个 AgendaFilter 类源码。
http://www.bstek.com
第 54  页  共 74  页
代码清单 2-43
packagetest;
importorg.drools.runtime.rule.Activation;
importorg.drools.runtime.rule.AgendaFilter;
public classTestAgendaFilter implementsAgendaFilter {
privateString startName;
publicTestAgendaFilter(String startName){
this.startName=startName;
 }
public booleanaccept(Activation activation) {
  String ruleName=activation.getRule().getName();
if(ruleName.startsWith(this.startName)){
return true;
  }else{
return false;   
  }
 }
}
从实现类中可以看到,我们采用的过滤方法是规则名的前缀,通过 Activation 得到当前
的 Rule 对象,然后得到当前规则的 name,再用这个 name 与给定的 name 前缀进行比较,
如果相同就返回 true,否则就返回 false。
编写测试类,测试类代码如代码清单 2-44 所示。
代码清单 2-44
packagetest;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
importorg.drools.runtime.rule.AgendaFilter;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder kb =
KnowledgeBuilderFactory.newKnowledgeBuilder();
  kb.add(newClassPathResource("test/test.drl"),
ResourceType.DRL);
http://www.bstek.com
第 55  页  共 74  页
  KnowledgeBase knowledgeBase =
KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(kb.getKnowledgePackages());
  StatefulKnowledgeSession statefulSession = knowledgeBase
    .newStatefulKnowledgeSession();
  AgendaFilter filter=newTestAgendaFilter("rule1");
  statefulSession.fireAllRules(filter);
  statefulSession.dispose();
 }
}
在测试类当中,给出的过滤规则名称的前缀是“rule1”,这样只要当前的 Agenda 当中
有名称以“rule1”开头的规则,那么就会执行,否则将不会被执行,本例子当中符合要求
的就是名为 rule1 的规则。运行测试类,运行结果如图 2-8 所示。
图 2-8
从测试结果来看,我们编写的 AgendaFilter 实现类起到了作用。
2.2.3.12.  ruleflow-group
在使用规则流的时候要用到 ruleflow-group 属性,该属性的值为一个字符串,作用是用
来将规则划分为一个个的组,然后在规则流当中通过使用 ruleflow-group 属性的值,从而使
用对应的规则。后面在讨论规则流的时候还要对该属性进行详细介绍。
2.2.4.  注释
在编写规则的时候适当添加注释是一种好的习惯,好的注释不仅仅可以帮助别人理解你
编写的规则也可以帮助你自己日后维护规则。在 Drools 当中注释的写法与编写 Java 类的注
释的写法完全相同,注释的写法分两种:单行注释与多行注释。
http://www.bstek.com
第 56  页  共 74  页
2.2.4.1.  单行注释
单行注释可以采用“#”或者“//”来进行标记,如代码片段 2-45 中的规则就添加了两
个注释,一个以“//”开头,一个是以“#”开头。
代码清单 2-45
//规则rule1的注释
rule "rule1"
when
eval(true) #没有条件判断
then
  System.out.println("rule1 execute");
end
对于单行注释来说,您可以根据自己的喜好来选择注释的标记符,我个人的习惯所有的
单行注释全部采用“//”来实现,这样就和 Java 语法一致了。
2.2.4.2.  多行注释
如果要注释的内容较多,可以采用 Drools 当中的多行注释标记来实现。Drools 当中的
多行注释标记与 Java 语法完全一样,以“/*”开始,以“*/”结束,代码清单 2-46 演示了
这种用法。
代码清单 2-46
/*
规则rule1的注释
这是一个测试用规则
*/
rule "rule1"
when
eval(true) #没有条件判断
then
  System.out.println("rule1 execute");
end
代码清单 2-46 当中,在规则名称部分,添加了一个多行注释文本,用于表明该规则的
作用,实际应用当中我们推荐在每一个规则开始前添加这么一个多行注释,用于说明该规则
的作用。
http://www.bstek.com
第 57  页  共 74  页
2.3.  函数
函数是定义在规则文件当中一代码块,作用是将在规则文件当中若干个规则都会用到的
业务操作封装起来,实现业务代码的复用,减少规则编写的工作量。
函数的编写位置可以是规则文件当中 package 声明后的任何地方, Drools 当中函数声明
的语法格式如代码清单 2-47 所示:
代码清单 2-47
function void/ObjectfunctionName(Type arg...) {
/*函数体的业务代码*/
}
 Drools 当中的函数以 function 标记开头,如果函数体没有返回值,那么 function 后面就
是 void,如果有返回值这里的 void 要换成对应的返回值对象,接下来就是函数的名称函数
名称的定义可以参考 Java 类当中方法的命名原则,对于一个函数可以有若干个输入参数,
所以函数名后面的括号当中可以定义若干个输入参数。定义输入参数的方法是先声明参数类
型,然后接上参数名,这点和 Java 当中方法的输入参数定义是完全一样的,最后就是用 “{…}”
括起来的业务逻辑代码,业务代码的书写采用的是标准的 Java 语法。
代码清单 2-48 演示了一个简单的函数的编写方法。
代码清单 2-48
function voidprintName(String name) {
 System.out.println("您的名字是:"+name);
}
该函数的名称为 printName,不需要返回值,它需要一个 String 类型的输入参数,函数
体的业务逻辑比较简单,将输入参数组合后在控制台打印出来。
可以看到 Drools 当中函数的定义与 Java 当中方法的定义极为相似,与 Java 当中定义方
法相比,唯一不同之处就是在 Drools 的函数定义当中没有可见范围的设定,而 Java 当中可
以通过 public、 private 之类来设置方法的可见范围。在 Drools 当中函数的可见范围是当前的
函数所在的规则文件,位于其它规则文件当中的规则是不可以调用不在本规则文件当中的函
数的。所以 Drools 当中函数的可见范围可以简单将其与 Java 当中方法的 private 类型划上等
号。下面我们来看一个函数应用的例子,加深对 Drools 函数的理解。
代码清单 2-49 当中的规则文件包含一个函数和两个规则,在这两个规则当中都调用了
这个函数。
http://www.bstek.com
第 58  页  共 74  页
代码清单 2-49
packagetest
importjava.util.List;
importjava.util.ArrayList;
/*
一个测试函数
用来向Customer对象当中添加指定数量的Order对象的函数
*/
function voidsetOrder(Customer customer,intorderSize) {
 List ls=newArrayList();
for(inti=0;i   Order order=newOrder();
  ls.add(order);   
 }
 customer.setOrders(ls);
}
/*
测试规则
*/
rule "rule1"
when
  $customer :Customer();
then
  setOrder($customer,5);
  System.out.println("rule 1 customer has order
size:"+$customer.getOrders().size());
end
/*
测试规则
*/
rule "rule2"
when
  $customer :Customer();
then
  setOrder($customer,10);
  System.out.println("rule 2 customer has order
size:"+$customer.getOrders().size());
end
这个函数没有返回值,它有两个输入参数,第一个参数为一个 Customer 对象,第二个
http://www.bstek.com
第 59  页  共 74  页
为一个数字,用来决定添加到 Customer 对象当中 Order 的数量。可以看到这个函数体就是
用标准的 Java 语法编写,很容易理解。
在 Drools 当中,函数的调用通常是在规则的 RHS 部分中完成,在名为 rule1 和 rule2 的
这两个规则当中 RHS 部分都调用了 setOrder 函数,调用完成后将当前的 Customer 对象所拥
有的 Order 数量在控制台打印出来,从而验证函数调用后对 Customer 对象产生的影响。
编写测试类,测试类代码如代码清单 2-50 所示。
代码清单 2-50
packagetest;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder kb =
KnowledgeBuilderFactory.newKnowledgeBuilder();
  kb.add(newClassPathResource("test/test.drl"),
ResourceType.DRL);
  KnowledgeBase knowledgeBase =
KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(kb.getKnowledgePackages());
  StatefulKnowledgeSession statefulSession = knowledgeBase
    .newStatefulKnowledgeSession();
  statefulSession.insert(newCustomer());
  statefulSession.fireAllRules();
  statefulSession.dispose();
 }
}
运行测试类,可以看到如图 2-9 所示的结果。
图 2-9
http://www.bstek.com
第 60  页  共 74  页
从运行结果中可以看到,经过函数的调用, Customer 对象当中 Order 的数量已发生变化。
实际应用当中,可以考虑使用在 Java 类当中定义静态方法的办法来替代在规则文件当
中定义函数。我们知道 Java 类当中的静态方法,不需要将该类实例化就可以使用该方法,
利用这种特性, Drools 为我们提供了一个特殊的 import 语句: import function,通过该 import
语句,可以实现将一个 Java 类中静态方法引入到一个规则文件当中,使得该文件当中的规
则可以像使用普通的 Drools 函数一样来使用 Java 类中某个静态方法。
代码清单 2-51 是一个包括名为 printInfo 静态方法的 Java 类。
代码清单 2-51
packagetest;
public classRuleTools {
public static voidprintInfo(String name){
  System.out.println("your name is :"+name);
 }
}
名为 RuleTools 类中静态方法 printInfo 在规则当中调用方法如代码清单 2-52 所示。
代码清单 2-52
packagetest
import functiontest.RuleTools.printInfo;
/*
测试规则
*/
rule "rule1"
when
eval(true);
then
  printInfo("高杰");
end
在代码清单 2-52 中,通过使用 import  function 关键字,将 test.RuleTools 类中静态方法
printInfo 引入到当前规则文件中,在名为 rule1 的规则的 RHS 当中,将 printInfo 静态方法作
为一个普通的 Drools 函数来进行使用,有兴趣的读者可以编写测试类,测试一下该规则的
运行结果。
http://www.bstek.com
第 61  页  共 74  页
2.4.  查询
查询是 Drools 当中提供的一种根据条件在当前的 WorkingMemory 当中查找 Fact 的方
法。查询是定义在规则文件当中,和函数一样,查询的定义可以是 package 语句下的任意位
置,在 Drools 当中查询可分为两种:一种是不需要外部传入参数;一种是需要外部传入参
数。下面我们就分别对这两种类型的查询进行介绍。
2.4.1.  无参数查询
在 Drools 当中查询以 query 关键字开始,以 end 关键字结束,在 package 当中一个查询
要有唯一的名称,查询的内容就是查询的条件部分,条件部分内容的写法与规则的 LHS 部
分写法完全相同。查询的格式如下:
query "query name"
#conditions
end
代码清单 2-53 是一个简单的查询示例。
代码清单 2-53
query "testQuery"
 customer:Customer(age>30,orders.size >10)
end
在这个查询当中,查询的名称为 testQuery,条件是取到所有的 age>30,并且拥有 Order
数量大于 10 的 Customer 对象的集合。从这个示例当中可以看出,条件的写法与规则的 LHS
部分完全相同。
查 询 的 调 用 是 由 StatefulSession 完 成 的 , 通 过 调 用 StatefulSession 对 象 的
getQueryResults(String  queryName) 方法实现对查询的调用,该方法的调用会返回一个
QueryResults 对象,QueryResults 是一个类似于 Collection 接口的集合对象,在它当中存放
在若干个 QueryResultsRow 对象,通过 QueryResultsRow 可以得到对应的 Fact 对象,从而实
现根据条件对当前 WorkingMemory 当中 Fact 对象的查询。我们来编写一个针对于代码清单
2-53 当中查询的测试用例。
代码清单 2-54
packagetest;
importjava.util.ArrayList;
http://www.bstek.com
第 62  页  共 74  页
importjava.util.List;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
importorg.drools.runtime.rule.QueryResults;
importorg.drools.runtime.rule.QueryResultsRow;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder kb =
KnowledgeBuilderFactory.newKnowledgeBuilder();
  kb.add(newClassPathResource("test/test.drl"),
ResourceType.DRL);
  KnowledgeBase knowledgeBase =
KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(kb.getKnowledgePackages());
  StatefulKnowledgeSession statefulSession = knowledgeBase
    .newStatefulKnowledgeSession();
//向当前WorkingMemory当中插入Customer对象
  statefulSession.insert(generateCustomer("张三",20,21));
  statefulSession.insert(generateCustomer("李四",33,11));
  statefulSession.insert(generateCustomer("王二",43,12));
//调用查询
  QueryResults
queryResults=statefulSession.getQueryResults("testQuery");
for(QueryResultsRow qr:queryResults){
   Customer cus=(Customer)qr.get("customer");
//打印查询结果
   System.out.println("customer name :"+cus.getName());
  }
  statefulSession.dispose();
 }
/**
 * 产生包括指定数量Order的Customer
 * */
public staticCustomer generateCustomer(String name,intage,int
orderSize){
  Customer cus=newCustomer();
http://www.bstek.com
第 63  页  共 74  页
  cus.setName(name);
  cus.setAge(age);   
  Listls=newArrayList();
for(inti = 0; i < orderSize; i++) {
   ls.add(newOrder());
  }
  cus.setOrders(ls);
returncus;
 }
}
在代码清单 2-54 的测试用例当中,向当前 WorkingMemory 中插入了三个 Customer 对
象,其中只有后两个能满足 testQuery 的查询条件,所以在查询结果输入当中只能在控制台
输出后两个 Customer 的 name 属性的值。测试用例运行结果如图 2-10 所示。
图 2-10
2.4.2.  参数查询
和函数一样,查询也可以接收外部传入参数,对于可以接收外部参数的查询格式如下:
query "query name"(Object obj,...)
#conditions
end
和不带参数的查询相比,唯一不同之外就是在查询名称后面多了一个用括号括起来的输
入参数,查询可接收多个参数,多个参数之间用“,”分隔,每个参数都要有对应的类型声
明,代码清单 2-55 演示了一个简单的带参数的查询示例。
代码清单 2-55
query "testQuery"(int$age,String $gender)
 customer:Customer(age>$age,gender==$gender)
end
在这个查询当中,有两个外部参数需要传入,一个是类型为 int 的 $age;一个是类型为
String 的$gender(这里传入参数变量名前添加前缀“$”符号,是为了和条件表达式中相关
变量区别开来)。 对于带参数的查询,可以采用 StatefulSession 提供的 getQueryResults(String
http://www.bstek.com
第 64  页  共 74  页
queryName,new Object[]{})方法来实现,这个方法中第一个参数为查询的名称,第二个 Object
对象数组既为要输入的参数集合。代码清单 2-56 演示了代码清单 2-55 中查询的调用方法。
代码清单 2-56
packagetest;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
importorg.drools.runtime.rule.QueryResults;
importorg.drools.runtime.rule.QueryResultsRow;
public classTest {
public static voidmain(String[] args) {
  KnowledgeBuilder kb =
KnowledgeBuilderFactory.newKnowledgeBuilder();
  kb.add(newClassPathResource("test/test.drl"),
ResourceType.DRL);
  KnowledgeBase knowledgeBase =
KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(kb.getKnowledgePackages());
  StatefulKnowledgeSession statefulSession = knowledgeBase
    .newStatefulKnowledgeSession();
//向当前WorkingMemory当中插入Customer对象
  statefulSession.insert(generateCustomer("张三",20,"F"));
  statefulSession.insert(generateCustomer("李四",33,"M"));
  statefulSession.insert(generateCustomer("王二",43,"F"));
//调用查询
  QueryResults
queryResults=statefulSession.getQueryResults("testQuery", new
Object[]{newInteger(20),"F"});
for(QueryResultsRow qr:queryResults){
   Customer cus=(Customer)qr.get("customer");
//打印查询结果
   System.out.println("customer name :"+cus.getName());
  }
  statefulSession.dispose();
 }
/**
http://www.bstek.com
第 65  页  共 74  页
 * 产生Customer对象
 * */
public staticCustomer generateCustomer(String name,intage,String
gender){
  Customer cus=newCustomer();
  cus.setAge(age);
  cus.setName(name);
  cus.setGender(gender);
returncus;
 }
}
测试用例当中,在调用 getQueryResults 方法,执行名为 testQuery 的查询结果时,我们
给出的参数是 age>20,且 gender=”F”的条件,执行测试用例可以看到如图 2-11 所示的输出
结果。
图 2-11
从输出结果来看,满足条件的只有 name 属性为“王二”的 Customer 对象。
2.5.  对象定义
在 Drools 当中,可以定义两种类型的对象:一种是普通的类型 Java  Fact 的对象;另一
种是用来描述 Fact  对象或其属性的元数据对象。
2.5.1.  Fact 对象定义
我们知道在 Drools 当中是通过向 WorkingMemory 中插入 Fact 对象的方式来实现规则引
擎与业务数据的交互,对于 Fact 对象就是普通的具有若干个属性及其对应的 getter 与 setter
方法的 JavaBean 对象。 Drools 除了可以接受用户在外部向 WorkingMemory 当中插入现成的
Fact 对象,还允许用户在规则文件当中定义一个新的 Fact 对象。
在规则文件当中定义 Fact 对象要以 declare 关键字开头,以 end 关键字结尾,中间部分
就是该 Fact 对象的属性名及其类型等信息的声明。代码清单 2-57 是一个简单的在规则当中
http://www.bstek.com
第 66  页  共 74  页
定义的 Fact 对象示例。
代码清单 2-57
declareAddress
 city : String
 addressName : String
end
在代码清单 2-57 当中定义的 Fact 对象名为 Address,它有两个皆为 String 类型的,名为
city 和 addressName 属性,该对象与代码清单 2-58 当中定义务 Address 对象是等效的。
代码清单 2-58
public classAddress {
privateString city;
privateString addressName;
publicString getCity() {
return city;
 }
public voidsetCity(String city) {
this.city= city;
 }
publicString getAddressName() {
return addressName;
 }
public voidsetAddressName(String addressName) {
this.addressName= addressName;
 }
}
相比代码清单 2-58 当中定义的 Address 对象,在代码清单 2-57 当中定义的 Address 对
象只有属性的声明,没有与这些属性对应的 getter 与 setter 方法的定义。
通过 declare 关键字声明了一个 Fact 对象之后,在规则当中可以像使用普通的 JavaBean
一样来使用我们声明好的 Fact 对象,代码清单 2-59 演示了在规则当中使用 Address 对象的
情形。
代码清单 2-59
declareAddress
 city : String
 addressName : String
end
rule "rule1"
salience2
when
http://www.bstek.com
第 67  页  共 74  页
eval(true);
then
#使用定义的Address对象,并利用insert操作符将其插入到当前的
WorkingMemory当中
  Address add=newAddress();
  add.setCity("中国上海");
  add.setAddressName("中国上海松江区");
insert(add);
end
rule "rule2"
salience1
when
  $add:Address()
then
  System.out.println($add.getCity()+","+$add.getAddressName());
end
在代码清单 2-59 当中,有一个通过 declare 关键字定义的名为 Address 的 Fact 对象,在
名为 rule1 的规则当中 RHS 部分创建了这个 Address 对象的实例,分别设置了该实例对象的
city 属性及 addressName 属性的值,最后将其插入到当前的 WorkingMemory 当中;名为 rule2
的规则首先判断了当前的 WorkingMemory 当中有没有 Address 对象的实例,如果有就在其
RHS 部分当中在控制台打印出它的 city 属性及 addressName 属性的值,
在 rule1 当中,我们设置了它的 salience 属性值为 2,rule2 的 salience 属性值为 1,因为
rule1 的 salience 属性值大于 rule2 的,所经它的优先级较高,会先于 rule1 执行。我们这样
做的目的是想让 rule1 先执行,通过它的 RHS 部分向当前的 WorkingMemory 当中插入一个
Address 对象,这样 rule2 再执行的时候就可以检测到当前 WorkingMemory 当中有一个
Address 对象,就可以通过其 RHS 部分打印出 Address 对象的 city 与 addressName 属性的值。
事实上,因为我们在 rule1 当中通过 insert 操作符将 Address 对象的实例插入到当前的
WorkingMemory 当中,根据前面对于 insert 操作符的介绍我们知道,不管 rule2 有没有执行
过,因为有新对象插入到当前 WorkingMemory 当中,所以 rule2 都会再执行一次,所以这里
rule1 与 rule2 的属性不设置也同样可以达到目的。
编写测试类运行代码清单 2-59 当中的规则,可以看到如图 2-12 所示的结果。
图 2-12
http://www.bstek.com
第 68  页  共 74  页
在使用 declare 关键字来声明一个 Fact 对象时,对于属性类型的定义,除了可以使用类
似 java.lang.String 之类的简单类型之外,也可以使用另外一些 JavaBean 作为某个属性的类
型,代码清单 2-60 演示了这种用法。
代码清单 2-60
importjava.util.Date;
declareAddress
 city : String
 addressName : String
end
declarePerson
 name:String
 birthday:Date
 address:Address //使用declare声明的对象作为address属性类型
 order:Order //使用名为Order的JavaBean作为order属性类型
end
rule "rule1"
when
eval(true);
then
#使用定义的Address对象,并利用insert操作符将其插入到当前的
WorkingMemory当中
  Address add=newAddress();
  add.setCity("中国上海");
  add.setAddressName("中国上海松江区");
  Order order=newOrder();
  order.setName("测试订单");
  Person person=newPerson();
  person.setName("高杰");
  person.setBirthday(newDate());
  person.setAddress(add);//将Address对象的实例赋给address属性
  person.setOrder(order);//将Order对象实例赋给order属性
insert(person);
end
rule "rule2"
when
  $person:Person()
then
http://www.bstek.com
第 69  页  共 74  页
  System.out.println("姓名:"+$person.getName()+"\r住址:
"+$person.getAddress().getAddressName()+"\r拥有的订单:
"+$person.getOrder().getName());
end
代码清单 2-60 当中,通过 declare 关键字我们声明了一个名为 Person 的 Fact 对象,该
对象的 birthday 属性类型是 java.util.Date 类型,它的 address 属性类型是一个通过 declare 关
键字声明的名为 Address 的 Fact 对象,它的 order 属性类型是一个在外部定义的名为 Order
的 JavaBean 对象。
在 rule1 的 RHS 部分中,对 Person 对象的所有属性进行了初始化,最后将其插入到当
前的 WorkingMemory 当中,因为是使用 insert 操作符插入到 WorkingMemory,所以在这里
就没有使用设置优先级的 salience 属性。名为 rule2 规则的 LHS 部分首先也是判断当前
WorkingMemory 当中有没有 Person 对象的实例,如果存在,那么就在其 RHS 部分中向控制
台打印该 Person 对象的 name 属性、address 属性及 order 属性,运行这两个规则可以看到如
图 2-13 所示的结果。
图 2-13
在 Java 类中,可以通过 KnowledgeBase 对象得到在规则文件当中使用 declare 关键字定
义的 Fact 对象、可以将其实例化、可以为该实例化对象赋值、同时可以将其插入到当前的
WorkingMemory 当中。代码清单 2-61 演示了这种用法。
代码清单 2-61
packagetest;
importorg.drools.KnowledgeBase;
importorg.drools.KnowledgeBaseFactory;
importorg.drools.builder.KnowledgeBuilder;
importorg.drools.builder.KnowledgeBuilderFactory;
importorg.drools.builder.ResourceType;
importorg.drools.definition.type.FactType;
importorg.drools.io.impl.ClassPathResource;
importorg.drools.runtime.StatefulKnowledgeSession;
public classTest {
public static voidmain(String[] args) throwsException{
http://www.bstek.com
第 70  页  共 74  页
  KnowledgeBuilder kb =
KnowledgeBuilderFactory.newKnowledgeBuilder();
  kb.add(newClassPathResource("test/demo.drl"),
ResourceType.DRL);
  KnowledgeBase knowledgeBase =
KnowledgeBaseFactory.newKnowledgeBase();
  knowledgeBase.addKnowledgePackages(kb.getKnowledgePackages());
  Order order=newOrder();
  order.setName("测试定单");
//获取规则文件当中定义的Address对象并对其进行实例化
  FactType
addressType=knowledgeBase.getFactType("test","Address");
  Object add=addressType.newInstance();
  addressType.set(add, "city","中国上海");
  addressType.set(add, "addressName","中国上海松江区");
//获取规则文件当中定义的Person对象并对其进行实例化
  FactType
personFact=knowledgeBase.getFactType("test","Person");
  Object obj=personFact.newInstance();//实例化该对象f
  personFact.set(obj,"name","高杰");//设置该对象的name属性
  personFact.set(obj, "order", order);
  personFact.set(obj, "address", add);
  StatefulKnowledgeSession statefulSession = knowledgeBase
    .newStatefulKnowledgeSession();
//将实例化好的Person对象插入到当前的WorkingMemory当中
  statefulSession.insert(obj);
  statefulSession.fireAllRules();
  statefulSession.dispose();
 }
}
此时删除 2-60 代码当中 rule1 这个规则,运行这个测试类,可以得到如图 2-13 所示的
结果。
2.5.2.  元数据定义
我们知道,元数据就是用来描述数据的数据。在 Drools5 当中,可以为 Fact 对象、Fact
对象的属性或者是规则来定义元数据,元数据定义采用的是 “@”符号开头,后面是元数据
http://www.bstek.com
第 71  页  共 74  页
的属性名 (属性名可以是任意的),然后是括号,括号当中是该元数据属性对应的具体值 (值
是任意的)。具体格式如下:
@metadata_key( metadata_value )
在这里 metadata_value 属性值是可选的。具体实例如下:
@author(gaojie)
这个元数据属性名为 author,值为 gaojie。
代码清单 2-62
declareUser
 @author(gaojie)
 @createTime(2009-10-25)
 username : String @maxLenth(30)
 userid : String @key
 birthday : java.util.Date
end
代码清单 2-62 是一个元数据在通过 declare 关键字声明对象时的具体应用实例,在这个
例子当中,一共有四个元数据的声明,首先对于 User 对象有两个元数据的声明:author 说
明这个对象的创建者是 gaojie; createTime 说明创建的时间为 2009-10-25 日。接下来是对 User
对象的 username 属性的描述,说明该属性的 maxLenth 的值为 30。最后是对 userid 属性的
描述,注意这个描述只有一个无数据的属性名 key,而没有为这个属性 key 赋于具体的值。
3.  DSL
DSL 的全称是 Domain Specific Language,翻译过来可称之为 “域特定语言”,是 Drools5
当中的一种模版,这个模版可以描述规则当中的条件 (LHS 部分)或规则当中的条件 (RHS
部分),这样在使用的时候可以可以在规则当中多处引用这个模版,同时因为模版的定义方
式采用的是 key-value 键值对对应的方式,所以通过在 key 当中使用自然语言就可以实现用
自然语言的方式来描述规则的方法。
我们知道,规则文件是一个文本文件,除了使用 Drools5 提供的规则编辑器之外,如果
对规则编写语法特别熟悉,完全可以采用记事本来编写业务规则。DSL 的定义也是一个文
本文件,一个具有属性文件结构的文本文件,一旦在这个文件当中定义好 DSL 后,就可以
在规则文件当中引用这个 DSL 文件,使用这个文件当中定义好的 DSL。 Drools5 也为我们提
供了编写 DSL 文件的编辑器,通过这个编辑器,可以实现对 DSL 的快速定义。
http://www.bstek.com
第 72  页  共 74  页
3.1. DSL 文件的创建
DSL 文件的建立,可以借助于 Drools5 提供的 Eclipse 插件来完成,打开 Eclipse,选择
菜单 FileNewOther,在弹出的窗口中展开列表树当中的 Drools 节点,选择其下的 Domain
Specific Language,如图 3-1 所示。
图 3-1
点击 Next 按钮,输入 DSL 文件名及其要存放的文件夹后,点击 Finish 按钮后 IDE 会自
动将当前新建的 DSL 文件用 Drools5 提供的 IDE 打开,如图 3-2 所示。
http://www.bstek.com
第 73  页  共 74  页
图 3-2
默认情况下,新创建的 DSL 文件会包含若干个 DSL 语句的示例,这些 DSL 示例语句
向我们演示了 DSL 的语法规范。
通过使用 Drools5 为提供的 DSL 文件编辑器,可以实现对 DSL 语句的增加、删除、修
改及排序操作。
http://www.bstek.com
第 74  页  共 74  页
3.2.  使用 DSL
4.  规则流


你可能感兴趣的:(java)