分享

Spring Web Flow: A Practical Introduction

 root_gao 2014-02-12

Spring Web Flow

A Practical Introduction
This article introduces Spring Web Flow, a sub project of the popular Spring Framework. Core Web Flow principles are explained and illustrated. The article serves as a practical introduction to building a web application using Spring Web Flow and the Spring Framework by discussing a sample application.

The reader is assumed to have a basic understanding of JEE web applications, XML, the Spring Framework and particularly the Spring Web MVC framework. Check the resources for links to background information.
02/01/2007 - By Erwin Vervaet (erwin@) - Spring Web Flow version 1.0

Introduction

Traditionally, defining the user interface (UI) flow in a web application has been a less than intuitive process. Frameworks like Struts and Spring Web MVC force you to cut the UI flow into individual controllers and views. Struts, for instance, will map a request to an action. The action will then select a view and forward to it. Although this is a simple and functional system, it has a major disadvantage: the overall UI flow of the web application is not at all clear from looking at the action definitions in the struts-config.xml file. Flexibility also suffers since actions cannot easily be reused.

The Spring Web MVC framework offers a slightly higher level of functionality: form controllers that implement a predefined work flow. Two such controllers are provided out of the box: SimpleFormController and AbstractWizardFormController. However, these are still hard coded examples of a more general work flow concept.

This is where Spring Web Flow comes in, allowing you to represent the UI flow in (part of) a web application in a clear and simple way. As we will see, this has several advantages:

  • The UI flow in a web application is clearly visible by looking at the corresponding web flow definition (typically in an XML file).
  • Web Flows can be designed to be self contained. This allows you to see a part of your application as a module and reuse it in multiple situations.
  • Web Flows can define any reasonable UI flow in a web application, always using the same consistent technique. You're not forced into using specialized controllers for very particular situations.

For now it suffices to say that a Web Flow is composed of a set of states. A state is a point in the flow where something happens: for instance showing a view or executing an action. Each state has one or more transitions that are used to move to another state. A transition is triggered by an event. To give you an impression of what a Web Flow might look like, the following piece of XML defines a Web Flow roughly equivalent to the work flow implemented by the SimpleFormController. A more detailed explanation of Web Flow principles will follow later on in the article.

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www./schema/webflow"
	xmlns:xsi="http://www./2001/XMLSchema-instance"
	xsi:schemaLocation="http://www./schema/webflow
			    http://www./schema/webflow/spring-webflow-1.0.xsd">

	<start-state idref="showForm" />
	
	<view-state id="showForm" view="form">
		<render-actions>
			<action bean="formAction" method="setupForm"/>
		</render-actions>
		<transition on="submit" to="doSubmit">
			<action bean="formAction" method="bindAndValidate"/>
		</transition>
	</view-state>
	
	<action-state id="doSubmit">
		<action bean="formAction" method="doSubmit"/>
		<transition on="success" to="showSuccess"/>
		<transition on="error" to="showForm"/>
	</action-state>
	
	<end-state id="showSuccess" view="success"/>
</flow>

Readers that are familiar with business process management (BPM) will realize that a Web Flow is a specialized case of a general purpose work flow, so it could theoretically be implemented using a general purpose BPM system. Since simplicity is an important design goal for Spring Web Flow, it does not use such a general purpose work flow engine. Instead it uses a simple Web Flow implementation targeting the definition of UI flows in a web application.

The source code for the sample application discussed in the remainder of this article (the so called Phonebook) is included in the Spring Web Flow 1.0.x distribution. It might be a good idea to download this if you haven't already done so. That way you can study the sample while reading through the article. You can also try the Phonebook sample application online at http://spring./swf-phonebook/.

Sample Application

The simple application we will use to illustrate the functionality offered by Spring Web Flow is a phonebook application. This is an application typically found on company intranets and most of you will probably be familiar with its concepts. Basically it allows you to locate an employee of the company using some search criteria. Once you have found the right person, you can consult detailed information such as phone number, desk location, manager, colleagues and so on. Figure 1 sketches the basic requirements and UI flow of this phonebook application.

Phonebook Application Sketch
Figure 1. Overview of the sample application

As the sketch illustrates, the application really consists of two modules: a search module that allows us to locate a particular person, and a detail module that displays details for a particular person. The search module will use the detail module to display details for one of the people in the search result. The sketch also shows that we can directly access the details for a person's colleague from the detail page. This means that the detail module will also recursively use the detail module to show colleague details.
Later on in the article, we will see that we can define each module in a separate Web Flow. This means we will end up with 2 flows: a search flow and a detail flow.

Since our focus in this article is the implementation of the web interface of the application, we will just provide a basic business layer containing hard-coded dummy data. The domain objects are contained in the package org.springframework.webflow.samples.phonebook. We have 4 main classes:

  • Person - A simple JavaBean containing person details (first name, last name, phone number, ...). A person object is uniquely identified by an id identity field. A person object also maintains a list of references to its colleagues, which are also Person instances.
  • SearchCriteria - A query object representing a search in the phonebook. We can search using the first name, the last name or both.
  • SearchCriteriaValidator - A validator to validate a SearchCriteria object.
  • PhoneBook - Our main business facade interface. It defines the business methods that we need:
    • public List<Person> search(SearchCriteria criteria);
    • public Person getPerson(Long id);
    The StubPhonebook implementation of the Phonebook interface just hard-codes some dummy data.

With the business functionality out of the way, we are ready to go and develop the web interface for our phonebook application with Spring Web Flow.

Spring Web MVC Setup

Before we can start using the Web Flow controller, we need to configure a basic Spring web application. The first thing we must do is make sure we have the necessary jar files in our /WEB-INF/lib directory. A Spring web application using Spring Web Flow typically requires the following jars in the classpath: spring.jar (the Spring Framework), commons-logging.jar (Apache commons logging), spring-binding (Spring binding framework), spring-webflow (Spring Web Flow) and ognl.jar (OGNL expression language).

Since this will be a normal JEE web application, we'll need a web.xml deployment descriptor in the /WEB-INF directory that describes the elements used in the application. The deployment descriptor for the sample application is shown below. It defines the following things:

  • The ContextLoaderListener, which initializes the Spring Framework when our web application is loaded by the servlet engine (e.g. Tomcat). The root application context will be loaded from the services-config.xml classpath resource.
  • A Spring dispatcher servlet named phonebook, loading its configuration from two application context files: phonebook-servlet-config.xml and phonebook-webflow-config.xml. This servlet is also configured to handle all requests matching the URL pattern *.htm, e.g. all requests for the sample application.
  • The welcome page of the application will be index.jsp.
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java./xml/ns/j2ee"
    xmlns:xsi="http://www./2001/XMLSchema-instance"
    xsi:schemaLocation="http://java./xml/ns/j2ee http://java./xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">
	
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			classpath:org/springframework/webflow/samples/phonebook/stub/services-config.xml
		</param-value>
	</context-param>
	
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<servlet>
		<servlet-name>phonebook</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>
				/WEB-INF/phonebook-servlet-config.xml
				/WEB-INF/phonebook-webflow-config.xml
			</param-value>
		</init-param>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>phonebook</servlet-name>
		<url-pattern>*.htm</url-pattern>
	</servlet-mapping>

	<welcome-file-list>
		<welcome-file>index.jsp</welcome-file>
	</welcome-file-list>
</web-app>

The root web application context, defined in services-config.xml configures our business facade: the phonebook bean. By seperating the definition of these business services from web related artifacts, we're following a common best practice.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www./schema/beans"
	xmlns:xsi="http://www./2001/XMLSchema-instance"
	xsi:schemaLocation="http://www./schema/beans
			    http://www./schema/beans/spring-beans-2.0.xsd">

	<bean id="phonebook" class="org.springframework.webflow.samples.phonebook.stub.StubPhonebook"/>
</beans>

Finally we need to properly configure the dispatcher servlet that we defined in web.xml above. It reads its configuration from two application context files: phonebook-servlet-config.xml contains classic Spring Web MVC artifacts and phonebook-webflow-config.xml contains Spring Web Flow specific elements. Alternatively, we could also have combined all beans into a single web application context.
For Spring Web MVC, the only thing we really need to configure is a view resolver. This object is responsible for translating symbolical view names (e.g. "searchCriteria") to actual view implementations (e.g. a JSP file /WEB-INF/jsp/searchCriteria.jsp). This is done using the InternalResourceViewResolver as shown below. So in the case of our sample application, the pages will be located in a directory called /WEB-INF/jsp.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www./schema/beans"
	xmlns:xsi="http://www./2001/XMLSchema-instance"
	xsi:schemaLocation="http://www./schema/beans
			    http://www./schema/beans/spring-beans-2.0.xsd">

	<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/jsp/"/>
		<property name="suffix" value=".jsp"/>
	</bean>
</beans>

We leave all other dispatcher servlet configuration at its default values. This implies we will be using a simple BeanNameUrlHandlerMapping to locate the controller that will handle a request. Consult the Spring Framework reference documentation for further details on how to configure the dispatcher servlet (see resources).

The Web Flow Controller

Let's now investigate how Spring Web Flow is integrated into a Spring Web MVC application. The component that does this integration is a specialized Spring Web MVC controller: org.springframework.webflow.executor.mvc.FlowController. This controller will handle all things Web Flow on behalf of the application. As any Spring Web MVC controller, it needs to be defined as a normal JavaBean in the application context of the dispatcher servlet. In our case we need to include the following bean definition in the phonebook-servlet-config.xml file.

<bean name="/phonebook.htm" class="org.springframework.webflow.executor.mvc.FlowController">
	<property name="flowExecutor" ref="flowExecutor"/>
</bean>

As you can see, the Web Flow controller references a flow executor. All flow execution management responsabilities will be delegated to that flow executor. The flow executor is defined in the phonebook-webflow-config.xml application context.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www./schema/beans"
       xmlns:xsi="http://www./2001/XMLSchema-instance"
       xmlns:flow="http://www./schema/webflow-config"
       xsi:schemaLocation="
           http://www./schema/beans
           http://www./schema/beans/spring-beans-2.0.xsd
           http://www./schema/webflow-config
           http://www./schema/webflow-config/spring-webflow-config-1.0.xsd">

	<flow:executor id="flowExecutor" registry-ref="flowRegistry"/>
	
	<flow:registry id="flowRegistry">
		<flow:location path="/WEB-INF/flows/**-flow.xml"/>
	</flow:registry>
</beans>

Using the convenient Spring 2 Web Flow configuration schema, we setup a flow executor and give it a reference to a flow registry. A flow registry loads flow definitions from XML files and makes them available for execution using the flow executor. In our sample application, the flow registry will load flows from XML files found in the /WEB-INF/flows/ directory that have the -flow.xml suffix. The registry will automatically assign an identifier to each loaded flow based on the file name. The search flow defined in search-flow.xml will be assigned the id search-flow. The detail flow defined in detail-flow.xml will receive the id detail-flow.

Remember that we are using the BeanNameUrlHandlerMapping, locating handlers for a request by obtaining a bean name from the request. If we assume the sample application will be available under the context root /swf-phonebook, a request of the form http://server/swf-phonebook/phonebook.htm will be mapped to the Web Flow controller defined above.

When the Web Flow controller is invoked by the dispatcher servlet to handle a request, it examines the request parameters to determine what to do. The following request parameters are recognized:

Name Description
_flowId The value of this parameter provides the id of a flow for which a new execution should be launched. Possible values in our sample application are search-flow and detail-flow.
_flowExecutionKey The unique id of an ongoing flow execution. The flow executor will use this to resume an ongoing flow execution.
_eventId The event that will be triggered in the current state of the flow. It is required when accessing an ongoing flow execution and not used when launching a new flow execution.

Using the values of the request parameters specified above, the flow controller implements the following logic:

  1. When a request comes in, the "_flowExecutionKey" parameter is retrieved from the request.
    • When present, the flow controller will restore the ongoing flow execution and resume it.
    • If it is not present, the flow controller launches a new flow execution for the flow identified using the "_flowId" parameter. The new execution will be assigned a unique key.
  2. For an existing flow execution, the value of the "_eventId" request parameter will be used to trigger a transition defined in the current state of the flow. If no event id is specified, the flow execution will just be refreshed.
  3. The outcome of the steps above is a response instruction containing a model and a view to render. The flow execution key is available in the model using the name "flowExecutionKey". This key can be used by the view to reference back to the ongoing flow execution.

We now have enough information to add a link to our search flow in the index.jsp welcome page. Since we want to launch a new flow, we only need to provide the "_flowId" request paramter in the request to the Web Flow controller. The following piece of HTML does exactly that:

<a href="phonebook.htm?_flowId=search-flow">Phonebook</a>

Since the URL of the welcome page will be of the form http://server/swf-phonebook/, this relative URL will resolve to http://server/swf-phonebook/phonebook.htm?_flowId=search-flow. As we explained before, this will invoke the Web Flow controller, launching a new execution of the search flow.

Web Flow Principles

With all the infrastructure in place, we are now ready to start looking at Web Flows and what they can do for you. Technically, a Web Flow is nothing more than an XML file representation of the UI flow of a web application. This XML format is defined in an XML schema. To properly indicate that an XML file contains a Web Flow definition, it should contain the following schema reference:

<flow xmlns="http://www./schema/webflow"
	xmlns:xsi="http://www./2001/XMLSchema-instance"
	xsi:schemaLocation="http://www./schema/webflow
			    http://www./schema/webflow/spring-webflow-1.0.xsd">

The Web Flow schema defines that a flow is composed of a set of states. Each state will have a unique id in the flow. The following state types are supported.

  • start-state - Each flow must have a single start state. This is a marker state defining the id of another state in the flow where flow execution will start.
  • action-state - A flow can have multiple action states, defining places in the Web Flow where actions take place. An action normally involves such things as processing user input, interacting with the business layer or preparing model data for a view.
  • view-state - View states define points in the flow where a view (page) will be shown. A Web Flow can have multiple view states.
  • decision-state - Decision states take routing decisions, selecting possible paths through a flow.
  • subflow-state - This state type launches another Web Flow as a subflow. It allows you to reuse a flow from inside another flow. Attribute mappers are used to exchange data between a flow and its subflows.
  • end-state - A state signaling the end of flow execution. Note that a Web Flow can have multiple end states, each representing another final situation (success, error, ...). An end state can optionally have an associated view that will be rendered when the end state is reached if there is no other view to render (e.g. in a parent flow).

As explained in the introduction, each state in a Web Flow (except for the start and end states) defines a number of transitions to move from one state to another. A transition is triggered by an event signaled inside a state. The way a state signals events depends on the state type. For a view state, the event is based on user input submitted to the controller using the "_eventId" request parameter. In case of an action state, the executed actions signal events. Subflow-states signal events based on the outcome of the subflow they spawned.

To step away from theory, lets try to convert the UI flow of the sample application, as depicted in Figure 1, into a Web Flow. As we mentioned before, our phonebook application really consists of two modules: a search module and a detail module. Starting with just the view states for the search module, we end up with the following flow definition.

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www./schema/webflow"
      xmlns:xsi="http://www./2001/XMLSchema-instance"
      xsi:schemaLocation="http://www./schema/webflow
                          http://www./schema/webflow/spring-webflow-1.0.xsd">

	<start-state idref="enterCriteria"/>

	<view-state id="enterCriteria" view="searchCriteria">
		<transition on="search" to="displayResults"/>
	</view-state>

	<view-state id="displayResults" view="searchResults">
		<transition on="newSearch" to="enterCriteria"/>
		<transition on="select" to="...detail module..."/>
	</view-state>
</flow>

As you can see, this closely follows our initial UI flow design. Note that this flow does not define an end state. This is typical for a Web Flow since there are many web applications that never really end. The flow for the detail module is very similar (see below). In this case we do have an end state since we need to end this flow and return to the calling flow when going back.

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www./schema/webflow"
      xmlns:xsi="http://www./2001/XMLSchema-instance"
      xsi:schemaLocation="http://www./schema/webflow
                          http://www./schema/webflow/spring-webflow-1.0.xsd">

	<start-state idref="displayDetails" />

	<view-state id="displayDetails" view="details">
		<transition on="back" to="finish" />
		<transition on="select" to="...detail module..." />
	</view-state>

	<end-state id="finish" />
</flow>

These Web Flow definitions reference three views (using the view attribute): "searchCriteria", "searchResults" and "details". Given the view resolver definition that we're using (discussed earlier), we end up with three corresponding JSP pages: /WEB-INF/jsp/searchCriteria.jsp, /WEB-INF/jsp/searchResults.jsp and /WEB-INF/jsp/details.jsp. Let's now look at the implementation of these JSPs.

Views

Implementing views used in a Spring Web Flow based application is not that different from view implementation in any other Spring Web MVC application. The only point of interest is linking back to the flow controller and signaling an event. The sample application uses JSP 2.0 as view technology with embedded JSP EL expressions. The Spring 2 bind and form tag libraries are also used.

Let's start by looking at the "searchCriteria" view, implemented in /WEB-INF/jsp/searchCriteria.jsp. This page shows a simple input form submitting phonebook search criteria to the server.

<form:form commandName="searchCriteria" method="post">
<table>
	<tr>
		<td>Search Criteria</td>
	</tr>
	<tr>
		<td colspan="2">
			<hr/>
		</td>
	</tr>
	<spring:hasBindErrors name="searchCriteria">
	<tr>
		<td colspan="2">
			<div class="error">Please provide valid search criteria</div>
		</td>
	</tr>
	</spring:hasBindErrors>
	<tr>
		<td>First Name</td>
		<td>
			<form:input path="firstName" />
		</td>
	</tr>
	<tr>
		<td>Last Name</td>
		<td>
			<form:input path="lastName" />
		</td>
	</tr>
	<tr>
		<td colspan="2">
			<hr/>
		</td>
	</tr>
	<tr>
		<td colspan="2" class="buttonBar">
			<input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
			<input type="submit" class="button" name="_eventId_search" value="Search"/>
		</td>
	</tr>		
</table>
</form:form>

First of all, notice that we do not specify an "action" on the form. This page is served by the flow controller so the Spring Web MVC form tag library will automatically post the form back to the flow controller. In other words, the form submission will be handled by our Web Flow controller. The next thing to notice is the two special inputs that are contained in the form:

  • The hidden field "_flowExecutionKey" is filled with the flow execution key of the current flow execution. As explained above, this key is exposed to the view using the name "flowExecutionKey".
  • The "_eventId" is encoded in the name of a button field. Alternatively we could have used a hidden field with as name "_eventId" and as value "search".

When the form is submitted, the "search" event will be signaled in the current state of the flow. The current state is the "enterCriteria" view state, so in response to the "search" event, the flow will transition to the "displayResults" view state and render the "searchResults" view.

The "searchResults" view implemented in /WEB-INF/jsp/searchResults.jsp and the "details" view implemented in /WEB-INF/jsp/details.jsp are very similar. Here is an example from the "details" view that uses an anchor to submit the "select" event in the detail-flow. Note that the anchor also submits an "id" request parameter allong with the "select" event.

<a href="phonebook.htm?_flowExecutionKey=${flowExecutionKey}&_eventId=select&id=${colleague.id}">
	${colleague.firstName} ${colleague.lastName}<br/>
</a>

Of course the Web Flows discussed so far don't really do anything. They just navigate between pages. To do some actual processing in a Web Flow, we need actions. We'll discuss this next.

Actions

Actions can be executed at several different locations in a Web Flow: as part of a transition, in a seperate action state, as render actions in a view state and so on. Actions are pieces of code that do useful work in the flow like processing user input, triggering business functionality or preparing model data for a view. When their work is done, actions signal an event allowing the Web Flow to continue. An action is just a simple Java class implementing the org.springframework.webflow.execution.Action interface.

public interface Action {
    public Event execute(RequestContext context) throws Exception;
}

Let's examine this method signature. First, the RequestContext argument provides the action code access to information about the ongoing flow execution. Among other things, the action can get access to the different scopes defined by Spring Web Flow through the request context. The union of all data available in the different scopes will be exposed to a view when it is rendered. Spring Web Flow defines the following scopes:

  • request scope - Local to a single request into the Web Flow system.
  • flash scope - Scoped at the level of a user event signaled in an ongoing flow execution. Survives any refreshes of that flow execution.
  • flow scope - Persists for the life of a flow session. The states in a flow can share information using flow scope. A parent flow has a different flow session than a subflow.
  • conversation scope - Available to all flow sessions in a conversation.

Finally, the execute method returns the event that will be signaled.

Spring Web Flow provides an action with HTML form handling functionality out-of-the-box: org.springframework.webflow.action.FormAction. This action will bind request parameters to a form backing object, validate the form object and put it in a configured flow scope. It's functionality is very similar to that offered by the Spring BaseCommandController. Our sample application uses this action to capture the search criteria in a SearchCriteria object. Let's add form handling functionality to our search flow:

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www./schema/webflow"
      xmlns:xsi="http://www./2001/XMLSchema-instance"
      xsi:schemaLocation="http://www./schema/webflow
                          http://www./schema/webflow/spring-webflow-1.0.xsd">

	<start-state idref="enterCriteria"/>

	<view-state id="enterCriteria" view="searchCriteria">
		<render-actions>
			<action bean="formAction" method="setupForm"/>
		</render-actions>
		<transition on="search" to="displayResults">
			<action bean="formAction" method="bindAndValidate"/>
		</transition>
	</view-state>

	<view-state id="displayResults" view="searchResults">
		<transition on="newSearch" to="enterCriteria"/>
		<transition on="select" to="...detail module..."/>
	</view-state>
	
	<import resource="search-flow-beans.xml"/>
</flow>

As you can see, we've added a call to the "setupForm" method of the form action as a render action of the "enterCriteria" view state. Render actions are executed by a view state before they render the view. In this case we use the "setupForm" method to prepare the Spring form handling machinery to properly display the form.
The next thing to notice is that we've added a call to the "bindAndValidate" method of the form action in the "search" transition. When the "search" transition is triggered, it will execute this "bindAndValidate" method to bind incoming request parameters to the form backing object and validate them. If all is well, the flow will transition to the "displayResults" state. If binding or validation fails, the transition will roll back and re-enter the "enterCriteria" state.

The action definitions shown above reference a bean named "formAction". This bean can be defined in a Spring application context associated with the flow: /WEB-INF/flows/search-flow-beans.xml, like so:

<beans xmlns="http://www./schema/beans"
       xmlns:xsi="http://www./2001/XMLSchema-instance"
       xsi:schemaLocation="http://www./schema/beans
                           http://www./schema/beans/spring-beans.xsd">

	<bean id="formAction" class="org.springframework.webflow.action.FormAction">
		<property name="formObjectClass" value="org.springframework.webflow.samples.phonebook.SearchCriteria"/>
		<property name="validator">
			<bean class="org.springframework.webflow.samples.phonebook.SearchCriteriaValidator"/>
		</property>
	</bean>
</beans>

We tell the form action to use the SearchCriteria class as form backing object. Our custom SearchCriteriaValidator will ensure that the submitted search criteria are not empty. By default the form action will store the form backing object in flow scope and will assign it a name based on the class name: "searchCriteria" in this case.

Now that we've filled our SearchCriteria object with user submitted values, we need to pass it to our Phonebook service to actually execute the search. We could write a custom action to do this, but Spring Web Flow allows us to directly call methods on any Spring managed bean from inside a Web Flow. Let's do that and add the necessary definitions to our search flow:

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www./schema/webflow"
      xmlns:xsi="http://www./2001/XMLSchema-instance"
      xsi:schemaLocation="http://www./schema/webflow
                          http://www./schema/webflow/spring-webflow-1.0.xsd">

	<start-state idref="enterCriteria"/>

	<view-state id="enterCriteria" view="searchCriteria">
		<render-actions>
			<action bean="formAction" method="setupForm"/>
		</render-actions>
		<transition on="search" to="displayResults">
			<action bean="formAction" method="bindAndValidate"/>
		</transition>
	</view-state>

	<view-state id="displayResults" view="searchResults">
		<render-actions>
			<bean-action bean="phonebook" method="search">
				<method-arguments>
					<argument expression="flowScope.searchCriteria"/>			
				</method-arguments>
				<method-result name="results"/>
			</bean-action>
		</render-actions>
		<transition on="newSearch" to="enterCriteria"/>
		<transition on="select" to="...detail module..."/>
	</view-state>
	
	<import resource="search-flow-beans.xml"/>
</flow>

By adding a call to the "search" method on the "phonebook" bean (defined earlier) to the render actions of the "displayResults" view state, the search will be executed each time the "searchResults" view needs to be rendered. We pass the "searchCriteria" object found in flow scope as an argument to the "search" method. It was the form action that put the "searchCriteria" object into flow scope during the "bindAndValidate" action. The search results are put into request scope (the default) under the name "results". The "searchResults" view will have access to this result list.

Similar definitions can be found in the detail flow. It calls the "getPerson" method on the "phonebook" bean to load the details of a selected person.

Subflows and Attribute Mappers

Let's now look at the subflow state. This state type allows a Web Flow to use another flow as a subflow. While the subflow is active, execution of the parent flow session is suspended and the subflow handles all requests. When the subflow reaches an end state, execution continues in the parent flow. The event signaled to continue the parent flow is the id of the end state that was reached in the subflow.

As explained before, our sample application uses 2 modules: a search module and a detail module, each defined in a separate Web Flow. It should now be clear that the search flow will use the detail flow as a subflow to show person details. Since we can directly access a person's colleague's details from the detail flow, the detail flow will also use itself as a subflow! What we have really done now is package the detail flow as a reusable web application module! It can easily be reused in different situations: starting from the search flow, from inside the detail flow, or even as a stand-alone flow if that would be useful. This is a very powerful functionality offered by the Spring Web Flow system.

Let's now look at the complete search flow definition to see how it invokes the detail flow as a subflow.

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www./schema/webflow"
      xmlns:xsi="http://www./2001/XMLSchema-instance"
      xsi:schemaLocation="http://www./schema/webflow
                          http://www./schema/webflow/spring-webflow-1.0.xsd">

	<start-state idref="enterCriteria"/>

	<view-state id="enterCriteria" view="searchCriteria">
		<render-actions>
			<action bean="formAction" method="setupForm"/>
		</render-actions>
		<transition on="search" to="displayResults">
			<action bean="formAction" method="bindAndValidate"/>
		</transition>
	</view-state>

	<view-state id="displayResults" view="searchResults">
		<render-actions>
			<bean-action bean="phonebook" method="search">
				<method-arguments>
					<argument expression="flowScope.searchCriteria"/>			
				</method-arguments>
				<method-result name="results"/>
			</bean-action>
		</render-actions>
		<transition on="newSearch" to="enterCriteria"/>
		<transition on="select" to="browseDetails"/>
	</view-state>

	<subflow-state id="browseDetails" flow="detail-flow">
		<attribute-mapper>
			<input-mapper>
				<mapping source="requestParameters.id" target="id" from="string" to="long"/>
			</input-mapper>
		</attribute-mapper>
		<transition on="finish" to="displayResults"/>
	</subflow-state>

	<import resource="search-flow-beans.xml"/>
</flow>

When the "select" event is triggered in the "displayResults" view state, the flow transitions to the "browseDetails" subflow state, which will launch the detail-flow as a subflow to show person details. The detail flow defines a "finish" end state, so the subflow state in the parent flow has a corresponding transition taking the flow back to the "displayResults" view state. Here is the complete definition of the detail flow:

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www./schema/webflow"
      xmlns:xsi="http://www./2001/XMLSchema-instance"
      xsi:schemaLocation="http://www./schema/webflow
                          http://www./schema/webflow/spring-webflow-1.0.xsd">

	<input-mapper>
		<input-attribute name="id"/>
	</input-mapper>

	<start-state idref="displayDetails" />

	<view-state id="displayDetails" view="details">
		<render-actions>
			<bean-action bean="phonebook" method="getPerson">
				<method-arguments>
					<argument expression="flowScope.id" />
				</method-arguments>
				<method-result name="person" />
			</bean-action>
		</render-actions>
		<transition on="back" to="finish" />
		<transition on="select" to="browseColleagueDetails" />
	</view-state>

	<subflow-state id="browseColleagueDetails" flow="detail-flow">
		<attribute-mapper>
			<input-mapper>
				<mapping source="requestParameters.id" target="id" from="string" to="long" />
			</input-mapper>
		</attribute-mapper>
		<transition on="finish" to="displayDetails" />
	</subflow-state>

	<end-state id="finish" />
</flow>

We are now left with one remaining unanswered question: how will the detail flow know which person details to show? Or in other words: how does the parent flow pass this kind of information to the subflow? The answer is an attribute mapper. Attribute mappers map model data from a parent flow to a subflow and back again.

Mapping data to the subflow happens before the subflow session is started. Mapping data from the subflow back to the parent flow is done when the subflow completes and the parent flow session resumes. A model mapper is optional in a subflow state. If no mapper is specified, no mapping will be done. Input and output mappers effectively allow you to define an input-output contract for a Web Flow. Our search flow uses an attribute mapper to map the "id" request parameter to an "id" input attribute for the subflow. The String request parameter will also be converted to a long during the mapping process.

The detail flow then captures this "id" input attribute and places it in flow scope (the default in this case). The "id" value in flow scope is passed on to the "getPerson" method of our Phonebook service later on when the "displayDetails" view state renders its view.

Testing the Application

Let's now put our sample application to the test to see what we have created. Follow the instructions included in the Spring Web Flow distribution to build the Phonebook WAR file and deploy it to the servlet engine of your choice.

Once your server has been started, open the URL http://localhost:8080/swf-phonebook/ with your browser. You can also just use the publicly available Phonebook sample that can be found at http://spring./swf-phonebook/. Accessing this URL will display the welcome page of the application: index.jsp. This page simply contains a link to the phonebook sample application. Click this link and you will end up on the phonebook search criteria page.

Criteria Page
Figure 2. The search criteria page

Leaving both fields empty will be refused by the SearchCriteriaValidator that we defined: a validation error message will appear at the top of the search criteria page. Entering some valid criteria will trigger a search and displays the results page.

Results Page
Figure 3. The search results page

From this page we can go back to the criteria page to start a new search or consult person details for one of the people we found. Click the user id link to get details for a particular person. The detail subflow will be launched, using our Phonebook service to retrieve the person details.

Detail Page
Figure 4. The person detail page

The person detail page allows us to go back to the search results page, or directly consult the details for one of the colleagues of a person. The latter results in a recursive invocation of the detail flow. Notice that the Back button behaves differently depending on the situation: you either go back to the search results page or, in case you consulted the details of a colleague, you go back to the details of the previous person.

Also note that the application is completely stable when using the browser Back and Refresh buttons. Spring Web Flow handles this using web continuations. A discussion of this technique is beyond the scope of this article.

Conclusion

This concludes our practical introduction to Spring Web Flow. We talked about the general concepts that underpin this framework and how it is integrated into the Spring Web MVC framework. As an illustration, we used a phonebook sample application.

Spring Web Flow delivers a number of important benefits:

  • Web Flows capture the UI flow in a web application in a clear and easily readable way.
  • They provide a consistent manner to model navigation throughout a web application.
  • Spring Web Flow handles all navigational control for you, making sure your applications works correctly even when the user uses the dreaded browser back and refresh buttons.
  • Web Flows allow you to package a part of a web application as a self contained, reusable module.
  • Spring Web Flow offers sophisticated state management, providing several new scopes for conversation related data.
  • Web Flows are closely integrated with a Spring application context allowing you to leverage all of Spring's powerful features, e.g. AOP.

There is much more to Spring Web Flow than what we have been able to discuss in this practical introduction. We encourage you to give it a try and tap into the powerfull features it offers! An excellent way to learn more is by reading Working with Spring Web Flow.

Resources and References

About the Author

Erwin Vervaet is a software engineer with a keen interest in applying modern IT concepts and tools. He holds a master's degree in computer science from the Katholieke Universiteit Leuven in Belgium. Erwin has been using Java since its inception and has extensive experience applying it in IT research, e-commerce projects, open source initiatives, industrial software systems and business support applications. He currently works as an independent consultant, running his own software and consultancy company: Ervacon.
Erwin also enjoys teaching and speaking on Java and Spring related subjects. As the originator of the Spring Web Flow project, he currently co-leads it's development together with Keith Donald.
You can contact Erwin at erwin@.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多