 | Work in Progress
Pending upgrade to Preview 5. |
You've downloaded Spring Web Flow and are ready to build your first Flow. This step-by-step walk through shows you how to do that in detail.
To help you accomplish your goal, we will walk you through the process of building a common page flow found in most web applications: the search + master/detail flow. Such a flow lets your customer:
- Search for some objects meeting some criteria (search)
- Select from a resulting list of returned objects (results) to view and/or edit their details (master)
- View and/or edit a single object's details (detail)
Spring Web Flow lets you capture this three step process as a module (called a Flow) you can reuse in multiple situations. As you'll see, you can also break that module down into a hierarchy of finer-grained modules (called sub flows), each reusable in their own right. For example, the search Flow could be a top-level module for finding and displaying objects of a certain type, spawning the detail Flow for viewing/editing a single object. This means all of the logic encapsulated within the detail flow can be reused outside of the search context.
The process of building a flow
A good process for developers to follow when building a new web flow follows:
- Design the flow
- Build the Flow definition in configuration code
- Test the FlowExecution
- Complete the Action implementations
- Complete the views
- Deploy the Flow for in-container testing
Note: many of these steps can happen in parallel; most notably, the coding of individual artifacts such as Action implementations and view resources (jsps, etc.)
Design the flow
Before you can go off and implement your first flow, you must first understand what your requirements are and what you're trying to accomplish. Typically you'll work this out early on with business analysts, sketching a logical page flow out on a whiteboard, in a UML state diagram, or a user story narrative.
As mentioned, this quick start will walk your through designing and building a search + master/detail flow. To do this, we'll pull from the [Phonebook] sample application, which you may refer to as a reference.
Flow design for the Phonebook sample application
[Phonebook] should allow the customer to:
- View a search page, where the customer can input the name criteria of the people they wish to locate.
- From the search page, on submit, bind and validate the search criteria, execute the search, and view a results page, where the customer can select a person they wish to view the details for. On an input validation error, go back to the search page.
- From the results page, on select, get the selected person's details and view the details page. 'Details' are things like the person's name, contact information, and colleagues (other people they work with).
- From the details page, on select collegue, get and view the details page of the selected colleague. On back, go back to the details page of the previously edited person. Note: this step demonstrates the power inherent in the ability to nest flows n levels deep.
Note the use of italics on verbs in the narrative above. Italicized verbs denote events or actions that must happen to achieve our goal; for example, "View a search page", "on submit, bind and validate the search criteria" As you'll see shortly, these verbs correspond directly to Flow implementation artifacts.
With a high-level narrative of what we need [Phonebook] to do done, let's look at a sketch. A sketch is always a good suppliment to a narrative:

And, finally, let's look at a UML state diagram, which goes into a bit more detail:

The state diagram above provides the most detailed glimpse into the design of the Flow: from it, you see all the states of the flow, what actions occur at what state, and the transitions between states.
You'll see shortly that a Flow implementation maps very elegantly from such a diagram. Indeed, if you understand the picture above, you understand Spring Web Flow. Specifically:
- Flow definitions have one or more States, one of which is marked the StartState.
- Each State, when entered, executes some kind of action. What action is executed varies based on the state's type and is executed in polymorphic fashion. For example, ViewStates render view resources. ActionStates execute controller Actions. SubFlowStates spawn sub flows. EndStates terminate an active FlowExecution.
- Each State has one or more Transitions. A Transition is a path from one state to another. A Transition executes on the signaling of an event.
So for example, in the diagram above, the start state is View Search Criteria. When a new FlowExecution (client session) is created for this Flow, the start state is entered. That causes the search criteria view to be rendered, returning control to the web browser. From there, the browser signals an event, typically by clicking a form submit button. When the event signaled is received server-side, it is mapped to a supported transition. This transition defines a path to another state, and when executed, causes that state to enter and become the new state for the active FlowExecution. When entered, that state executes whatever action it's supposed to, based on its type.
For example, the user signaling the submit event in the View Search Criteria state triggers a transition to the Bind And Validate Search Criteria state. That state is an ActionState which executes an Action implementation that binds form input to a backing form object. When the Action completes, it signals a result event such as success or error. The signaled event is then used as a grounds for another transition, which leads to another state, which excutes another action. This process of event->stateTransition->action continues until an EndState is entered and the FlowExecution terminates.
Show me the code! The Search Flow implementation.
Okay, so you've got the design flushed out! We know what Flows we need to build: a top-level search flow and a person detail flow. But how do we build them? With a FlowBuilder of course!
Flow Builders
The FlowBuilder interface provides a common strategy for constructing Flow definitions. Spring Web Flow ships two FlowBuilder implementations out of the box: the XmlFlowBuilder, for xml-based flow definitions, and the AbstractFlowBuilder, a base class for java-based flow definitions. We'll demonstrate both here.
Xml-based definition of the search flow, consumable by the XmlFlowBuilder
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE webflow PUBLIC "-//SPRING//DTD WEBFLOW//EN"
"http://www.springframework.org/dtd/spring-webflow.dtd">
<webflow id="person.Search" start-state="criteria.view">
<view-state id="criteria.view" view="person.Search.criteria.view">
<transition on="submit" to="bindAndValidate"/>
</view-state>
<action-state id="bindAndValidate">
<action bean="person.Search.criteria.formAction"/>
<transition on="success" to="executeQuery"/>
<transition on="error" to="criteria.view"/>
</action-state>
<action-state id="executeQuery">
<action bean="person.Search.executeQuery"/>
<transition on="success" to="results.view"/>
</action-state>
<view-state id="results.view" view="person.Search.results.view">
<transition on="newSearch" to="criteria.view"/>
<transition on="select" to="personId.set"/>
</view-state>
<action-state id="personId.set">
<action bean="person.Search.id.set"/>
<transition on="success" to="person.Detail"/>
<transition on="error" to="error"/>
</action-state>
<subflow-state id="person.Detail" flow="person.Detail">
<attribute-mapper bean="id.attributeMapper"/>
<transition on="finish" to="criteria.view"/>
<transition on="error" to="error"/>
</subflow-state>
<end-state id="error" view="error.view"/>
</webflow>
Java-based FlowBuilder implementation for the search flow
/**
* Java-based flow builder that searches for people in the phonebook. The flow
* defined by this class is exactly the same as that defined in the
* "search-flow.xml" XML flow definition.
* <p>
* This encapsulates the page flow of searching for some people, selecting a
* person you care about, and viewing their person's details and those of their
* collegues in a reusable, self-contained module.
*
* @author Keith Donald
*/
public class SearchPersonFlowBuilder extends AbstractFlowBuilder {
protected String flowId() {
return "person.Search";
}
public void buildStates() throws FlowBuilderException {
addViewState("viewCriteria", "person.Search.criteria.view",
on(submit(), "bindAndValidateCriteria"));
addActionState("bindAndValidateCriteria",
method("bindAndValidate", action("person.Search.criteria.formAction")),
new Transition[] {
on(error(), "viewCriteria"),
on(success(), "executeQuery")
});
addActionState("executeQuery",
action(ExecuteQueryAction.class, AutowireMode.BY_TYPE),
new Transition[] {
on(error(), "viewCriteria"),
on(success(), "viewResults")
});
addViewState("viewResults", "person.Search.results.view",
new Transition[] {
on("newSearch", "viewCriteria"),
on(select(), "setId")
});
EventParameterMapperAction setAction =
new EventParameterMapperAction(new Mapping("id",
getConversionExecutor(Long.class)));
setAction.setTargetScope(ScopeType.FLOW);
addActionState("setId", setAction,
new Transition[] {
on(error(), "error"),
on(success(), "person.Detail")
});
addSubFlowState("person.Detail",
flow("person.Detail", PersonDetailFlowBuilder.class),
attributeMapper("id.attributeMapper"),
new Transition[] {
on(finish(), "executeQuery"),
on(error(), "error")
});
addEndState(error(), "error.view");
}
}
Note: which FlowBuilder strategy you use is your preference. Use of an XML-based builder might be a better fit for UI developers not comfortable with Java. Use of a Java-based builder gives you the full power of the Java language, but might intimidate some folks (though we tried very hard to make the builder API as elegant and easy to use as possible). One of the advantages of a Java-based approach is the ability to leverage the Spring IoC container to do strongly-typed service lookup and dependency injection (see the executeAction(Class actionImplementationClass) definition above). This makes it easy to navigate to all executable code for a Flow from a single place!
Step-by-step breakdown of the Search Flow Builder code
Let's look at each state definition in turn for the Search Flow, comparing both XML and java variants.
The Search Criteria View State (the Start State)
The first state added to the search Flow is a ViewState that, when entered, displays the [Phonebook] search criteria form. This state is also marked as the StartState for the flow, which means it will be the first state entered when a new FlowExecution (client session) is activated.
 | What is a ViewState?
A ViewState triggers the rendering of a view resource template (jsp, etc) when entered, and returns control back to the client. The web flow system expects its logical viewName to be mapped to a physical view resource template by the calling front controller. In Spring MVC, the familiar ViewResolver takes care of this. In Struts 1.x, an ActionForward handles view resolution. |
Here is the search criteria view state definition in XML:
<view-state id="criteria.view" view="person.Search.criteria.view">
<transition on="submit" to="bindAndValidate"/>
</view-state>
The id attribute is the identifier of the state definition. The state identifier must be locally unique to the Flow in which it is defined.
The view attribute is the logical name of the view to render when this state is entered. The value above is qualified using best practice naming conventions.
The transition element defines a Transition from this state to another state. The on attribute defines which event should trigger the transition, and the to attribute defines the id of the state to enter when that event is signaled. So in this case, the above transition element says:
- on an occurence of the submit event, enter the bindAndValidate state.
Here is the same search criteria view state in Java Code:
addViewState("viewCriteria", "person.Search.criteria.view",
on(submit(), "bindAndValidateCriteria"));
Both the XML and Java-based declaration produce the same thing: a fully configured ViewState instance for the Flow being built. The base AbstractFormBuilder, from which the SearchPersonFlowBuilder extends, provides a number of cleary-documented helper methods like addViewState(...) to make the process of adding new states to the Flow trivial. In this case, the helper method above:
- Assigns the state identifier to "viewCriteria"
- Assigns the viewName property to "person.Search.criteria.view".
- Produces a single Transition that enters the bindAndValidateCriteria state on an occurrence of the submit event.
- Assigns this state as the StartState by default, since it is the first state added.
So in summary, with this ViewState defined and added to the flow, when a new FlowExecution is created for this Flow, the [Phonebook] search criteria form view will be displayed.
The Search Criteria Bind And Validate State
The next state added to the search Flow is an ActionState that, when entered, binds form input submitted in the HttpServletRequest to a backing PhonebookQuery form object and validates it.
 | What is an ActionState?
An ActionState executes one or more implementations of the Action interface when entered. Actions can do anything you want them to do, but they typically delegate down to the service-layer to perform business operations, and/or prep views with dynamic model data for rendering. In essence, Actions act as thin bridges between the web-tier (browser/views) and the middle-tier (service layer).
When an Action completes execution, it signals a logical result outcome. This result is treated as an event used as grounds for a state transition in the calling ActionState. For example, an Action that returns a success outcome might cause a success ViewState to enter, while a error outcome might cause a error ViewState to enter.
Actions can also be chained together with ease, either by configuring multiple Actions for a single ActionState to execute in order, or chaining multiple ActionStates together as a supported path through the Flow. This is a form of the Chain of Responsibility pattern. |
Here is the search criteria bind and validate action state definition in XML:
<action-state id="bindAndValidate">
<action bean="person.Search.criteria.formAction"/>
<transition on="success" to="executeQuery"/>
<transition on="error" to="criteria.view"/>
</action-state>
Again, the id attribute is the identifier of the state definition. Nothing new here.
The action element is a descriptor for an Action implementation to execute when this state is entered. More than one may be defined. The bean attribute is the identifier of the Action implementation exported in the registry backing this FlowBuilder's configured FlowServiceLocator, which is by default a Spring BeanFactory. When this state definition is parsed, the Action implementation is located using the specified identifier. If no bean is found with that id, a descriptive NoSuchActionException will be thrown (we always fail-fast!).
Again, the transition element defines a Transition from this state to another state. Here you see two transitions are defined, that read:
- on an occurence of the success event, enter the executeQuery state.
- on an occurence of the error event, enter the criteria.view state (go back to the form view to revise validation errors).
Here is the same search criteria bind and validate state definition in Java Code:
addActionState("bindAndValidateCriteria",
method("bindAndValidate", action("person.Search.criteria.formAction")),
new Transition[] {
on(error(), "viewCriteria"),
on(success(), "executeQuery")
});
Again, both the XML and Java-based declaration produce the same thing: a fully configured ActionState instance for the Flow being built. The addBindAndValidate(...) helper method above makes the process of adding new states to the Flow trivial. In this case, the helper method:
- Assigns the state identifier to bindAndValidateCriteria.
- Resolves the Action implementation exported in the registry with te name "person.Search.criteria.formAction", using the configured FlowServiceLocator (as a convenience default).
- Creates a Transition that enters the viewCriteria state on an occurence of the error event.
- Creates a Transition that enters the executeQuery state on an occurence of the success event.
You might be wondering what the action definition exported in the registry accessed using the FlowServiceLocator looks like. In case of a Spring BeanFactory registry, the action definition looks like this:
<bean id="person.Search.criteria.formAction"
class="org.springframework.webflow.action.FormAction">
<property name="formObjectName" value="query"/>
<property name="formObjectClass" value="org.springframework.samples.phonebook.domain.PhoneBookQuery"/>
<property name="formObjectScopeAsString" value="flow"/>
<property name="validator">
<bean id="queryValidator" class="org.springframework.samples.phonebook.domain.PhoneBookQueryValidator"/>
</property>
</bean>
The action uses the predefined FormAction class, which is configured similarly to the well known Spring SimpleFormController. The FormAction creates a form object of the required type and uses the Spring DataBinder to populate it with request parameter data.
So in summary, with this ActionState added to the flow, when a new FlowExecution is created and the submit button is clicked from the search criteria form view, form input in the request is bound and validated to a backing PhonebookQuery object. If an error occurs, the criteria form view is redisplayed so the user can revise their edits. If the action completes successfully, the PhonebookQuery is exeuted.
 |
By now, you've likely noted that resolving an Action implementation by an identifier in a registry like the Spring BeanFactory is classic dependency lookup. This is fine, we generally want Actions to be externally managed so they can be easily configured and wired with middle-tier services by an assembler, like the Spring IoC container. However, dependency lookup has tradeoffs worth noting. Specifically, you must ensure an Action with the id you are asking is actually exported in the registry. Even with a descriptive error message to help you, and a fast-executing unit test to test configuration early, it's still possible to make a typo there (e.g I wanted Action 'foo' but exported it under id 'bar' by accident, oops). Second, you can't see in one place what code is executed when this ActionState is entered. You have to trace to where the selected Action implementation is exported and see what class it is, typically navigating to a Spring config file.
So what's the solution? With a Xml-based builder, you may specify the action class to use and have the container instantiate and autowire it for you. With a Java-based FlowBuilder, you can also take advantage of strongly-typed dependency injection.
The options are:
- By type (implementationClass) dependency lookup
- Local instantiation with an optional autowire mode
- Dependency Injection
We'll look at how the PhoneBook sample uses these approaches shortly. The bottom line benefit is this: you no longer have to lookup by ID (no String match required), and you see what exactly what code is to be executed from one place. |
The Execute Query State
The next state added to the search Flow is another ActionState that, when entered, executes the search query, pulling criteria values from the bound and validated PhonebookQuery form object. By now, you should be able to surmise what it would look like--it's just another ActionState definition. The only thing new here is, instead of executing an out-of-the-box FormAction, we're executing a custom ExecuteQueryAction we created.
Let's also use this as a chance to show action instantation with autowiring by type:
addActionState("executeQuery",
action(ExecuteQueryAction.class, AutowireMode.BY_TYPE),
new Transition[] {
on(error(), "viewCriteria"),
on(success(), "viewResults")
});
The action(Class, AutowireMode) method above will create the Action implementation and autowire the action's dependent collaborators by type.
Dependency injection, as you might imagine, is straightforward. Just define a setter method for the action:
public void setExecuteQueryAction(ExecuteQueryAction action) {
this.executeQueryAction = action;
}
Note: in this case it doesn't make sense to inject a generic Action interface, as a FlowBuilder is a configuration object that really should have knowledge of the selected Action implementation. Indeed, its arguable the only reason the FlowBuilder doesn't instantiate the Action itself is because of the benefits gained from leveraging the Spring BeanFactory's powerful configuration and dependency wiring capabilities.
The ExecuteQueryAction implementation
So what does our custom ExecuteQueryAction implementation look like? Here it is:
public class ExecuteQueryAction extends AbstractAction {
private PhoneBook phoneBook;
public void setPhoneBook(PhoneBook phoneBook) {
this.phoneBook = phoneBook;
}
protected Event doExecuteAction(RequestContext context) throws Exception {
PhoneBookQuery query = (PhoneBookQuery)context.getFlowScope().getAttribute("query");
context.getRequestScope().setAttribute("persons", phoneBook.query(query));
return success();
}
}
It's defining characteristics:
- Thin, delegating down to the PhoneBook service facade to execute business operations.
- Clean, implemented in very few lines of code. What code is there is focused on action logic that does some processing and returns a result.
As you can see, the ExecuteQueryAction is using flow Scope to access the bound and validated PhoneBookQuery form object. This form object was placed in flow scope by the BindAndValidate action that executed previously.
 | What is the RequestContext?
The RequestContext is a context for scoped data accessible to any Action implementation. Data may be accessed or set in request scope, as well as flow scope. All attributes set in the RequestContext are automatically exposed to any rendered views. |
So in summary, when ExecuteQueryAction executes, the PhoneBookQuery is executed and the query results are set in request scope. On success, a results ViewState is entered, displaying the results list of people that meet the search criteria. From there, the customer can select a person they are interested in, to view their details. The select event triggers a transition to an ActionState that executes a EventParameterMapperAction to bind the id of the selected person into flow scope, so it can be passed as input to the person details sub flow.
There is some new stuff discussed above, so let's look at it in turn. First, consider the EventParameterMapperAction state definition.
In XML:
<action-state id="personId.set">
<action bean="person.Search.id.set"/>
<transition on="success" to="person.Detail"/>
<transition on="error" to="error"/>
</action-state>
And Java:
EventParameterMapperAction setAction =
new EventParameterMapperAction(new Mapping("id",
getConversionExecutor(Long.class)));
setAction.setTargetScope(ScopeType.FLOW);
addActionState("setId", setAction,
new Transition[] {
on(error(), "error"),
on(success(), "person.Detail")
});
The above XML definition presents nothing new; however, the java based definition demonstrates how a Action implementation can be directly instantiated in Java code. This makes sense when you get no real value out of having the Action externally managed by Spring. For example, the EventParameterMapperAction instance above is very simple--it doesn't take a lot to be configured (all it needs is a Mapping object which tells it what parameter(s) in an incoming event to map to an attribute in flow scope and if any type conversion is neccessary).
The Person Details Sub Flow State
At this point you may be asking the question, "Why do we need a EventParameterMapperAction to set the id event parameter in flow scope?" Well, the RequestContext provides access to a flow-scoped data structure, for managing data local to a single FlowSession. Any attribute in the flow scope may be accessed or set by any executable Action implementation. In addition, any attribute in flow scope may be passed as input down to a spawned sub flow when a SubFlowState is entered. In this case, that's exactly what we want: we want to pass the id of a selected Person as input to the Person Details sub flow, because that's what that Flow needs to work. As you're about to see, Flows can be passed input attributes, and they can also produce output attributes. They are "black box composites" with a well-defined contract for input/output.