首页 > 范文大全 > 正文

基于Spring创建工作流引擎

开篇:润墨网以专业的文秘视角,为您筛选了一篇基于Spring创建工作流引擎范文,如需获取更多写作素材,在线客服老师一对一协助。欢迎您的阅读与分享!

Spring框架()比较深入地讨论了控制反转和依赖注射。由于Spring可以管理对象之间的依赖关系,因而不需要粘合代码(glue code),即完全是为了让类与类能相互协作而编写的代码。

作为Spring bean的工作流组件

在我们深入讨论之前,有必要先来介绍一下Spring的几个主要概念。从BeanFactory接口继承而来的ApplicationContext接口充当Spring里面的实际控制实体或者容器。ApplicationContext负责为一组名为Spring bean的bean创建实例、进行配置及生命周期管理。只要对基于XML的配置文件中的spring bean进行装配,就可以对ApplicationContext进行配置。这个配置文件规定了Spring bean互相协作的特性。因而,用Spring的行语来讲,与其他Spring beans交互的Spring bean就叫协作对象(collaborator)。默认情况下,Spring bean在ApplicationContext中作为单例(singleton)而存在,但是单例属性可以设置成false,实际上改变了Spring调用原型模式的行为。

回到我们前面航线费率下调的那个例子,SMTP发送例程的抽象被装配成了工作流过程例子中的最后一个活动。由于是第5个活动,这个bean被命名为activity5。要发送消息,activity5就需要委托协作对象和错误处理例程。

< bean id="activity5"

class="org.iocworkflow.test.sequence.ratedrop.SendMessage">

< property name="delegate">

< ref bean="smtpSenderDelegate">< /ref>

< /property>

< property name="errorHandler">

< ref bean="mailErrorHandler"/>

< /property>

< /bean>

把工作流组件实施成Spring bean带来了两个所需的结果: 一是容易进行单元测试; 二是大大提高了重用性。考虑到控制反转容器具有的特性,单元测试的高效优点一目了然。使用像Spring这样的控制反转容器,协作对象之间的依赖关系在测试期间很容易用假的替代关系来置换。在上述例子中,很容易从独立的测试ApplicationContext中检索到像activity5这样的Spring bean。如果用activity5代替假的SMTP委托协作对象,就有可能对activity5单独进行单元测试。

第二个结果: 重用性可通过XSL转换这样的工作流活动来实现。XSL转换被抽象成工作流活动后,现在就可以由处理XSL转换的任何工作流重复使用。

装配工作流

在提供的API中,Spring控制一小组接口,这些接口的交互方式组成了工作流。关键接口如下:

Activity: 封装了工作流中每一步的业务逻辑。

ProcessContext: 类型ProcessContext的对象在工作流中的各活动之间传递。实现这个接口的对象负责在工作流从一个活动转换到另一个活动过程中保持对象状态。

ErrorHandler: 提供了处理错误的回调方法。

Processor: 描述了为主工作流线程充当执行者的bean。

以下有关Spring bean配置的代码利用航线费率例子描述了简单工作流的过程。

< !-- Airline rate drop as a simple sequence workflow process -->

< bean id="rateDropProcessor" class="org.iocworkflow.SequenceProcessor" >

< property name="activities">

< list>

< ref bean="activity1"/>< !--Build recipients-->

< ref bean="activity2"/>< !--Construct DOM tree-->

< ref bean="activity3"/>< !--Apply XSL Transform-->

< ref bean="activity4"/>< !--Write Audit Data-->

< ref bean="activity5"/>< !--Attempt to send message-->

< /list>

< /property>

< property name="defaultErrorHandler">

< ref bean="defaultErrorHandler">< /ref>

/property>

< property name="processContextClass">

< value>org.iocworkflow.test.sequence.ratedrop.RateDropContext< /value>

< /property>

< /bean>

SequenceProcessor类是为顺序模式建模的一个具体子类。有5个活动被连接到了工作流处理器(processor),它会按顺序执行这5个活动。

与大多数过程性后端过程相比,工作流解决方案确实与众不同,它能够非常可靠地处理错误。可以为每个活动单独装配错误处理例程。这种类型的例程在单个活动层面提供了粒度很细的错误处理机制。如果没有为某个活动装配任何错误处理例程,那么为整个工作流处理器定义的错误处理例程就会处理问题。就这个例子而言,如果在工作流过程期间的任何时刻出现了未得到处理的错误,它就会向外传播,让使用defaultErrorHandler属性装配而成的ErrorHandler bean来处理。

比较复杂的工作流框架为数据存储区在工作流转换期间赋予了持久性的状态。在本文中,我们只关注状态转换自动进行的简单工作流。只有在实际工作流运行期间才能从ProcessContext处得到状态信息。可以看到,ProcessContext接口只有两个方法:

public interface ProcessContext extends Serializable {

public boolean stopProcess();

public void setSeedData(Object seedObject);

}

航线例子工作流所用的具体的ProcessContext类是RateDropContext类。RateDropContext类封装执行航线费率下调工作流所必需的数据。

到现在为止,根据默认ApplicationContext的行为,所有bean实例都已经成了单例。但是对于航线工作流的每次调用,我们都要创建RateDropContext类的新实例。为了满足这种需求,需要配置SequenceProcessor,采用完全符合标准的类名作为processContextClass属性。对于每次工作流执行,SequenceProcessor都使用指定的类名,从Spring检索到ProcessorContext类的新实例。这种机制要起到作用,非单例的Spring bean或者类型org.iocworkflow.test.sequence.simple.SimpleContext的原型就必须位于ApplicationContext里面。

为工作流播种

我们已经知道了如何使用Spring组建简单工作流,现在着重介绍使用种子数据(seed data)来创建实例。想知道如何为工作流播种,不妨看一下实际的接口Processor所采用的方法:

public interface Processor {

public boolean supports(Activity activity);

public void doActivities();

public void doActivities(Object seedData);

public void setActivities(List activities);

public void setDefaultErrorHandler(ErrorHandler defaultErrorHandler);

}

大多数情况下,工作流过程需要一些初始的刺激才能启动。启动处理器有两个方法: doActivities(ObjectseedData)方法,或者没有变量的doActivities()。以下代码片段是包含在示例代码中为SequenceProcessor实现的doActivities():

public void doActivities(Object seedData) {

// 由Spring注入检索

List activities = getActivities();

//检索工作流ProcessContext的新实例

ProcessContext context = createContext();

if (seedData != null)

context.setSeedData(seedData);

//按顺序执行每个活动

for (Iterator it = activities.iterator(); it.hasNext();) {

Activity activity = (Activity) it.next();

try {

context = activity.execute(context);

} catch (Throwable th) {

//在活动层面确定有没有错误处理例程

ErrorHandler errorHandler = activity.getErrorHandler();

if (errorHandler == null) {

getDefaultErrorHandler().handleError(context, th);

break;

} else {

//使用默认处理例程处理错误

errorHandler.handleError(context, th);

}

}

//确保过程可以继续执行

if (processShouldStop(context, activity))

break;

}

}

在这个航线费率下调的例子中,工作流过程的种子数据包括: 航线信息和费率下调信息。利用容易测试的航线工作流例子,就很容易通过doActivities(Object seedData)方法为单一工作流过程提供种子数据,并启动它:

BaseProcessor processor = (BaseProcessor)context.getBean("rateDropProcessor");

processor.doActivities(createSeedData());

rateDropProcessor bean是从ApplicationContext检索而来的。rateDropProcessor其实装配成SequenceProcessor的实例,以处理顺序执行。createSeedData()方法为负责封装初始化航线工作流所需的所有种子数据的对象创建了实例。

处理器选项

虽然SequenceProcessor是包括在源代码里面的Processor的惟一具体子类,但Processor接口的许多实现是可以想象得到的。可以开发工作流处理器的其他子类,以控制不同类型的工作流,譬如说,有着不同执行路径如并行分叉(Parallel Splits)模式的其他工作流。SequenceProcessor之所以非常适用于简单工作流,就是因为活动顺序已事先确定。虽然本文并没有介绍,但排他选择(Exclusive Choice)模式是适用于使用Spring的简单工作流来实现的另一种模式。就排他选择模式而言,每个活动执行完毕后,Processor具体类会询问ProcessorContext: 接下来要执行哪一个Activity。

注意: 想进一步了解并行分叉、排他选择及其他工作流模式,请参阅W.M.P. van der Aalst等人所著的《工作流模式》一书。

启动工作流

考虑到工作流过程常常需要异步执行的特点,有必要使用分离的执行线程来启动工作流。有几种方法能够以异步方式启动工作流。我们主要介绍两种: 主动轮询队列来启动工作流,或者通过企业服务总线(ESB)如开源ESB: Mule的事件驱动启动方法。

图3和图4描绘了这两种启动策略。图3中,主动轮询出现在这种情况下: 工作流中的第一个活动不断检查资源,譬如数据源或者POP3电子邮件账户。如果图3中的轮询活动发现有任务等待处理,就开始启动工作流。

另一方面,图4表示了使用Java消息服务(JMS)的J2EE应用程序把事件放到队列中的情形。通过ESB配置的事件侦听器收到图4中的事件后,为工作流播种,从而启动工作流过程。

主动轮询是启动工作流过程的一种比较简单的解决方案。SequenceProcessor极其灵活,可以让轮询启动顺畅进行。尽管不是令人满意,但在没有时间配置及部署事件驱动型子系统的许多情况下,主动轮询显然是合理的选择。

使用Spring的ScheduledTimerTask,就很容易实现轮询方案。一个不足就是,必须创建额外的Activity才能进行轮询。创建这个负责轮询的Activity是为了查询某个实体,譬如数据库表、pop邮件账户或者Web服务,然后确定某个新的工作是否等待处理。

在本文提供的例子中,PollingTest-Case类为基于轮询的工作流处理器创建实例。主动轮询启动不同于事件驱动启动的地方在于,Spring偏爱使用没有变量的doActivities()方法。反过来,在事件驱动启动中,启动处理器的实体通过doActivities(Object seedData)方法提供了种子数据。主动轮询的另一个缺点是: 资源被不必要地重复耗用。这种资源耗用可能让人无法接受,具体耗用多少取决于应用程序环境。

以下代码例子演示了使用主动轮询来控制工作流启动的一个活动:

public class PollForWork implements Activity

{

public ProcessContext execute(ProcessContext context) throws Exception {

//先检查是否有工作需要处理

boolean workIsReady = lookIntoDatabaseForWork();

if (workIsReady) {

//轮询行动还必须加载任何种子数据

((MyContext) context).setSeedData(createSeedData());

} else {

//没有工作要处理,终止工作流过程

((MyContext) context).setStopEntireProcess(true);

}

return context;

}

}

理想情况下,会有含有相应种子数据的线程以异步方式启动工作流。这方面的一个例子就是收到来自Java消息服务队列的消息。监听JMS队列或者主题的客户端会收到通知: 应当在onMessage()方法中开始处理。然后,可以使用Spring和doActivities(Object seedData)方法,获得工作流处理器bean。

如果使用ESB,可以将用于发送启动事件的实际机制与工作流处理器分离开来。开源项目Mule ESB具有与Spring紧密集成的优点。JMS、JVM或者POP3邮箱等任何传输机制都能开始传送事件。

工作流的连续运行

工作流引擎等后端进程应当能够在没有干预的情况下连续运行。有几种方法可以独立运行基于Spring的工作流。正如本文随带的单元测试演示的那样,拥有main()方法的简单Java类就足够了。一种更可靠的部署机制就是,把工作流嵌入到某种形式的J2EE组件中。Spring很好地支持与J2EE兼容的Web应用程序归档或者war文件之间的集成。使用相对专有的可部署组件,譬如JBoss应用服务器支持的基于Java管理扩展(JMX)的服务归档即sar文件,就可以进行部署。至于JBoss 4.0,sar文件已经被名为部署者(deployer)的一种格式所取代。

结论

在本文中读者朋友已经看到了通过设计模式对工作流过程进行分类,其中我们主要介绍了顺序模式。通过使用接口,就可以为基本的工作流组件建模。通过把实现的不同接口装配成Spring,就能获得顺序工作流引擎。最后讨论了启动及部署工作流的不同方法。

这里介绍的简单工作流方法肯定不是石破天惊的,也不是革命性的。但是,使用Spring处理工作流这样的通用任务充分证明了反转控制容器所能获得的效率。由于不需要粘合代码,Spring使得遵守面向对象的约束不那么麻烦。