This tutorial explains how to configure Spring Webflow 2, showing how it integrates with the view layer, in this case, JSP pages. The web application will allow two different types of request, flow executions and requests to Spring Web MVC. When a request comes in, it will try to find a flow. If not successful, it will try to map the request to an MVC handler.
Environment
JDK 1.6.0_24
Spring
3.0.5.RELEASE
Webflow
2.3.1.RELEASE
Tomcat 6.0
Maven 2
Getting all needed files
First of all you need to import the Spring libraries to your project. Using Maven 2, you just need to add the following dependencies:
<properties>
<org.spring.version>3.0.5.RELEASE</org.spring.version>
<webflow.version>2.3.1.RELEASE</webflow.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${org.spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${org.spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${org.spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.spring.version}</version>
</dependency>
<!-- Spring Webflow -->
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-webflow</artifactId>
<version>${webflow.version}</version>
</dependency>
</dependencies>
Configuring Spring
I've divided the configuration, which consists in Spring configuration and integration with webflow, into three files, which are the following:
- root-context.xml: The parent context, loaded by the Spring listener. This file contains back end configuration like repositories or business services. This tutorial is quite simple and I just pretend to explain how to configure Webflow, so this file will be empty.
- webflow-config.xml: This file configures webflow and it will be imported by the application context (app-context.xml). It could go inside the application context file but I keep it separate because I prefer to have the configuration structured.
- app-context.xml: The child context which extends the root context. This context contains the web layer configuration like view resolvers, beans and controllers.
The content of the above files is described below:
app-context.xml
<?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:mvc=http://www.springframework.org/schema/mvc
xmlns:context=http://www.springframework.org/schema/context
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<import resource="webflow-config.xml"/>
<!--
Detects mvc annotations like @RequestMapping -->
<mvc:annotation-driven/>
<!--
Detects @Component, @Service, @Controller, @Repository,
@Configuration -->
<context:component-scan base-package="org.spring.tutorial.controller"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix"
value="/WEB-INF/views/"/>
<property name="suffix"
value=".jsp"/>
</bean>
</beans>
I haven't included any controller definition because I prefer to use annotations. The component-scan tag will be responsible of scanning the base package to look for classes annotated with @Component and @Controller among others. In this tutorial I've annotated my controllers with @Controller annotation. You should keep in mind that component-scan tag will also search the classpath, so it is not recommended to set the base-package with values like "com" or "org".
The view resolver is used by Spring Web MVC requests, and is responsible of resolving view logic names returned by the MVC controllers.
webflow-config.xml
<?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-3.0.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">
<bean class="org.springframework.webflow.scope.ScopeRegistrar"/>
<!-- Spring
Webflow central configuration component -->
<webflow:flow-executor id="flowExecutor"
flow-registry="flowRegistry"/>
<webflow:flow-registry id="flowRegistry">
<webflow:flow-location path="/WEB-INF/flows/main/main.xml"
/>
<!-- Could use a pattern instead
<webflow:flow-location-pattern value="/**/*-flow.xml"/>
-->
</webflow:flow-registry>
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor"
ref="flowExecutor" />
</bean>
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="flowRegistry"
ref="flowRegistry"/>
<property name="order"
value="0"/>
</bean>
</beans>
ScopeRegistar: Registers webflow scopes. This allows you to use them in addition to the usual Spring scopes. These scopes are flash, view, flow and conversation.
Flow registry: Registers all the flows that will be used in the web application. It is required to specify where is every flow definition located, which means the xml file. In this tutorial, we specify the exact location of the file with the flow-location property, but you could instead specify a pattern.
FlowHandlerAdapter: Activates the flow management with Spring MVC.
FlowHandlerMapping: The first mapping that will be invoked. It will check if the request path maps with the id of a flow registered in the flowRegistry. If it finds it, the flow will be started. If not, will invoke the next mapping. On this example, you will define a request mapping for an MVC handler.
Configuring the webapp
The web.xml is as follows:
<!-- root context
configuration -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/root-context.xml</param-value>
</context-param>
<!-- Loads the root context into the servletContext before
any servlets are initialized -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Spring servlet -->
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/app-context.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/spring/*</url-pattern>
</servlet-mapping>
Implementing MVC handler
The MVC mapping is configured with annotations (@RequestMapping).
import
org.springframework.stereotype.Controller;
import
org.springframework.ui.Model;
import
org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MvcController {
@RequestMapping("/viewMvc")
public String
getData(Model model) {
model.addAttribute("message", "Hello
world MVC");
return "mvcView";
}
}
Building the flow
The flow is really simple. It consists of an initial view which takes an input field, a navigation to an action state which will build the message, and a final view where the message will be shown.
This flow must be defined in the flow registry in order to be found. We did this in the webflow-config.xml. The following is the detail of the main.xml file:
<view-state id="startPage">
<transition on="next"
to="setMessage"/>
</view-state>
<action-state id="setMessage">
<evaluate expression="setMessageController"/>
<transition on="ok"
to="finalView"/>
</action-state>
<end-state id="finalView" view="finalView.jsp"/>
The initial view (startPage) is composed of a form and a submit button which will take us to the next state. It is not necessary to specify the 'view' attribute if the physical view name is the same as the id of the view-state.
<form method="post">
Enter
your name: <input
type="text" name="inputName"
value=""/>
<input type="hidden"
name="_flowExecutionKey" value="${flowExecutionKey}"/>
<input type="submit"
class="button" name="_eventId_next"
value="Next view"/>
</form>
You need to send a request parameter named 'flowExecutionKey' when you submit the form. This way, when a request comes in with this parameter, Webflow will be able to resume the current flow execution and navigate to the next state.
Specifying "_eventId_next" as the name attribute on the component that executes the action, will launch the event "next", which will execute the transition to the next state defined in the flow definition. You could also do this with a hidden field:
<input type="hidden"
name="_eventId" value="next"/>
The next state "setMessage" will invoke the controller that will build the message. This controller must implement Action interface:
import
org.springframework.stereotype.Controller;
import
org.springframework.webflow.execution.Action;
import
org.springframework.webflow.execution.Event;
import
org.springframework.webflow.execution.RequestContext;
@Controller("setMessageController")
public class
WebflowController implements Action {
@Override
public Event
execute(RequestContext req) throws Exception {
String
name = req.getRequestParameters().get("inputName");
req.getFlowScope().put("message", "Hello
"+name);
return new Event(this, "ok");
}
}
Finally, we will show the message at the final view. This is done with Expression Language: ${message}
Accessing the webapp
This url will be handled by Spring Web MVC and invoke the MVC controller:
http://localhost:8080/myWebapp/spring/viewMvc
This url will be handled by Spring Webflow and will start the main flow:
Project structure
As specified in the flow registry, flows are located in the WEB-INF/flows/ folder. The main folder contains the flow definition and the views associated to the flow.
The views folder contains MVC views. The location is specified at the view resolver.
Labels: Spring, Webflow