大多数web和企业Java应用可以分成三个部分:一个和用户交互的前台, 一个和后台系统,例如数据库交互的服务层,以及他们中间的业务逻辑。 现在使用框架构建前台和后台系统已经成为普遍共识(例如, Struts, Cocoon, Spring, Hibernate, JDO, 和 Entity Beans), 但却没有一个标准的方法来构建业务逻辑。一些框架,例如 EJB 和 Spring 只在一个高层实现业务逻辑,但对于我们组织逻辑代码没有任何帮助,所以,为什么没有一个框架来替换冗繁,易错的if...then语句呢,这个框架应该和其它前台或后台框架一样,易于配置,具有可读性和重用性。下面我们将介绍Drools 规则引擎,这个来解决我们问题的框架。
我们经常能见到噩梦般的业务逻辑代码:
if ((user.isMemberOf(AdministratorGroup)&& user.isMemberOf(teleworkerGroup))|| user.isSuperUser(){
// more checks for specific cases
if((expenseRequest.code().equals("B203")||(expenseRequest.code().equals("A903")&&(totalExpenses<200)&&(bossSignOff> totalExpenses))&&(deptBudget.notExceeded)) {
//issue payments
} else if {
//check lots of other conditions
}
} else {
// even more business logic
}
当然会有一些优秀的程序员可以写出更漂亮些的代码,但那是相当费神的。而Drools等规则引擎 提供了一种很好的解决途径。
先来看Drools的一个简单的例子:
应用:某公司要对某一次参与生产的员工计算工资。
工资=产量*基本工资*系数
系数与该员工的产品合格率有关: 合格率=1 系数=1;0.9<=合格率<1 系数=0.95 ;0.85<=合格率<9 系数=0.9;0.8<=合格率<0.85 系数=0.8;合格率<0.8 系数=0.6;
假设我们有这么一个类:
- public class Employee {
- private String name;
- private int product;
- private float hgl;
- ...
- }
public class Employee {
private String name;
private int product;
private float hgl;
...
}
上面应用用Drools的drl 应该这样描述:
- rule "FirstGz"
- when
- e:Employee(hgl==1);
- then
- System.out.println(e.getName()+"的工资:"+e.getProduct()*50*1);
- end
- rule "SecondGz"
- when
- e:Employee(hgl<1,hgl>=0.9);
- then
- System.out.println(e.getName()+"的工资:"+e.getProduct()*50*0.95);
- retract(e);
- end
- rule "3Gz"
- when
- e:Employee(hgl<0.9,hgl>=0.85);
- then
- System.out.println(e.getName()+"的工资:"+e.getProduct()*50*0.9);
- retract(e);
- end
- rule "4Gz"
- when
- e:Employee(hgl<0.85,hgl>=0.8);
- then
- System.out.println(e.getName()+"的工资:"+e.getProduct()*50*0.85);
- retract(e);
- end
- rule "5Gz"
- when
- e:Employee(hgl<0.8);
- then
- System.out.println(e.getName()+"的工资:"+e.getProduct()*50*0.6);
- retract(e);
- end
rule "FirstGz"
when
e:Employee(hgl==1);
then
System.out.println(e.getName()+"的工资:"+e.getProduct()*50*1);
end
rule "SecondGz"
when
e:Employee(hgl<1,hgl>=0.9);
then
System.out.println(e.getName()+"的工资:"+e.getProduct()*50*0.95);
retract(e);
end
rule "3Gz"
when
e:Employee(hgl<0.9,hgl>=0.85);
then
System.out.println(e.getName()+"的工资:"+e.getProduct()*50*0.9);
retract(e);
end
rule "4Gz"
when
e:Employee(hgl<0.85,hgl>=0.8);
then
System.out.println(e.getName()+"的工资:"+e.getProduct()*50*0.85);
retract(e);
end
rule "5Gz"
when
e:Employee(hgl<0.8);
then
System.out.println(e.getName()+"的工资:"+e.getProduct()*50*0.6);
retract(e);
end
这样就把业务逻辑和程序分开来了。如果要修改业务逻辑,那也是很容易的,只要修改Rule文件就可以了,而Rule文件是可以在一种类似XML的独立文件,可以很方便的替换修改。
我们使用Drools就是为了让它处理数据与规则的关系,因此Drools要获得数据和获得规则,然后进行执行。因此Drools分为编制和运行时两个部分。
编制是指产生rule的过程,Drools用DRL,或者XML来描述规则。
编制的过程包括为规则建立DRL 或XML 文件,传入一个由Antlr 3 文法器定义的解析器中。解析器对文件中规则文法的正确性进行检查并为descr 建立一个中间结构,在AST 中的descr 代表规则的描述。AST 然后将descr 传入Package Builder中,由其进行打包。Package Builder 同时负责包括打包中用到的所有代码产生器和编译器。Package 对象是自包含并可配置的,它是一个包含规则的序列化的对象。
RuleBase 是运行时组件,包含一个或多个Package。Package 在任何时候都可以向RuleBase中添加或删除。一个RuleBase 可以同时初始化多个Working Memory,在其间维护着一个弱引用,除非重新进行配置。Working Memory 包含许多子组件,如Working Memory Event Support(事件支持),Truth Maintenance System(真值维护系统), Agenda 和 Agenda Event Support(事件支持)。向Working Memory 中设置对象的工作可能要在建立了一个或多个激活的规则后才结束。Agenda 负有规划激活规则运行的责任。
以上是Drools的总体架构,其主要有以下类实现:
编制:
XmlParser,DrlParser 分别用来解析XML描述的规则文件和DRL描述的规则文件。
PackageBuilder 创建package实例。
例如:
PackageBuilder builder = new PackageBuilder();
builder.addPackageFromDrl( new InputStreamReader( getClass().getResourceAsStream( "package1.drl" ) ) );
builder.addPackageFromXml( new
InputStreamReader( getClass().getResourceAsStream( "package2.xml" ) ) );
Package pkg = builder.getPackage();
运行时的类:
RuleBase 使用RuleBaseFactory 实例化,默认情况下返回一个ReteOO 的RuleBase。Package通过使用addPackage 方法按顺序加入。你可以指定任何名称空间的Packages 或者同一名称的多个包加入RuleBase。
RuleBase ruleBase = RuleBaseFactory.newRuleBase();
ruleBase.addPackage( pkg );
事实数据相关类:
WorkingMemory 保存运行时事实数据的地方。
由ruleBase产生:WorkingMemory wm= ruleBase.newStatefulSession();
加载事实数据:
wm.insert(object );
insert方法返回一个FactHandle对象指向workingMemory中对象的引用。如果要对Object进行修改删除等操作都要通过FactHander对象来完成。
在准备好Rule,和Fact后 就可以调用 WorkingMemory对象的 fireAllRules()方法执行规则引擎。
Agenda上面提到过它负有规划激活规则运行的责任。
它运行过程分两个阶段:
1) WorkingMemory Actions : assert 新的 facts ,修改存在的 facts 和 retract facts
都是 WorkingMemory Actions 。通过在应用程序中调用 fireAllRules() 方法,会使引擎
转换到 Agenda Evaluatioin 阶段。
2) Agenda Evaluation :尝试选择一条规则进行激发( fire )。如果规则没有找到就
退出,否则它就尝试激发这条规则,然后转换到 WorkingMemory Actions 阶段,直到 Agenda
中为空。
Drools提供了一些监听器来获得规则引擎执行过程中发生的一些事件:
WorkingMemoryEventListene,AgendEventListener和RuleFlowEventListener
从名称来看我们也大概能知道他们分别的作用:
WorkingMemoryEventListene是监听WorkingMemory中发生的一些时间,WorkingMemory发生的事件那就是Fact的插入,删除,修改。
对应的借口为:
objectInserted(ObjectInsertedEvent e);
objectRetracted(ObjectRetractedEvent e);
objectUpdated(ObjectUpdatedEvent e);
AgendEventListener是舰艇运行过程中Agenda管理调配规则发生的一些事件:
Action 在我理解应该是一个冲突就是上面提到过的 完全符合规则条件的,包含规则和数据的对象。
activationCancelled action被取消,可能是因为在规则的执行过程中,某个对象被修改或者某个对象被删除引起。
activationCreated 当有数据能匹配到规则,就能发生这个事件。
afterActivationFired 在规则执行后触发这个事件
agendaGroupPopped 规则组。。。
agendaGroupPushed
beforeActivationFired 在规则执行前触发这个事件
Drools4.0 自带一种非XML 格式的规则语言。这种格式通过使用标点符号,使得语言非常的轻量化,并且通过DSL(域规则语言)支持自然语言的扩展,这使得你可以将该语言演化到与你需要解决的问题域更为相配。
规则文件通常是以drl 扩展名结尾。在一个drl 文件中可以包含多个规则,函数等等。但是你也可以将规则分开到多个规则文件中(在这种情况下建议采用.rule 扩展名,但不是必需的),分散规则利于管理巨量规则的情况。DRL 是简单的text 文件格式。
完整的DRL文件结构是:
package package-name
imports
globals
functions
queries
rules
其中package是必须的。
Imports的含义和Java里的imports含义相似。
Globals表示全局的Fact但它并不进入匹配过程。
Functions queries rules就是我们要写的业务逻辑了,我们用的最多的就是Rules。
RULE的基本结构:
rule “ruleName”
when
<LHS>
Then
<RHS>
end
规则指定“when”作为一系列条件的集合(称为LHS),然后在“then”中指定一系列操作
(称为RHS)。(有些类似if…then)
不多介绍语法,介绍一下下面的例子:
rule "Purchase notification"
salience 10
when
$c : Customer()
$p : Purchase( customer == $c)
then
System.out.println( "Customer " + $c.name + " just purchased " + $p.product.name );
end
第一行 Purchase notification是这个Rule的name,Rule name和Java的名称一样 在同一个包内不能重名。salience 10表示rule执行的优先级,当一个Fact进入WorkingMemory后可能有几个规则被触发,那你可以执行一个优先级来执行这些规则的执行顺序。
When下的部分是LHS, $c : Customer()表示触发规则的对象必须是Customer类型。并创建了一个Customer对象的引用 $c,$c可以在这个rule内使用。$p : Purchase( customer == $c)表示存在一个Purchase对象并且该对象的customer属性为上面描述的customer对象。如果符合上面两个条件规则引擎会执行then部分。Then部分的语法由于基本可以理解为Java语法。