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, I peresent a sample.
Sample Project
Here I am presenting a simple usecase. The usecase is a simple CRUD based on testflow entity. Here is state diagram
image 1 |
At the begining of the flow you see a list of testflow entity. You can view the detail in view state. Then you can edit it or back. The back is end of the flow. In Edit state you can save or delete or back to view state.
The class diagram is available here
image 2 |
Here is a complete simple SpringWebFlow configuration. This configuration uses SpringMVC as presentation. You also can use JSF, Struts, ...
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:webflow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">
<webflow:flow-executor id="flowExecutor">
<webflow:flow-execution-listeners>
<webflow:listener ref="securityFlowExecutionListener" />
</webflow:flow-execution-listeners>
</webflow:flow-executor>
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
<webflow:flow-location-pattern value="/**/*-config.xml"/>
</webflow:flow-registry>
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator" development="true" />
<bean id="mvcViewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
<property name="viewResolvers" ref="tilesViewResolver"/>
<property name="useSpringBeanBinding" value="true" />
</bean>
<bean name="/user/flows.html" class="org.springframework.webflow.mvc.servlet.FlowController">
<property name="flowExecutor" ref="flowExecutor"/>
<property name="flowUrlHandler">
<bean class="org.springframework.webflow.context.servlet.WebFlow1FlowUrlHandler"/>
</property>
</bean>
<!-- Installs a listener to apply Spring Security authorities -->
<bean id="securityFlowExecutionListener" class="org.springframework.webflow.security.SecurityFlowExecutionListener" />
</beans>
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. |
- 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.
- 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.
- 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 test flow in the view.jsp general tiles 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="<spring:url value="/user/flows.html?_flowId=testflow-config"/>">Webflow Sample</a>
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:
<?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">
...
- 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.
My sample of testflow-config.xml is available here:
<?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="list" view="testflow.list" model="testflow">
<on-render>
<evaluate expression="testflowService.findAll()" result="requestScope.testflowList"/>
</on-render>
<transition on="add" to="edit">
<evaluate expression="testflowService.createEntity()" result="flowScope.testflow"/>
</transition>
<transition on="view" to="view">
<evaluate expression="testflowService.findById(requestParameters.selectedId)"
result="flowScope.testflow"></evaluate>
</transition>
</view-state>
<view-state id="view" view="testflow.view" model="testflow">
<transition on="back" to="finish"></transition>
<transition on="edit" to="edit"></transition>
</view-state>
<view-state id="edit" view="testflow.edit" model="testflow">
<transition on="back" to="view"></transition>
<transition on="save" to="saving">
</transition>
<transition on="delete" to="deleting">
</transition>
</view-state>
<decision-state id="saving">
<on-entry>
<evaluate expression="testflowService.saveSuccessMessage"
result="flashScope.saveSuccessMessage"></evaluate>
<evaluate expression="testflowService.saveErrorMessage"
result="flashScope.saveErrorMessage"></evaluate>
</on-entry>
<if test="testflowService.saveOrUpdate(testflow)" then="view" else="edit"/>
</decision-state>
<decision-state id="deleting">
<on-entry>
<evaluate expression="testflowService.deleteSuccessMessage"
result="flashScope.deleteSuccessMessage"></evaluate>
<evaluate expression="testflowService.deleteErrorMessage"
result="flashScope.deleteErrorMessage"></evaluate>
</on-entry>
<if test="testflowService.delete(testflow)" then="view" else="edit"/>
</decision-state>
<end-state id="finish"/>
</flow>
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 "list" view, implemented in [Project Root]/web/WEB-INF/pages/testflow/testflow-listjsp. This page shows a list of testflow entity.
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>List Page</title></head>
<body>
<form:form commandName="testflow">
<input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
<input type="hidden" name="selectedId" id="selectedId"/>
<table>
<tr>
<th>firstname</th>
<th>lastname</th>
<th></th>
</tr>
<c:forEach items="${testflowList}" var="testflow">
<tr>
<td>${testflow.firstname}</td>
<td>${testflow.lastname}</td>
<td><input type="submit" name="_eventId_view" value="view"
onclick="document.getElementById('selectedId').value = '${testflow.id}';"/></td>
</tr>
</c:forEach>
</table>
<input type="submit" name="_eventId_add" value="add"/>
</form:form>
</body>
</html>
- 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 by add button, the "add" event will be signaled in the current state of the flow. The current state is the "list" view state, so in response to the "add" event, the flow will transition to the "edit" view state and render the "edit" view but befor the transition "testflowService.createEntity()" will be called and set the result to "flowScope.testflow".
The "edit" view implemented in [Project Root]/web/WEB-INF/pages/testflow/testflow-edit.jsp and the "view" view implemented in [Project Root]/web/WEB-INF/pages/testflow/testflow-view.jsp are very similar.
Scopes
There are several scopes defined by Spring Web Flow through the request context and flow configuration file. 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.
<?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="list" view="testflow.list" model="testflow">
<on-render>
<evaluate expression="testflowService.findAll()" result="requestScope.testflowList"/>
</on-render>
<transition on="add" to="edit">
<evaluate expression="testflowService.createEntity()" result="flowScope.testflow"/>
</transition>
<transition on="view" to="view">
<evaluate expression="testflowService.findById(requestParameters.selectedId)"
result="flowScope.testflow"></evaluate>
</transition>
</view-state>
<view-state id="view" view="testflow.view" model="testflow">
<transition on="back" to="finish"></transition>
<transition on="edit" to="edit"></transition>
</view-state>
<view-state id="edit" view="testflow.edit" model="testflow">
<transition on="back" to="view"></transition>
<transition on="save" to="saving">
</transition>
<transition on="delete" to="deleting">
</transition>
</view-state>
<decision-state id="saving">
<on-entry>
<evaluate expression="testflowService.saveSuccessMessage"
result="flashScope.saveSuccessMessage"></evaluate>
<evaluate expression="testflowService.saveErrorMessage"
result="flashScope.saveErrorMessage"></evaluate>
</on-entry>
<if test="testflowService.saveOrUpdate(testflow)" then="view" else="edit"/>
</decision-state>
<decision-state id="deleting">
<on-entry>
<evaluate expression="testflowService.deleteSuccessMessage"
result="flashScope.deleteSuccessMessage"></evaluate>
<evaluate expression="testflowService.deleteErrorMessage"
result="flashScope.deleteErrorMessage"></evaluate>
</on-entry>
<if test="testflowService.delete(testflow)" then="view" else="edit"/>
</decision-state>
<end-state id="finish"/>
</flow>
I think another post is needed to take Subflows and Attribute Mappers sample. But now it is the time to download the application and try it yourselves. After deploying the start page URL will be: "http://localhost:8080/user/flows.html?_flowId=testflow-config"
The mysql schema script is available in "[Project Root]/db/springwebflow.sql" you can restore it to your mysqlserver. The datasource property is available here: "[Project Root]/src/database.properties". When you run the application you should first login. The Administrator specifications is:
Username: administrator
Password: 123456
Enjoy yourselves with Spring-WebFlow
all rights reserved by Mostafa Rastgar and Programmer Assistant weblog
No comments:
Post a Comment