Many J2EE applications require processing to be executed in a context separate from that of the main system. In many cases, these backend processes perform several tasks, with some tasks dependent upon a previous task's status. With the requirement of interdependent processing tasks, an implementation using a single procedural-style set of method calls usually proves inadequate. Utilizing Spring, a developer can easily separate a backend process into an aggregation of activities. The Spring container joins those activities to form a simple workflow.
For this article's purposes, simple workflow is defined as any set of activities performed in a predetermined order without user interaction. This approach, however, is not suggested as a replacement for existing workflow frameworks. For scenarios where more advanced interactions are necessary, such as forking, joining, or transitions based on user input, a standalone open source or commercial workflow engine is better equipped. One open source project has successfully integrated a more complex workflow design with Spring (see
OSWorkflow
).
If the workflow tasks at hand are simplistic, the simple workflow approach makes sense as opposed to a fully functional standalone workflow framework, especially if Spring is already in use, as quick implementation is guaranteed without incurring ramp-up time. Additionally, given the nature of Spring's lightweight Inversion-of-Control container, Spring cuts down on resource overhead.
This article briefly introduces workflow as a programming topic. Using workflow concepts, Spring is employed as the framework for driving a workflow engine. Then, production deployment options are discussed. Let's begin with the idea of simple workflow by focusing on workflow design patterns and related background information.
Simple workflow
Modeling workflow is a topic that has been studied as far back as the 1970s, and many developers have attempted to create a standardized workflow modeling specification. Workflow Patterns
,
a white paper by W.H.M. van der Aalst et al. (July 2003), has succeeded in classifying a set of design patterns that accurately model the most common workflow scenarios. Among the most trivial of the workflow patterns is the Sequence pattern. Fitting the criteria of a simple workflow, the Sequence workflow pattern consists of a set of activities executed in sequence.
UML (Unified Modeling Language) activity diagrams are commonly used as a mechanism to model workflow. Figure 1 shows a basic Sequence workflow process modeled using a standard UML activity diagram.
![](http://www.javaworld.com/javaworld/jw-04-2005/p_w_picpaths/jw-0411-spring1.gif)
Figure 1. Sequence workflow pattern
The Sequence workflow is a standard workflow pattern prevalent in J2EE applications. A J2EE application usually requires a sequence of events to occur in a background thread or asynchronously. Figure 2's activity diagram illustrates a simple workflow for notifying interested travelers that the airfare to their favorite destination has decreased.
Figure 2. Simple workflow for airfare decrease. Click on thumbnail for full-sized p_w_picpath.
The airline workflow in Figure 1 is responsible for creating and sending dynamic email notifications. Each step in the process represents an activity. Some external event must occur before the workflow is set in motion. In this case, that event is a rate decrease for an airline's flight route.
Let's walk through the airline workflow's business logic. If the first activity finds no users interested in rate-drop notifications, the entire workflow is canceled. If interested users are discovered, the remaining activities are completed. Subsequently, an XSL (Extensible Stylesheet Language) transformation generates the message content, after which audit information is recorded. Finally, an attempt to send the message through an SMTP server is made. If the submission completes without error, success is logged and the process terminates. But, if an error occurs while communicating with the SMTP server, a special error-handling routine will take over. This error-handling code will attempt to resend the message.
Given the airline example, one question is evident: How could you efficiently break up a sequential process into individual activities? This problem is handled eloquently using Spring. Let's quickly discuss Spring as an Inversion of Control framework.
Inverting control
Spring allows us to remove the responsibility of controlling an object's dependencies by moving this responsibility to the Spring container. This transfer of responsibility is known as Inversion of Control (IoC) or Dependency Injection. See Martin Fowler's "
Inversion of Control Containers and the Dependency Injection Pattern
" (martinfowler.com, January 2004) for a more in-depth discussion on IoC and Dependency Injection. By managing dependencies between objects, Spring eliminates the need for glue code, code written for the sole purpose of making classes collaborate with each other.
Workflow components as Spring beans
Before we get too far, now is a good time to walk through the main concepts behind Spring. The
ApplicationContext
interface, inheriting from the BeanFactory
interface, imposes itself as the actual controlling entity or container within Spring. The ApplicationContext
is responsible for instantiation, configuration, and lifecycle management of a set of beans known as Spring beans. The ApplicationContext
is configured by wiring up Spring beans in an XML-based configuration file. This configuration file dictates the nature in which Spring beans collaborate with each other. Thus, in Spring speak, Spring beans that interact with others are known as collaborators. By default, Spring beans exist as singletons in the ApplicationContext
, but the singleton attribute can be set to false, effectively changing them to behave in what Spring calls prototype mode.
Back to our example, in the airfare decrease, an abstraction of an SMTP send routine is wired as the last activity in the workflow process example (example code available in
Resources
). Being the fifth activity, this bean is aptly named
activity5
. To send a message, activity5
requires a delegate collaborator and an error handler:
Implementing the workflow components as Spring beans results in two desirable by-products, ease of unit testing and a great degree of reusability. Efficient unit testing is evident given the nature of IoC containers. Using an IoC container like Spring, collaborator dependencies can easily be swapped with mock replacements during testing. In the airline example, an
Activity
Spring bean such as activity5
can easily be retrieved from a standalone test ApplicationContext
. Substituting a mock SMTP delegate into activity5
makes it possible to unit test activity5
separately.
The second by-product, reusability, is realized by workflow activities such as an XSL transformation. An XSL transformation, abstracted into a workflow activity, can now be reused by any workflow dealing with XSL transformations.
Wiring up the workflow
In the provided API (downloadable from
Resources
), Spring controls a small set of players to interact in a manner that constitutes a workflow. The key interfaces are:
Activity
: Encapsulates business logic of a single step in the workflow process.ProcessContext
: Objects of typeProcessContext
are passed between activities in the workflow. Objects implementing this interface are responsible for maintaining state as the workflow transitions from one activity to the next.ErrorHandler
: Provides a callback method for handling errors.Processor
: Describes a bean serving as the executer of the main workflow thread.
The following excerpt from the
sample code
is a Spring bean configuration that binds the airline example as a simple workflow process.
The
SequenceProcessor
class is a concrete subclass that models a Sequence pattern. Wired to the processor are five activities that the workflow processor will execute in order.
When compared with most procedural backend process, the workflow solution really stands out as being capable of highly robust error handling. An error handler may be separately wired for each activity. This type of handler provides fine-grained error handling at the individual activity level. If no error handler is wired for an activity, the error handler defined for the overall workflow processor will handle the problem. For this example, if an unhandled error occurs any time during the workflow process, it will propagate out to be handled by the
ErrorHandler
bean, which is wired up using the defaultErrorHandler
property.
More complex workflow frameworks persist state to a datastore between transitions. In this article, we're only interested in simple workflow cases where state transition is automatic. State information is only available in the
ProcessContext
during the actual workflow's runtime. Having only two methods, you can see the ProcessContext
interface is on a diet:
The concrete
ProcessContext
class used for the airline example workflow is the RateDropContext
class. The RateDropContext
class encapsulates the data necessary to execute an airline rate drop workflow.
Until now, all bean instances have been singletons as per the default
ApplicationContext
's behavior. But we must create a new instance of the RateDropContext
class for every invocation of the airline workflow. To handle this requirement, the SequenceProcessor
is configured, taking a fully qualified class name as the processContextClass
property. For every workflow execution, the SequenceProcessor
retrieves a new instance of ProcessContext
from Spring using the class name specified. For this to work, a nonsingleton Spring bean or prototype of type org.iocworkflow.test.sequence.simple.SimpleContext
must exist in the ApplicationContext
(see rateDrop.xml
for the entire listing).
Seeding the workflow
Now that we know how to piece together a simple workflow using Spring, let's focus on instantiation using seed data. To understand how to seed the workflow, let's look at methods exposed on the actual
Processor
interface:
In most cases, workflow processes require some initial stimuli for kickoff. Two options exist for kicking off a processor: the
doActivities(Object seedData)
method or its no-argument alternative. The following code listing is the doAvtivities()
implementation for the SequenceProcessor
included with the
sample code
:
In the example of an airfare reduction, the workflow process seed data includes airline route information and rate decrease. With the easy-to-test airline workflow example, seeding and kicking off a single workflow process via the
doActivities(Object seedData)
method is simple:
This excerpt is from the example test case included with this article. The
rateDropProcessor
bean is retrieved from the ApplicationContext
. The rateDropProcessor
is actually wired as an instance of SequenceProcessor
to handle sequential execution. The createSeedData()
method instantiates an object encapsulating all the seed data needed for the airline workflow to be initiated.
转载于:https://blog.51cto.com/senvenfoot/74558