Files
spring-webflow/spring-webflow-reference/src/spring-mvc.xml

329 lines
16 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<chapter id="spring-mvc">
<title>Spring MVC Integration</title>
<sect1 id="spring-mvc-introduction">
<title>Introduction</title>
<para>
This chapter shows how to integrate Web Flow into a Spring MVC web application.
The <code>booking-mvc</code> sample application is a good reference for Spring MVC with Web Flow.
This application is a simplified travel site that allows users to search for and book hotel rooms.
</para>
</sect1>
<sect1 id="spring-mvc-config-web.xml">
<title>Configuring web.xml</title>
<para>
The first step to using Spring MVC is to configure the <code>DispatcherServlet</code> in <code>web.xml</code>.
You typically do this once per web application.
</para>
<para>
The example below maps all requests that begin with <code>/spring/</code> to the DispatcherServlet.
An <code>init-param</code> is used to provide the <code>contextConfigLocation</code>.
This is the configuration file for the web application.
</para>
<programlisting language="xml"><![CDATA[
<servlet>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/web-application-config.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<url-pattern>/spring/*</url-pattern>
</servlet-mapping>]]></programlisting>
</sect1>
<sect1 id="spring-mvc-config-spring-url-mapping">
<title>Dispatching to flows</title>
<para>
The <code>DispatcherServlet</code> maps requests for application resources to handlers.
A flow is one type of handler.
</para>
<sect2>
<title>Registering the FlowHandlerAdapter</title>
<para>
The first step to dispatching requests to flows is to enable flow handling within Spring MVC.
To this, install the <code>FlowHandlerAdapter</code>:
</para>
<programlisting language="xml"><![CDATA[
<!-- Enables FlowHandler URL mapping -->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor" />
</bean>]]>
</programlisting>
</sect2>
<sect2>
<title>Defining flow mappings</title>
<para>
Once flow handling is enabled, the next step is to map specific application resources to your flows.
The simplest way to do this is to define a <code>FlowHandlerMapping</code>:
</para>
<programlisting language="xml"><![CDATA[
<!-- Maps request paths to flows in the flowRegistry;
e.g. a path of /hotels/booking looks for a flow with id "hotels/booking" -->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="flowRegistry" ref="flowRegistry"/>
<property name="order" value="0"/>
</bean>]]>
</programlisting>
<para>
Configuring this mapping allows the Dispatcher to map application resource paths to flows in a flow registry.
For example, accessing the resource path <code>/hotels/booking</code> would result in a registry query for the flow with id <code>hotels/booking</code>.
If a flow is found with that id, that flow will handle the request.
If no flow is found, the next handler mapping in the Dispatcher's ordered chain will be queried or a "noHandlerFound" response will be returned.
</para>
</sect2>
<sect2>
<title>Flow handling workflow</title>
<para>
When a valid flow mapping is found, the <code>FlowHandlerAdapter</code> figures out whether to
start a new execution of that flow or resume an existing execution based on information present the HTTP request.
There are a number of defaults related to starting and resuming flow executions the adapter employs:
</para>
<itemizedlist>
<listitem>
<para>
HTTP request parameters are made available in the input map of all starting flow executions.
</para>
</listitem>
<listitem>
<para>
When a flow execution ends without sending a final response, the default handler will attempt
to start a new execution in the same request.
</para>
</listitem>
<listitem>
<para>
Unhandled exceptions are propagated to the Dispatcher unless the exception is a NoSuchFlowExecutionException.
The default handler will attempt to recover from a NoSuchFlowExecutionException by starting over a new execution.
</para>
</listitem>
</itemizedlist>
<para>
Consult the API documentation for <code>FlowHandlerAdapter</code> for more information.
You may override these defaults by subclassing or by implementing your own FlowHandler, discussed in the next section.
</para>
</sect2>
</sect1>
<sect1 id="spring-mvc-config-flow-handlers">
<title>Implementing custom FlowHandlers</title>
<para>
<code>FlowHandler</code> is the extension point that can be used to customize how flows are executed in a HTTP servlet environment.
A <code>FlowHandler</code> is used by the <code>FlowHandlerAdapter</code> and is responsible for:
</para>
<itemizedlist>
<listitem>
<para>Returning the <code>id</code> of a flow definition to execute</para>
</listitem>
<listitem>
<para>Creating the input to pass new executions of that flow as they are started</para>
</listitem>
<listitem>
<para>Handling outcomes returned by executions of that flow as they end</para>
</listitem>
<listitem>
<para>Handling any exceptions thrown by executions of that flow as they occur</para>
</listitem>
</itemizedlist>
<para>
These responsibilities are illustrated in the definition of the <code>org.springframework.mvc.servlet.FlowHandler</code> interface:
</para>
<programlisting language="java">
public interface FlowHandler {
public String getFlowId();
public MutableAttributeMap createExecutionInputMap(HttpServletRequest request);
public String handleExecutionOutcome(FlowExecutionOutcome outcome,
HttpServletRequest request, HttpServletResponse response);
public String handleException(FlowException e,
HttpServletRequest request, HttpServletResponse response);
}
</programlisting>
<para>
To implement a FlowHandler, subclass <code>AbstractFlowHandler</code>. All these operations are optional, and if not implemented
the defaults will apply. You only need to override the methods that you need. Specifically:
</para>
<itemizedlist>
<listitem>
<para>
Override <code>getFlowId(HttpServletRequest)</code> when the id of your flow cannot be directly derived from the HTTP request.
By default, the id of the flow to execute is derived from the pathInfo portion of the request URI.
For example, <code>http://localhost/app/hotels/booking?hotelId=1</code> results in a flow id of <code>hotels/booking</code> by default.
</para>
</listitem>
<listitem>
<para>
Override <code>createExecutionInputMap(HttpServletRequest)</code> when you need fine-grained control over extracting
flow input parameters from the HttpServletRequest. By default, all request parameters are treated as flow input parameters.
</para>
</listitem>
<listitem>
<para>
Override <code>handleExecutionOutcome</code> when you need to handle specific flow execution outcomes in a custom manner.
The default behavior sends a redirect to the ended flow's URL to restart a new execution of the flow.
</para>
</listitem>
<listitem>
<para>
Override <code>handleException</code> when you need fine-grained control over unhandled flow exceptions.
The default behavior attempts to restart the flow when a client attempts to access an ended or expired flow execution.
Any other exception is rethrown to the Spring MVC ExceptionResolver infrastructure by default.
</para>
</listitem>
</itemizedlist>
<sect2 id="spring-mvc-flow-handler-example">
<title>Example FlowHandler</title>
<para>
A common interaction pattern between Spring MVC And Web Flow is for a Flow to redirect to a @Controller when it ends.
FlowHandlers allow this to be done without coupling the flow definition itself with a specific controller URL.
An example FlowHandler that redirects to a Spring MVC Controller is shown below:
</para>
<programlisting language="java"><![CDATA[
public class BookingFlowHandler extends AbstractFlowHandler {
public String handleExecutionOutcome(FlowExecutionOutcome outcome,
HttpServletRequest request, HttpServletResponse response) {
if (outcome.getId().equals("bookingConfirmed")) {
return "/booking/show?bookingId=" + outcome.getOutput().get("bookingId");
} else {
return "/hotels/index";
}
}
}]]>
</programlisting>
<para>
Since this handler only needs to handle flow execution outcomes in a custom manner, nothing else is overridden.
The <code>bookingConfirmed</code> outcome will result in a redirect to show the new booking.
Any other outcome will redirect back to the hotels index page.
</para>
</sect2>
<sect2>
<title>Deploying a custom FlowHandler</title>
<para>
To install a custom FlowHandler, simply deploy it as a bean.
The bean name must match the id of the flow the handler should apply to.
</para>
<programlisting language="xml"><![CDATA[
<bean name="hotels/booking" class="org.springframework.webflow.samples.booking.BookingFlowHandler" />]]>
</programlisting>
<para>
With this configuration, accessing the resource <code>/hotels/booking</code> will launch the <code>hotels/booking</code> flow using the custom BookingFlowHandler.
When the booking flow ends, the FlowHandler will process the flow execution outcome and redirect to the appropriate controller.
</para>
</sect2>
<sect2 id="spring-mvc-flow-handler-redirects">
<title>FlowHandler Redirects</title>
<para>
A FlowHandler handling a FlowExecutionOutcome or FlowException returns a <code>String</code> to indicate the resource to redirect to after handling.
In the previous example, the <code>BookingFlowHandler</code> redirects to the <code>booking/show</code> resource URI for <code>bookingConfirmed</code> outcomes,
and the <code>hotels/index</code> resource URI for all other outcomes.
</para>
<para>
By default, returned resource locations are relative to the current servlet mapping.
This allows for a flow handler to redirect to other Controllers in the application using relative paths.
In addition, explicit redirect prefixes are supported for cases where more control is needed.
</para>
<para>
The explicit redirect prefixes supported are:
</para>
<itemizedlist>
<listitem><para><code>servletRelative:</code> - redirect to a resource relative to the current servlet</para></listitem>
<listitem><para><code>contextRelative:</code> - redirect to a resource relative to the current web application context path</para></listitem>
<listitem><para><code>serverRelative:</code> - redirect to a resource relative to the server root</para></listitem>
<listitem><para><code>http://</code> or <code>https://</code> - redirect to a fully-qualified resource URI</para></listitem>
</itemizedlist>
<para>
These same redirect prefixes are also supported within a flow definition when using the <code>externalRedirect:</code> directive in
conjunction with a view-state or end-state; for example, <code>view="externalRedirect:http://springframework.org"</code>
</para>
</sect2>
</sect1>
<sect1 id="spring-mvc-config-spring-view-resolution">
<title>View Resolution</title>
<para>
Web Flow 2 maps selected view identifiers to files located within the flow's working directory unless otherwise specified.
For existing Spring MVC + Web Flow applications, an external <code>ViewResolver</code> is likely already handling this mapping for you.
Therefore, to continue using that resolver and to avoid having to change how your existing flow views are packaged, configure Web Flow as follows:
</para>
<programlisting language="xml"><![CDATA[
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
<webflow:location path="/WEB-INF/hotels/booking/booking.xml" />
</webflow:flow-registry>
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator"/>
<bean id="mvcViewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
<property name="viewResolvers" ref="myExistingViewResolverToUseForFlows"/>
</bean>]]>
</programlisting>
<para>
The MvcViewFactoryCreator is the factory that allows you to configure how the Spring MVC view system is used inside Spring Web Flow.
Use it to configure existing ViewResolvers, as well as other services such as a custom MessageCodesResolver.
You may also enable data binding use Spring MVC's native BeanWrapper by setting the <code>useSpringBinding</code> flag to true.
This is an alternative to using OGNL or the Unified EL for view-to-model data binding.
See the JavaDoc API of this class for more information.
</para>
</sect1>
<sect1 id="spring-mvc-resuming-on-event">
<title>Signaling an event from a View</title>
<para>
When a flow enters a view-state it pauses, redirects the user to its execution URL, and waits for a user event to resume.
Events are generally signaled by activating buttons, links, or other user interface commands.
How events are decoded server-side is specific to the view technology in use.
This section shows how to trigger events from HTML-based views generated by templating engines such as JSP, Velocity, or Freemarker.
</para>
<sect2 id="webflow-event-named-html-button">
<title>Using a named HTML button to signal an event</title>
<para>
The example below shows two buttons on the same form that signal <code>proceed</code> and <code>cancel</code> events when clicked, respectively.
</para>
<programlisting language="xml"><![CDATA[
<input type="submit" name="_eventId_proceed" value="Proceed" />
<input type="submit" name="_eventId_cancel" value="Cancel" />]]>
</programlisting>
<para>
When a button is pressed Web Flow finds a request parameter name beginning with <code>_eventId_</code> and treats the remaining substring as the event id.
So in this example, submitting <code>_eventId_proceed</code> becomes <code>proceed</code>.
This style should be considered when there are several different events that can be signaled from the same form.
</para>
</sect2>
<sect2 id="webflow-event-hidden-parameter">
<title>Using a hidden HTML form parameter to signal an event</title>
<para>
The example below shows a form that signals the <code>proceed</code> event when submitted:
</para>
<programlisting language="xml"><![CDATA[
<input type="submit" value="Proceed" />
<input type="hidden" name="_eventId" value="proceed" />]]>
</programlisting>
<para>
Here, Web Flow simply detects the special <code>_eventId</code> parameter and uses its value as the event id.
This style should only be considered when there is one event that can be signaled on the form.
</para>
</sect2>
<sect2 id="webflow-event-link">
<title>Using a HTML link to signal an event</title>
<para>
The example below shows a link that signals the <code>cancel</code> event when activated:
</para>
<programlisting language="xml"><![CDATA[
<a href="${flowExecutionUrl}&_eventId=cancel">Cancel</a>]]>
</programlisting>
<para>
Firing an event results in a HTTP request being sent back to the server.
On the server-side, the flow handles decoding the event from within its current view-state.
How this decoding process works is specific to the view implementation.
Recall a Spring MVC view implementation simply looks for a request parameter named <code>_eventId</code>.
If no <code>_eventId</code> parameter is found, the view will look for a parameter that
starts with <code>_eventId_</code> and will use the remaining substring as the event id.
If neither cases exist, no flow event is triggered.
</para>
</sect2>
</sect1>
</chapter>