摘要 这篇文章解释了在J2EE应用中规则引擎及声明性业务逻辑的优点,并且描述如何为流行的Spring框架开发简单的规则引擎。此文需要读者对Spring有基本的了解。
任何大一点的软件项目都包含了许多叫做业务逻辑的东西。业务逻辑的准确描述还是有争议的。在为典型应用软件的生成的大量代码中,到处都是为如订单处理、武器控制系统、图形绘制等功能工作的零碎代码。这些代码与其他如处理持久化、日志、事务、语言偏好、框架特性及其他现代企业级应用有明显不同。
业务逻辑通常与其他代码块紧密的混和在一起。当重量级的侵入式框架(如EJB)被使用时,区别业务逻辑与框架生成的代码就变得非常困难。
有一个软件需求在需求定义文档很难准确描述,却拥有使软件项目成功或失败的能力:适应性,这是用来衡量软件响应业务变更容易程度的标准。
现代企业要求响应快速及灵活,他们对企业软件也有同样的要求。可能你今天辛苦实现的业务规则在明天就被废弃了而且要求你根据变更快速而准确的改变。当你的包含业务逻辑的代码隐藏在大量其他代码中时,修改就变得缓慢、痛若且易出错了。
在今天的企业级软件中没有奇迹,比较流行的是规则引擎和各种业务过程管理(BPM)系统。如果你看一下市场上的宣传,这类工具都承诺一件事:保存在仓库中的捕获业务逻辑的圣杯能够清晰的分离且由自己维护,并随时准备让你现有的应用来调用。
虽然商业的规则引擎和BPM系统有许多优点,但也有不少缺点。最大的缺点就是价格,通常很容易就达到7位数。另一个就是除了主要的行业规范和众多记在纸上的标准外缺乏事实上的标准。而且随着越来越多的软件项目采用敏捷、轻量级的快速开发方法,这些重量级的工具变得不符合潮流了。
在这篇文章中,我们建立了一个简单的规则引擎,一方面平衡系统与业务逻辑的分离,另一方面由于他基于目前流行的强大的J2EE框架因而不需要承受商业软件的复杂性与不协调性。
J2EE世界中的Spring时代 在企业级软件的复杂性变得不能忍受及业务逻辑问题越来越重要时,Spring及类似的框架产生了。可以断定Spring在以后很长一段时间内是企业级Java中的佼佼者。Spring提供了很多工具及少量代码约定使J2EE的开发更面向对象,更容易也更有趣。
Spring的核心是IoC原则,这是一个奇特而超负荷的名字,但包含下面的简单想法:
●功能代码需要分开到更小的可管理片断
●这些片断是简单的,标准的JavaBean(简单的Java类拥有但不包含全部的JavaBean规范)
●你不需要参与管理这些Bean(如创建、销毁、设置依赖)
●相反Spring容器通过上下文定义来为你做这些(通常为XML文件格式)
Spring也提供了很多其他特性,如完整而强大的MVC框架,简便的JDBC开发包装及其他框架。但那些主题已经超出这篇幅文章的讨论范围。
在我描述需要什么来创建基于SPRING应用的简单规则引擎之前,让我们想一下为什么这是一种好的想法。
规则引擎设计有两点有趣的特性使其更有价值:
●首先,从应用领域分离了业务逻辑代码。
●其次,可配置性意味着业务规则的定义及其使用的顺序被存储在应用的外部,这样就可以由规则创建人员来控制而不是应用的使用者或者开发人员了。
Spring为规则引擎提供了一个好的方法。一个良好编码的Spring应用的强组件化的设计会使你的代码变成更小的、可管理的分散片断,这样就更易在Spring的上下文定义中配置。
继续了解在规则引擎设计的需求与Spring设计提供的功能之间的结合点。
基于Spring的规则引擎的设计 我们在Spring控制的JavaBean基础上开始设计,这里我们叫做规则引擎组件。我们来定义下面两种我们可能需要的组件类型:
●操作—在应用逻辑中确定用来做什么的组件
●规则—在一系列行为的逻辑流中做出决定的组件
我们都是面向对象设计的追随者,下面的基类建立了所有我们的组件需要通过参数被其他组件调用的基本功能:
public abstract class AbstractComponent { public abstract void execute(Object arg) throws Exception; }
当然基类是抽象的因为我们根本不需要这样的实例。
AbstractAction的代码扩展了基类来实现其他具体的操作:
public abstract class AbstractAction extends AbstractComponent { private AbstractComponent nextStep; public void execute(Object arg) throws Exception { this.doExecute(arg); if(nextStep != null) nextStep.execute(arg); } protected abstract void doExecute(Object arg) throws Exception; public void setNextStep(AbstractComponent nextStep) { this.nextStep = nextStep; } public AbstractComponent getNextStep() { return nextStep; }}
你可以看到,AbstractAction做两件事:首先他保存在规则引擎中被激活的下一个组件的定义;其次在他的execute()方法中,调用被具体类实现的doExecute()方法,在doExecute()返回后,如果存在下一个组件则调用他。
我们的AbstractRule也相当简单:
public abstract class AbstractRule extends AbstractComponent { private AbstractComponent positiveOutcomeStep; private AbstractComponent negativeOutcomeStep; public void execute(Object arg) throws Exception { boolean outcome = makeDecision(arg); if(outcome) positiveOutcomeStep.execute(arg); else negativeOutcomeStep.execute(arg); } protected abstract boolean makeDecision(Object arg) throws Exception;
// 为简单起见,positiveOutcomeStep和negativeOutcomeStep 的Getters 和 setters均已省略
在其execute()方法中,AbstractAction调用由子类实现的makeDecision()方法,然后根据方法的返回值,调用组件定义的肯定或否定结果的方法。
在我们介绍了SpringRuleEngine类后我们的设计就基本完成了:
public class SpringRuleEngine { private AbstractComponent firstStep; public void setFirstStep(AbstractComponent firstStep) { this.firstStep = firstStep; } public void processRequest(Object arg) throws Exception { firstStep.execute(arg); } }
这就是我们规则引擎主类的全部:定义第一个业务逻辑中的组件及开始执行的方法。
但是请稍等,在哪里绑定我们的类使之可以工作呢?下面你就可以看到如何利用Spring来帮助我们完成工作的方法了。
在操作中的基于Spring的规则引擎 让我们看一下这个框架如何工作的具体实例吧。想象下面的用例:我们需要开发负责贷款申请的应用程序。我们需要满足下面的条件:
●检查应用的完整性否则驳回
●检查应用是否来自我们授权处理业务的应用。
●检查申请者的月收支比是否满足我们的要求。
●输入的申请通过我们不知道实现细节的持久服务被存储在数据库中,我们只知道他的接口(可能这个开发被外包到印度了)
●业务规则是可以改变的,这也是为什么需要规则引擎的设计了。
首先,设计一个表示贷款申请的类:
public class LoanApplication { public static final String INVALID_STATE = "Sorry we are not doing business in your state"; public static final String INVALID_INCOME_EXPENSE_RATIO = "Sorry we cannot provide the loan given this expense/income ratio"; public static final String APPROVED = "Your application has been approved"; public static final String INSUFFICIENT_DATA = "You did not provide enough information on your application"; public static final String INPROGRESS = "in progress"; public static final String[] STATUSES = new String[] { INSUFFICIENT_DATA, INVALID_INCOME_EXPENSE_RATIO, INVALID_STATE, APPROVED, INPROGRESS }; private String firstName; private String lastName; private double income; private double expences; private String stateCode; private String status; public void setStatus(String status) { if(!Arrays.asList(STATUSES).contains(status)) throw new IllegalArgumentException("invalid status:" + status); this.status = status; }// 其他getters and setters已被省略}
我们使用的持久服务拥有如下接口:
public interface LoanApplicationPersistenceInterface { public void recordApproval(LoanApplication application) throws Exception; public void recordRejection(LoanApplication application) throws Exception; public void recordIncomplete(LoanApplication application) throws Exception;}
我们迅速开发一个什么也不做只是用来满足接口约定的MockLoanApplicationPersistence类来欺骗接口。
我们使用下面的SpringRuleEngine类的子类来加载Spring上下文并开始处理:
public class LoanProcessRuleEngine extends SpringRuleEngine { public