# 11. Spring MVC 集成
# 11.1.导言
本章展示了如何将 Web 流集成到 Spring MVC Web 应用程序中。对于具有 Web 流的 Spring MVC,booking-mvc
示例应用程序是一个很好的参考。这个应用程序是一个简化的旅游网站,允许用户搜索和预订酒店房间。
# 11.2.配置 web.xml
使用 Spring MVC 的第一步是在web.xml
中配置DispatcherServlet
。你通常在每个 Web 应用程序中执行一次此操作。
下面的示例将所有以/spring/
开头的请求映射到 DispatcherServlet。init-param
用于提供contextConfigLocation
。这是 Web 应用程序的配置文件。
<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>
# 11.3.向流量调度
DispatcherServlet
将对应用程序资源的请求映射到处理程序。流是处理程序的一种类型。
# 11.3.1.注册 FlowhandlerAdapter
将请求发送到流的第一步是在 Spring MVC 中启用流处理。为此,请安装FlowHandlerAdapter
:
<!-- Enables FlowHandler URL mapping -->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor" />
</bean>
# 11.3.2.定义流映射
一旦启用了流处理,下一步就是将特定的应用程序资源映射到你的流。最简单的方法是定义FlowHandlerMapping
:
<!-- 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>
配置此映射允许 Dispatcher 将应用程序资源路径映射到流注册中心中的流。例如,访问资源路径/hotels/booking
将导致使用 IDhotels/booking
的流的注册表查询。如果找到带有该 ID 的流,则该流将处理该请求。如果没有找到流,那么将查询 Dispatcher 的有序链中的下一个处理程序映射,或者返回一个“nohandlerfound”响应。
# 11.3.3.流程处理工作流程
当找到一个有效的流映射时,FlowHandlerAdapter
会根据 HTTP 请求提供的信息,计算出是启动该流的新执行,还是恢复现有的执行。有许多与适配器使用的流执行的启动和恢复相关的默认值:
HTTP 请求参数在所有初始流执行的输入映射中都是可用的。
当流执行结束而未发送最终响应时,默认处理程序将尝试在同一请求中启动新的执行。
未处理的异常将传播到 Dispatcher,除非该异常是 NosuchFlowExecutionException 异常。默认处理程序将尝试通过重新执行 NosuchFlowExecutionException 来恢复 NosuchFlowExecutionException。
有关更多信息,请参阅FlowHandlerAdapter
的 API 文档。你可以通过子类化或实现你自己的 Flowhandler(将在下一节中讨论)来覆盖这些默认值。
# 11.4.实现自定义 Flowhandlers
FlowHandler
是可用于自定义在 HTTP Servlet 环境中如何执行流的扩展点。aFlowHandler
由FlowHandlerAdapter
使用,并负责:
返回要执行的流定义的
id
创建输入以在该流开始时传递新的执行。
处理通过执行该流返回的结果,并在其结束时将其执行
处理由该流的执行引发的任何异常
这些职责在org.springframework.mvc.servlet.FlowHandler
接口的定义中得到了说明:
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);
}
要实现 Flowhandler,子类AbstractFlowHandler
。所有这些操作都是可选的,如果没有实现,那么将应用默认值。你只需要覆盖你需要的方法。具体而言:
当你的流的 ID 不能直接从 HTTP 请求派生时,覆盖
getFlowId(HttpServletRequest)
。默认情况下,要执行的流的 ID 是从请求 URI 的 PathInfo 部分派生的。例如,在默认情况下,http://localhost/app/hotels/booking?hotelId=1
会导致流 ID 为hotels/booking
。当需要对从 HttpServletRequest 提取流输入参数进行细粒度控制时,覆盖
createExecutionInputMap(HttpServletRequest)
。默认情况下,所有请求参数都被视为流输入参数。当需要以自定义方式处理特定的流执行结果时,覆盖
handleExecutionOutcome
。默认行为发送重定向到结束的流的 URL,以重新启动该流的新执行。当需要对未处理的流异常进行细粒度控制时,覆盖
handleException
。当客户端试图访问已结束或过期的流执行时,默认行为会尝试重启流。默认情况下,任何其他异常都会被重新抛出到 Spring MVC ExceptionResolver 基础结构中。
# 11.4.1.Flowhandler 示例
Spring MVC 和 Web 流之间的一种常见交互模式是,当流结束时,将其重定向到 @Controller。Flowhandler 允许在不将流定义本身与特定的控制器 URL 耦合的情况下完成此操作。下面显示了一个重定向到 Spring MVC 控制器的 Flowhandler 示例:
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";
}
}
}
由于此处理程序只需要以自定义的方式处理流执行结果,因此不重写其他内容。bookingConfirmed
结果将导致重定向,以显示新的预订。任何其他结果将重定向回酒店索引页面。
# 11.4.2.部署自定义 Flowhandler
要安装自定义 Flowhandler,只需将其部署为 Bean。 Bean 名称必须与处理程序应用到的流的 ID 匹配。
<bean name="hotels/booking" class="org.springframework.webflow.samples.booking.BookingFlowHandler" />
通过这种配置,访问资源/hotels/booking
将使用自定义 BookingFlowhandler 启动hotels/booking
流。当预订流程结束时,Flowhandler 将处理流程执行结果并将其重定向到适当的控制器。
# 11.4.3.Flowhandler 重定向
处理 FlowExecutionResult 或 FlowException 的 Flowhandler 返回String
,以指示处理后要重定向到的资源。在前面的示例中,对于BookingFlowHandler
结果,booking/show
资源 URI 重定向到bookingConfirmed
,对于所有其他结果,hotels/index
资源 URI 重定向到booking/show
。
默认情况下,返回的资源位置是相对于当前 Servlet 映射的。这允许流处理程序使用相对路径重定向到应用程序中的其他控制器。此外,对于需要更多控制的情况,支持显式重定向前缀。
支持的显式重定向前缀如下:
servletRelative:
-相对于当前资源重定向 ServletcontextRelative:
-相对于当前 Web 应用程序上下文路径重定向到资源serverRelative:
-重定向到相对于服务器根目录的资源http://
或https://
-重定向到完全限定的资源 URI
当结合视图状态或结束状态使用externalRedirect:
指令时,流定义中也支持这些相同的重定向前缀;例如,view="externalRedirect:http://springframework.org"
# 11.5.视图分辨率
除非另有说明,否则 Web Flow2 将选定的视图标识符映射到位于流的工作目录中的文件。对于现有的 Spring MVC+Web 流应用程序,外部ViewResolver
可能已经在为你处理此映射。因此,要继续使用该解析器,并避免更改打包现有流视图的方式,请按以下方式配置 Web 流:
<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>
MVCViewFactoryCreator 是一个工厂,它允许你配置如何在 Spring Web 流中使用 Spring MVC 视图系统。使用它来配置现有的 viewresolver,以及其他服务,例如定制的 MessageCodesresolver。你还可以通过将useSpringBinding
标志设置为 true 来启用使用 Spring MVC 的本机 BeanWrapper 的数据绑定功能。这是使用统一 EL 进行视图到模型数据绑定的一种替代方法。有关更多信息,请参见该类的 Javadoc API。
# 11.6.从视图发送事件信号
当流进入视图状态时,它会暂停,将用户重定向到其执行 URL,并等待用户事件恢复。事件通常通过激活按钮、链接或其他用户界面命令来发出信号。事件如何在服务器端进行解码取决于所使用的视图技术。本节展示了如何从基于 HTML 的视图中触发事件,这些视图是由 JSP、Velocity 或 Freemarker 等模板引擎生成的。
# 11.6.1.使用命名的 HTML 按钮发送事件信号
下面的示例显示了相同窗体上的两个按钮,当单击时分别表示proceed
和cancel
事件。
<input type="submit" name="_eventId_proceed" value="Proceed" />
<input type="submit" name="_eventId_cancel" value="Cancel" />
当按下按钮时,WebFlow 查找一个以_eventId_
开头的请求参数名,并将其余的子字符串作为事件 ID。所以在这个例子中,提交_eventId_proceed
变成了proceed
。当有几个不同的事件可以从相同的形式发出信号时,应该考虑这种样式。
# 11.6.2.使用隐藏的 HTML 表单参数来表示事件
下面的示例显示了一个表单,该表单在提交时表示proceed
事件:
<input type="submit" value="Proceed" />
<input type="hidden" name="_eventId" value="proceed" />
在这里,WebFlow 只检测特殊的_eventId
参数,并将其值用作事件 ID。只有当窗体上有一个事件可以用信号表示时,才应该考虑这种样式。
# 11.6.3.使用 HTML 链接发出事件信号
下面的示例显示了一个链接,该链接在激活时发出cancel
事件的信号:
<a href="${flowExecutionUrl}&_eventId=cancel">Cancel</a>
触发一个事件会导致一个 HTTP 请求被发送回服务器。在服务器端,流处理从其当前视图状态中解码事件。这个解码过程的工作方式是特定于视图实现的。回想一下 Spring MVC 视图实现只是查找一个名为_eventId
的请求参数。如果没有找到_eventId
参数,则视图将查找一个以_eventId_
开头的参数,并将剩余的子字符串用作事件 ID。如果两种情况都不存在,则不会触发流事件。
# 11.7.在页面上嵌入流
默认情况下,当流进入视图状态时,它会在呈现视图之前执行客户端重定向。这种方法被称为 post-redirect-get。它的优点是将一个视图的表单处理与下一个视图的呈现分离开来。因此,浏览器的后退和刷新按钮可以无缝地工作,而不会引起任何浏览器警告。
通常情况下,从用户的角度来看,客户端重定向是透明的。然而,在有些情况下,后重定向可能不会带来同样的好处。例如,流可以嵌入在页面上并通过 Ajax 请求只刷新属于该流的页面的区域来驱动。在这种情况下,不仅没有必要使用客户端重定向,而且在保持页面周围内容不变方面也不是所需的行为。
第 12.5 节,“处理 Ajax 请求”解释了如何在 Ajax 请求期间执行部分呈现。本节的重点是解释如何在 Ajax 请求期间控制流执行重定向行为。要指示一个流应该以“页面嵌入”模式执行,你所需要做的就是在启动该流时附加一个额外的参数:
/hotels/booking?mode=embedded
当以“嵌入页面”模式启动时,流将不会在 Ajax 请求期间发出流执行重定向。Mode=Embedded 参数只需要在启动流时传递。你唯一关心的另一个问题是使用 Ajax 请求,并且只呈现更新显示该流的页面部分所需的内容。
# 11.7.1.嵌入式模式与默认重定向行为
默认情况下,WebFlow 在进入每个视图状态时都会进行客户端重定向。但是,如果在 Ajax 请求期间保持相同的视图状态(例如,没有“to”属性的转换),那么就不会有客户端重定向。这种行为对于 Spring Web Flow2 用户应该非常熟悉。它适用于支持浏览器后退按钮的顶级流,同时还可以利用 Ajax 和部分呈现,用于在表单验证、分页槽搜索结果等视图中保持不变的用例。但是,在转换到新的视图状态之后,总是会进行客户端重定向。这使得不可能在页面或模态对话框中嵌入流并执行多个视图状态,而不会导致整页刷新。因此,如果你的用例需要嵌入一个流,你可以在“嵌入”模式中启动它。
# 11.7.2.嵌入式流程示例
如果你想看到嵌入在页面和模态对话框中的流的示例,请参阅 WebFlow-Showcase 项目。你可以在本地签出源代码,将其构建为 Maven 项目,并将其导入到 Eclipse 中:
cd some-directory
svn co https://src.springframework.org/svn/spring-samples/webflow-showcase
cd webflow-showcase
mvn package
# import into Eclipse
# 11.8.将流输出保存到 MVC 闪存范围
当end-state
执行内部重定向时,流输出可以自动保存到 MVC 闪存范围。当在流的末尾显示摘要屏幕时,这一点特别有用。对于向后兼容性,默认情况下禁用此功能,以便将saveOutputToFlashScopeOnRedirect
上的FlowHandlerAdapter
设置为true
。
<!-- Enables FlowHandler URL mapping -->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor" />
<property name="saveOutputToFlashScopeOnRedirect" value="true" />
</bean>
下面的示例将在重定向到summary
屏幕之前将confirmationNumber
添加到 MVC 闪存范围。
<end-state id="finish" view="externalRedirect:summary">
<output name="confirmationNumber" value="booking.confirmationNumber" />
</end-state>