Bootstrap

开源业务规则引擎JBoss Drools入门介绍 Demo示例

1.1 现状


Drools简介
Drools(JBoss Rules )具有一个易于访问企业策略、易于调整以及易于管理的开源业务 规则引擎,符合业内标准,速度快、效率高。业务分析师或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。
JBoss Rules 的前身是Codehaus的一个开源项目叫Drools。最近被纳入JBoss门下,更名为JBoss Rules,成为了JBoss 应用服务器的规则引擎。
Drools是为Java量身定制的基于Charles Forgy的 RETE算法的规则引擎的实现。具有了OO接口的RETE,使得商业规则有了更自然的表达。
Drools的用XML的<Conditons>、<Consequence> 节点表达If--Then句式,而里面可以嵌入上述语言的代码作为判断语句和执行语句。
其中Java代码会使用Antlr进行解释,而Groovy和Python本身就是脚本语言,可以直接调用。
Drools的聪明之处在于,用XML节点来规范If--Then句式和事实的定义,使引擎干起活来很舒服。
而使用Java,Groovy等原生语言来做判断和执行语句,让程序员很容易过渡、移植,学习曲线很低。


在很多行业应用中比如银行、保险领域,业务规则往往非常复杂,并且规则处于不断更新变化中,而现有很多系统做法基本上都是将业务规则绑定在程序代码中。

1.2 问题

主要存在的问题有以下几个方面:

1) 当业务规则变更时,对应的代码也得跟着更改,每次即使是小的变更都需要经历开发、测试验证上线等过程,变更成本比较大。

2) 长时间系统变得越来越难以维护。

3) 开发团队一般是由一个熟悉业务的BA(业务分析人员)和若干个熟悉技术的开发人员组成,开发人员对业务规则的把握能力远不及BA,但实际上却承担了将业务规则准确无误实现的重任。

4) 系统僵化,新需求插入困难。

5) 新需求上线周期较长。

1.3 解决方案

能否让我们的业务系统更灵活一点呢?

思路:将业务规则从技术实现中提取出来,实现技术和业务分离,开发人员处理 技术、业务分析人员定义业务规则,各自做自己所擅长的事情。

方案:目前已经有比较成熟的开源产品支持,这就是本文所要介绍的Drools,我们将业务规则定义在Database或者BRMS(Business Rule Management System)中,通过管理DB或者BRMS实现业务逻辑的动态改变。

1.4 适用情景

什么时候应该使用规则引擎?

虽然规则引擎能解决我们的许多问题,但我们还需要认真考虑一下规则引擎对我

们的项目本身是否是合适的。需要关注的点有:

Ø 我的应用程序有多复杂?

对于那些只是把数据从数据库中传入传出,并不做更多事情的应用程序,最好不要使用规则引擎。但是,当在Java中有一定量的商业逻辑处理的话,可以考虑Drools的使用。这是因为很多应用随着时间的推移越来越复杂,而Drools可以让你更轻松应对这一切。

Ø 我的应用的生命周期有多久?

如果我们应用的生命周期很短,也没有必要使用Drools,使用规则引擎将会在中长期得到好处。

Ø 我的应用需要改变吗?

这个答案一般情况下是肯定的,“这世界唯一不变的只有变化”,我们需求也是这样的,无论是在开发过程中或是在开发完成以后,Drools能从频繁变化的需求中获得好处。

2 什么是规则引擎

规则引擎是基于规则的专家系统的核心部分,主要由三部分组成:规则库(Knowledge base)+Working Memory(Fact base)+推理机(规则引擎),规则引擎根据既定事实和知识库按照一定的算法执行推理逻辑得到正确的结果。

3 Drools简介

Drools 是一个基于Charles Forgy's的RETE算法的,易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准,速度快、效率高。

业务分析师人员或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。

4 竞争产品比较

与Drools功能类似的同类开源产品主要有:OpenRules、OpenLexicon等,商业产品功能比较强也比较贵,这里不做比较,主要差别如下表:

Drools

OpenRules

OpenLexicon

规则表示方法

支持以下四种:

The Rule Language

Domain Specific Language Decision Tables

XML Rule Language

Decision Table

无规则语言,通过Web界面配置规则,并保存在数据库中

规则算法

RETE算法

不详

不祥

规则开发

Rule IDE(Eclipse插件)

Excel

XML

Excel

基于Web的配置界面

规则知识库

支持XML、Excel、BRMS

Excel

Database

易用性

通过Rule IDE,可以方便地编辑DRL和DSL文件

业务人员直接使用Decision Table估计还是有一定的难度

不太好用

开放性

非常开放

不开放

不开放

可拓展性

通过与J2EE技术结合,具有很强的拓展性。

不太好

很不好

JSR-94标准

支持

支持

不支持

成熟度

较低

比较新,不成熟

厂商背景

JBoss

小厂商

小厂商

5 开发环境搭建

打开Drools主页(http://www.jboss.org/drools),我们可以看到如下界面。

clip_image002

其中,Drools Expert是我们想要的,这个是规则引擎。

Drools Guvnor这个是BRMS系统,如果我们需要将业务规则存储在BRMS中,可以安装Guvnor。

Drools Flow这个是工作流引擎,本文不作介绍。

选择Drools Expert进入如下页面:

clip_image004

下载红色的这三项。

安装Drools:

l 解压缩drools-5.0-bin.zip到任意目录,比如:D:/tools/drools/drools-5.0-bin

解压缩drools-5.0-eclipse-all.zip到eclipse的安装目录

l 启动eclipse,查看工具栏是否有drools的图标,如果有则说明安装正常。

否则,打开菜单->windows->open perspective->other->drools。如果perspective当中也没有,请删除eclipse/configuration/org.eclipse.update目录,再重启eclipse。

l 配置drools的Runtime环境,就是要指向drools-5.0-bin目录

clip_image006

clip_image008

至此,开发环境已经搭建好了。

6 创建HelloWorld程序

创建一个HelloWorld的程序也很简单。

l 新创建一个项目,选Drools Project

clip_image010

输入项目名称和路径:

clip_image012

选择Demo文件,这里我们全部勾选:

clip_image014

点完Finish,可以看到如下界面,我们的Demo已经创建完了。

clip_image016

运行DroolsTest和DecisionTableTest类,均可以看到有如下输出:

Hello World

Goodbye cruel world

到这里我们的HelloWorld已经创建完了,没有写一行代码,简单吧?

主要生成文件:

Sample.drl—基于DRL的规则库

Sample.xls—基于Decision table 的规则库

DroolsTest—Sample.drl的测试代码

主要代码如下:

public classDroolsTest {

public static final voidmain(String[] args) {

try{

// load up the knowledge base

KnowledgeBase kbase =readKnowledgeBase();

StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();

KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, "test");

// go !

Message message =newMessage();

message.setMessage("Hello World");

message.setStatus(Message.HELLO);

ksession.insert(message);

ksession.fireAllRules();

logger.close();

}catch(Throwable t) {

t.printStackTrace();

}

}

private staticKnowledgeBase readKnowledgeBase()throwsException {

KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();

kbuilder.add(ResourceFactory.newClassPathResource("Sample.drl"), ResourceType.DRL);

KnowledgeBuilderErrors errors = kbuilder.getErrors();

if(errors.size() > 0) {

for(KnowledgeBuilderError error : errors) {

System.err.println(error);

}

throw newIllegalArgumentException("Could not parse knowledge.");

}

KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();

kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());

returnkbase;

}

public static classMessage {

public static final intHELLO= 0;

public static final intGOODBYE= 1;

privateString message;

private intstatus;

publicString getMessage() {

return this.message;

}

public voidsetMessage(String message) {

this.message = message;

}

public intgetStatus() {

return this.status;

}

public voidsetStatus(intstatus) {

this.status = status;

}

}

}

主要功能:

1) 读取并创建知识库。

2) 创建会话和事实库。

3) 调用规则引擎执行规则并输出结果。

DecisionTableTest—Sample.xls的测试代码。

7 规则语言

在我们生成的HelloWorld里我们可以有2个文件Sample.drl和Sample.xls,这2个文件都是用来定义规则库的,Sample.xls我就不介绍了,用excel打开可以看到注释。

对于Sample.drl文件,Drools定义了一套自己的语言来描述规则。

格式为:

Rule 规则名

When 相当于IF,使用java bean里面的属性进行条件判断。

Then 满足条件后执行一定的操作,在这里返回结果或者更新状态信息。

End

clip_image018

本文做入门介绍,需要更深入了解可以看Drools的在线文档。

8 在线资料

百度文库《Drools5规则引擎开发教程》

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

JBoss官方文档:

http://www.jboss.org/drools/documentation.html

网友资料:

http://hi.baidu.com/caijian5219999/blog/category/drools%D2%B5%CE%F1%B9%E6%D4%F2%D2%FD%C7%E6

http://hi.baidu.com/caijian5219999/home



Drools Demo示例:

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-activetrue:通过这个标签,可以控制当前的规则只会被执行一次,因为一个规则的重复执行不一定是本身触发的,也可能是其他规则触发的,所以这个是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<String> 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 关键字

functionvoidconsole {

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. *@authorquzishen
  4. */
  5. publicclassPointDomain{
  6. //用户名
  7. privateStringuserName;
  8. //是否当日生日
  9. privatebooleanbirthDay;
  10. //增加积分数目
  11. privatelongpoint;
  12. //当月购物次数
  13. privateintbuyNums;
  14. //当月退货次数
  15. privateintbackNums;
  16. //当月购物总金额
  17. privatedoublebuyMoney;
  18. //当月退货总金额
  19. privatedoublebackMondy;
  20. //当月信用卡还款次数
  21. privateintbillThisMonth;
  22. /**
  23. *记录积分发送流水,防止重复发放
  24. *@paramuserName用户名
  25. *@paramtype积分发放类型
  26. */
  27. publicvoidrecordPointLog(StringuserName,Stringtype){
  28. System.out.println("增加对"+userName+"的类型为"+type+"的积分操作记录.");
  29. }
  30. publicStringgetUserName(){
  31. returnuserName;
  32. }
  33. //其他gettersetter方法省略
  34. }

定义积分规则接口

[java] view plain copy
  1. /**
  2. *规则接口
  3. *@authorquzishen
  4. */
  5. publicinterfacePointRuleEngine{
  6. /**
  7. *初始化规则引擎
  8. */
  9. publicvoidinitEngine();
  10. /**
  11. *刷新规则引擎中的规则
  12. */
  13. publicvoidrefreshEnginRule();
  14. /**
  15. *执行规则引擎
  16. *@parampointDomain积分Fact
  17. */
  18. publicvoidexecuteRuleEngine(finalPointDomainpointDomain);
  19. }

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

[java] view plain copy
  1. importjava.io.File;
  2. importjava.io.FileNotFoundException;
  3. importjava.io.FileReader;
  4. importjava.io.IOException;
  5. importjava.io.Reader;
  6. importjava.util.ArrayList;
  7. importjava.util.List;
  8. importorg.drools.RuleBase;
  9. importorg.drools.StatefulSession;
  10. importorg.drools.compiler.DroolsParserException;
  11. importorg.drools.compiler.PackageBuilder;
  12. importorg.drools.spi.Activation;
  13. /**
  14. *规则接口实现类
  15. *@authorquzishen
  16. */
  17. publicclassPointRuleEngineImplimplementsPointRuleEngine{
  18. privateRuleBaseruleBase;
  19. /*(non-Javadoc)
  20. *@seecom.drools.demo.point.PointRuleEngine#initEngine()
  21. */
  22. publicvoidinitEngine(){
  23. //设置时间格式
  24. System.setProperty("drools.dateformat","yyyy-MM-ddHH:mm:ss");
  25. ruleBase=RuleBaseFacatory.getRuleBase();
  26. try{
  27. PackageBuilderbackageBuilder=getPackageBuilderFromDrlFile();
  28. ruleBase.addPackages(backageBuilder.getPackages());
  29. }catch(DroolsParserExceptione){
  30. e.printStackTrace();
  31. }catch(IOExceptione){
  32. e.printStackTrace();
  33. }catch(Exceptione){
  34. e.printStackTrace();
  35. }
  36. }
  37. /*(non-Javadoc)
  38. *@seecom.drools.demo.point.PointRuleEngine#refreshEnginRule()
  39. */
  40. publicvoidrefreshEnginRule(){
  41. ruleBase=RuleBaseFacatory.getRuleBase();
  42. org.drools.rule.Package[]packages=ruleBase.getPackages();
  43. for(org.drools.rule.Packagepg:packages){
  44. ruleBase.removePackage(pg.getName());
  45. }
  46. initEngine();
  47. }
  48. /*(non-Javadoc)
  49. *@seecom.drools.demo.point.PointRuleEngine#executeRuleEngine(com.drools.demo.point.PointDomain)
  50. */
  51. publicvoidexecuteRuleEngine(finalPointDomainpointDomain){
  52. if(null==ruleBase.getPackages()||0==ruleBase.getPackages().length){
  53. return;
  54. }
  55. StatefulSessionstatefulSession=ruleBase.newStatefulSession();
  56. statefulSession.insert(pointDomain);
  57. //fire
  58. statefulSession.fireAllRules(neworg.drools.spi.AgendaFilter(){
  59. publicbooleanaccept(Activationactivation){
  60. return!activation.getRule().getName().contains("_test");
  61. }
  62. });
  63. statefulSession.dispose();
  64. }
  65. /**
  66. *从Drl规则文件中读取规则
  67. *@return
  68. *@throwsException
  69. */
  70. privatePackageBuildergetPackageBuilderFromDrlFile()throwsException{
  71. //获取测试脚本文件
  72. List<String>drlFilePath=getTestDrlFile();
  73. //装载测试脚本文件
  74. List<Reader>readers=readRuleFromDrlFile(drlFilePath);
  75. PackageBuilderbackageBuilder=newPackageBuilder();
  76. for(Readerr:readers){
  77. backageBuilder.addPackageFromDrl(r);
  78. }
  79. //检查脚本是否有问题
  80. if(backageBuilder.hasErrors()){
  81. thrownewException(backageBuilder.getErrors().toString());
  82. }
  83. returnbackageBuilder;
  84. }
  85. /**
  86. *@paramdrlFilePath脚本文件路径
  87. *@return
  88. *@throwsFileNotFoundException
  89. */
  90. privateList<Reader>readRuleFromDrlFile(List<String>drlFilePath)throwsFileNotFoundException{
  91. if(null==drlFilePath||0==drlFilePath.size()){
  92. returnnull;
  93. }
  94. List<Reader>readers=newArrayList<Reader>();
  95. for(StringruleFilePath:drlFilePath){
  96. readers.add(newFileReader(newFile(ruleFilePath)));
  97. }
  98. returnreaders;
  99. }
  100. /**
  101. *获取测试规则文件
  102. *
  103. *@return
  104. */
  105. privateList<String>getTestDrlFile(){
  106. List<String>drlFilePath=newArrayList<String>();
  107. drlFilePath
  108. .add("D:/workspace2/DroolsDemo/src/com/drools/demo/point/addpoint.drl");
  109. drlFilePath
  110. .add("D:/workspace2/DroolsDemo/src/com/drools/demo/point/subpoint.drl");
  111. returndrlFilePath;
  112. }
  113. }

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

[java] view plain copy
  1. importorg.drools.RuleBase;
  2. importorg.drools.RuleBaseFactory;
  3. /**
  4. *RuleBaseFacatory单实例RuleBase生成工具
  5. *@authorquzishen
  6. */
  7. publicclassRuleBaseFacatory{
  8. privatestaticRuleBaseruleBase;
  9. publicstaticRuleBasegetRuleBase(){
  10. returnnull!=ruleBase?ruleBase:RuleBaseFactory.newRuleBase();
  11. }
  12. }

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

addpoint.drl

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

subpoint.drl

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

测试方法:

[java] view plain copy
  1. publicstaticvoidmain(String[]args)throwsIOException{
  2. PointRuleEnginepointRuleEngine=newPointRuleEngineImpl();
  3. while(true){
  4. InputStreamis=System.in;
  5. BufferedReaderbr=newBufferedReader(newInputStreamReader(is));
  6. Stringinput=br.readLine();
  7. if(null!=input&&"s".equals(input)){
  8. System.out.println("初始化规则引擎...");
  9. pointRuleEngine.initEngine();
  10. System.out.println("初始化规则引擎结束.");
  11. }elseif("e".equals(input)){
  12. finalPointDomainpointDomain=newPointDomain();
  13. pointDomain.setUserName("hellokity");
  14. pointDomain.setBackMondy(100d);
  15. pointDomain.setBuyMoney(500d);
  16. pointDomain.setBackNums(1);
  17. pointDomain.setBuyNums(5);
  18. pointDomain.setBillThisMonth(5);
  19. pointDomain.setBirthDay(true);
  20. pointDomain.setPoint(0l);
  21. pointRuleEngine.executeRuleEngine(pointDomain);
  22. System.out.println("执行完毕BillThisMonth:"+pointDomain.getBillThisMonth());
  23. System.out.println("执行完毕BuyMoney:"+pointDomain.getBuyMoney());
  24. System.out.println("执行完毕BuyNums:"+pointDomain.getBuyNums());
  25. System.out.println("执行完毕规则引擎决定发送积分:"+pointDomain.getPoint());
  26. }elseif("r".equals(input)){
  27. System.out.println("刷新规则文件...");
  28. pointRuleEngine.refreshEnginRule();
  29. System.out.println("刷新规则文件结束.");
  30. }
  31. }
  32. }

执行结果:

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

增加对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



;