`
welody
  • 浏览: 20656 次
  • 性别: Icon_minigender_1
  • 来自: 青岛
社区版块
存档分类
最新评论

(转)基于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 static final SpringRuleEngine getEngine(String name) {
      ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("SpringRuleEngineContext.xml");
      return (SpringRuleEngine) context.getBean(name);
   }
}

这时候,我们已经有了代码框架了,因此是时候写JUnit测试了,代码如下。其中包含一些假设:我们期望公司仅在两种州运作,德克萨斯和密歇根。而且我们只接受收支比在70%或更好的人的贷款申请。
public class SpringRuleEngineTest extends TestCase {

   public void testSuccessfulFlow() throws Exception {
      SpringRuleEngine engine = LoanProcessRuleEngine.getEngine("SharkysExpressLoansApplicationProcessor");
      LoanApplication application = new LoanApplication();
      application.setFirstName("John");
      application.setLastName("Doe");
      application.setStateCode("TX");
      application.setExpences(4500);
      application.setIncome(7000);
      engine.processRequest(application);
      assertEquals(LoanApplication.APPROVED, application.getStatus());
   }
  
   public void testInvalidState() throws Exception {
      SpringRuleEngine engine = LoanProcessRuleEngine.getEngine("SharkysExpressLoansApplicationProcessor");
      LoanApplication application = new LoanApplication();
      application.setFirstName("John");
      application.setLastName("Doe");
      application.setStateCode("OK");
      application.setExpences(4500);
      application.setIncome(7000);
      engine.processRequest(application);
      assertEquals(LoanApplication.INVALID_STATE, application.getStatus());
   }
  
   public void testInvalidRatio() throws Exception {
      SpringRuleEngine engine = LoanProcessRuleEngine.getEngine("SharkysExpressLoansApplicationProcessor");
      LoanApplication application = new LoanApplication();
      application.setFirstName("John");
      application.setLastName("Doe");
      application.setStateCode("MI");
      application.setIncome(7000);
      application.setExpences(0.80 * 7000); //too high     
      engine.processRequest(application);
      assertEquals(LoanApplication.INVALID_INCOME_EXPENSE_RATIO, application.getStatus());
   }
  
   public void testIncompleteApplication() throws Exception {     
      SpringRuleEngine engine = LoanProcessRuleEngine.getEngine("SharkysExpressLoansApplicationProcessor");
      LoanApplication application = new LoanApplication();
      engine.processRequest(application);
      assertEquals(LoanApplication.INSUFFICIENT_DATA, application.getStatus());
   }

显然单元测试会失败因为我们还没有实现任何的逻辑。然而,随着项目的进展,越来越多的测试通过,最后JUnit测试就全部通过了。

  让我们继续操作和规则的实现。我们希望操作与持久服务交互因此我们需要一个通用基类:
public abstract class AbstractPersistenceAwareAction extends AbstractAction {
   private LoanApplicationPersistenceInterface persistenceService;
   public void setPersistenceService(LoanApplicationPersistenceInterface persistenceService) {
      this.persistenceService = persistenceService;
   }
   public LoanApplicationPersistenceInterface getPersistenceService() {
      return persistenceService;
   }
}

在我们的进程中第一个请求贷款申请的业务规则已经完成:
public class ValidApplicationRule extends AbstractRule {
   protected boolean makeDecision(Object arg) throws Exception {
      LoanApplication application = (LoanApplication) arg;
      if(application.getExpences() == 0 ||
            application.getFirstName() == null ||
            application.getIncome() == 0 ||
            application.getLastName() == null ||
            application.getStateCode() == null) {
         application.setStatus(LoanApplication.INSUFFICIENT_DATA);
         return false;
      }
      return true;
   }
}

    注意这个类的一些有趣的现象:他是完全自包含的。他可以由自己实例化或者任何外部的应用容器,而类中的逻辑可以独立的开发和测试。这些现象使类成为基于规则应用的完美的程序块。现在是时候通过Spring容器来绑定对象了。就象你在单元测试中看到的一样,我们使用LoanProcessRuleEngine类作为入口指向请求名为SharkysExpressLoansApplicationProcessor的Bean的规则引擎。下面是这个Bean如何在 SpringRuleEngineContext.xml定义:
   <!-- 规则引擎处理器 -->
   <bean id="SharkysExpressLoansApplicationProcessor" class="SpringRuleEngine">
      <property name="firstStep">
         <ref bean="ValidApplicationRule"/>
      </property>
   </bean>
这个Bean简单地指明ValidApplicationRule为业务处理的第一个步骤。这个组件如下定义:
   <!-- validation -->
   <bean id="ValidApplicationRule" class="ValidApplicationRule">
      <property name="positiveOutcomeStep">
         <ref bean="ValidStateRule"/>
      </property>
      <property name="negativeOutcomeStep">
         <ref bean="RejectionAction"></ref>
      </property>
   </bean>

你可以看到,规则自身也在Spring上下文中定义:如果贷款申请是合法的,应用会检查是否正确的州;否则控制传递给RejectionAction。

  RejectionAction也很简单:
public class ProcessRejectionAction extends AbstractPersistenceAwareAction {
   protected void doExecute(Object arg) throws Exception {
      LoanApplication application = (LoanApplication) arg;
      if(LoanApplication.INSUFFICIENT_DATA.equals(application.getStatus()))
         this.getPersistenceService().recordIncomplete(application);
      else
         this.getPersistenceService().recordRejection(application);
   }
}

它在Spring上下文中如下定义(注意引用了被伪类欺骗的持久服务):
   <!-- rejection -->
   <bean id="RejectionAction" class="ProcessRejectionAction">
      <property name="persistenceService">
         <ref bean="LoanApplicationPersistenceService"/>
      </property>

   </bean>
   <!-- persistence service -->
   <bean id="LoanApplicationPersistenceService" class="MockLoanApplicationPersistence"/>

我们的下一个业务规则检查是否贷款申请来自合法的州:
public class ValidStateRule extends AbstractRule {

   private List validStates;
  
   protected boolean makeDecision(Object arg) throws Exception {
      LoanApplication application = (LoanApplication) arg;
      if(validStates.contains(application.getStateCode())) {
         return true;
      }
      application.setStatus(LoanApplication.INVALID_STATE);
      return false;
   }

   public void setValidStates(List validStates) {
      this.validStates = validStates;
   }
} 

有趣的是,我们的代码并不知道哪些州是合法的。这些业务信息由上下文来定义:
<!-- check valid state -->

   <bean id="ValidStateRule" class="ValidStateRule">
      <property name="validStates">
         <list>
            <value>TX</value>
            <value>MI</value>
         </list>
      </property>
      <property name="positiveOutcomeStep">
         <ref bean="ValidIncomeExpenseRatioRule"/>
      </property>
      <property name="negativeOutcomeStep">
         <ref bean="RejectionAction"></ref>
      </property>
   </bean>

Spring的内建功能再一次让我们从代码中抽取出逻辑流程和参数数据,仅在外部配置就可以了。

  如你所见,Spring应用容器为我们的类提供了所有必须的绑定。在启动时,Spring创建所有必须的对象并设置相关的依赖。在运行时,我们的业务规则通过定义在Spring上下文而不是代码中的逻辑和参数数据来执行。

  贷款申请示例的其他部分可以用相同方法来配置和开发。完整的源程序及对应的Spring配置可以在资源中找到。

  总结

  在这篇文章中,我演示了通过Spring帮助你开发基于规则应用的众多方法中的一部分。你也可以使用其内置的AOP来支持在你的规则和操作中混合日志及事务代码而不会污染你先前的业务逻辑。

  SPRING应用上下文是可以重新加载的。应用可以修改业务规则和参数(通过修改XML文件)并在运行中重新加载上下文。想象这么做的GUI应用。这么做可以提供与价格为数百万的商业的规则引擎系统的类似功能。

  很期望这篇文章可以帮助你尝试一些新鲜而以令人振奋的方法来使你的代码在将来更加有效。
分享到:
评论

相关推荐

    基于Spring的业务规则引擎

    基于Spring的业务规则引擎,业务规则管理系统的应用

    基于Spring boot 实现的股票基金爬虫工具.zip

    爬虫通常由搜索引擎、数据挖掘工具、监测系统等应用于网络数据抓取的场景。 爬虫的工作流程包括以下几个关键步骤: URL收集: 爬虫从一个或多个初始URL开始,递归或迭代地发现新的URL,构建一个URL队列。这些URL...

    基于Spring Boot的课堂签到小程序.zip

    爬虫通常由搜索引擎、数据挖掘工具、监测系统等应用于网络数据抓取的场景。 爬虫的工作流程包括以下几个关键步骤: URL收集: 爬虫从一个或多个初始URL开始,递归或迭代地发现新的URL,构建一个URL队列。这些URL...

    基于微信原生组件的仿抖音短视频小程序+基于Spring Boot的小程序后台管理系统.zip

    爬虫通常由搜索引擎、数据挖掘工具、监测系统等应用于网络数据抓取的场景。 爬虫的工作流程包括以下几个关键步骤: URL收集: 爬虫从一个或多个初始URL开始,递归或迭代地发现新的URL,构建一个URL队列。这些URL...

    基于SSM的爬虫系统,Spring+Spring MVC+MyBatis+Druid+Redis+MQ.zip

    爬虫通常由搜索引擎、数据挖掘工具、监测系统等应用于网络数据抓取的场景。 爬虫的工作流程包括以下几个关键步骤: URL收集: 爬虫从一个或多个初始URL开始,递归或迭代地发现新的URL,构建一个URL队列。这些URL...

    基于SSHI架构的开发平台技术方案

    集成JBOSS RULE 5规则引擎集成,灵活业务处理 后台大量封装好的基础类,提供简单、易用的调用方式 前台提供完整的管理系统开发框架,基于JQuery技术 包括:表格控件,树形菜单,树形表格,弹出框,对话框,选项卡,...

    JAVA毕业设计之基于springboot的智能家居系统(springboot+mysql)完整源码.zip

    基于Spring Boot的智能家居系统是一个现代化的、高效的家居管理解决方案。这个系统使用了当前流行的Java开发框架Spring Boot和MySQL数据库,以实现一个功能齐全的智能家居控制平台。 技术栈 Spring Boot: 作为主要...

    camel项目实例

    Apache Camel 是一个非常强大的基于规则的路由以及媒介引擎,该引擎提供了一个基于POJO的 企业应用模式(Enterprise Integration Patterns)的实现,你可以采用其异常强大且十分易用的API (可以说是一种Java的领域定义...

    Mailspring:原始作者之一的美丽,快速且维护良好的@Nylas Mail叉子

    Mailspring的同步引擎由Electron应用程序生成,并在您的计算机上本地运行。 它将在将来开源,但是 设置开发环境时,Mailspring使用我们为平台提供的最新版本的同步过程,因此您无需提取源代码或安装其编译时依赖项...

    springboot参考指南

    测试Spring应用 iii. 35.3. 测试Spring Boot应用 i. 35.3.1. 使用Spock测试Spring Boot应用 iv. 35.4. 测试工具 i. 35.4.1. ConfigFileApplicationContextInitializer ii. 35.4.2. EnvironmentTestUtils iii. 35.4....

    java源码编辑-drools:Drools是用Java语言编写的开放源码规则引擎,使用Rete算法对所编写的规则求值。Drools允许使用声

    建立本开源项目的初衷是基于个人学习与工作中对 Java 相关技术栈的总结记录,在这里也希望能帮助一些在学习 Java 过程中遇到问题的小伙伴,如果您需要转载本仓库的一些文章到自己的博客,请按照以下格式注明出处,...

    Java毕业设计-[其他类别]UrlRewriter Java v2.0 RC1_urlrewriterjava.rar

    UrlRewriter Java v2.0 RC1适用于各种Java Web应用程序,包括基于Servlet、Spring MVC、Struts等框架的应用程序。开发人员可以根据项目需求,通过定制和扩展UrlRewriter Java v2.0 RC1,实现更高级的功能和更好的...

    apache-camel-demo

    Apache Camel是一个基于规则路由和中介引擎,提供企业集成模式的Java对象(POJO)的实现,通过应用程序接口(或称为陈述式的Java领域特定语言(DSL))来配置路由和中介的规则。领域特定语言意味着Apache Camel支持你...

    基于jbpm与activiti的工作流平台技术架构介绍

    基础组件包括: Spring基础组件库,报表引擎,数据库访问模块,短信模块,后台定时任务调用组件,短信访问组件,搜索引擎组件,JMS消息组件,Activiti工作流组件,Cas统一用户认证组件,Spring安全认证组件。...

    低清版 大型门户网站是这样炼成的.pdf

    7.7 基于httpclient构建通用静态页面发布引擎 514 7.7.1 优秀的模拟浏览器httpclient 514 7.7.2 构建通用静态页面发布引擎 516 7.8 小结 518 第8章 通用新闻采集系统 519 8.1 新闻采集系统需求分析 519 8.2 ...

    电影数据爬虫开发项目,有ES7和ES6两个.zip

    基于Spring Boot2.5+ Spring Data ElasticSearch4.2环境搭建的 爬虫(Web Crawler)是一种自动化程序,用于从互联网上收集信息。其主要功能是访问网页、提取数据并存储,以便后续分析或展示。爬虫通常由搜索引擎、...

    SpringBoot项目家具商城系统.zip

    家具商城系统是一个基于Spring Boot框架的Web应用,旨在为家具零售商和消费者提供一个全面的在线销售和购物平台。这个系统不仅简化了家具的销售流程,还提供了丰富的用户体验功能,如3D展示、风格推荐和个性化搜索,...

    后台脚手架框架,包括用户管理、角色管理、权限系统等功能.zip

    基于Spring、Spring Boot、MyBatis、Shiro框架,,Spring Boot 快速开发平台,完善的 XSS 防范及脚本过滤,彻底杜绝 XSS 攻击,采用前后端分离技术实现 爬虫(Web Crawler)是一种自动化程序,用于从互联网上收集...

    java开源包1

    GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet....

Global site tag (gtag.js) - Google Analytics