Spring Web FlowThis 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.02/01/2007 - By Erwin Vervaet (erwin@) - Spring Web Flow version 1.0 IntroductionTraditionally, 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:
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 ApplicationThe 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. 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. 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:
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 SetupBefore 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:
<?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. <?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 ControllerLet'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:
Using the values of the request parameters specified above, the flow controller implements the following logic:
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 PrinciplesWith 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.
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. ViewsImplementing 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:
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. ActionsActions 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:
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 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 MappersLet'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 ApplicationLet'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. 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. 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. 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. ConclusionThis 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:
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. |
|