Tutorial: Build a Shopping Cart with Spring Web Flow 2.0
This article shows you how to get started with Spring Web Flow (SWF) 2.0 by building a simple shopping cart application. We'll do this as a series of steps:
- First we'll just get Spring MVC working.
- We'll use Spring Web Flow to create a minimal flow with a single state.
- Finally we'll build the app out to contain multiple flows and subflows that make calls against backend logic.
SWF 2.0 is at the time of this writing very new, and there are several differences as compared to SWF 1.0.x. We will not however spend much time discussing those differences; instead we'll just focus on SWF 2.0.
To get the most out of this article, you should already be familiar with Spring in general and Spring MVC in particular.
An Overview of Spring Web Flow
Spring Web Flow builds upon Spring MVC to support user-level, application-directed control flows. For example, an e-commerce application may need to guide the end user through a checkout process that spans several web pages. The flow is not simply a multipage form (Spring MVC itself already supports that); rather it is a multistep process involving standard control flow constructions such as decisions ("please confirm or cancel your order"), loops ("we recommend products x, y, z... add as many as you like to your order") and subflows ("are you a new customer? please create an account"). In general, implementing such flows is not trivial. SWF is an elegant solution to just this sort of problem.
To understand better, contrast application-directed interactions with the user-directed interactions that typically constitute the main part of an application's functionality. In user-directed interactions, the end user picks some function from a set of available functions, provides some parameters around that function, and then asks the application to carry it out. For instance, the end user tells Photoshop to fill a selected region with "evil green" (RGB 00FF00). The app dutifully does just that, and control returns to the end user.
There are many cases, however, in which we want the application to drive a complex interaction between itself and the end user. Perhaps the most obvious example is the one I mentioned above: the e-commerce checkout process. See Figure 1 for one possible flow (and in fact this is the flow we're going to implement):
In the checkout flow above, we have a starting state, an end state, and several intermediate states. In the first state, we show the customer the contents of a shopping cart, along with some recommended products that the customer may want to buy. The customer can add any of those products to the shopping cart, or he might decide to move forward by indicating whether he is a new or returning customer. If he's a new customer, he'll need to create an account; otherwise he can just log in with his existing account. In either case, he'll need to select a payment method, provide shipping information, and so forth. At any step along the way he can cancel out of the checkout process.
Benefits of using Spring Web Flow
While you can certainly implement that flow without SWF, there are some challenges involved if you're doing it from scratch. Let's take a look at some of those.
Understanding flow logic. For one, because the logic behind the flow itself is reasonably complex, it would be nice to be able to isolate the flow and its logic from the various pieces (like JSP pages, business logic, etc.) that make it up. But the most straightforward implementation of flow logic would most likely involve distributing the flow logic (such as state transitions) across lots of different files. So one challenge is that you either have to do a lot of extra work to externalize the flow logic, or else you have to live with that logic being distributed, which makes it much harder to understand.
Reusing flow logic. Another challenge is related to the fact that a flow has a logical structure that stands on its own, and in many cases you'd like to be able to reuse that—either across multiple apps, or else in multiple places within a single app. This is hard to accomplish without an easy way to isolate that flow.
Getting the implementation right. A third challenge is just that it's easy to get the technical details wrong. For instance, what happens if the end user hits the browser's back button in the middle of the flow? If the previous page had a form, we don't usually want the browser to confuse the user with a warning about resubmitting data or whatever. Coding up flows from scratch means that you have to handle this sort of thing explicitly. It would be nice not to have to mess around with this kind of thing—to have it handled automatically for you.
That's enough of an overview for us to get started. Let's now look at setting up our sample project.
Spring MVC Setup
Since Spring Web Flow is build on Spring MVC, let's start by getting Spring MVC working. We'll create a simple home page for our shopping cart, and we'll serve this up using a plain old Spring controller (well, it will be annotated) rather than serving it using Spring Web Flow. That's because the home page itself isn't part of any particular flow; it simply provides entry points into various flows, such as creating an account, logging in, adding an item to a shopping cart, and checking out. Besides allowing us to make sure we have Spring MVC working before moving forward, this approach will also allow us to see how to integrate Spring MVC and Spring Web Flow.
For your convenience, here's a download of the minimalistic (i.e. only Spring MVC, no SWF) shopping cart we're about to take a look at:
The download above does not include its dependencies. You will need to grab those separately. I've provided the links below.
Dependencies for mycart1.zip
These are all part of the Spring 2.5.4 distribution. Spring Web Flow 2.0 requires Spring 2.5.4 or higher. [download]
spring.jar
(located in/spring-framework-2.5.4/dist
)spring-webmvc.jar
(located in/spring-framework-2.5.4/dist/modules
)commons-logging.jar
(located in/spring-framework-2.5.4/jakarta-commons
)
We will be adding dependencies as we progress; for the moment we're just getting Spring MVC set up.
Create a Spring MVC Controller
Here's a very simple Spring MVC controller. We'll be updating this over the course of the article.
mycart.CartController
- package mycart;
-
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
-
- @Controller
- public class CartController {
-
- @RequestMapping("/home.do")
- public void doHome() {
- }
- }
This controller doesn't do much at all. Basically we're using annotations to map /home.do
requests to a JSP. For more details on how that works, please see my article Annotation-Based MVC in Spring 2.5.
Create a JSP
Here's the home page JSP I just mentioned. Like CartController
, we'll be updating this.
/WEB-INF/jsp/home.jsp
-
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- <html>
- <head>
- <title>Products for Geeks - GeekWareztitle>
- head>
- <body>
- <h1>Welcome to GeekWarezh1>
- body>
- html>
Create web.xml
Here's our web.xml
file:
/WEB-INF/web.xml
- xml version="1.0" encoding="UTF-8"?>
- <web-app xmlns="http://java.sun.com/xml/ns/javaee"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
- http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
- version="2.5">
-
-
mycart org.springframework.web.servlet.DispatcherServlet mycart *.do
All we're doing here is creating the Spring MVC DispatcherServlet
front controller. Because we've named it mycart
, the default behavior for DispatcherServlet
is to look for a Spring application context configuration file at /WEB-INF/mycart-servlet.xml
, which we are about to see.
Eventually this front controller will handle not only our "normal" non-SWF requests, but also our SWF requests. However I'm getting ahead of myself.
Create the Spring application context file
Here's mycart-servlet.xml
, which DispatcherServlet
loads as just explained:
/WEB-INF/mycart-servlet.xml
- xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-2.5.xsd">
-
-
-
- <context:annotation-config/>
-
- <context:component-scan base-package="mycart"/>
-
-
- <bean class=
- "org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
-
-
- <bean class=
- "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
-
-
- <bean id="viewResolver" class=
- "org.springframework.web.servlet.view.InternalResourceViewResolver">
- <property name="prefix" value="/WEB-INF/jsp/"/>
- <property name="suffix" value=".jsp"/>
- bean>
- beans>
Nothing special here assuming you already know Spring MVC.
Milestone 1: Spring MVC is Working
At this point you should be able to deploy the application. Point your browser at
http://localhost:8080/mycart1/home.do
and you should get a very simple home page. If so, congratulations, Spring MVC is working.
Now it's time to create our first flow using Spring Web Flow.
Creating a Simple Flow: Spring Application Context Configuration
Now we have Spring MVC working, so it's time to expand on that and get Spring Web Flow working too. We'll start by creating a very simple flow—one that has only a single view state. First we'll look at the updated Spring application context configuration. After that we'll look at the flow definition itself.
To make it easier for you to follow along, here's the updated, one-state version of our shopping cart:
Dependencies for mycart2.zip
Because we're now using SWF, we have additional dependencies. Here's the full set so far:
These come from the Spring 2.5.4 distribution. [download]
spring.jar
(located in/spring-framework-2.5.4/dist
)spring-webmvc.jar
(located in/spring-framework-2.5.4/dist/modules
)commons-logging.jar
(located in/spring-framework-2.5.4/jakarta-commons
)
You will need the following JARs from the Spring Web Flow 2.0 distribution. [download]
spring-webflow-2.0.0.jar
spring-binding-2.0.0.jar
spring-js-2.0.0.jar
You will need the following JAR from the OGNL web site:
ognl-2.6.9.jar
Spring Web Flow uses either OGNL or Unified EL for parsing expressions. For this article I arbitrarily picked OGNL though you can use jboss-el.jar
(currently the only Unified EL implementation that will work with SWF) too.
web.xml and mycart.CartController
For now we aren't making any changes to either of these at all.
Spring application context overview
Before jumping into the code, let's do a quick overview of the various components involved. Please see Figure 2.
We already saw DispatcherServlet
in web.xml
. (So it's not part of mycart-servlet.xml
; we're discussing it here just to show the relationship between the servlet and the FlowController
.) DispatcherServlet
is the Spring MVC front controller and it receives any Spring MVC requests—including SWF requests, as SWF is based on Spring MVC—according to your web.xml
configuration.
We're using SimpleUrlHandlerMapping
to map flow requests from DispatcherServlet
to FlowController
.
So now FlowController
has the request. FlowController
is just a Spring MVC controller that receives flow requests and passes them to FlowExecutor
for actual processing.
FlowExecutor
contains the actual logic for processing Spring Web Flow requests. Among other things it determines for any given request which flow is involved and figures out what state transition to enact, based on the request.
When FlowExecutor
needs a flow, it grabs the flow from the FlowRegistry
, which is responsible for loading the flow from a flow configuration file and maintaining it on behalf of the FlowExecutor
.
FlowBuilderServices
is just a container for various services that FlowRegistry
needs when constructing flows. This includes, for example, a service to provide for the creation of view factories. In our example, we will see that we're specifically using a MvcViewFactoryCreator
, which creates view factories for Spring MVC views.
Finally, we define a ViewResolver
, which is a Spring MVC interface that carries logical view names to physical resources (such as JSPs). For example, a ViewResolver
might carry the logical name checkout/viewcart
to the physical resource /WEB-INF/jsp/checkout/viewcart.jsp
.
Spring application context configuration file
And finally here's the configuration file itself:
/WEB-INF/mycart-servlet.xml
- xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:flow="http://www.springframework.org/schema/webflow-config"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
- http://www.springframework.org/schema/context
- http://www.springframework.org/schema/context/spring-context-2.5.xsd
- http://www.springframework.org/schema/webflow-config
- http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">
-
-
-
-
-
-
- <context:annotation-config/>
-
- <context:component-scan base-package="mycart"/>
-
-
- <bean class=
- "org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
-
-
- <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
- <property name="mappings">
- <value>
- /account/register.do=flowController
- value>
- property>
- <property name="alwaysUseFullPath" value="true"/>
- bean>
-
-
- <bean class=
- "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
-
-
- <bean class=
- "org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
-
-
- <bean id="viewResolver" class=
- "org.springframework.web.servlet.view.InternalResourceViewResolver">
- <property name="prefix" value="/WEB-INF/jsp/"/>
- <property name="suffix" value=".jsp"/>
- bean>
-
-
-
-
- <bean id="flowController" class=
- "org.springframework.webflow.mvc.servlet.FlowController">
- <property name="flowExecutor" ref="flowExecutor"/>
- bean>
-
- <flow:flow-executor id="flowExecutor" flow-registry="flowRegistry"/>
-
-
- <flow:flow-registry id="flowRegistry"
- flow-builder-services="flowBuilderServices">
- <flow:flow-location path="/WEB-INF/flows/register.xml"/>
- flow:flow-registry>
-
- <flow:flow-builder-services id="flowBuilderServices"
- view-factory-creator="viewFactoryCreator"/>
-
- <bean id="viewFactoryCreator" class=
- "org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
- <property name="viewResolvers">
- <list>
- <ref bean="viewResolver"/>
- list>
- property>
- bean>
- beans>
Now let's look at our basic flow definition, which will initially at least be much simpler than the application context file.
Creating a Simple Flow: Flow Definition File and JSPs
We'll now add a SWF flow definition file, update our original home page JSP, and add a new JSP for registering as a new customer.
The flow definition file
Here's a definition for our first flow, which will be a bare-bones registration process. We'll add more flows to the application but this is our starting point just to get SWF working. You'll need to create a directory /WEB-INF/flows
, and then add the following flow definition file to that directory, naming the file register.xml
.
/WEB-INF/flows/register.xml
- xml version="1.0" encoding="UTF-8"?>
- <flow xmlns="http://www.springframework.org/schema/webflow"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/webflow
- http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
-
-
- <view-state id="register" view="account/registerForm">
- <transition on="submitRegistration" to="accountAdded"/>
- <transition on="cancelRegistration" to="cancelRegistration"/>
- view-state>
-
- <end-state id="accountAdded"
- view="externalRedirect:contextRelative:/home.do"/>
- <end-state id="cancelRegistration"
- view="externalRedirect:contextRelative:/home.do"/>
- flow>
Here we have a single state, called a view state, and all it does is show us the hardcoded JSP that we created earlier. We're identifying the state itself as register
, and the logical view name associated with this view state is account/registerForm
. This is the name that the view resolver we defined in the Spring app context file will map to a physical location; in this case the physical location will be /WEB-INF/jsp/account/registerForm.jsp
.
By default, SWF interprets the first state in the file as being the start state, or entry point into the flow. There must be exactly one start state. The flow itself is called register
(this is because we've named the flow definition file register.xml
). As it happens we've also named the first state register
though there's no requirement for the start state to have the same name as the flow.
I've defined a couple of transitions out of the register
state. One transition, submitRegistration
, responds to registration submission events on the user interface, such as the user clicking a submit button on a registration form. The other transition, cancelRegistration
, responds to cancelation events on the UI, such as the user clicking a cancel link. Each of these transitions leads to another state in the flow. In this case, both of the transitions lead to end states, which are exits out of the flow, but transitions can lead to other view states (or even other sorts of state) as well. As shown there can be multiple end states for a flow. Here my end states happen to be doing the same thing; they're redirecting the user to a context-relative (as in relative to the servlet context) path /home.do
, which you will recall is just the home page. There are some other relativizations you can do as well:
Relativization | Example |
---|---|
Servlet mapping-relative | externalRedirect:/hotels/index |
Servlet-relative path | externalRedirect:servletRelative:/hotels/index |
Context-relative path | externalRedirect:contextRelative:/dispatcher/hotels/index |
Server-relative path | externalRedirect:serverRelative:/springtravel/dispatcher/hotels/index |
Absolute URL | externalRedirect:http://www.paypal.com/ |
Anyway we're doing the context-relative redirection in this case. This is how we jump out of SWF and return control to the application. (Or how we return control to a calling flow, but we'll get to that.)
The JSPs
Now let's revisit our home page JSP. It's still pretty similar but I'm adding a registration link.
/WEB-INF/jsp/home.jsp
-
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- <html>
- <head>
- <title>Products for Geeks - GeekWareztitle>
- head>
- <body>
- <h1>Welcome to GeekWarezh1>
-
- <div><a href="account/register.do">Registera>div>
- body>
- html>
The registration link takes us into the register
flow, since the last path segment is register.do
. In our Spring app context we told the flow registry about register.xml
, so SWF will know to map requests for /account/register.do
to the register
flow.
Now we need a registration form. The following page is our current version of a registration "form", but as you can see it isn't really a form at all (yet). It is just a couple of links:
/WEB-INF/jsp/account/registerForm.jsp
-
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- <html>
- <head>
- <title>Register - GeekWareztitle>
- head>
- <body>
- <h1>Registerh1>
-
- <div>
- <a href="${flowExecutionUrl}&_eventId=submitRegistration">Submita> |
- <a href="${flowExecutionUrl}&_eventId=cancelRegistration">Cancela>
- div>
- body>
- html>
The point of the two links is to give the user a way to generate submitRegistration
and cancelRegistration
events. We saw in the flow definition that these two events trigger state transitions. Of special importance is the URL for each of these links. Notice that by using ${flowExecutionUrl}
, we're still pointing the user at the same /account/register.do
servlet path. That's because we're still working within the register
flow. The ${flowExecutionUrl}
URL includes an execution
HTTP parameter whose value is a key that SWF uses for flow-tracking purposes. In addition we add an _eventId
parameter that we use to drive state transitions. The event IDs are what we're referencing when we define transitions in the flow definition file.
So really so far all we can do at this point is move back and forth between the home page and the registration page, but we're doing it with Spring Web Flow.
Milestone 2: Spring Web Flow is Working
Point your web browser at
http://localhost:8080/mycart2/home.do
making any adjustments you need to make for the port or application context. Also note that the context is now mycart2
instead of mycart1
like it was in the first version of the code. If you're able to click back and forth between the home page and the registration page, then congrats, Spring Web Flow 2.0 is working! Celebrate!
Building Out the Application
Now that we've gotten Spring MVC and SWF working, it's time to build out the shopping cart application. Please download the following file before continuing:
We won't be looking at every aspect of this application, but I'll highlight the most interesting pieces from a SWF point of view.
Dependencies for mycart3.zip
Besides the dependencies we had for mycart2.zip
, there are four others you'll need:
cglib-nodep-2.1_3.jar
jstl.jar
standard.jar
sitemesh-2.3.jar
(from www.opensymphony.com/sitemesh/download.action)
After you download mycart3.zip
(and its dependencies), you can deploy it and then point your browser at
http://localhost:8080/mycart/home.do
Here are some notes to keep in mind:
- My registration and login flows don't actually "do" anything. They're not hooked up to Spring Security and they're not hooked up to a database. So when you register you can just hit the submit button, and when you log in you can just hit the login button. I just wanted to focus on the flows themselves.
- I'm using Sitemesh to ensure a unified layout across the pages. Even if you are not familiar with Sitemesh, don't worry: it is very straightforward. Basically you just put a servlet filter in front of the pages and the filter decorates the pages with a template (
/WEB-INF/jsp/pagetemplate.jsp
in this case) according to a simple configuration file (/WEB-INF/decorators.xml
). If however you don't want to use Sitemesh, simply remove the filter fromweb.xml
and Sitemesh is gone.
Multiple flows
Any given app may contain multiple flows, and mycart3
is such an app. We have four different flows:
addToCart
: add an item to a shopping cartcheckout
: shopping cart checkout processlogin
: log into the appregister
: register for a new user account
In some of the cases we have what we would intuitively consider a flow in that there are multiple states involved; in other cases there is only one state to speak of and so it may seem strange to regard the flow as being a flow. However we'll see how that can make sense in the following section.
To create multiple flows, you will need to do the following. First, create the flow definition files and put them in your /WEB-INF/flows
directory (or wherever you decided to put them). Then add the flow locations to the flow registry, and the flow URL mappings to your SimpleUrlHandlerMapping
(if that's what you're using), in your Spring application context, like so:
/WEB-INF/mycart-servlet.xml
- ...
-
- <bean id="flowUrlMappings" class=
- "org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
- <property name="mappings">
- <value>
- /addToCart.do=flowController
- /checkout.do=flowController
- /account/login.do=flowController
- /account/register.do=flowController
- value>
- property>
- <property name="alwaysUseFullPath" value="true"/>
- bean>
-
- <flow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
- <flow:flow-location path="/WEB-INF/flows/addToCart.xml"/>
- <flow:flow-location path="/WEB-INF/flows/checkout.xml"/>
- <flow:flow-location path="/WEB-INF/flows/login.xml"/>
- <flow:flow-location path="/WEB-INF/flows/register.xml"/>
- flow:flow-registry>
-
- ...
Once you have those in place you should be set up for multiple flows. Point your links at them and try them out!
Subflows
Going hand-in-hand with the idea of multiple flows is the idea that some flows might be subflows of other flows. In mycart3
, all four flows can be independently accessed, but in addition to that we have the addToCart
, login
and register
flows being subflows to the checkout
flow. See Figure 3.
Here's the idea. The three flows we've identified as subflows are defined as separate flows because clearly there are use cases where it makes sense for them to be accessed outside of a checkout process. For example, we want to be able to add products to a shopping cart while we're browsing the product catalog. And of course we want people to be able to register and login even if they're not in a checkout process.
But those are also flows that we might want to include as part of a checkout process too. During checkout, we might want to recommend products to the customer. Or if the user hasn't yet logged in or registered, we'd want them to do that as part of the checkout process rather than forcing them to do that before they could enter the checkout process.
By defining the login, registration and add-to-cart as flows, we make them available both independently (as top-level flows) and also as part of a larger flow (like the checkout flow). That's why we have these defined as flows even though they are currently implemented as single-state flows. (It is however easy to imagine these being multi-state flows. For example, the login flow may ask you to answer a challenge question if it detects that you're coming in from an unusual IP address, or the add-to-cart flow may ask you to enter a quantity before proceeding.)
So here is the checkout flow:
/WEB-INF/flows/checkout.xml
- xml version="1.0" encoding="UTF-8"?>
- <flow xmlns="http://www.springframework.org/schema/webflow"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://www.springframework.org/schema/webflow
- http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
-
-
- <on-start>
- <set name="flowScope.products" value="cartService.products"/>
- <set name="flowScope.shippingOptions" value="cartService.shippingOptions"/>
- on-start>
-
-
- <view-state id="viewCart" view="viewcart">
-
- <set name="requestScope.shoppingCart" value="shoppingCart"/>
- <set name="requestScope.recommendations" value="cartService.recommendations"/>
- on-render>
- <transition on="addToCart" to="addProductToCart"/>
- <transition on="register" to="register"/>
- <transition on="login" to="login"/>
- view-state>
-
- <subflow-state id="addProductToCart" subflow="addToCart">
-
- <subflow-state id="register" subflow="register">
- <transition on="accountAdded" to="paymentAndShipmentOptions"/>
- <transition on="cancelRegistration" to="viewCart"/>
- subflow-state>
-
-
- <subflow-state id="login" subflow="login">
-
- <view-state id="paymentAndShipmentOptions" view="options">
- <transition on="submit" to="confirmOrder"/>
- <transition on="back" to="viewCart"/>
- view-state>
-
-
- <view-state id="confirmOrder" view="confirmorder">
- <on-render>
- <set name="requestScope.shoppingCart" value="shoppingCart"/>
- on-render>
- <transition on="continue" to="thankYou">
- <evaluate expression="cartService.submitOrderForPayment()"/>
- transition>
- view-state>
-
-
- <view-state id="thankYou" view="thanks">
- <transition on="continue" to="shop"/>
- view-state>
-
-
- <end-state id="shop" view="externalRedirect:contextRelative:/home.do"/>
-
- <global-transitions>
- <transition on="cancelCheckout" to="shop"/>
- global-transitions>
- flow>
In the code above, we are using the
element to call a subflow from a parent flow. The subflow
attribute specifies one of the flows you registered with the flow registry in the Spring configuration. The flow starts at the subflow's start state, and continues until the subflow hits an end state. The end state IDs provide keys that you can reference from the calling flow to effect state transitions. For example, in the flow above, accountAdded
is one of the end states for the register
flows, and so one of the
elements references that end state.
Recall from register.xml
(see page 4) that the end states specified a view
attribute. If the register
flow is called directly, then SWF will use the view
attribute to decide which view to show the user when the flow reaches a given end state. If, however, the flow is called as part of a subflow (instead of being called directly), SWF will ignore the view
attribute and instead follow whatever transition is defined in the calling
.
Using EL to Call Services
SWF allows you to make calls against service beans using an expression language (EL). You can use either Unified EL or else OGNL for that, as mentioned earlier in the article. We happen to be using OGNL though I understand that Unified EL and OGNL are mostly the same, at least where their core syntax is concerned.
There are various places in your flow where you might want to invoke service beans, and SWF provides various mechanisms for doing that. Here's a table showing how to call services from different locations in your flow definition file:
Location | How to call the service bean |
---|---|
On flow start | /flow/on-start element |
On state entry | /flow/view-state/on-entry element |
Immediately before rendering a view | /flow/view-state/on-render element |
On state exit | /flow/view-state/on-exit element |
On state transition | /flow/view-state/transition/evaluate element |
On flow end | /flow/on-end element |
Note that the above does not represent a 100% complete list of places where you can use EL, but it gives you most of the major cases and also the basic idea.
In checkout.xml
we've used a number of the methods above. For example, we use
to grab the product catalog and shipping options from the cart service and place them on the flow scope, which allows them to be used for the duration of the flow. The value
attribute on the set
element is specified using EL. (It looks a lot like JSP EL, if you are familiar with that.) The cartService
bean is available because we defined a Spring bean with that ID.
We also use
to prepare the viewCart
state for rendering. In this case, we place the user's shopping cart on the request scope so the JSP can easily reference it, and we also pull some recommendations off of the cart service and place those on the request scope as well. Once again the value
attribute is specified using EL.
Global Transitions
At the end of checkout.xml
, you will see that I've defined a
element. This allows me to define a transition that applies to all states in the flow. In this case, any time the user raises the cancelCheckout
event, the global transition I've defined will kick in and carry the user to the shop
state. Pretty handy for transitions that occur in multiple places throughout the flow.
That's Our Whirlwind Tour
That concludes this tutorial on Spring Web Flow 2.0. We've really only scratched the surface—for instance, we haven't even touched the new AJAX support that SWF 2.0 introduces—but this should give you an overall feel for how things work. The SWF 2.0 distribution comes with a hotel booking sample application that shows you how to get SWF working with form validation and persistence as well (areas I've suppressed in this article so that I could focus on flow definition).
If you have suggestions and especially corrections to the article or the code, please leave them below. SWF 2.0 is new so I have no doubt that you will be able to show me places where I could be doing something better. Thanks!
Copy paste, u do nothing...
ReplyDeleteDiscover stunning Webflow events templates for your website! Create memorable experiences with ease. Boost engagement and conversions now!
ReplyDeleteflowgiri
Uncover Striking Webflow Templates for Your Website Events! Effortlessly Craft Unforgettable Experiences. Elevate Engagement and Drive Conversions with Style!
ReplyDelete