摘要
spring是支持控制反转编程机制的一个相对新的框架本文把spring作为简单工作流引擎将它用在了更加通用的地方在对工作流简单介绍之后将要介绍在基本工作流场景中基于Spring的工作流API的使用(个英文单词; //)
许多JEE应用程序要求在一个和主机分离的上下文中执行处理过程在许多情况下这些后台的进程执行多个任务一些任务依赖于以前任务的状态由于这些处理任务之间存在相互依赖的关系使用一套基于过程的方法调用常常不能满足要求开发人员能够利用Spring来容易地将后台进程分离成活动的集合Spring容器连接这些活动并将它们组织成简单的工作流
在本文中简单工作流被定义成不需要用户干预以一定顺序执行的任意活动的集合然而我们并不建议将这种方式代替存在的工作流框架在一些场景中需要更多的用户交互例如基于用户输入而进行的转向连接或传输这时比较好的方法是配用一个单独的开源或者商业的工作流引擎一个开源项目已经成功地将更复杂的工作流设计集成到spring中(参加OSWorkflow)
如果你手上的工作流任务是简单的那么与功能完备的独立工作流框架相比简单工作流的策略就会变得有意义特别地如果已经使用了spring这种快速实现可以保证时间不会变得更加漫长此外考虑到spring轻量级的控制反转容器的特点spring在资源负载上减少了资源负载
这篇文章简短地从编程主题的角度介绍工作流通过使用工作流的概念spring被用来作为驱动工作流引擎的框架然后讨论了生产部署选项现在让我们从工作流的设计模式和相关背景信息来介绍简单工作流的思想吧
简单工作流
工作流模型是一个早在年代就有人开始研究的主题许多开发者都试图创建工作流模型规范WHM van der Aalst等人写了《工作流模型》白皮书(年月)它成功地提炼出一组设计模式这些设计模式准确地将大多数通用的工作流场景建模当中最普通的工作流模式是顺序模式 (Sequence pattern)顺序工作流模式满足了简单工作流的设计原则并且由一组顺序执行的活动组成
UML(统一建模语言)活动图通常被用来作为一个机制对工作流建模图显示了一个基本的使用标准UML活动图对顺序工作流过程的建模过程
图 顺序工作流模式顺序工作流是一个在JEE中流行的标准工作流模式JEE应用程序在后台线程中通常需要一些顺序发生的事件或者异步事件图中的活动图描述了一个简单的工作流用来通知感兴趣的旅行者他们感兴趣的目的地的机票价格已经下降的事件
图 机票价格下降的简单工作流图中的航线工作流负责创建和发送动态的email通知过程中的每一步表示了一个活动(activity)在工作流处于活动之前一些额外事件必须发生在这个例子中事件是飞行路线费率的减少
让我们来简要的看一下航线工作流的业务逻辑如果第一个活动找不到对费率减少通知感兴趣的用户那么整个工作流就被取消如果发现了感兴趣的用户那么接下来的活动继续执行随后一个XSL(扩展样式表)转换生成消息内容之后记录审计信息 (audit information)最后工作流试图通过SMTP服务器发送这个消息如果这个任务没有错误地完成便在日志中记录成功的信息进程结束但是如果在和SMTP服务器通讯时发生了错误一个特别的错误处理例程将要管理这些错误错误处理代码将会试着去重新发送消息
考虑这个航线的例子一个明显的问题是你怎么样有效地将顺序处理过程分解为单独的活动?这个问题被spring巧妙的处理了下面让我们快速地讨论spring的反转控制框架
控制反转
Spring通过使用spring容器来负责控制对象之间的依赖关系使得我们不再对对象之间的依赖负责 这种依赖关系的实现就是大家所知道的控制反转(IoC)或依赖注射参见Martin Fowlers Inversion of Control Containers and the Dependency Injection Pattern( 年月)得到关于控制反转和依赖注射的更加深入的讨论通过管理对象之间的依赖关系spring就不需要那些只是为了使类能够相互协作而将对象粘合的代码
作为spring beans的工作流组件
在进一步讨论之前现在是简要介绍spring中主要概念的恰当时候接口ApplicationContext是从接口BeanFactory继承的它被用来作为在spring容器内实际的控制实体和容器
ApplicationContext负责对一组作为spring beans的一组bean的初始化配置和生命期管理我们通过装配在一个基于XML的配置文件中的spring beans来配置ApplicationContext这个配置文件说明了spring beans互相协作的本质特点这样用spring的术语来说与其他spring beans交互的spring beans就被叫着协作者(collaborators)缺省情况下spring beans是作为单例存在于ApplicationContext中的但是单例的属性能够被设置为false从而有效地改变他们在spring中调用原型模式时的行为
回到我们的例子在飞机票价下降的时候一个SMTP发送例程的抽象就被装配在工作流过程例子中的最后的活动(例子代码可以在 Resources中得到)由于是第个活动我们命名它为activity要发送消息activity就要求一个代理协作者和一个错位处理句柄
<bean id=activity class=orgiocworkflowtestsequenceratedropSendMessage><property name=delegate> <ref bean=smtpSenderDelegate></ref></property><property name=errorHandler> <ref bean=mailErrorHandler/></property> </bean>
将工作流组件实施成spring beans产生了两个令人喜悦的结果就是容易进行单元测试和很大程度上可重用能力IoC容器的特点明显地提供了有效的单元测试使用像spring这样的Ioc容器在测试期间协作者之间的依赖能够容易的用假的替代者替代在这个航线的例子中能够容易地从唯一的测试ApplicationContext中检索出像activity活动这样的spring bean用一个假的SMTP代理SMTP服务器就有可能单独地测试activity
第二个意外的结果可重用能力是通过像XSL转换这样的工作流活动实现的一个被抽象成工作流活动的XSL转换现在能够被任何处理XSL转换的工作流所重用
装配工作流
在提供的API中(从Resources下载)spring控制了一些操作者以一种工作流的方式交互关键接口如下
Activity: 封装了工作流中一个单步业务逻辑
ProcessContext:在工作流活动之间传递具有ProcessContext类型的对象实现了这个接口的对象负责维护对象在工作流转换中从一个活动转换到另一个活动的状态
ErrorHandler: 提供错误处理的回调方法
Processor: 描述一个作为主工作流线程的执行者的bean
下面从例子源码中摘录的代码是将航线例子装配为简单工作流过程的spring bean的配置
<! Airline rate drop as a simple sequence workflow process > <bean id=rateDropProcessor class=orgiocworkflowSequenceProcessor ><property name=activities> <list><ref bean=activity/><!Build recipients><ref bean=activity/><!Construct DOM tree><ref bean=activity/><!Apply XSL Transform><ref bean=activity/><!Write Audit Data><ref bean=activity/><!Attempt to send message> </list></property><property name=defaultErrorHandler> <ref bean=defaultErrorHandler></ref>/property><property name=processContextClass> <value>orgiocworkflowtestsequenceratedropRateDropContext</value></property> </bean>
SequenceProcessor类是一个对顺序模式建模的具体子类有个活动被连接到工作流处理器工作流处理器将顺序执行这个活动
与大多数过程式后台进程相比工作流的解决方案真正的突出了高度强壮的错误处理错误处理句柄可以单独地处理每个活动这种类型的句柄在单一活动级别提供了细致的错误处理如果没有单独处理单个活动的错误处理句柄那么全局工作流处理器的错误处理句柄将会处理出现的问题例如如果在工作流处理过程中的任意时刻一个没有被处理的错误出现了那么它将会向外传播被使用defaultErrorHandler属性装配的ErrorHandler Bean处理
更复杂的工作流框架将工作流转换之间的状态持久化存储到数据库中在这篇文章中我们仅仅对状态转换是自动完成的工作流感兴趣状态信息仅仅在实际工作流运行时在ProcessContext中得到在ProcessContext中你仅仅能看到ProcessContext的接口的两个方法
public interface ProcessContext extends Serializable {public boolean stopProcess();public void setSeedData(Object seedObject); }
用于航线例子工作流的具体的Proce