spring-mvc.md 14.7 KB
Newer Older
dallascao's avatar
dallascao 已提交
1 2
# 11. Spring MVC 集成

茶陵後's avatar
茶陵後 已提交
3
## 11.1.导言
dallascao's avatar
dallascao 已提交
4 5 6

本章展示了如何将 Web 流集成到 Spring MVC Web 应用程序中。对于具有 Web 流的 Spring MVC,`booking-mvc`示例应用程序是一个很好的参考。这个应用程序是一个简化的旅游网站,允许用户搜索和预订酒店房间。

茶陵後's avatar
茶陵後 已提交
7
## 11.2.配置 web.xml
dallascao's avatar
dallascao 已提交
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

使用 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>
```

茶陵後's avatar
茶陵後 已提交
29
## 11.3.向流量调度
dallascao's avatar
dallascao 已提交
30 31 32

`DispatcherServlet`将对应用程序资源的请求映射到处理程序。流是处理程序的一种类型。

茶陵後's avatar
茶陵後 已提交
33
### 11.3.1.注册 FlowhandlerAdapter
dallascao's avatar
dallascao 已提交
34 35 36 37 38 39 40 41 42 43 44

将请求发送到流的第一步是在 Spring MVC 中启用流处理。为此,请安装`FlowHandlerAdapter`:

```
<!-- Enables FlowHandler URL mapping -->
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
	<property name="flowExecutor" ref="flowExecutor" />
</bean>
			
```

茶陵後's avatar
茶陵後 已提交
45
### 11.3.2.定义流映射
dallascao's avatar
dallascao 已提交
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

一旦启用了流处理,下一步就是将特定的应用程序资源映射到你的流。最简单的方法是定义`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`将导致使用 ID`hotels/booking`的流的注册表查询。如果找到带有该 ID 的流,则该流将处理该请求。如果没有找到流,那么将查询 Dispatcher 的有序链中的下一个处理程序映射,或者返回一个“nohandlerfound”响应。

茶陵後's avatar
茶陵後 已提交
61
### 11.3.3.流程处理工作流程
dallascao's avatar
dallascao 已提交
62 63 64 65 66 67 68 69 70 71 72

当找到一个有效的流映射时,`FlowHandlerAdapter`会根据 HTTP 请求提供的信息,计算出是启动该流的新执行,还是恢复现有的执行。有许多与适配器使用的流执行的启动和恢复相关的默认值:

* HTTP 请求参数在所有初始流执行的输入映射中都是可用的。

* 当流执行结束而未发送最终响应时,默认处理程序将尝试在同一请求中启动新的执行。

* 未处理的异常将传播到 Dispatcher,除非该异常是 NosuchFlowExecutionException 异常。默认处理程序将尝试通过重新执行 NosuchFlowExecutionException 来恢复 NosuchFlowExecutionException。

有关更多信息,请参阅`FlowHandlerAdapter`的 API 文档。你可以通过子类化或实现你自己的 Flowhandler(将在下一节中讨论)来覆盖这些默认值。

茶陵後's avatar
茶陵後 已提交
73
## 11.4.实现自定义 Flowhandlers
dallascao's avatar
dallascao 已提交
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112

`FlowHandler`是可用于自定义在 HTTP Servlet 环境中如何执行流的扩展点。a`FlowHandler``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 基础结构中。

茶陵後's avatar
茶陵後 已提交
113
### 11.4.1.Flowhandler 示例
dallascao's avatar
dallascao 已提交
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132

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`结果将导致重定向,以显示新的预订。任何其他结果将重定向回酒店索引页面。

茶陵後's avatar
茶陵後 已提交
133
### 11.4.2.部署自定义 Flowhandler
dallascao's avatar
dallascao 已提交
134 135 136 137 138 139 140 141 142 143

要安装自定义 Flowhandler,只需将其部署为 Bean。 Bean 名称必须与处理程序应用到的流的 ID 匹配。

```
<bean name="hotels/booking" class="org.springframework.webflow.samples.booking.BookingFlowHandler" />
			
```

通过这种配置,访问资源`/hotels/booking`将使用自定义 BookingFlowhandler 启动`hotels/booking`流。当预订流程结束时,Flowhandler 将处理流程执行结果并将其重定向到适当的控制器。

茶陵後's avatar
茶陵後 已提交
144
### 11.4.3.Flowhandler 重定向
dallascao's avatar
dallascao 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161

处理 FlowExecutionResult 或 FlowException 的 Flowhandler 返回`String`,以指示处理后要重定向到的资源。在前面的示例中,对于`BookingFlowHandler`结果,`booking/show`资源 URI 重定向到`bookingConfirmed`,对于所有其他结果,`hotels/index`资源 URI 重定向到`booking/show`

默认情况下,返回的资源位置是相对于当前 Servlet 映射的。这允许流处理程序使用相对路径重定向到应用程序中的其他控制器。此外,对于需要更多控制的情况,支持显式重定向前缀。

支持的显式重定向前缀如下:

* `servletRelative:`-相对于当前资源重定向 Servlet

* `contextRelative:`-相对于当前 Web 应用程序上下文路径重定向到资源

* `serverRelative:`-重定向到相对于服务器根目录的资源

* `http://``https://`-重定向到完全限定的资源 URI

当结合视图状态或结束状态使用`externalRedirect:`指令时,流定义中也支持这些相同的重定向前缀;例如,`view="externalRedirect:http://springframework.org"`

茶陵後's avatar
茶陵後 已提交
162
## 11.5.视图分辨率
dallascao's avatar
dallascao 已提交
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180

除非另有说明,否则 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。

茶陵後's avatar
茶陵後 已提交
181
## 11.6.从视图发送事件信号
dallascao's avatar
dallascao 已提交
182 183 184

当流进入视图状态时,它会暂停,将用户重定向到其执行 URL,并等待用户事件恢复。事件通常通过激活按钮、链接或其他用户界面命令来发出信号。事件如何在服务器端进行解码取决于所使用的视图技术。本节展示了如何从基于 HTML 的视图中触发事件,这些视图是由 JSP、Velocity 或 Freemarker 等模板引擎生成的。

茶陵後's avatar
茶陵後 已提交
185
### 11.6.1.使用命名的 HTML 按钮发送事件信号
dallascao's avatar
dallascao 已提交
186 187 188 189 190 191 192 193 194 195 196

下面的示例显示了相同窗体上的两个按钮,当单击时分别表示`proceed``cancel`事件。

```
<input type="submit" name="_eventId_proceed" value="Proceed" />
<input type="submit" name="_eventId_cancel" value="Cancel" />
			
```

当按下按钮时,WebFlow 查找一个以`_eventId_`开头的请求参数名,并将其余的子字符串作为事件 ID。所以在这个例子中,提交`_eventId_proceed`变成了`proceed`。当有几个不同的事件可以从相同的形式发出信号时,应该考虑这种样式。

茶陵後's avatar
茶陵後 已提交
197
### 11.6.2.使用隐藏的 HTML 表单参数来表示事件
dallascao's avatar
dallascao 已提交
198 199 200 201 202 203 204 205 206 207 208

下面的示例显示了一个表单,该表单在提交时表示`proceed`事件:

```
<input type="submit" value="Proceed" />
<input type="hidden" name="_eventId" value="proceed" />
			
```

在这里,WebFlow 只检测特殊的`_eventId`参数,并将其值用作事件 ID。只有当窗体上有一个事件可以用信号表示时,才应该考虑这种样式。

茶陵後's avatar
茶陵後 已提交
209
### 11.6.3.使用 HTML 链接发出事件信号
dallascao's avatar
dallascao 已提交
210 211 212 213 214 215 216 217 218 219

下面的示例显示了一个链接,该链接在激活时发出`cancel`事件的信号:

```
<a href="${flowExecutionUrl}&_eventId=cancel">Cancel</a>
			
```

触发一个事件会导致一个 HTTP 请求被发送回服务器。在服务器端,流处理从其当前视图状态中解码事件。这个解码过程的工作方式是特定于视图实现的。回想一下 Spring MVC 视图实现只是查找一个名为`_eventId`的请求参数。如果没有找到`_eventId`参数,则视图将查找一个以`_eventId_`开头的参数,并将剩余的子字符串用作事件 ID。如果两种情况都不存在,则不会触发流事件。

茶陵後's avatar
茶陵後 已提交
220
## 11.7.在页面上嵌入流
dallascao's avatar
dallascao 已提交
221 222 223 224 225 226 227 228 229 230 231 232 233

默认情况下,当流进入视图状态时,它会在呈现视图之前执行客户端重定向。这种方法被称为 post-redirect-get。它的优点是将一个视图的表单处理与下一个视图的呈现分离开来。因此,浏览器的后退和刷新按钮可以无缝地工作,而不会引起任何浏览器警告。

通常情况下,从用户的角度来看,客户端重定向是透明的。然而,在有些情况下,后重定向可能不会带来同样的好处。例如,流可以嵌入在页面上并通过 Ajax 请求只刷新属于该流的页面的区域来驱动。在这种情况下,不仅没有必要使用客户端重定向,而且在保持页面周围内容不变方面也不是所需的行为。

[第 12.5 节,“处理 Ajax 请求”](spring-js.html#spring-js-ajax)解释了如何在 Ajax 请求期间执行部分呈现。本节的重点是解释如何在 Ajax 请求期间控制流执行重定向行为。要指示一个流应该以“页面嵌入”模式执行,你所需要做的就是在启动该流时附加一个额外的参数:

```
/hotels/booking?mode=embedded
```

当以“嵌入页面”模式启动时,流将不会在 Ajax 请求期间发出流执行重定向。Mode=Embedded 参数只需要在启动流时传递。你唯一关心的另一个问题是使用 Ajax 请求,并且只呈现更新显示该流的页面部分所需的内容。

茶陵後's avatar
茶陵後 已提交
234
### 11.7.1.嵌入式模式与默认重定向行为
dallascao's avatar
dallascao 已提交
235 236 237

默认情况下,WebFlow 在进入每个视图状态时都会进行客户端重定向。但是,如果在 Ajax 请求期间保持相同的视图状态(例如,没有“to”属性的转换),那么就不会有客户端重定向。这种行为对于 Spring Web Flow2 用户应该非常熟悉。它适用于支持浏览器后退按钮的顶级流,同时还可以利用 Ajax 和部分呈现,用于在表单验证、分页槽搜索结果等视图中保持不变的用例。但是,在转换到新的视图状态之后,总是会进行客户端重定向。这使得不可能在页面或模态对话框中嵌入流并执行多个视图状态,而不会导致整页刷新。因此,如果你的用例需要嵌入一个流,你可以在“嵌入”模式中启动它。

茶陵後's avatar
茶陵後 已提交
238
### 11.7.2.嵌入式流程示例
dallascao's avatar
dallascao 已提交
239 240 241 242 243 244 245 246 247 248 249

如果你想看到嵌入在页面和模态对话框中的流的示例,请参阅 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
```

茶陵後's avatar
茶陵後 已提交
250
## 11.8.将流输出保存到 MVC 闪存范围
dallascao's avatar
dallascao 已提交
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270

`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>
		
```