253 lines
12 KiB
XML
253 lines
12 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<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>
|
|
<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>
|
|
<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>FlowIdHandlerMapping</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.FlowIdHandlerMapping">
|
|
<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>
|
|
<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>
|
|
</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 executions of a flow are managed in a HTTP servlet environment.
|
|
A <code>FlowHandler</code> is used by the <code>FlowHandlerAdapter</code> and 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>
|
|
<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>
|
|
</sect1>
|
|
</chapter> |