使用 Drools 规则引擎实现业务逻辑.docx
- 文档编号:26723172
- 上传时间:2023-06-22
- 格式:DOCX
- 页数:33
- 大小:114.63KB
使用 Drools 规则引擎实现业务逻辑.docx
《使用 Drools 规则引擎实现业务逻辑.docx》由会员分享,可在线阅读,更多相关《使用 Drools 规则引擎实现业务逻辑.docx(33页珍藏版)》请在冰豆网上搜索。
使用Drools规则引擎实现业务逻辑
使用规则引擎可以通过降低实现复杂业务逻辑的组件的复杂性,降低应用程序的维护和可扩展性成本。
本文展示了如何使用Drools规则引擎让Java™应用程序更适应变化。
Drools的一个好处是具有允许将Java代码直接嵌入规则文件中的附加语法。
要求施加在当今软件产品上的大多数复杂性是行为和功能方面的,从而导致组件实现具有复杂的业务逻辑。
实现J2EE或J2SE应用程序中业务逻辑最常见的方法是编写Java代码来实现需求文档的规则和逻辑。
在大多数情况下,该代码的错综复杂性使得维护和更新应用程序的业务逻辑成为一项令人畏惧的任务,甚至对于经验丰富的开发人员来说也是如此。
任何更改,不管多么简单,仍然会产生重编译和重部署成本。
规则引擎试图解决(或者至少降低)应用程序业务逻辑的开发和维护中固有的问题和困难。
可以将规则引擎看作实现复杂业务逻辑的框架。
大多数规则引擎允许您使用声明性编程来表达对于某些给定信息或知识有效的结果。
您可以专注于已知为真的事实及其结果,也就是应用程序的业务逻辑。
有多个规则引擎可供使用,其中包括商业和开放源码选择。
商业规则引擎通常允许使用专用的类似英语的语言来表达规则。
其他规则引擎允许使用脚本语言(比如Groovy或Python)编写规则。
本文为您介绍Drools引擎,并使用示例程序帮助您理解如何使用Drools作为Java应用程序中业务逻辑层的一部分。
俗话说得好,“惟一不变的是变化。
”软件应用程序的业务逻辑正是如此,尤其是当今开发的软件应用程序。
出于以下原因,实现应用程序业务逻辑的组件可能必须更改:
∙在开发期间或部署后修复代码缺陷
∙应付特殊状况,即客户一开始没有提到要将业务逻辑考虑在内
∙处理客户已更改的业务目标
∙符合组织对敏捷或迭代开发过程的使用
如果存在这些可能性,则迫切需要一个无需太多复杂性就能处理业务逻辑更改的应用程序,尤其是当更改复杂if-else逻辑的开发人员并不是以前编写代码的开发人员时。
Drools是用Java语言编写的开放源码规则引擎,使用Rete算法(参阅参考资料)对所编写的规则求值。
Drools允许使用声明方式表达业务逻辑。
可以使用Java/XML语法编写规则,这对于入门Drools十分有用,因为您可以将Java代码直接嵌入规则文件中。
还可以使用Groovy/XML语法或Python/XML语法在Drools中编写规则。
Drools还具有其他优点:
∙非常活跃的社区
∙易用
∙快速的执行速度
∙在Java开发人员中流行
∙JSR94兼容(JSR94是JavaRuleEngineAPI)(参阅参考资料)
∙免费
因此,您应该熟悉使用EclipseIDE开发Java代码。
您应该熟悉JUnit测试框架并知道如何在Eclipse中使用。
您还应该相当了解XML。
本文展示如何使用Drools作为示例Java应用程序中业务逻辑层的一部分。
下列假设为应用程序解决的虚构问题设置了场景:
∙名为XYZ的公司构建两种类型的计算机机器:
Type1和Type2。
机器类型按其架构定义。
∙XYZ计算机可以提供多种功能。
当前定义了四种功能:
DDNSServer、DNSServer、Gateway和Router。
∙在发运每台机器之前,XYZ在其上执行多个测试。
∙在每台机器上执行的测试取决于每台机器的类型和功能。
目前,定义了五种测试:
Test1、Test2、Test3、Test4和Test5。
∙当将测试分配给计算机时,也将测试到期日期分配给机器。
分配给计算机的测试不能晚于该到期日期执行。
到期日期值取决于分配给机器的测试。
∙XYZ使用可以确定机器类型和功能的内部开发的软件应用程序,自动化了执行测试时的大部分过程。
然后,基于这些属性,应用程序确定要执行的测试及其到期日期。
∙目前,为计算机分配测试和测试到期日期的逻辑是该应用程序的已编译代码的一部分。
包含该逻辑的组件用Java语言编写。
∙分配测试和到期日期的逻辑一个月更改多次。
当开发人员需要使用Java代码实现该逻辑时,必须经历一个冗长乏味的过程。
并非所有应用程序都应使用规则引擎。
如果业务逻辑代码包括很多if-else语句,则应考虑使用其中一个。
维护复杂的Boolean逻辑可能是非常困难的任务,而规则引擎可以帮助您组织该逻辑。
当您可以使用声明方法而非命令编程语言表达逻辑时,变化引入错误的可能性会大大降低。
如果代码变化可能导致大量的财政损失,则也应考虑规则引擎。
许多组织在将已编译代码部署到托管环境中时具有严格的规则。
例如,如果需要修改Java类中的逻辑,在更改进入生产环境之前,将会经历一个冗长乏味的过程:
1.必须重新编译应用程序代码。
2.在测试中转环境中删除代码。
3.由数据质量审核员检查代码。
4.由托管环境架构师批准更改。
5.计划代码部署。
即使对一行代码的简单更改也可能花费组织的几千美元。
如果需要遵循这些严格规则并且发现您频繁更改业务逻辑代码,则非常有必要考虑使用规则引擎。
对客户的了解也是该决策的一个因素。
尽管您使用的是一个简单的需求集合,只需Java代码中的简单实现,但是您可能从上一个项目得知,您的客户具有在开发周期期间甚至部署之后添加和更改业务逻辑需求的倾向(以及财政和政治资源)。
如果从一开始就选择使用规则引擎,您可能会过得舒服一些。
因为在对为计算机分配测试和到期日期的逻辑进行更改时,公司会发生高额成本,所以XYZ主管已经要求软件工程师寻找一种灵活的方法,用最少的代价将对业务规则的更改“推”至生产环境。
于是Drools走上舞台了。
工程师决定,如果它们使用规则引擎来表达确定哪些测试应该执行的规则,则可以节省更多时间和精力。
他们将只需要更改规则文件的内容,然后在生产环境中替换该文件。
对于他们来说,这比更改已编译代码并在将已编译代码部署到生产环境中时进行由组织强制的冗长过程要简单省时得多(参阅侧栏何时使用规则引擎?
)。
目前,在为机器分配测试和到期日期时必须遵循以下业务规则:
∙如果计算机是Type1,则只能在其上执行Test1、Test2和Test5。
∙如果计算机是Type2且其中一个功能为DNSServer,则应执行Test4和Test5。
∙如果计算机是Type2且其中一个功能为DDNSServer,则应执行Test2和Test3。
∙如果计算机是Type2且其中一个功能为Gateway,则应执行Test3和Test4。
∙如果计算机是Type2且其中一个功能为Router,则应执行Test1和Test3。
∙如果Test1是要在计算机上执行的测试之一,则测试到期日期距离机器的创建日期3天。
该规则优先于测试到期日期的所有下列规则。
∙如果Test2是要在计算机上执行的测试之一,则测试到期日期距离机器的创建日期7天。
该规则优先于测试到期日期的所有下列规则。
∙如果Test3是要在计算机上执行的测试之一,则测试到期日期距离机器的创建日期10天。
该规则优先于测试到期日期的所有下列规则。
∙如果Test4是要在计算机上执行的测试之一,则测试到期日期距离机器的创建日期12天。
该规则优先于测试到期日期的所有下列规则。
∙如果Test5是要在计算机上执行的测试之一,则测试到期日期距离机器的创建日期14天。
捕获为机器分配测试和测试到期日期的上述业务规则的当前Java代码如清单1所示:
Machinemachine=...
//Assigntests
Collections.sort(machine.getFunctions());
intindex;
if(machine.getType().equals("Type1")){
Testtest1=...
Testtest2=...
Testtest5=...
machine.getTests().add(test1);
machine.getTests().add(test2);
machine.getTests().add(test5);
}elseif(machine.getType().equals("Type2")){
index=Collections.binarySearch(machine.getFunctions(),"Router");
if(index>=0){
Testtest1=...
Testtest3=...
machine.getTests().add(test1);
machine.getTests().add(test3);
}
index=Collections.binarySearch(machine.getFunctions(),"Gateway");
if(index>=0){
Testtest4=...
Testtest3=...
machine.getTests().add(test4);
machine.getTests().add(test3);
}
...
}
//Assigntestsduedate
Collections.sort(machine.getTests(),newTestComparator());
...
Testtest1=...
index=Collections.binarySearch(machine.getTests(),test1);
if(index>=0){
//Setduedateto3daysafterMachinewascreated
TimestampcreationTs=machine.getCreationTs();
machine.setTestsDueTime(...);
return;
}
index=Collections.binarySearch(machine.getTests(),test2);
if(index>=0){
//Setduedateto7daysafterMachinewascreated
TimestampcreationTs=machine.getCreationTs();
machine.setTestsDueTime(...);
return;
}
...
清单1中的代码不是太复杂,但也并不简单。
如果要对其进行更改,需要十分小心。
一堆互相缠绕的if-else语句正试图捕获已经为应用程序标识的业务逻辑。
如果您对业务规则不甚了解,就无法一眼看出代码的意图。
回页首
使用Drools规则的示例程序附带在本文的ZIP存档中(参阅下载)。
程序使用Drools规则文件以声明方法表示上一节定义的业务规则。
我建议您在继续之前下载ZIP存档。
它包含要导入到Eclipse工作区的Eclipse(v3.1)Java项目。
选择该选项以导入ExistingProjectsintoWorkspace(参见图1):
然后选择下载的存档文件并将其导入工作区中。
您将在工作区中发现一个名为DroolsDemo的新Java项目,如图2所示:
如果启用了Buildautomatically选项,则代码应该已编译并可供使用。
如果未启用该选项,则现在构建DroolsDemo项目。
回页首
现在来看一下示例程序中的代码。
该程序的Java类的核心集合位于demo包中。
在该包中可以找到Machine和Test域对象类。
Machine类的实例表示要分配测试和测试到期日期的计算机机器。
下面来看Machine类,如清单2所示:
publicclassMachine{
privateStringtype;
privateListfunctions=newArrayList();
privateStringserialNumber;
privateCollectiontests=newHashSet();
privateTimestampcreationTs;
privateTimestamptestsDueTime;
publicMachine(){
super();
this.creationTs=newTimestamp(System.currentTimeMillis());
}
...
在清单2中可以看到Machine类的属性有:
∙type(表示为string属性)——保存机器的类型值。
∙functions(表示为list)——保存机器的功能。
∙testsDueTime(表示为timestamp变量)——保存分配的测试到期日期值。
∙tests(Collection对象)——保存分配的测试集合。
注意,可以为机器分配多个测试,而且一个机器可以具有一个或多个功能。
出于简洁目的,机器的创建日期值设置为创建Machine类的实例时的当前时间。
如果这是真实的应用程序,创建时间将设置为机器最终构建完成并准备测试的实际时间。
Test类的实例表示可以分配给机器的测试。
Test实例由其id和name惟一描述,如清单3所示:
publicclassTest{
publicstaticIntegerTEST1=newInteger
(1);
publicstaticIntegerTEST2=newInteger
(2);
publicstaticIntegerTEST3=newInteger(3);
publicstaticIntegerTEST4=newInteger(4);
publicstaticIntegerTEST5=newInteger(5);
privateIntegerid;
privateStringname;
privateStringdescription;
publicTest(){
super();
}
...
示例程序使用Drools规则引擎对Machine类的实例求值。
基于Machine实例的type和functions属性的值,规则引擎确定应分配给tests和testsDueTime属性的值。
在demo包中,还会发现Test对象的数据访问对象(TestDAOImpl)的实现,它允许您按照ID查找Test实例。
该数据访问对象极其简单;它不连接任何外部资源(比如关系数据库)以获得Test实例。
相反,在其定义中硬编码了预定义的Test实例集合。
在现实世界中,您可能会具有连接外部资源以检索Test对象的数据访问对象。
demo中比较重要(如果不是最重要的)的一个类是RulesEngine类。
该类的实例用作封装逻辑以访问Drools类的包装器对象。
可以在您自己的Java项目中容易地重用该类,因为它所包含的逻辑不是特定于示例程序的。
清单4展示了该类的属性和构造函数:
publicclassRulesEngine{
privatestaticLoggerlogger=Logger.getLogger(RulesEngine.class);
privateRuleBaserules;
privateStringrulesFile;
privatebooleandebug=false;
publicRulesEngine(StringrulesFile)throwsRulesEngineException{
super();
this.rulesFile=rulesFile;
try{
rules=RuleBaseLoader.loadFromInputStream(this.getClass()
.getResourceAsStream("/rules/"+rulesFile));
}catch(Exceptione){
thrownewRulesEngineException("Couldnotloadrulesfile:
"
+rulesFile,e);
}
}
...
在清单4中可以看到,RulesEngine类的构造函数接受字符串值形式的参数,该值表示包含业务规则集合的文件的名称。
该构造函数使用RuleBaseLoader类的静态loadFromInputStream()方法将规则文件中包含的规则加载到内存中。
(注意,该代码假设规则文件位于程序类路径中名为rules的文件夹中。
)loadFromInputStream()方法返回DroolsRuleBase类的实例,它被分配给RulesEngine类的rules属性。
可以将RulesBase类的实例看作规则文件中所包含规则的内存中表示。
清单5展示了RulesEngine类的executeRules()方法:
publicListexecuteRules(WorkingEnvironmentCallbackcallback)
throwsRulesEngineException{
try{
WorkingMemoryworkingMemory=rules.newWorkingMemory();
if(debug){
workingMemory.addEventListener(
newDebugWorkingMemoryEventListener());
}
callback.initEnvironment(workingMemory);
workingMemory.fireAllRules();
returnworkingMemory.getObjects();
}catch(FactExceptionfe){
logFactException(fe);
thrownewRulesEngineException(
"Exceptionoccurredwhileattemptingtoexecute"
+"rulesfile:
"+rulesFile,fe);
}
}
executeRules()方法几乎包含了Java代码中的所有魔力。
调用该方法执行先前加载到类构造函数中的规则。
DroolsWorkingMemory类的实例用于断言或声明知识,规则引擎应使用它来确定应执行的结果。
(如果满足规则的所有条件,则执行该规则的结果。
)将知识当作规则引擎用于确定是否应启动规则的数据或信息。
例如,规则引擎的知识可以包含一个或多个对象及其属性的当前状态。
规则结果的执行在调用WorkingMemory对象的fireAllRules()方法时执行。
您可能奇怪(我希望您如此)知识是如何断言到WorkingMemory实例中的。
如果仔细看一下该方法的签名,将会注意到所传递的参数是WorkingEnvironmentCallback接口的实例。
executeRules()方法的调用者需要创建实现该接口的对象。
该接口只需要开发人员实现一个方法(参见清单6):
publicinterfaceWorkingEnvironmentCallback{
voidinitEnvironment(WorkingMemoryworkingMemory)throwsFactException;
}
所以,应该是executeRules()方法的调用者将知识断言到WorkingMemory实例中的。
稍后将展示这是如何实现的。
清单7展示了TestsRulesEngine类,它也位于demo包中:
publicclassTestsRulesEngine{
privateRulesEnginerulesEngine;
privateTestDAOtestDAO;
publicTestsRulesEngine(TestDAOtestDAO)throwsRulesEngineException{
super();
rulesEngine=newRulesEngine("testRules.xml");
this.testDAO=testDAO;
}
publicvoidassignTests(finalMachinemachine){
rulesEngine.executeRules(newWorkingEnvironmentCallback(){
publicvoidinitEnvironment(WorkingMemoryworkingMemory)
throwsFactException{
workingMemory.assertObject(machine);
Iteratorfunctions=machine.getFunctions().iterator();
while(functions.hasNext()){
workingMemory.assertObject(functions.next());
}
workingMemory.setApplicationData("testDAO",testDAO);
};
});
}
}
TestsRulesEngine类只有两个实例变量。
rulesEngine属性是RulesEngine类的实例。
testDAO属性保存对TestDAO接口的具体实现的引用。
rulesEngine对象使用“testRules.xml”字符串作为其构造函数的参数来进行实例化。
testRules.xml文件以声明方式捕获要解决的问题中的业务规则。
TestsRulesEngine类的assignTests()方法调用RulesEngine类的executeRules()方法。
在该方法中,创建了WorkingEnvironmentCallback接口的匿名实例,然后该实例被作为参数传递给executeRules()方法。
如果查看
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 使用 Drools 规则引擎实现业务逻辑 规则 引擎 实现 业务 逻辑