提交 71b715bc 编写于 作者: 茶陵後's avatar 茶陵後 👍

#23 spring web flow 基本格式审核调整

上级 90348f31
......@@ -638,13 +638,39 @@ module.exports = {
initialOpenGroupIndex: 0 // 可选的, 默认值是 0
}
],
'/spring-web-flow/': [
{
title: 'Spring Web Flow 文档',
sidebarDepth: 2,
collapsable: false,
children: [
"/spring-web-flow/preface.md",
"/spring-web-flow/introduction.md",
"/spring-web-flow/whatsnew.md",
"/spring-web-flow/defining-flows.md",
"/spring-web-flow/el.md",
"/spring-web-flow/views.md",
"/spring-web-flow/actions.md",
"/spring-web-flow/flow-managed-persistence.md",
"/spring-web-flow/flow-security.md",
"/spring-web-flow/flow-inheritance.md",
"/spring-web-flow/system-setup.md",
"/spring-web-flow/spring-mvc.md",
"/spring-web-flow/spring-js.md",
"/spring-web-flow/spring-faces.md",
"/spring-web-flow/testing.md",
"/spring-web-flow/field-mappings.md"
],
initialOpenGroupIndex: 0 // 可选的, 默认值是 0
}
],
'/spring-web-services/': [
{
title: 'Spring Web Services 文档',
sidebarDepth: 2,
collapsable: false,
children: [
"/spring-web-services/spring-web-service.md",
"/spring-web-flow/spring-web-service.md",
],
initialOpenGroupIndex: 0 // 可选的, 默认值是 0
}
......
# Spring Web Flow
\ No newline at end of file
# 6. 执行动作
## 6.1.导言
本章向你展示了如何使用`action-state`元素来控制流中某个点上的动作的执行。它还将展示如何使用`decision-state`元素来做出流路由决策。最后,将讨论几个从流中可能的各个点调用操作的示例。
## 6.2.界定行动状态
当你希望调用某个操作时,使用`action-state`元素,然后根据该操作的结果转换到另一个状态:
```
<action-state id="moreAnswersNeeded">
<evaluate expression="interview.moreAnswersNeeded()" />
<transition on="yes" to="answerQuestions" />
<transition on="no" to="finish" />
</action-state>
```
下面的完整示例说明了一个面试流程,该流程使用上面的 Action-State 来确定是否需要更多的答案才能完成面试:
```
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<on-start>
<evaluate expression="interviewFactory.createInterview()" result="flowScope.interview" />
</on-start>
<view-state id="answerQuestions" model="questionSet">
<on-entry>
<evaluate expression="interview.getNextQuestionSet()" result="viewScope.questionSet" />
</on-entry>
<transition on="submitAnswers" to="moreAnswersNeeded">
<evaluate expression="interview.recordAnswers(questionSet)" />
</transition>
</view-state>
<action-state id="moreAnswersNeeded">
<evaluate expression="interview.moreAnswersNeeded()" />
<transition on="yes" to="answerQuestions" />
<transition on="no" to="finish" />
</action-state>
<end-state id="finish" />
</flow>
```
在执行每个操作之后,Action-State 会检查结果,以查看是否与声明的向另一种状态的转换匹配。这意味着,如果配置了多个动作,它们将在有序的链中执行,直到其中一个返回一个结果事件,该结果事件与动作状态的状态转换相匹配,而其余的则被忽略。这是责任链模式的一种形式。
动作执行的结果通常是转换出此状态的标准。当前请求上下文中的附加信息也可以作为自定义转换标准的一部分进行测试,该标准允许根据上下文状态推理复杂的转换表达式。
还请注意,一个动作状态就像其他任何状态一样,可以有另外一个输入动作,这些动作从头到尾以列表的形式执行。
## 6.3.决定状态的定义
使用`decision-state`元素作为 Action-State 的替代选项,以使用方便的 if/else 语法做出路由决策。下面的示例显示了上面的`moreAnswersNeeded`状态,该状态现在实现为一个决策状态,而不是一个动作状态:
```
<decision-state id="moreAnswersNeeded">
<if test="interview.moreAnswersNeeded()" then="answerQuestions" else="finish" />
</decision-state>
```
## 6.4.行动成果事件映射
动作经常调用普通 Java 对象上的方法。当从动作状态和决策状态调用时,可以使用这些方法返回值来驱动状态转换。由于转换是由事件触发的,因此方法返回值必须首先映射到事件对象。下表描述了如何将常见的返回值类型映射到事件对象:
**表 6.1.动作方法将值返回到事件 ID 映射**
|Method return type|映射的事件标识符表达式|
|------------------|----------------------------------|
| java.lang.String |字符串的值|
|java.lang.Boolean |是(为真),否(为假)|
| java.lang.Enum |枚举名称|
| any other type |成功|
下面的示例 Action State 演示了这一点,它调用了一个返回布尔值的方法:
```
<action-state id="moreAnswersNeeded">
<evaluate expression="interview.moreAnswersNeeded()" />
<transition on="yes" to="answerQuestions" />
<transition on="no" to="finish" />
</action-state>
```
## 6.5.动作实现
虽然将动作代码编写为 POJO 逻辑是最常见的,但还有其他几种动作实现选项。有时你需要编写需要访问流上下文的动作代码。你总是可以调用 POJO 并将 FlowRequestContext 作为 EL 变量传递给它。或者,你可以实现`Action`接口或从`MultiAction`基类进行扩展。当你的操作代码和 Spring Web 流 API 之间具有天然的耦合时,这些选项提供了更强的类型安全性。下面是这些方法的示例。
### 6.5.1.调用 POJO 动作
```
<evaluate expression="pojoAction.method(flowRequestContext)" />
```
```
public class PojoAction {
public String method(RequestContext context) {
...
}
}
```
### 6.5.2.调用自定义操作实现
```
<evaluate expression="customAction" />
```
```
public class CustomAction implements Action {
public Event execute(RequestContext context) {
...
}
}
```
### 6.5.3.调用多操作实现
```
<evaluate expression="multiAction.actionMethod1" />
```
```
public class CustomMultiAction extends MultiAction {
public Event actionMethod1(RequestContext context) {
...
}
public Event actionMethod2(RequestContext context) {
...
}
...
}
```
## 6.6.动作例外
动作通常调用封装复杂业务逻辑的服务。这些服务可能会抛出操作代码应该处理的业务异常。
### 6.6.1.使用 POJO 操作处理业务异常
下面的示例调用一个操作,该操作捕获业务异常,将错误消息添加到上下文中,并返回结果事件标识符。结果被视为一个流事件,然后调用流可以对其进行响应。
```
<evaluate expression="bookingAction.makeBooking(booking, flowRequestContext)" />
```
```
public class BookingAction {
public String makeBooking(Booking booking, RequestContext context) {
try {
BookingConfirmation confirmation = bookingService.make(booking);
context.getFlowScope().put("confirmation", confirmation);
return "success";
} catch (RoomNotAvailableException e) {
context.addMessage(new MessageBuilder().error().
.defaultText("No room is available at this hotel").build());
return "error";
}
}
}
```
### 6.6.2.使用多操作处理业务异常
下面的示例在功能上与上一个示例相同,但实现为一个多操作,而不是一个 POJO 操作。多操作要求其操作方法的签名`Event ${methodName}(RequestContext)`,提供更强的类型安全性,而 POJO 操作允许更大的自由度。
```
<evaluate expression="bookingAction.makeBooking" />
```
```
public class BookingAction extends MultiAction {
public Event makeBooking(RequestContext context) {
try {
Booking booking = (Booking) context.getFlowScope().get("booking");
BookingConfirmation confirmation = bookingService.make(booking);
context.getFlowScope().put("confirmation", confirmation);
return success();
} catch (RoomNotAvailableException e) {
context.getMessageContext().addMessage(new MessageBuilder().error().
.defaultText("No room is available at this hotel").build());
return error();
}
}
}
```
### 6.6.3.使用异常处理程序元素
通常,建议在操作中捕获异常并返回驱动标准转换的结果事件,也可以向具有`bean`属性的任何状态类型添加`exception-handler`子元素,该属性引用类型`FlowExecutionExceptionHandler`的 Bean。这是一个高级选项,如果使用不正确,可能会使流执行处于无效状态。将内建`TransitionExecutingFlowExecutionExceptionHandler`作为一个正确实现的示例。
## 6.7.其他动作执行示例
### 6.7.1.启动
下面的示例展示了一个操作,该操作通过调用服务上的一个方法来创建一个新的 Booking 对象:
```
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<input name="hotelId" />
<on-start>
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
result="flowScope.booking" />
</on-start>
</flow>
```
### 6.7.2.进入
下面的示例显示了一个状态条目操作,该操作设置特殊的`fragments`变量,该变量使视图状态呈现其视图的部分片段:
```
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
<on-entry>
<render fragments="hotelSearchForm" />
</on-entry>
</view-state>
```
### 6.7.3.on-exit
下面的示例显示了一个状态退出操作,该操作释放正在编辑的记录上的锁:
```
<view-state id="editOrder">
<on-entry>
<evaluate expression="orderService.selectForUpdate(orderId, currentUser)"
result="viewScope.order" />
</on-entry>
<transition on="save" to="finish">
<evaluate expression="orderService.update(order, currentUser)" />
</transition>
<on-exit>
<evaluate expression="orderService.releaseLock(order, currentUser)" />
</on-exit>
</view-state>
```
### 6.7.4.on-end
下面的示例展示了使用流开始和结束操作的等效对象锁定行为:
```
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<input name="orderId" />
<on-start>
<evaluate expression="orderService.selectForUpdate(orderId, currentUser)"
result="flowScope.order" />
</on-start>
<view-state id="editOrder">
<transition on="save" to="finish">
<evaluate expression="orderService.update(order, currentUser)" />
</transition>
</view-state>
<on-end>
<evaluate expression="orderService.releaseLock(order, currentUser)" />
</on-end>
</flow>
```
### 6.7.5.on-render
下面的示例展示了一个呈现操作,该操作加载要在呈现视图之前显示的酒店列表:
```
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
<transition on="select" to="reviewHotel">
<set name="flowScope.hotel" value="hotels.selectedRow" />
</transition>
</view-state>
```
### 6.7.6.on-transition
下面的示例展示了一个转换操作,该操作将一个子流结果事件属性添加到一个集合中:
```
<subflow-state id="addGuest" subflow="createGuest">
<transition on="guestCreated" to="reviewBooking">
<evaluate expression="booking.guestList.add(currentEvent.attributes.newGuest)" />
</transition>
</subfow-state>
```
### 6.7.7.命名动作
下面的示例展示了如何在动作状态下执行一系列动作。每个动作的名称成为动作结果事件的限定符。
```
<action-state id="doTwoThings">
<evaluate expression="service.thingOne()">
<attribute name="name" value="thingOne" />
</evaluate>
<evaluate expression="service.thingTwo()">
<attribute name="name" value="thingTwo" />
</evaluate>
<transition on="thingTwo.success" to="showResults" />
</action-state>
```
在此示例中,当`thingTwo`成功完成时,流将转换为`showResults`
### 6.7.8.串流动作
有时,一个动作需要将自定义响应流回客户端。一个例子可能是处理打印事件时呈现 PDF 文档的流。这可以通过使内容的动作流然后在 ExternalContext 上记录“响应完成”状态来实现。ResponseComplete 标志告诉暂停视图状态不要呈现响应,因为另一个对象已经处理了它。
```
<view-state id="reviewItinerary">
<transition on="print">
<evaluate expression="printBoardingPassAction" />
</transition>
</view-state>
```
```
public class PrintBoardingPassAction extends AbstractAction {
public Event doExecute(RequestContext context) {
// stream PDF content here...
// - Access HttpServletResponse by calling context.getExternalContext().getNativeResponse();
// - Mark response complete by calling context.getExternalContext().recordResponseComplete();
return success();
}
}
```
在本例中,当打印事件被触发时,流将调用 printboardingpassAction。该操作将呈现 PDF,然后将响应标记为已完成。
### 6.7.9.处理文件上传
另一个常见的任务是使用 Web 流结合 Spring MVC 的`MultipartResolver`处理多部分文件上传。一旦正确设置了解析器[如此处所述](http://static.springsource.org/spring/docs/2.5.x/reference/mvc.html#mvc-multipart),并且将提交的 HTML 表单配置为`enctype="multipart/form-data"`,你就可以在转换操作中轻松处理文件上传。
|[[note](images/note.png)|Note|
|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---|
|下面的文件上载示例与使用 JSF 的 WebFlow 无关。有关如何使用 JSF 上载文件的详细信息,请参见[第 13.8 节,“使用 JSF 处理文件上传”](spring-faces.html#spring-faces-file-upload)。| |
给定一种形式,如:
```
<form:form modelAttribute="fileUploadHandler" enctype="multipart/form-data">
Select file: <input type="file" name="file"/>
<input type="submit" name="_eventId_upload" value="Upload" />
</form:form>
```
以及用于处理上载的支持对象,例如:
```
package org.springframework.webflow.samples.booking;
import org.springframework.web.multipart.MultipartFile;
public class FileUploadHandler {
private transient MultipartFile file;
public void processFile() {
//Do something with the MultipartFile here
}
public void setFile(MultipartFile file) {
this.file = file;
}
}
```
你可以使用以下示例中的转换操作来处理上载:
```
<view-state id="uploadFile" model="uploadFileHandler">
<var name="fileUploadHandler" class="org.springframework.webflow.samples.booking.FileUploadHandler" />
<transition on="upload" to="finish" >
<evaluate expression="fileUploadHandler.processFile()"/>
</transition>
<transition on="cancel" to="finish" bind="false"/>
</view-state>
```
`MultipartFile`绑定到`FileUploadHandler` Bean 作为正常形式绑定过程的一部分,以便在执行转换操作期间可以对其进行处理。
\ No newline at end of file
# 3. 定义流
## 3.1.导言
本章从用户部分开始。它展示了如何使用流定义语言实现流。在本章结束时,你应该已经对语言结构有了很好的了解,并且能够编写流定义。
## 3.2.什么是流动?
流封装了一个可重用的步骤序列,这些步骤可以在不同的上下文中执行。下面是[加勒特信息架构](http://www.jjg.net/ia/visvocab/)图,其中引用了一个流程,该流程封装了酒店预订过程的步骤:
<img src="images/hotels-site.png" align="middle" />
示出对流程的引用的站点地图
## 3.3.一个典型的流的构成是什么?
在 Spring Web 流中,流由一系列称为“状态”的步骤组成。输入一个状态通常会导致向用户显示一个视图。在该视图中,会发生由状态处理的用户事件。这些事件可以触发向其他状态的转换,从而导致视图导航。
下面的示例显示了上一个图中引用的预订酒店流程的结构:
<img src="images/hotels-site-bookhotel-flow.png" align="middle" />
流程图
## 3.4.流程是如何编写的?
流是由 Web 应用程序开发人员使用一种简单的基于 XML 的流定义语言编写的。本指南的下一步将向你介绍这种语言的元素。
## 3.5.基本语言要素
### 3.5.1.流量
每个流都以以下根元素开始:
```
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
</flow>
```
流的所有状态都在这个元素中定义。定义的第一个状态成为流的起点。
### 3.5.2.view-state
使用`view-state`元素定义呈现视图的流的一个步骤:
```
<view-state id="enterBookingDetails" />
```
按照惯例,视图状态将其 ID 映射到流所在目录中的视图模板。例如,如果流本身位于`/WEB-INF/hotels/booking`目录中,则上面的状态可能呈现`/WEB-INF/hotels/booking/enterBookingDetails.xhtml`
### 3.5.3.过渡
使用`transition`元素来处理在一个状态中发生的事件:
```
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
```
这些转换驱动视图导航。
### 3.5.4.end-state
使用`end-state`元素来定义流结果:
```
<end-state id="bookingCancelled" />
```
当一个流转换到一个结束状态时,它就会终止,并返回结果。
### 3.5.5.检查点:基本语言元素
使用三个元素`view-state``transition``end-state`,你可以快速表示视图导航逻辑。团队通常在添加流行为之前就这样做了,这样他们就可以首先专注于与最终用户一起开发应用程序的用户界面。下面是一个样例流程,它使用以下元素实现了它的视图导航逻辑:
```
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
<view-state id="reviewBooking">
<transition on="confirm" to="bookingConfirmed" />
<transition on="revise" to="enterBookingDetails" />
<transition on="cancel" to="bookingCancelled" />
</view-state>
<end-state id="bookingConfirmed" />
<end-state id="bookingCancelled" />
</flow>
```
## 3.6.行动
大多数流需要表达的不仅仅是查看导航逻辑。通常,它们还需要调用应用程序的业务服务或其他操作。
在一个流中,你可以在几个点上执行操作。这些要点是:
* 在流启动时
* 进入国家时
* On View 渲染
* 关于转换执行
* 在国家退出时
* 在流端
动作是用简洁的表达式语言定义的。 Spring 默认情况下,Web 流使用统一的 EL。接下来的几节将介绍用于定义操作的基本语言元素。
### 3.6.1.评估
你最常使用的动作元素是`evaluate`元素。使用`evaluate`元素在流中的某个点计算表达式。使用这个单一标记,你可以在 Spring bean 或任何其他流变量上调用方法。例如:
```
<evaluate expression="entityManager.persist(booking)" />
```
#### 分配评估结果
如果表达式返回一个值,该值可以保存在流的数据模型`flowScope`中:
```
<evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels" />
```
#### 转换求值结果
如果表达式返回一个可能需要转换的值,请使用`result-type`属性指定所需的类型:
```
<evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels"
result-type="dataModel"/>
```
### 3.6.2.检查点:流操作
现在查看示例预订流程,并添加以下操作:
```
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<input name="hotelId" />
<on-start>
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
result="flowScope.booking" />
</on-start>
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
<view-state id="reviewBooking">
<transition on="confirm" to="bookingConfirmed" />
<transition on="revise" to="enterBookingDetails" />
<transition on="cancel" to="bookingCancelled" />
</view-state>
<end-state id="bookingConfirmed" />
<end-state id="bookingCancelled" />
</flow>
```
这个流现在开始时在流范围内创建一个 Booking 对象。要预订的酒店的 ID 是从一个流输入属性获得的。
## 3.7.输入/输出映射
每个流都有一个定义良好的输入/输出契约。流可以在开始时传递输入属性,在结束时返回输出属性。在这方面,调用流在概念上类似于调用具有以下签名的方法:
```
FlowOutcome flowId(Map<String, Object> inputAttributes);
```
...其中`FlowOutcome`具有以下签名:
```
public interface FlowOutcome {
public String getName();
public Map<String, Object> getOutputAttributes();
}
```
### 3.7.1.输入
使用`input`元素声明一个流输入属性:
```
<input name="hotelId" />
```
输入值以属性的名称保存在流作用域中。例如,上面的输入将以`hotelId`的名称保存。
#### 声明输入类型
使用`type`属性声明输入属性的类型:
```
<input name="hotelId" type="long" />
```
如果输入值与声明的类型不匹配,将尝试进行类型转换。
#### 分配一个输入值
使用`value`属性指定一个表达式,将输入值分配给:
```
<input name="hotelId" value="flowScope.myParameterObject.hotelId" />
```
如果表达式的值类型可以确定,那么如果没有指定`type`属性,则该元数据将用于类型强制。
#### 根据需要标记输入
使用`required`属性强制输入不为空或空:
```
<input name="hotelId" type="long" value="flowScope.hotelId" required="true" />
```
### 3.7.2.输出
使用`output`元素声明一个流输出属性。输出属性在表示特定流结果的结束状态中声明。
```
<end-state id="bookingConfirmed">
<output name="bookingId" />
</end-state>
```
输出值是在属性的名称下从流作用域获得的。例如,上面的输出将被分配`bookingId`变量的值。
#### 指定输出值的源
使用`value`属性表示特定的输出值表达式:
```
<output name="confirmationNumber" value="booking.confirmationNumber" />
```
### 3.7.3.检查点:输入/输出映射
现在查看带有输入/输出映射的样例预订流程:
```
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<input name="hotelId" />
<on-start>
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
result="flowScope.booking" />
</on-start>
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
<view-state id="reviewBooking">
<transition on="confirm" to="bookingConfirmed" />
<transition on="revise" to="enterBookingDetails" />
<transition on="cancel" to="bookingCancelled" />
</view-state>
<end-state id="bookingConfirmed" >
<output name="bookingId" value="booking.id"/>
</end-state>
<end-state id="bookingCancelled" />
</flow>
```
流现在接受`hotelId`输入属性,并在确认新预订时返回`bookingId`输出属性。
## 3.8.变量
流可以声明一个或多个实例变量。这些变量在流开始时被分配。当流恢复时,变量持有的任何`@Autowired`瞬态引用也会重新布线。
### 3.8.1.var
使用`var`元素声明一个流变量:
```
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>
```
确保变量的类实现`java.io.Serializable`,因为实例状态在流请求之间被保存。
## 3.9.可变作用域
Web 流可以将变量存储在以下几个范围中的一个:
### 3.9.1.流动范围
流作用域在流开始时被分配,在流结束时被销毁。对于默认的实现,存储在流作用域中的任何对象都需要是可序列化的。
### 3.9.2.视图作用域
`view-state`进入时,视图作用域被分配,而当状态退出时,视图作用域被销毁。视图作用域是*只有*可从`view-state`中引用的。对于默认实现,存储在视图作用域中的任何对象都需要是可序列化的。
### 3.9.3.请求范围
当一个流被调用时,请求作用域被分配,当流返回时,请求作用域被销毁。
### 3.9.4.闪光范围
当一个流开始时,flash 作用域被分配,在每个视图呈现后被清除,当该流结束时被销毁。对于默认的实现,存储在 Flash 作用域中的任何对象都需要是可序列化的。
### 3.9.5.会话范围
对话范围在顶级流启动时被分配,在顶级流结束时被销毁。会话范围由顶级流及其所有子流共享。对于默认的实现,对话范围的对象存储在 HTTP会话中,并且通常应该是可序列化的,以考虑典型的会话复制。
要使用的范围通常是在上下文中确定的,例如,取决于定义变量的位置--在流定义的开始(流范围),在视图状态(视图范围)内,等等。在其他情况下,例如在 EL 表达式和 Java 代码中,需要显式地指定它。随后的章节解释了如何做到这一点。
## 3.10.调用子流
一个流可以调用另一个流作为子流。流将等待直到子流返回,然后对子流结果做出响应。
### 3.10.1.子流-状态
使用`subflow-state`元素调用另一个流作为子流:
```
<subflow-state id="addGuest" subflow="createGuest">
<transition on="guestCreated" to="reviewBooking">
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
</transition>
<transition on="creationCancelled" to="reviewBooking" />
</subflow-state>
```
上面的示例调用`createGuest`流,然后等待它返回。当流返回`guestCreated`结果时,新的客人将被添加到预订的客人列表中。
#### 传递一个子流输入
使用`input`元素将输入传递给子流:
```
<subflow-state id="addGuest" subflow="createGuest">
<input name="booking" />
<transition to="reviewBooking" />
</subflow-state>
```
#### 映射子流输出
当一个子流完成时,它的结束状态 ID 将返回给调用流,作为用于继续导航的事件。
子流还可以在结果转换中创建调用流可以引用的输出属性,如下所示:
```
<transition on="guestCreated" to="reviewBooking">
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
</transition>
```
在上面的示例中,`guest`是由`guestCreated`结果返回的输出属性的名称。
### 3.1 0.2.检查点:调用子流
现在查看调用子流的样例预订流程:
```
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<input name="hotelId" />
<on-start>
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
result="flowScope.booking" />
</on-start>
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
<view-state id="reviewBooking">
<transition on="addGuest" to="addGuest" />
<transition on="confirm" to="bookingConfirmed" />
<transition on="revise" to="enterBookingDetails" />
<transition on="cancel" to="bookingCancelled" />
</view-state>
<subflow-state id="addGuest" subflow="createGuest">
<transition on="guestCreated" to="reviewBooking">
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
</transition>
<transition on="creationCancelled" to="reviewBooking" />
</subflow-state>
<end-state id="bookingConfirmed" >
<output name="bookingId" value="booking.id" />
</end-state>
<end-state id="bookingCancelled" />
</flow>
```
该流现在调用`createGuest`子流,将一个新的来宾添加到来宾列表中。
\ No newline at end of file
# 4. 表达式语言
## 4.1.导言
Web Flow 使用 EL 访问其数据模型并调用操作。本章将让你熟悉 EL 语法、配置和你可以从流定义中引用的特殊 EL 变量。
EL 用于流中的许多事情,包括:
1. 访问客户端数据,例如声明流输入或引用请求参数。
2. 访问 Web 流中的`RequestContext`中的数据,例如`flowScope``currentEvent`
3. 通过操作在 Spring 管理的对象上调用方法。
4. 解析表达式,如状态转换条件、子流 ID 和视图名称。
EL 还用于将表单参数绑定到模型对象,并从模型对象的属性反向呈现格式化的表单字段。然而,当使用 Web 流与 JSF 一起使用时,这种方法并不适用,在这种情况下,标准的 JSF 组件 Lifecyle 就适用了。
### 4.1.1.表达式类型
要理解的一个重要概念是,在 Web 流中有两种类型的表达式:标准表达式和模板表达式。
#### 标准表达式
第一种也是最常见的一种表达式是*标准表达式*。这样的表达式由 EL 直接求值,不需要用`#{}`这样的分隔符括起来。例如:
```
<evaluate expression="searchCriteria.nextPage()" />
```
上面的表达式是一个标准表达式,在计算`searchCriteria`变量时调用`nextPage`方法。如果你试图将这个表达式包含在一个特殊的分隔符中,比如`#{}`,你将得到一个`IllegalArgumentException`。在这种情况下,分隔符被视为多余的。`expression`属性唯一可接受的值是一个表达式字符串。
#### 模板表达式
第二种表达式是*模板表达式*。模板表达式允许将文本与一个或多个标准表达式混合在一起。每个标准表达式块都显式地被`#{}`分隔符包围。例如:
```
<view-state id="error" view="error-#{externalContext.locale}.xhtml" />
```
上面的表达式是一个模板表达式。求值的结果将是一个字符串,该字符串将诸如`error-``.xhtml`等文字文本与求值`externalContext.locale`的结果连接在一起。如你所见,这里需要显式分隔符来划分模板中的标准表达式块。
|[[note](images/note.png)|Note|
|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---|
|有关接受标准表达式和接受模板表达式的 XML 属性的完整列表,请参见 Web Flow XML 模式。<br/>你还可以在 Eclipse 中使用 F2(或在其他 IDE 中使用等效的快捷方式)在键入特定的流定义属性时访问可用的文档。| |
## 4.2.EL 实现
### 4.2.1. Spring El
Web 流使用[Spring Expression Language](https://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html)( Spring el)。 Spring 创建 EL 是为了提供一种单一的、支持良好的表达式语言,用于在 Spring 产品组合中的所有产品中使用。在 Spring 框架中,它作为一个单独的 JAR`org.springframework.expression`分发。
### 4.2.2.统一 El
使用[Unified EL](https://en.wikipedia.org/wiki/Unified_Expression_Language)还意味着对`el-api`的依赖,尽管你的 Web 容器通常是*提供*。 Spring 虽然 EL 是默认的和推荐使用的表达式语言,但如果你希望这样做,可以用统一的 EL 替换它。你需要以下 Spring 配置来将`WebFlowELExpressionParser`插入`flow-builder-services`:
```
<webflow:flow-builder-services expression-parser="expressionParser"/>
<bean id="expressionParser" class="org.springframework.webflow.expression.el.WebFlowELExpressionParser">
<constructor-arg>
<bean class="org.jboss.el.ExpressionFactoryImpl" />
</constructor-arg>
</bean>
```
请注意,如果你的应用程序正在注册自定义转换器,那么确保 WebFloweLexPressionParser 配置了具有这些自定义转换器的转换服务是很重要的。
```
<webflow:flow-builder-services expression-parser="expressionParser" conversion-service="conversionService"/>
<bean id="expressionParser" class="org.springframework.webflow.expression.el.WebFlowELExpressionParser">
<constructor-arg>
<bean class="org.jboss.el.ExpressionFactoryImpl" />
</constructor-arg>
<property name="conversionService" ref="conversionService"/>
</bean>
<bean id="conversionService" class="somepackage.ApplicationConversionService"/>
```
## 4.3.EL 便携性
通常,你会发现 Spring EL 和 Unified EL 具有非常相似的语法。
然而,请注意,EL 也有一些优点。例如 Spring EL 与 Spring 3 的类型转换紧密集成,这允许你充分利用其功能。特别是,当前仅在 Spring EL 中支持对泛型类型的自动检测以及对格式注释的使用。
在从 Unified EL 升级到 Spring EL 时,需要记住一些小的更改,如下所示:
1. 在流定义中用`${}`去毛的表达式必须更改为`#{}`
2. 测试当前事件`#{currentEvent == 'submit'}`的表达式必须更改为`#{currentEvent.id == 'submit'}`
3. 解析诸如`#{currentUser.name}`之类的属性可能会导致 nullpointerexception,而无需进行诸如`#{currentUser != null ? currentUser.name : null}`之类的检查。一个更好的选择是安全导航操作符`#{currentUser?.name}`
有关 Spring EL 语法的更多信息,请参阅 Spring 文档中的[语言参考](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/expressions.html#expressions-language-ref)部分。
## 4.4.特殊 EL 变量
你可以从流中引用几个隐式变量。这些变量将在本节中讨论。
记住这条通则。引用数据作用域(FlowScope、ViewScope、RequestScope 等)的变量只应在为其中一个作用域分配新变量时使用。
例如,当将调用`bookingService.findHotels(searchCriteria)`的结果分配给一个名为“Hotels”的新变量时,你必须在它的前缀加上一个范围变量,以便让 Web 流知道你希望将它存储在哪里:
```
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow" ... >
<var name="searchCriteria" class="org.springframework.webflow.samples.booking.SearchCriteria" />
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
</on-render>
</view-state>
</flow>
```
但是,当在下面的示例中设置诸如“SearchCriteria”之类的现有变量时,你可以直接引用该变量,而无需用任何作用域变量对其进行前缀:
```
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow" ... >
<var name="searchCriteria" class="org.springframework.webflow.samples.booking.SearchCriteria" />
<view-state id="reviewHotels">
<transition on="sort">
<set name="searchCriteria.sortBy" value="requestParameters.sortBy" />
</transition>
</view-state>
</flow>
```
以下是可以在流定义中引用的隐式变量列表:
### 4.4.1.flowscope
使用`flowScope`分配流变量。流作用域在流开始时被分配,在流结束时被销毁。对于默认的实现,存储在流作用域中的任何对象都需要是可序列化的。
```
<evaluate expression="searchService.findHotel(hotelId)" result="flowScope.hotel" />
```
### 4.4.2.viewscope
使用`viewScope`分配一个视图变量。当`view-state`进入时,视图作用域被分配,而当状态退出时,视图作用域被销毁。视图作用域是*只有*可从`view-state`中引用的。对于默认实现,存储在视图作用域中的任何对象都需要是可序列化的。
```
<on-render>
<evaluate expression="searchService.findHotels(searchCriteria)" result="viewScope.hotels"
result-type="dataModel" />
</on-render>
```
### 4.4.3.RequestScope
使用`requestScope`分配一个请求变量。当一个流被调用时,请求作用域被分配,当流返回时,请求作用域被销毁。
```
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
```
### 4.4.4.FlashScope
使用`flashScope`分配一个 flash 变量。当一个流开始时,flash 作用域被分配,在每个视图呈现后被清除,当该流结束时被销毁。对于默认的实现,存储在 Flash 作用域中的任何对象都需要是可序列化的。
```
<set name="flashScope.statusMessage" value="'Booking confirmed'" />
```
### 4.4.5.ConversationScope
使用`conversationScope`分配一个会话变量。对话范围在顶级流启动时被分配,在顶级流结束时被销毁。会话范围由顶级流及其所有子流共享。对于默认的实现,对话范围的对象存储在 HTTP会话中,并且通常应该是可序列化的,以考虑典型的会话复制。
```
<evaluate expression="searchService.findHotel(hotelId)" result="conversationScope.hotel" />
```
### 4.4.6.requestParameters
使用`requestParameters`访问客户端请求参数:
```
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
```
### 4.4.7.currentEvent
使用`currentEvent`访问当前`Event`的属性:
```
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
```
### 4.4.8.currentuser
使用`currentUser`访问经过身份验证的`Principal`:
```
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
result="flowScope.booking" />
```
### 4.4.9.messagecontext
使用`messageContext`访问上下文以检索和创建流执行消息,包括错误和成功消息。有关更多信息,请参见`MessageContext`Javadocs。
```
<evaluate expression="bookingValidator.validate(booking, messageContext)" />
```
### 4.4.10.ResourceBundle
使用`resourceBundle`访问消息资源。
```
<set name="flashScope.successMessage" value="resourceBundle.successMessage" />
```
### 4.4.11.FlowRequestContext
使用`flowRequestContext`访问`RequestContext`API,这是当前流请求的表示。有关更多信息,请参见 API Javadocs。
### 4.4.12.FlowExecutionContext
使用`flowExecutionContext`访问`FlowExecutionContext`API,这是当前流状态的表示。有关更多信息,请参见 API Javadocs。
### 4.4.13.flowexecutionurl
使用`flowExecutionUrl`访问当前流执行视图状态的上下文相关 URI。
### 4.4.14.ExternalContext
使用`externalContext`访问客户端环境,包括用户会话属性。有关更多信息,请参见`ExternalContext`API Javadocs。
```
<evaluate expression="searchService.suggestHotels(externalContext.sessionMap.userProfile)"
result="viewScope.hotels" />
```
## 4.5.范围搜索算法
正如本节前面提到的,在一个流作用域中分配变量时,需要引用该作用域。例如:
```
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
```
当只访问其中一个作用域中的变量时,引用该作用域是可选的。例如:
```
<evaluate expression="entityManager.persist(booking)" />
```
当没有指定作用域时,就像上面使用`booking`一样,使用作用域搜索算法。该算法将在请求、闪存、视图、流和会话范围中查找变量。如果没有找到这样的变量,将抛出一个`EvaluationException`
\ No newline at end of file
# 附录 A.流定义语言 1.0 到 2.0 的映射
自 1.0 版本发布以来,流定义语言已经发生了变化。这是 1.0 版本中的语言元素的列表,以及它们如何映射到 2.0 版本中的元素。虽然大多数变化是语义上的,但也有一些结构上的变化。有关 Web Flow1.0 和 2.0 之间的更改的更多详细信息,请参见升级指南。
**表 A.1.映射**
| SWF 1.0 | SWF 2.0 |评论| | |
|--------------------|---------------------|--------------------------------------------------------------------------|------------------------|--------------------------------------------------------------------------|
| *action* | *\** |使用 \<evaluate /\>| | |
| | bean | | \* | |
| | name | | \* | |
| | method | | \* | |
| *action-state* | *action-state* | | | |
| | id | | id | |
| | \* | | parent | |
| *argument* | *\** |使用 \<evaluate expression="func(arg1, arg2, ...)"/\>| | |
| | expression | | | |
| | parameter-type | | | |
| *attribute* | *attribute* | | | |
| | name | | name | |
| | type | | type | |
| | value | | value | |
| *attribute-mapper* | *\** |输入和输出元素可以直接在流或子流中| | |
| | bean | | \* | now subflow-attribute-mapper attribute on subflow-state |
| *bean-action* | *\** |使用 \<evaluate /\>| | |
| | bean | | \* | |
| | name | | \* | |
| | method | | \* | |
| *decision-state* | *decision-state* | | | |
| | id | | id | |
| | \* | | parent | |
| *end-actions* | *on-end* | | | |
| *end-state* | *end-state* | | | |
| | id | | id | |
| | view | | view | |
| | \* | | parent | |
| | \* | | commit | |
| *entry-actions* | *on-entry* | | | |
| *evaluate-action* | *evaluate* | | | |
| | expression | | expression | |
| | name | | \* |use \<evaluate ...\> \<attribute name=”name” value="..." /\> \</evaluate\>|
| | \* | | result | |
| | \* | | result-type | |
|*evaluation-result* | *\** |使用 \<evaluate result="..." /\>| | |
| | name | | \* | |
| | scope | | \* | |
|*exception-handler* | *exception-handler* | | | |
| | bean | | bean | |
| *exit-actions* | *on-exit* | | | |
| *flow* | *flow* | | | |
| | \* | | start-state | |
| | \* | | parent | |
| | \* | | abstract | |
|*global-transitions*|*global-transitions* | | | |
| *if* | *if* | | | |
| | test | | test | |
| | then | | then | |
| | else | | else | |
| *import* | *bean-import* | | | |
| | resource | | resource | |
| *inline-flow* | *\** |转换到新的顶级流| | |
| | id | | \* | |
| *input-attribute* | *input* | | | |
| | name | | name | |
| | scope | | \* | prefix name with scope \<input name="flowScope.foo" /\> |
| | required | | required | |
| | \* | | type | |
| | \* | | value | |
| *input-mapper* | *\** |输入可以是流,也可以是子流。| | |
| *mapping* | *input or output* | | | |
| | source | | name or value | name when in flow element, value when in subflow-state element |
| | target | | name or value | value when in flow element, name when in subflow-state element |
| | target-collection | | \* | no longer supported |
| | from | | \* | detected automatically |
| | to | | type | |
| | required | | required | |
| *method-argument* | *\** |使用 \<evaluate expression="func(arg1, arg2, ...)"/\>| | |
| *method-result* | *\** |使用 \<evaluate result="..." /\>| | |
| | name | | \* | |
| | scope | | \* | |
| *output-attribute* | *output* | | | |
| | name | | name | |
| | scope | | \* | prefix name with scope \<output name="flowScope.foo" /\> |
| | required | | required | |
| | \* | | type | |
| | \* | | value | |
| *output-mapper* | *\** |输出可以是流,也可以是子流。| | |
| *render-actions* | *on-render* | | | |
| *set* | *set* | | | |
| | attribute | | name | |
| | scope | | \* | prefix name with scope \<set name="flowScope.foo" /\> |
| | value | | value | |
| | name | | \* | use \<set ...\> \<attribute name=”name” value="..." /\> \</set\> |
| | \* | | type | |
| *start-actions* | *on-start* | | | |
| *start-state* | *\** |现在 \<flow start-state="..."\>,或者默认为流中的第一个状态| | |
| | idref | | \* | |
| *subflow-state* | *subflow-state* | | | |
| | id | | id | |
| | flow | | subflow | |
| | \* | | parent | |
| | \* | |subflow-attribute-mapper| |
| *transition* | *transition* | | | |
| | on | | on | |
| | on-exception | | on-exception | |
| | to | | to | |
| | \* | | bind | |
| | \* | | validate | |
| | \* | | history | |
| *value* | *value* | | | |
| *var* | *var* | | | |
| | name | | name | |
| | class | | class | |
| | scope | | \* | always flow scope |
| | bean | | \* | all Spring beans can be resolved with EL |
| *view-state* | *view-state* | | | |
| | id | | id | |
| | view | | view | |
| | \* | | parent | |
| | \* | | redirect | |
| | \* | | popup | |
| | \* | | model | |
| | \* | | history | |
| *\** |*persistence-context*| | | |
| *\** | *render* | | | |
| | \* | | fragments | |
| *\** | *secured* | | | |
| | \* | | attributes | |
| | \* | | match | |
# 9. 流继承
## 9.1.导言
流继承允许一个流继承另一个流的配置。继承可以在流和状态两个级别上发生。一个常见的用例是父流定义全局转换和异常处理程序,然后每个子流都可以继承这些设置。
为了找到父流,必须像其他任何流一样将其添加到`flow-registry`中。
## 9.2.流继承和 Java 继承一样吗?
流继承类似于 Java 继承,因为在父级中定义的元素是通过子级公开的,但是,有一些关键的区别。
子流不能覆盖父流中的元素。父流和子流之间的类似元素将被合并。父流中的唯一元素将被添加到子流中。
子流可以从多个父流继承。Java 继承仅限于一个类。
## 9.3.流继承的类型
### 9.3.1.流级继承
流级继承由`flow`元素上的`parent`属性定义。属性包含一个用逗号分隔的流标识符的列表。子流将按列出的顺序从每个父流继承,将元素和内容添加到结果流中。来自第一次合并的结果流将被认为是第二次合并中的子流,依此类推。
```
<flow parent="common-transitions, common-states">
```
### 9.3.2.状态级继承
状态级继承类似于流级继承,只是从父级继承了一个状态,而不是整个流。
与流继承不同,只允许有一个父代。此外,还必须定义要继承的流状态的标识符。流的标识符和该流中的状态由一个 # 分隔。
父状态和子状态必须是同一类型的。例如,一个视图状态不能从一个结束状态继承,只能从另一个视图状态继承。
```
<view-state id="child-state" parent="parent-flow#parent-view-state">
```
|[[note](images/note.png)|Note|
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---|
|流级继承的意图是将公共状态定义为<br/>添加到多个流定义中并在多个流定义之间共享,而状态级继承的意图<br/>是从单个<br/>父状态扩展并合并。流级继承非常适合组合<br/>和多重继承,但是在状态级,你仍然只能从单个父状态继承<br/>。| |
## 9.4.抽象流
通常,父流的设计不是为了直接执行。为了保护这些流不被运行,它们可以标记为`abstract`。如果一个抽象流试图运行,将抛出一个`FlowBuilderException`
```
<flow abstract="true">
```
## 9.5.继承算法
当一个子流继承自它的父流时,本质上发生的情况是父流和子流合并在一起以创建一个新的流。对于 Web 流定义语言中的每个元素都有规则,这些规则控制该特定元素如何合并。
有两种类型的元素:*可合并**不可合并*。如果元素相似,可合并元素将始终尝试合并在一起。父流或子流中的不可合并元素将始终完整地包含在结果流中。它们不会作为合并过程的一部分进行修改。
|[[note](images/note.png)|Note|
|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---|
|到父流中的外部资源的路径应该是绝对的。<br/>当两个流合并时,相对路径将中断,除非父流和子流位于同一目录中。<br/>一旦合并,父流中的所有相对路径将变得相对于子流。| |
### 9.5.1.可合并元素
如果元素的类型相同,且其键控属性相同,则父元素的内容将与子元素合并。合并算法将继续合并合并父元素和子元素的每个子元素。否则,父元素将作为一个新元素添加到子元素中。
在大多数情况下,添加的父流中的元素将在子流中的元素之后添加。此规则的例外包括将在开始时添加的动作元素(评估、渲染 和 集)。这允许子操作使用父操作的结果。
可合并的要素是:
* 动作状态:ID
* 属性:Name
* 决策状态:ID
* 结束状态:ID
* 流程:总是合并
* 如果:测试
* 端:总是合并
* 入门:总是合并
* On-Exit:总是合并
* On-Render:总是合并
* 启动:总是合并
* 输入:Name
* 输出:Name
* 安全:属性
* 子流-状态:id
* 转换:ON 和 ON-Exception
* 视图-状态:id
### 9.5.2.不可合并元素
不可合并的要素是:
* Bean-进口
* evaluate
* 异常处理程序
* 持久性-上下文
* render
* set
* VAR
\ No newline at end of file
# 7. 流管理的持久性
## 7.1.导言
大多数应用程序以某种方式访问数据。许多修改由多个用户共享的数据,因此需要事务性数据访问属性。它们通常将关系数据集转换成域对象,以支持应用程序处理。Web 流提供了“流管理的持久性”,其中流可以为你创建、提交和关闭对象持久性上下文。Web 流集成了 Hibernate 和 JPA 对象持久性技术。
除了流管理的持久性之外,还有一种模式是在应用程序的服务层中完全封装持久性 CECONTEXT 管理。在这种情况下,Web 层不涉及持久性,而是完全与传递给服务层并由服务层返回的分离对象一起工作。本章将重点讨论流管理的持久性,探讨如何以及何时使用该特性。
## 7.2.flowscoped 持续 concecontext
此模式在流启动时在`flowScope`中创建`PersistenceContext`,在流执行过程中使用该上下文进行数据访问,并在最后向持久性实体提交所做的更改。这种模式只在流执行结束时向数据库提交更改,从而提供了中间编辑的隔离。这种模式通常与乐观锁定策略结合使用,以保护由多个用户并行修改的数据的 Integrity。为了支持在一段较长的时间内保存和重新启动流的进程,必须使用流状态的持久存储。如果不需要保存和重新启动功能,则基于 HTTP会话的标准流状态存储就足够了。在这种情况下,会话在提交之前到期或终止可能会导致更改丢失。
要使用 FlowScoped PersistentenceContext 模式,首先将你的流标记为`persistence-context`:
```
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow.xsd">
<persistence-context />
</flow>
```
然后配置正确的`FlowExecutionListener`将此模式应用到你的流。如果使用 Hibernate,则注册`HibernateFlowExecutionListener`。如果使用 JPA,则注册`JpaFlowExecutionListener`
```
<webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">
<webflow:flow-execution-listeners>
<webflow:listener ref="jpaFlowExecutionListener" />
</webflow:flow-execution-listeners>
</webflow:flow-executor>
<bean id="jpaFlowExecutionListener"
class="org.springframework.webflow.persistence.JpaFlowExecutionListener">
<constructor-arg ref="entityManagerFactory" />
<constructor-arg ref="transactionManager" />
</bean>
```
要在结束时触发提交,请使用 commit 属性注释你的 End-State:
```
<end-state id="bookingConfirmed" commit="true" />
```
就是这样。当你的流开始时,侦听器将在`flowScope`中处理分配一个新的`EntityManager`。通过使用特殊的`persistenceContext`变量,在你的流中随时引用这个 EntityManager。此外,使用 Spring 托管数据访问对象进行的任何数据访问都将自动使用该 EntityManager。这样的数据访问操作应该总是在非事务或只读事务中执行,以保持中间编辑的隔离。
## 7.3.流管理的持久性和子流
管理的流`PersistenceContext`被自动扩展(传播)到子流,假设该子流还具有`<perstistence-context/>`变量。当子流重新使用其父进程启动的`PersistenceContext`时,当达到结束状态时,它会忽略提交标志,从而将最终决定(提交或不提交)推迟到其父进程。
\ No newline at end of file
# 8. 保障资金流动
## 8.1.导言
安全性对于任何应用程序都是一个重要的概念。最终用户不应该仅仅通过猜测 URL 就能够访问网站的任何部分。网站的敏感区域必须确保只处理授权的请求。 Spring 安全性是一种经过验证的安全平台,可以在多个级别上与你的应用程序集成。本节将重点讨论保护流执行的问题。
## 8.2.我如何确保一个流程?
确保流执行的安全性是一个三步过程:
* 使用身份验证和授权规则配置 Spring 安全性
* 用受保护的元素注释流定义,以定义安全规则
* 添加 SecurityFlowExecutionListener 以处理安全规则。
这些步骤中的每一个都必须完成,否则将不应用流安全规则。
## 8.3.安全元件
安全元素指定其包含的元素在完全进入之前应用授权检查。这可能不会在每个被保护的流执行阶段中发生一次。
可以保证流执行的三个阶段:流、状态和转换。在每种情况下,安全元素的语法都是相同的。固定元件位于它所固定的元件内部。例如,要保护状态,受保护元素直接发生在该状态内:
```
<view-state id="secured-view">
<secured attributes="ROLE_USER" />
...
</view-state>
```
### 8.3.1.安全属性
`attributes`属性是 Spring 安全授权属性的逗号分隔列表。通常,这些都是特定的安全角色。 Spring 安全访问决策管理器将这些属性与用户授予的属性进行比较。
```
<secured attributes="ROLE_USER" />
```
默认情况下,基于角色的访问决策管理器用于确定是否允许用户访问。如果你的应用程序不使用授权角色,则需要重写此内容。
### 8.3.2.匹配类型
有两种类型的匹配可用:`any``all`。如果至少向用户授予了一个所需的安全属性,则允许访问。All,只有当每个所需的安全属性都被授予给用户时,才允许访问。
```
<secured attributes="ROLE_USER, ROLE_ANONYMOUS" match="any" />
```
这个属性是可选的。如果未定义,默认值为`any`
只有在使用默认的访问决策管理器时,才会尊重`match`属性。
## 8.4.SecurityFlowExecutionListener
在流中定义安全规则本身并不能保护流的执行。还必须在 WebFlow 配置中定义`SecurityFlowExecutionListener`,并将其应用于流执行器。
```
<webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">
<webflow:flow-execution-listeners>
<webflow:listener ref="securityFlowExecutionListener" />
</webflow:flow-execution-listeners>
</webflow:flow-executor>
<bean id="securityFlowExecutionListener"
class="org.springframework.webflow.security.SecurityFlowExecutionListener" />
```
如果拒绝访问应用程序的一部分,将抛出`AccessDeniedException`。此异常稍后将被 Spring Security 捕获,并用于提示用户进行身份验证。重要的是,允许此异常在执行堆栈中不受约束地向上移动,否则可能不会提示最终用户进行身份验证。
### 8.4.1.自定义访问决策管理器
如果你的应用程序使用的是不基于角色的权限,则需要配置一个自定义`AccessDecisionManager`。通过在安全侦听器上设置`accessDecisionManager`属性,可以覆盖默认的决策管理器。请咨询[Spring Security reference documentation](http://static.springframework.org/spring-security/site/reference.html)以了解更多有关决策经理的信息。
```
<bean id="securityFlowExecutionListener"
class="org.springframework.webflow.security.SecurityFlowExecutionListener">
<property name="accessDecisionManager" ref="myCustomAccessDecisionManager" />
</bean>
```
## 8.5.配置 Spring 安全性
Spring 安全性具有可供选择的健壮配置。由于每个应用程序和环境都有自己的安全需求,因此[Spring Security reference documentation](http://static.springframework.org/spring-security/site/reference.html)是学习可用选项的最佳位置。
`booking-faces``booking-mvc`示例应用程序都被配置为使用 Spring 安全性。在 Spring 和 web.xml 级别都需要配置。
### 8.5.1. Spring 配置
Spring 配置定义了`http`细节(例如受保护的 URL 和登录/注销机制)和`authentication-provider`。对于样例应用程序,配置了本地身份验证提供程序。
```
<security:http auto-config="true">
<security:form-login login-page="/spring/login"
login-processing-url="/spring/loginProcess"
default-target-url="/spring/main"
authentication-failure-url="/spring/login?login_error=1" />
<security:logout logout-url="/spring/logout" logout-success-url="/spring/logout-success" />
</security:http>
<security:authentication-provider>
<security:password-encoder hash="md5" />
<security:user-service>
<security:user name="keith" password="417c7382b16c395bc25b5da1398cf076"
authorities="ROLE_USER,ROLE_SUPERVISOR" />
<security:user name="erwin" password="12430911a8af075c6f41c6976af22b09"
authorities="ROLE_USER,ROLE_SUPERVISOR" />
<security:user name="jeremy" password="57c6cbff0d421449be820763f03139eb"
authorities="ROLE_USER" />
<security:user name="scott" password="942f2339bf50796de535a384f0d1af3e"
authorities="ROLE_USER" />
</security:user-service>
</security:authentication-provider>
```
### 8.5.2.web.xml 配置
`web.xml`文件中,定义了一个`filter`来拦截所有请求。此筛选器将监听登录/注销请求,并对其进行相应的处理。它还将捕获`AccesDeniedException`s 并将用户重定向到登录页面。
```
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
```
\ No newline at end of file
# 1. 导言
## 1.1.这本指南涵盖的内容
本指南涵盖了 Spring Web 流程的所有方面。它涵盖了在最终用户应用程序中实现流以及使用功能集的工作。它还包括扩展框架和整体架构模型。
## 1.2.运行 Web 流需要什么
Java1.8 或更高。
Spring 5.0 或更高。
## 1.3.资源
你可以使用指定的标记在 StackOverflow 上提出问题并进行交互,请参见[Spring at StackOverflow](https://spring.io/questions)
使用[Spring Issue Tracker](https://jira.spring.io)报告错误并发出请求。
提交拉请求并使用源代码,请参见[GitHub 上的 Web 流](https://github.com/spring-projects/spring-webflow)
## 1.4.如何从 Maven Central 访问 Web 流工件
Web 流分布中的每个 JAR 在[Maven Central Repository](https://search.maven.org)中可用。如果你已经使用 Maven 作为你的 Web 开发项目的构建系统,这将使你能够轻松地将 Web 流集成到你的应用程序中。
要从 Maven Central 访问 Web Flow JAR,请在 POM 中声明以下依赖项:
```
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-webflow</artifactId>
<version>x.y.z.RELEASE</version>
</dependency>
```
如果使用 JavaServer Faces,请在 POM 中声明以下依赖项(包括传递依赖项“ Spring-binding”、“ Spring-webflow”):
```
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-faces</artifactId>
<version>x.y.z.RELEASE</version>
</dependency>
```
## 1.5.如何访问夜间版本和里程碑版本
可以使用 Maven 获得 Web 流开发分支的夜间快照。这些快照构建对于在下一个版本发布之前测试你所依赖的修补程序非常有用,并且为你提供了一种方便的方式来提供有关修补程序是否满足你的需求的反馈。
### 1.5.1.使用 Maven 访问快照和里程碑
对于里程碑和快照,你需要使用 SpringSource 存储库。将以下存储库添加到你的 Maven POM.xml 中:
```
<repository>
<id>spring</id>
<name>Spring Repository</name>
<url>http://repo.spring.io/snapshot</url>
</repository>
```
然后声明以下依赖项:
```
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-webflow</artifactId>
<version>x.y.z.BUILD-SNAPSHOT</version>
</dependency>
```
如果使用 JSF:
```
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-faces</artifactId>
<version>x.y.z.BUILD-SNAPSHOT</version>
</dependency>
```
\ No newline at end of file
# 前言
许多 Web 应用程序需要相同的步骤序列才能在不同的上下文中执行。通常,这些序列只是用户试图完成的一项更大任务的组成部分。这种可重用的序列称为流。
考虑一个典型的购物车应用程序。用户注册、登录和购物车签出都是可以在这种类型的应用程序的几个地方调用的流的示例。
Spring Web 流是 Spring 中用于实现流的模块。Web 流引擎插入 Spring Web MVC 平台,并提供声明性的流定义语言。本参考指南向你展示了如何使用和扩展 Spring Web 流。
# 13. JSF 集成
## 13.1.导言
Spring Web Flow 提供了一种 JSF 集成,允许你使用带有 Spring Web Flow 控制器的 JSF UI 组件模型。Web Flow 还提供了用于 JSF 环境的 Spring 安全标记库,有关更多详细信息,请参见[Section 13.9, “Using the Spring Security Facelets Tag Library”](spring-faces.html#spring-faces-security-taglib)
Spring Web Flow2.5 需要 JSF2.2 或更高版本。
## 13.2.配置 web.xml
第一步是将请求路由到`web.xml`文件中的`DispatcherServlet`。在这个示例中,我们将所有以`/spring/`开头的 URL 映射到 Servlet。 Servlet 需要进行配置。在 Servlet 中使用`init-param`来传递`contextConfigLocation`。这是 Web 应用程序 Spring 配置的位置。
```
<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>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
<url-pattern>/spring/*</url-pattern>
</servlet-mapping>
```
为了让 JSF 正确地引导,`FacesServlet`必须在`web.xml`中进行配置,就像正常情况一样,即使在使用 Spring Web 流的 JSF 时,通常根本不需要通过它路由请求。
```
<!-- Just here so the JSF implementation can initialize, *not* used at runtime -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Just here so the JSF implementation can initialize -->
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
```
在 web.xml 中,使用 Facelets 而不是 JSP 通常需要这样做:
```
!-- Use JSF view templates saved as *.xhtml, for use with Facelets -->
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
```
## 13.3.配置用于 JSF 的 Web 流
本节将解释如何使用 JSF 配置 Web 流。同时支持 Java 和 XML 风格的配置。以下是 XML 中的 Web 流和 JSF 的示例配置:
```
<?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"
xmlns:faces="http://www.springframework.org/schema/faces"
si:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd
http://www.springframework.org/schema/faces
http://www.springframework.org/schema/faces/spring-faces.xsd">
<!-- Executes flows: the central entry point into the Spring Web Flow system -->
<webflow:flow-executor id="flowExecutor">
<webflow:flow-execution-listeners>
<webflow:listener ref="facesContextListener"/>
</webflow:flow-execution-listeners>
</webflow:flow-executor>
<!-- The registry of executable flow definitions -->
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices" base-path="/WEB-INF">
<webflow:flow-location-pattern value="**/*-flow.xml" />
</webflow:flow-registry>
<!-- Configures the Spring Web Flow JSF integration -->
<faces:flow-builder-services id="flowBuilderServices" />
<!-- A listener maintain one FacesContext instance per Web Flow request. -->
<bean id="facesContextListener"
class="org.springframework.faces.webflow.FlowFacesContextLifecycleListener" />
</beans>
```
下面是 Java 配置中的相同示例:
```
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.faces.config.*;
@Configuration
public class WebFlowConfig extends AbstractFacesFlowConfiguration {
@Bean
public FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(flowRegistry())
.addFlowExecutionListener(new FlowFacesContextLifecycleListener())
.build();
}
@Bean
public FlowDefinitionRegistry flowRegistry() {
return getFlowDefinitionRegistryBuilder()
.setBasePath("/WEB-INF")
.addFlowLocationPattern("**/*-flow.xml").build();
}
```
主要的要点是安装`FlowFacesContextLifecycleListener`,它在 Web 流请求的持续时间内管理单个 FacesContext,以及使用`flow-builder-services`自定义名称空间中的`flow-builder-services`元素来配置 JSF 环境的呈现。
在 JSF 环境中,你还需要这样的 Spring MVC 相关配置:
```
<?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:faces="http://www.springframework.org/schema/faces"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/faces
http://www.springframework.org/schema/faces/spring-faces.xsd">
<faces:resources />
<bean class="org.springframework.faces.webflow.JsfFlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor" />
</bean>
</beans>
```
`resources`自定义名称空间元素将 JSF 资源请求委托给 JSF 资源 API。`JsfFlowHandlerAdapter`是通常用于 Web 流的`FlowHandlerAdapter`的替换。这个适配器用`JsfAjaxHandler`而不是`SpringJavaSciprtAjaxHandler`初始化自己。
当使用 Java Config 时,`AbstractFacesFlowConfiguration`基类会自动注册`JsfResourceRequestHandler`,因此没有进一步的工作要做。
## 13.4.替换 JSF 管理的 Bean 设施
当将 JSF 与 Spring Web 流一起使用时,你可以将 JSF Managed Bean 工具完全替换为 Web 流管理变量和 Spring Managed bean 的组合。它为你提供了对托管对象的生命周期的更多控制,并为域模型的初始化和执行提供了定义良好的挂钩。此外,由于你可能已经在业务层中使用了 Spring,因此它减少了必须维护两个不同的托管 Bean 模型的概念开销。
在进行纯粹的 JSF 开发时,你很快就会发现请求作用域不够长,不足以存储驱动复杂事件驱动视图的会话模型对象。在 JSF 中,通常的选择是开始将事情放入会话范围,在进入应用程序的另一个视图或功能区域之前,需要清理对象,这是一个额外的负担。真正需要的是介于请求和会话范围之间的托管范围。JSF 提供了可以通过 UIViewRoot.getViewMap()以编程方式访问的闪存和视图范围。 Spring Web Flow 提供对 Flash、View、Flow 和 Conversation 范围的访问。这些作用域通过 JSF 变量解析器无缝地集成在一起,并且在所有 JSF 应用程序中都是相同的。
### 13.4.1.使用流量变量
声明和管理模型的最简单和最自然的方法是使用[流量变量](defining-flows.html#flow-variables)。你可以在流程的开始声明这些变量:
```
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>
```
然后通过 EL 在一个流的 JSF 视图模板中引用这个变量:
```
<h:inputText id="searchString" value="#{searchCriteria.searchString}"/>
```
请注意,在从模板引用变量时,不需要在变量的作用域中加上前缀(如果需要更具体地说明,可以这样做)。与标准的 JSF bean 一样,将搜索所有可用的作用域以寻找匹配的变量,因此你可以在流定义中更改变量的作用域,而无需修改引用该变量的 EL 表达式。
你还可以定义视图实例变量,这些变量的作用域为当前视图,并在转换到另一个视图时自动进行清理。这在 JSF 中非常有用,因为视图通常被构造为在转换到另一个视图之前处理跨多个请求的多个页面内事件。
要定义视图实例变量,可以在`view-state`定义中使用`var`元素:
```
<view-state id="enterSearchCriteria">
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>
</view-state>
```
### 13.4.2.使用范围 Spring bean
虽然定义 AutoWired 流实例变量提供了很好的模块化和可读性,但可能会出现希望利用 Spring 容器的其他功能的情况,例如 AOP。在这些情况下,你可以在 Spring 应用程序上下文中定义 Bean,并为其提供一个特定的 Web 流范围:
```
<bean id="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria" scope="flow"/>
```
这种方法的主要区别在于,在通过 EL 表达式首次访问 Bean 之前,不会对 Bean 进行完全初始化。这种通过 EL 的惰性实例化非常类似于 JSF 托管 bean 的典型分配方式。
### 13.4.3.操纵模型
在视图呈现之前需要初始化模型(例如通过从数据库加载持久实体)是很常见的,但是 JSF 本身并没有为这种初始化提供任何方便的挂钩。流定义语言通过其[Actions](defining-flows.html#flow-actions)为此提供了一种自然的工具。 Spring Web 流为将一个动作的结果转换为特定于 JSF 的数据结构提供了一些额外的便利。例如:
```
<on-render>
<evaluate expression="bookingService.findBookings(currentUser.name)"
result="viewScope.bookings" result-type="dataModel" />
</on-render>
```
这将获取`bookingService.findBookings`方法的结果,并将其包装到自定义的 JSF 数据模型中,以便该列表可以在标准的 JSF DataTable 组件中使用:
```
<h:dataTable id="bookings" styleClass="summary" value="#{bookings}" var="booking"
rendered="#{bookings.rowCount > 0}">
<h:column>
<f:facet name="header">Name</f:facet>
#{booking.hotel.name}
</h:column>
<h:column>
<f:facet name="header">Confirmation number</f:facet>
#{booking.id}
</h:column>
<h:column>
<f:facet name="header">Action</f:facet>
<h:commandLink id="cancel" value="Cancel" action="cancelBooking" />
</h:column>
</h:dataTable>
```
### 13.4.4.数据模型实现
在上面的示例中,result-type=“datamodel”将使用自定义的`DataModel`类型包装 list\<Booking\>。自定义`DataModel`提供了额外的便利,例如可以在请求范围之外的存储中进行序列化,以及访问 EL 表达式中当前选定的行。例如,在从一个视图回发操作事件时,你可以在选定行的模型实例上执行操作,该视图中的操作事件是由 DataTable 中的组件触发的:
```
<transition on="cancelBooking">
<evaluate expression="bookingService.cancelBooking(bookings.selectedRow)" />
</transition>
```
Spring Web 流提供了两种自定义的数据模型类型:`OneSelectionTrackingListDataModel``ManySelectionTrackingListDataModel`。由于名称表示它们会跟踪一个或多个选定的行。这是在`SelectionTrackingActionListener`侦听器的帮助下完成的,该侦听器响应 JSF 操作事件,并调用`SelectinAware`数据模型上的 appoRiate 方法来记录当前单击的行。
要理解这是如何配置的,请记住`FacesConversionService`在启动时针对别名“datamodel”注册了`DataModelConverter`。在流定义中使用 result-type=“datamodel”时,将使用`DataModelConverter`。转换器然后用`OneSelectionTrackingListDataModel`的实例包装给定的列表。要使用`ManySelectionTrackingListDataModel`,你需要注册自己的自定义转换器。
## 13.5.使用 Spring Web 流处理 JSF 事件
Spring Web 流允许你以解耦的方式处理 JSF 动作事件,不需要你的 Java 代码中的 JSF API 的直接依赖。实际上,这些事件通常可以完全用 Flow Definiton 语言来处理,而不需要任何定制的 Java 操作代码。这允许更敏捷的开发过程,因为在连接事件(JSF 视图模板和 SWF 流定义)中操作的工件可以立即刷新,而不需要构建和重新部署整个应用程序。
### 13.5.1.处理 JSF 页面内操作事件
在 JSF 中,一个简单但常见的情况是,需要向一个导致以某种方式操纵模型的事件发出信号,然后重新显示相同的视图,以反映模型已更改的状态。流定义语言在`transition`元素中对此有特殊的支持。
这方面的一个很好的例子是一个分页列表结果表。假设你希望能够只加载和显示一个大型结果列表的一部分,并允许用户对结果进行分页。加载和显示列表的初始`view-state`定义为:
```
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
</view-state>
```
你可以构造一个 JSF DataTable,该 DataTable 显示当前的`hotels`列表,然后在表下面放置一个“more results”链接:
```
<h:commandLink id="nextPageLink" value="More Results" action="next"/>
```
此 CommandLink 从其 Action 属性发出“Next”事件的信号。然后,你可以通过添加`view-state`定义来处理该事件:
```
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
</transition>
</view-state>
```
在这里,你通过增加 SearchCriteria 实例上的页面数量来处理“Next”事件。然后使用更新的条件再次调用`on-render`操作,这将导致将结果的下一页加载到数据模型中。由于在`transition`元素上没有`to`属性,因此将重新呈现相同的视图,并且模型中的更改将反映在视图中。
### 13.5.2.处理 JSF 动作事件
除了页面内事件之外,下一个逻辑级别是需要导航到另一个视图的事件,并在此过程中对模型进行一些操作。使用纯 JSF 实现这一点需要向 faces-config.xml 添加导航规则,还可能需要在 JSF 托管的 Bean 中添加一些中间 Java 代码(这两个任务都需要重新部署)。使用流定义语言,你可以在一个地方以与页面内事件处理方式非常相似的方式简洁地处理这样的情况。
继续处理结果的分页列表的用例,假设我们希望显示的 DataTable 中的每一行都包含指向该行实例的详细页的链接。可以向包含以下`commandLink`组件的表中添加一列:
```
<h:commandLink id="viewHotelLink" value="View Hotel" action="select"/>
```
这会引发“select”事件,你可以通过向现有的`view-state`元素添加另一个`transition`元素来处理该事件:
```
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
</transition>
<transition on="select" to="reviewHotel">
<set name="flowScope.hotel" value="hotels.selectedRow" />
</transition>
</view-state>
```
这里,通过将当前选定的酒店实例从 DataTable 推入流作用域来处理“select”事件,以便它可以被“reviewhotel”`view-state`引用。
### 13.5.3.执行模型验证
在对模型应用更改之前,JSF 为在字段级验证输入提供了有用的工具,但是当你需要在应用了更新之后在模型级执行更复杂的验证时,通常,你不得不在托管的 Bean 中向你的 JSF 操作方法添加更多的自定义代码。这种类型的验证通常是域模型本身的责任,但是很难在不引入对域层中的 JSF API 的不希望的依赖的情况下,将任何错误消息传播回视图。
有了 WebFlow,你就可以在业务代码中使用通用的、低层次的`MessageContext`,在呈现时,添加在那里的任何消息都将可用于`FacesContext`
例如,假设你有一个视图,用户在其中输入必要的详细信息以完成酒店预订,并且你需要确保入住和退房日期符合给定的一组业务规则。你可以从`transition`元素调用这样的模型级验证:
```
<view-state id="enterBookingDetails">
<transition on="proceed" to="reviewBooking">
<evaluate expression="booking.validateEnterBookingDetails(messageContext)" />
</transition>
</view-state>
```
在这里,“Proceed”事件是通过调用 Booking 实例上的模型级验证方法来处理的,传递一般的`MessageContext`实例,以便可以记录消息。然后,这些消息可以与带有`h:messages`组件的任何其他 JSF 消息一起显示,
### 13.5.4.在 JSF 中处理 Ajax 事件
JSF 提供了用于发送 Ajax 请求以及在服务器端执行部分处理和呈现的内置支持。你可以的
通过 \<f:ajax\>指定用于部分呈现的 ID 列表
Facelets 标签。
在 Spring Web Flow 中,你还可以通过 Render 操作指定用于在服务器端进行部分呈现的 ID:
```
<view-state id="reviewHotels">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" result-type="dataModel" />
</on-render>
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
<render fragments="hotels:searchResultsFragment" />
</transition>
</view-state>
```
## 13.6.在页面上嵌入流
默认情况下,当流进入视图状态时,它会在呈现视图之前执行客户端重定向。这种方法被称为 post-redirect-get。它的优点是将一个视图的表单处理与下一个视图的呈现分离开来。因此,浏览器的后退和刷新按钮可以无缝地工作,而不会引起任何浏览器警告。
通常情况下,从用户的角度来看,客户端重定向是透明的。然而,在某些情况下,后重定向可能不会带来同样的好处。例如,有时在页面上嵌入一个流并通过 Ajax 请求只刷新呈现该流的页面区域来驱动它可能是有用的。在这种情况下,不仅没有必要使用客户端重定向,而且在保持页面周围内容不变方面也不是所需的行为。
要指示一个流应该在“页面嵌入”模式下执行,你所需要做的就是传递一个名为“模式”的额外的流输入属性,其值为“嵌入”。下面是一个顶级容器流以嵌入式模式调用子流的示例:
```
<subflow-state id="bookHotel" subflow="booking">
<input name="mode" value="'embedded'"/>
</subflow-state>
```
当以“页面嵌入”模式启动时,子流将不会在 Ajax 请求期间发出流执行重定向。
如果你想查看嵌入式流的示例,请参考 WebFlow-PrimeFaces-Showcase 项目。你可以在本地签出源代码,将其构建为 Maven 项目,并将其导入到 Eclipse 中:
```
cd some-directory
svn co https://src.springframework.org/svn/spring-samples/webflow-primefaces-showcase
cd webflow-primefaces-showcase
mvn package
# import into Eclipse
```
你需要查看的特定示例位于“Advanced Ajax”选项卡下,它被称为“带有嵌入式子流的 Top Flow”。
## 13.7.在相同的状态下重定向
默认情况下,只要当前请求不是 Ajax 请求,Web 流就会进行客户端重定向,即使它仍然处于相同的视图状态。例如,在表单验证失败之后,这是非常有用的。如果用户点击 Refresh 或 Back,他们将不会看到任何浏览器警告。如果网络流不做重定向,他们就会这么做。
这可能会导致 JSF 环境特有的问题,在 JSF 环境中,特定的 Sun Mojarra 侦听器组件缓存 FacesContext,假设在整个 JSF 生命周期中都有相同的实例。然而,在 Web 流中,呈现阶段被暂时搁置,客户端重定向执行。
Web 流的默认行为是可取的,而且 JSF 应用程序不太可能遇到这个问题。这是因为在 JSF 组件库中,Ajax 通常是默认启用的,并且在 Ajax 请求期间,Web 流不会重定向。但是,如果遇到此问题,你可以在相同的视图中禁用客户端重定向,如下所示:
```
<webflow:flow-executor id="flowExecutor">
<webflow:flow-execution-attributes>
<webflow:redirect-in-same-state value="false"/>
</webflow:flow-execution-attributes>
</webflow:flow-executor>
```
## 13.8.使用 JSF 处理文件上传
大多数 JSF 组件提供程序都包含某种形式的“文件上传”组件。通常,在使用这些组件时,JSF 必须完全控制对多部分请求的解析,并且 Spring MVC 的`MultipartResolver`不能使用。
Spring Web 流已经用来自 PrimeFaces 的文件上传组件进行了测试。检查你的 JSF 组件库的文档,以便其他提供者了解如何配置文件上传。
### 13.8.1.用 primeFaces 上传文件
PrimeFaces 提供了用于上载文件的`<p:fileUpload>`组件。为了使用该组件,你需要配置`org.primefaces.webapp.filter.FileUploadFilter` Servlet 过滤器。过滤器需要根据 Spring MVC 的`DispatcherServlet`在你的`web.xml`中进行配置:
```
<filter>
<filter-name>PrimeFaces FileUpload Filter</filter-name>
<filter-class>org.primefaces.webapp.filter.FileUploadFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>PrimeFaces FileUpload Filter</filter-name>
<servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
</filter-mapping>
<context-param>
<param-name>primefaces.UPLOADER</param-name>
<param-value>commons</param-value>
</context-param>
```
有关更多详细信息,请参见[PrimeFaces 文档](http://primefaces.org/documentation.html)
## 13.9.使用 Spring Security Facelets 标记库
要使用库,你需要创建一个`.taglib.xml`文件,并将其注册在`web.xml`中。
创建具有以下内容的`/WEB-INF/springsecurity.taglib.xml`文件:
```
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
"http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
<facelet-taglib>
<namespace>http://www.springframework.org/security/tags</namespace>
<tag>
<tag-name>authorize</tag-name>
<handler-class>org.springframework.faces.security.FaceletsAuthorizeTagHandler</handler-class>
</tag>
<function>
<function-name>areAllGranted</function-name>
<function-class>org.springframework.faces.security.FaceletsAuthorizeTagUtils</function-class>
<function-signature>boolean areAllGranted(java.lang.String)</function-signature>
</function>
<function>
<function-name>areAnyGranted</function-name>
<function-class>org.springframework.faces.security.FaceletsAuthorizeTagUtils</function-class>
<function-signature>boolean areAnyGranted(java.lang.String)</function-signature>
</function>
<function>
<function-name>areNotGranted</function-name>
<function-class>org.springframework.faces.security.FaceletsAuthorizeTagUtils</function-class>
<function-signature>boolean areNotGranted(java.lang.String)</function-signature>
</function>
<function>
<function-name>isAllowed</function-name>
<function-class>org.springframework.faces.security.FaceletsAuthorizeTagUtils</function-class>
<function-signature>boolean isAllowed(java.lang.String, java.lang.String)</function-signature>
</function>
</facelet-taglib>
```
接下来,在 web.xml 中注册上述文件 taglib:
```
<context-param>
<param-name>javax.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/springsecurity.taglib.xml</param-value>
</context-param>
```
现在你已经准备好在视图中使用标记库了。你可以使用授权标记有条件地包括嵌套内容:
```
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:sec="http://www.springframework.org/security/tags">
<sec:authorize ifAllGranted="ROLE_FOO, ROLE_BAR">
Lorem ipsum dolor sit amet
</sec:authorize>
<sec:authorize ifNotGranted="ROLE_FOO, ROLE_BAR">
Lorem ipsum dolor sit amet
</sec:authorize>
<sec:authorize ifAnyGranted="ROLE_FOO, ROLE_BAR">
Lorem ipsum dolor sit amet
</sec:authorize>
</ui:composition>
```
你还可以在任何 JSF 组件的呈现属性或其他属性中使用几个 EL 函数中的一个:
```
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:sec="http://www.springframework.org/security/tags">
<!-- Rendered only if user has all of the listed roles -->
<h:outputText value="Lorem ipsum dolor sit amet" rendered="#{sec:areAllGranted('ROLE_FOO, ROLE_BAR')}"/>
<!-- Rendered only if user does not have any of the listed roles -->
<h:outputText value="Lorem ipsum dolor sit amet" rendered="#{sec:areNotGranted('ROLE_FOO, ROLE_BAR')}"/>
<!-- Rendered only if user has any of the listed roles -->
<h:outputText value="Lorem ipsum dolor sit amet" rendered="#{sec:areAnyGranted('ROLE_FOO, ROLE_BAR')}"/>
<!-- Rendered only if user has access to given HTTP method/URL as defined in Spring Security configuration -->
<h:outputText value="Lorem ipsum dolor sit amet" rendered="#{sec:isAllowed('/secured/foo', 'POST')}"/>
</ui:composition>
```
## 13.10.第三方组件库集成
Spring Web 流 JSF 集成力求与任何第三方 JSF 组件库兼容。通过在 SWF 驱动的 JSF 生命周期内尊重 JSF 规范的所有标准语义,第三方库通常应该“正常工作”。需要记住的主要一点是,由于 Web 流请求不是通过标准 FacesServlet 路由的,所以 web.xml 中的配置会略有变化。通常,传统上映射到 FacesServlet 的任何内容都应该映射到 Spring DispatcherServlet。(如果你正在逐页迁移遗留的 JSF 应用程序,也可以映射到这两个应用程序)。
\ No newline at end of file
# 12. Spring JavaScript 快速引用
## 12.1.导言
*spring-js-resources*模块是一个旧模块,不再推荐使用,但仍作为可选模块提供,以实现向后兼容。其最初的目标是提供一个客户端编程模型,用于通过行为和 Ajax 远程处理逐步增强 Web 页面。
Spring JS API 的使用在[样品库](https://github.com/spring-projects/spring-webflow-samples)中进行了演示。
## 12.2.服务 JavaScript 资源
Spring 框架提供了一种用于服务静态资源的机制。参见[Spring Framework documentation](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-config-static-resources))。使用新的 \<mvc:resources\>元素,资源请求由`DispatcherSevlet`处理。下面是 XML 中的配置示例(也可以使用 Java 配置):
```
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<mvc:annotation-driven/>
<mvc:resources mapping="/resources/**" location="/, classpath:/META-INF/web-resources/" />
...
</beans>
```
此传入将对`/resources`的请求映射到在 Classpath 上`/META-INF/web-resources`下找到的资源。这就是捆绑 Spring JavaScript 资源的地方。然而,你可以在上述配置中修改位置属性,以便为来自任何 Classpath 或 Web 应用程序相对位置的资源提供服务。
请注意,完整的资源 URL 取决于如何映射 DispatcherServlet。在 MVC-Booking 示例中,我们选择将其映射为默认的 Servlet 映射“/”:
```
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
```
这意味着要加载`Spring.js`的完整 URL 是`/myapp/resources/spring/Spring.js`。如果你的`DispatcherServlet`被映射到`/main/*`,那么完整的 URL 将是`/myapp/main/resources/spring/Spring.js`
当使用默认的 Servlet 映射时,还建议将此添加到你的 Spring MVC 配置中,这确保你的 Spring MVC 映射未处理的任何资源请求都将被委派回 Servlet 容器。
```
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
...
<mvc:default-servlet-handler />
</beans>
```
## 12.3.在页面中包括 Spring JavaScript
Spring JS 的设计使得可以为任何流行的 JavaScript 工具包构建其 API 的实现。 Spring.js 的初始实现建立在 Dojo 工具箱的基础上。
在页面中使用 Spring JavaScript 需要正常地包括底层工具包、底层工具包的`Spring.js`基本接口文件和`Spring-(library implementation).js`文件。作为示例,下面包括使用`ResourceServlet`获得 Spring.js 的 Dojo 实现:
```
<script type="text/javascript" src="<c:url value="/resources/dojo/dojo.js" />"> </script>
<script type="text/javascript" src="<c:url value="/resources/spring/Spring.js" />"> </script>
<script type="text/javascript" src="<c:url value="/resources/spring/Spring-Dojo.js" />"> </script>
```
在使用底层库的小部件系统时,通常还必须包括一些 CSS 资源,以获得所需的外观和感觉。对于 Booking-MVC 引用应用程序,Dojo 的`tundra.css`包含在其中:
```
<link type="text/css" rel="stylesheet" href="<c:url value="/resources/dijit/themes/tundra/tundra.css" />" />
```
## 12.4. Spring JavaScript 装饰
Spring JavaScript 中的一个核心概念是将装饰应用到现有 DOM 节点的概念。这种技术用于逐步增强网页,使网页在功能较差的浏览器中仍能正常工作。使用`addDecoration`方法应用装饰。
下面的示例演示如何使用丰富的建议行为来增强 Spring MVC`<form:input>`标记:
```
<form:input id="searchString" path="searchString"/>
<script type="text/javascript">
Spring.addDecoration(new Spring.ElementDecoration({
elementId: "searchString",
widgetType: "dijit.form.ValidationTextBox",
widgetAttrs: { promptMessage : "Search hotels by name, address, city, or zip." }}));
</script>
```
`ElementDecoration`用于将丰富的小部件行为应用到现有的 DOM 节点。此装饰类型的目的不是完全隐藏底层工具包,因此直接使用该工具包的本机小部件类型和属性。这种方法允许你使用一个通用的装饰模型,以一致的方式集成底层工具包中的任何小部件。请参阅`booking-mvc`参考应用程序,以获取从建议到客户端验证的应用装饰的更多示例。
当使用`ElementDecoration`应用具有丰富验证行为的小部件时,一个常见的需求是在验证通过之前防止表单提交到服务器。这可以用`ValidateAllDecoration`来完成:
```
<input type="submit" id="proceed" name="_eventId_proceed" value="Proceed" />
<script type="text/javascript">
Spring.addDecoration(new Spring.ValidateAllDecoration({ elementId:'proceed', event:'onclick' }));
</script>
```
这将使用一个特殊的 onclick 事件处理程序来装饰“Proceed”按钮,该处理程序将触发客户端验证器,并且在表单通过之前不允许表单提交。
`AjaxEventDecoration`应用客户端事件侦听器,该侦听器向服务器触发远程 Ajax 请求。它还自动注册了一个回调函数,以便在响应中链接:
```
<a id="prevLink" href="search?searchString=${criteria.searchString}&page=${criteria.page - 1}">Previous</a>
<script type="text/javascript">
Spring.addDecoration(new Spring.AjaxEventDecoration({
elementId: "prevLink",
event: "onclick",
params: { fragments: "body" }
}));
</script>
```
这将通过 Ajax 调用来装饰“上一个结果”链接的 onclick 事件,传递一个特殊的参数,该参数指定要在响应中重新呈现的片段。请注意,如果 JavaScript 在客户机中不可用,那么这个链接仍然是完全有效的。(有关如何在服务器上处理此请求的详细信息,请参见[第 12.5 节,“处理 Ajax 请求”](spring-js.html#spring-js-ajax)
也可以对一个元素应用多个装饰。下面的示例显示了一个正在用 Ajax 和 Validate-All Submit Suppression 进行修饰的按钮:
```
<input type="submit" id="proceed" name="_eventId_proceed" value="Proceed" />
<script type="text/javascript">
Spring.addDecoration(new Spring.ValidateAllDecoration({elementId:'proceed', event:'onclick'}));
Spring.addDecoration(new Spring.AjaxEventDecoration({elementId:'proceed', event:'onclick',formId:'booking', params:{fragments:'messages'}}));
</script>
```
还可以使用 Dojo 的 QueryAPI 对单个语句中的多个元素应用装饰。下面的示例将一组复选框元素装饰为 Dojo 复选框小部件:
```
<div id="amenities">
<form:checkbox path="amenities" value="OCEAN_VIEW" label="Ocean View" /></li>
<form:checkbox path="amenities" value="LATE_CHECKOUT" label="Late Checkout" /></li>
<form:checkbox path="amenities" value="MINIBAR" label="Minibar" /></li>
<script type="text/javascript">
dojo.query("#amenities input[type='checkbox']").forEach(function(element) {
Spring.addDecoration(new Spring.ElementDecoration({
elementId: element.id,
widgetType : "dijit.form.CheckBox",
widgetAttrs : { checked : element.checked }
}));
});
</script>
</div>
```
## 12.5.处理 Ajax 请求
Spring JavaScript 的客户端 Ajax 响应处理建立在从服务器接收“碎片”的概念上。这些片段只是标准的 HTML,旨在替换现有页面的部分内容。服务器上所需的关键部分是一种确定需要将完整响应中的哪些部分拉出以进行部分呈现的方法。
为了能够呈现完整响应的部分片段,必须使用模板化技术构建完整响应,该模板化技术允许使用组合来构造响应,并且组合的成员部分可以被引用并单独呈现。 Spring JavaScript 提供了一些简单的 Spring MVC 扩展,它们利用磁贴来实现这一点。理论上,同样的技术可以用于任何支持合成的模板化系统。
Spring JavaScript 的 Ajax 远程处理功能是建立在这样一种理念之上的:Ajax 请求的核心处理代码不应与标准的浏览器请求不同,因此,在代码中不需要直接提供有关 Ajax 请求的特殊知识,并且可以对两种类型的请求使用相同的方法。
### 12.5.1.提供特定于库的 AjaxHandler
将各种 Ajax 库与 Web 流的 Ajax 感知行为集成在一起的关键接口是`org.springframework.js.AjaxHandler`。默认情况下配置了`SpringJavascriptAjaxHandler`,该配置能够检测通过 Spring JS 客户端 API 提交的 Ajax 请求,并且能够在需要重定向的情况下做出适当响应。为了集成不同的 Ajax 库(无论是纯 JavaScript 库,还是更高级别的抽象,例如支持 Ajax 的 JSF 组件库),可以将自定义的`AjaxHandler`注入到`FlowHandlerAdapter``FlowController`中。
### 12.5.2.用 Spring 个 MVC 控制器处理 Ajax 请求
为了用 Spring MVC 控制器处理 Ajax 请求,所需要的只是在你的 Spring 应用程序上下文中配置所提供的 Spring MVC 扩展,以呈现部分响应(请注意,这些扩展需要使用用于模板化的磁贴):
```
<bean id="tilesViewResolver" class="org.springframework.webflow.mvc.view.AjaxUrlBasedViewResolver">
<property name="viewClass" value="org.springframework.webflow.mvc.view.FlowAjaxTiles3View"/>
</bean>
```
这将配置`AjaxUrlBasedViewResolver`,后者反过来解释 Ajax 请求并创建`FlowAjaxTilesView`对象,以处理适当片段的呈现。注意,`FlowAjaxTilesView`能够同时处理 Web 流和纯 Spring MVC 请求的呈现。这些片段对应于瓷砖视图定义的各个属性。例如,以下面的 Tiles 视图定义为例:
```
<definition name="hotels/index" extends="standardLayout">
<put-attribute name="body" value="index.body" />
</definition>
<definition name="index.body" template="/WEB-INF/hotels/index.jsp">
<put-attribute name="hotelSearchForm" value="/WEB-INF/hotels/hotelSearchForm.jsp" />
<put-attribute name="bookingsTable" value="/WEB-INF/hotels/bookingsTable.jsp" />
</definition>
```
Ajax 请求可以指定要作为请求中的片段呈现的“Body”、“HotelSearchForm”或“BookingStable”。
### 12.5.3.用 Spring MVC+ Spring Web 流处理 Ajax 请求
Spring Web 流通过使用`render`元素直接在流定义语言中处理片段的可选呈现。这种方法的好处是,片段的选择与客户端代码完全解耦,这样就不需要以纯 Spring MVC 控制器方法当前必须采用的方式与请求一起传递特殊参数。例如,如果你希望将前面的示例磁贴视图中的“HotelSearchForm”片段呈现为一个富 JavaScript 弹出窗口:
```
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
<on-entry>
<render fragments="hotelSearchForm" />
</on-entry>
<transition on="search" to="reviewHotels">
<evaluate expression="searchCriteria.resetPage()"/>
</transition>
</view-state>
```
\ No newline at end of file
# 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`将导致使用 ID`hotels/booking`的流的注册表查询。如果找到带有该 ID 的流,则该流将处理该请求。如果没有找到流,那么将查询 Dispatcher 的有序链中的下一个处理程序映射,或者返回一个“nohandlerfound”响应。
### 11.3.3.流程处理工作流程
当找到一个有效的流映射时,`FlowHandlerAdapter`会根据 HTTP 请求提供的信息,计算出是启动该流的新执行,还是恢复现有的执行。有许多与适配器使用的流执行的启动和恢复相关的默认值:
* HTTP 请求参数在所有初始流执行的输入映射中都是可用的。
* 当流执行结束而未发送最终响应时,默认处理程序将尝试在同一请求中启动新的执行。
* 未处理的异常将传播到 Dispatcher,除非该异常是 NosuchFlowExecutionException 异常。默认处理程序将尝试通过重新执行 NosuchFlowExecutionException 来恢复 NosuchFlowExecutionException。
有关更多信息,请参阅`FlowHandlerAdapter`的 API 文档。你可以通过子类化或实现你自己的 Flowhandler(将在下一节中讨论)来覆盖这些默认值。
## 11.4.实现自定义 Flowhandlers
`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 基础结构中。
### 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:`-相对于当前资源重定向 Servlet
* `contextRelative:`-相对于当前 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 请求”](spring-js.html#spring-js-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>
```
\ No newline at end of file
# 10. 系统设置
## 10.1.导言
本章将向你展示如何设置在任何 Web 环境中使用的 Web 流系统。
## 10.2.Java 配置和 XML 命名空间
Web 流为基于 Java 和 XML 的配置提供了专门的配置支持。
要开始使用基于 XML 的配置,请声明 WebFlow Config XML 名称空间:
```
<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.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd">
<!-- Setup Web Flow here -->
</beans>
```
要在`@Configuration`类中开始使用 Java 配置扩展`AbstractFlowConfiguration`:
```
import org.springframework.context.annotation.Configuration;
import org.springframework.webflow.config.AbstractFlowConfiguration;
@Configuration
public class WebFlowConfig extends AbstractFlowConfiguration {
}
```
## 10.3.基本系统配置
下一节将展示在应用程序中设置 Web 流系统所需的最小配置。
### 10.3.1.FlowRegistry
`FlowRegistry`的 XML 中注册你的流:
```
<webflow:flow-registry id="flowRegistry">
<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
</webflow:flow-registry>
```
在 Java 中的`FlowRegistry`中注册你的流:
```
@Bean
public FlowDefinitionRegistry flowRegistry() {
return getFlowDefinitionRegistryBuilder()
.addFlowLocation("/WEB-INF/flows/booking/booking.xml")
.build();
}
```
### 10.3.2.流动执行器
部署 FlowExecutor,这是用于执行 XML 流的中心服务:
```
<webflow:flow-executor id="flowExecutor" />
```
部署 FlowExecutor,这是用于在 Java 中执行流的中心服务:
```
@Bean
public FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(flowRegistry()).build();
}
```
请参阅本指南的 Spring MVC 和 Spring Faces 部分,分别介绍如何将 Web 流系统与 MVC 和 JSF 环境集成。
## 10.4.flow-registry 选项
本节将探讨流注册中心配置选项。
### 10.4.1.指定流位置
使用`location`元素指定要注册的流定义的路径。默认情况下,流被分配的注册表标识符等于其文件名减去文件扩展名,除非定义了注册表池路径。
在 XML 中:
```
<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
```
在 Java 中:
```
return getFlowDefinitionRegistryBuilder()
.addFlowLocation("/WEB-INF/flows/booking/booking.xml")
.build();
```
### 10.4.2.分配自定义流标识符
指定一个 ID,以便将自定义注册表标识符分配给 XML 中的流:
```
<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" id="bookHotel" />
```
指定一个 ID,以便将自定义注册表标识符分配给 Java 中的流:
```
return getFlowDefinitionRegistryBuilder()
.addFlowLocation("/WEB-INF/flows/booking/booking.xml", "bookHotel")
.build();
```
### 10.4.3.分配流元属性
使用`flow-definition-attributes`元素将自定义的元属性分配给已注册的流。
在 XML 中:
```
<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml">
<webflow:flow-definition-attributes>
<webflow:attribute name="caption" value="Books a hotel" />
</webflow:flow-definition-attributes>
</webflow:flow-location>
```
在 Java 中:
```
Map<String, Object> attrs = ... ;
return getFlowDefinitionRegistryBuilder()
.addFlowLocation("/WEB-INF/flows/booking/booking.xml", null, attrs)
.build();
```
### 10.4.4.使用位置模式注册流
使用`flow-location-patterns`元素来注册与特定资源位置模式匹配的流:
在 XML 中:
```
<webflow:flow-location-pattern value="/WEB-INF/flows/**/*-flow.xml" />
```
在 Java 中:
```
return getFlowDefinitionRegistryBuilder()
.addFlowLocationPattern("/WEB-INF/flows/**/*-flow.xml")
.build();
```
### 10.4.5.流量定位基准路径
使用`base-path`属性为应用程序中的所有流定义一个基本位置。然后,所有的流动位置都相对于基本路径。基本路径可以是资源路径,例如“/WEB-INF”,或者是 Classpath 上的位置,例如“ Classpath:org/springframework/webflow/samples”。
在 XML 中:
```
<webflow:flow-registry id="flowRegistry" base-path="/WEB-INF">
<webflow:flow-location path="/hotels/booking/booking.xml" />
</webflow:flow-registry>
```
在 Java 中:
```
return getFlowDefinitionRegistryBuilder()
.setBasePath("/WEB-INF")
.addFlowLocationPattern("/hotels/booking/booking.xml")
.build();
```
定义了基本路径后,分配流标识符的算法会略有变化。流现在将被分配的注册表标识符等于它们的基本路径和文件名之间的路径段。例如,如果流定义位于“/WEB-INF/hotels/booking/booking-flow.xml”,而基本路径是“/WEB-INF”,则该流的剩余路径是“Hotels/booking”,它将成为流 ID。
|[[提示](images/tip.png)|Directory per flow definition|
|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------|
|回想一下,将每个流定义打包在一个唯一的目录中是一种最佳实践。<br/>这提高了模块化,允许使用流定义打包依赖的资源。<br/>它还防止了两个流在使用约定时具有相同的标识符。| |
如果没有指定基路径,或者流定义直接位于基路径上,则使用文件名(减去扩展名)中的流 ID 分配。例如,如果流定义文件是’booking.xml’,那么流标识符只是’booking’。
与注册表基路径相结合时,位置模式的功能特别强大。而不是流标识符成为“\*-流”,它们将基于目录路径。例如,在 XML 中:
```
<webflow:flow-registry id="flowRegistry" base-path="/WEB-INF">
<webflow:flow-location-pattern value="/**/*-flow.xml" />
</webflow:flow-registry>
```
在 Java 中:
```
return getFlowDefinitionRegistryBuilder()
.setBasePath("/WEB-INF")
.addFlowLocationPattern("/**/*-flow.xml")
.build();
```
在上面的示例中,假设在`/user/login``/user/registration``/hotels/booking``/flights/booking`目录中有位于`WEB-INF`中的流 ID,则最终得到的流 ID 分别为`user/login``hotels/booking``flights/booking`
### 10.4.6.配置 FlowRegistry 层次结构
使用`parent`属性在层次结构中将两个流注册中心链接在一起。当查询子注册中心时,如果它找不到请求的流,它将委托给其父注册中心。
在 XML 中:
```
<!-- my-system-config.xml -->
<webflow:flow-registry id="flowRegistry" parent="sharedFlowRegistry">
<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
</webflow:flow-registry>
<!-- shared-config.xml -->
<webflow:flow-registry id="sharedFlowRegistry">
<!-- Global flows shared by several applications -->
</webflow:flow-registry>
```
在 Java 中:
```
@Configuration
public class WebFlowConfig extends AbstractFlowConfiguration {
@Autowired
private SharedConfig sharedConfig;
@Bean
public FlowDefinitionRegistry flowRegistry() {
return getFlowDefinitionRegistryBuilder()
.setParent(this.sharedConfig.sharedFlowRegistry())
.addFlowLocation("/WEB-INF/flows/booking/booking.xml")
.build();
}
}
@Configuration
public class SharedConfig extends AbstractFlowConfiguration {
@Bean
public FlowDefinitionRegistry sharedFlowRegistry() {
return getFlowDefinitionRegistryBuilder()
.addFlowLocation("/WEB-INF/flows/shared.xml")
.build();
}
}
```
### 10.4.7.配置自定义 FlowBuilder 服务
使用`flow-builder-services`属性定制用于在流注册中心中构建流的服务和设置。如果没有指定 flow-builder-services 标记,则使用默认的服务实现。定义标记时,只需引用要自定义的服务。
在 XML 中:
```
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
<webflow:flow-location path="/WEB-INF/flows/booking/booking.xml" />
</webflow:flow-registry>
<webflow:flow-builder-services id="flowBuilderServices" />
```
在 Java 中:
```
@Bean
public FlowDefinitionRegistry flowRegistry() {
return getFlowDefinitionRegistryBuilder(flowBuilderServices())
.addFlowLocation("/WEB-INF/flows/booking/booking.xml")
.build();
}
@Bean
public FlowBuilderServices flowBuilderServices() {
return getFlowBuilderServicesBuilder().build();
}
```
可配置的服务是`conversion-service``expression-parser``view-factory-creator`。这些服务是通过引用你定义的自定义 bean 来配置的。
例如,在 XML 中:
```
<webflow:flow-builder-services id="flowBuilderServices"
conversion-service="conversionService"
expression-parser="expressionParser"
view-factory-creator="viewFactoryCreator" />
<bean id="conversionService" class="..." />
<bean id="expressionParser" class="..." />
<bean id="viewFactoryCreator" class="..." />
```
在 Java 中:
```
@Bean
public FlowBuilderServices flowBuilderServices() {
return getFlowBuilderServicesBuilder()
.setConversionService(conversionService())
.setExpressionParser(expressionParser)
.setViewFactoryCreator(mvcViewFactoryCreator())
.build();
}
@Bean
public ConversionService conversionService() {
// ...
}
@Bean
public ExpressionParser expressionParser() {
// ...
}
@Bean
public ViewFactoryCreator viewFactoryCreator() {
// ...
}
```
#### 转换-服务
使用`conversion-service`属性定制 Web 流系统使用的`ConversionService`。类型转换用于在流执行期间(例如在处理请求参数、调用操作等时)需要从一种类型转换为另一种类型。支持许多常见的对象类型,如数字、类和枚举。但是,你可能需要为自定义数据类型提供自己的类型转换和格式逻辑。有关如何提供自定义类型转换逻辑的重要信息,请阅读[第 5.7 节,“执行类型转换”](views.html#view-type-conversion)
#### 表达式分析器
使用`expression-parser`属性定制 Web 流系统使用的`ExpressionParser`。默认表达式分析器使用统一的 EL(如果在 Classpath 上可用),否则使用 Spring EL。
#### 视图-工厂-创建者
使用`view-factory-creator`属性定制 Web 流系统使用的`ViewFactoryCreator`。默认的 ViewFactoryCreator 生成 Spring MVC 视图工厂,它能够呈现 JSP、Velocity 和 Freemarker 视图。
可配置设置为`development`。这些设置是可以在流构建过程中应用的全局配置属性。
#### 发展
将此设置为`true`以打开流*发展模式*。开发模式切换到热重载流定义的更改,包括对依赖流资源(如消息包)的更改。
## 10.5.flow-executor 选项
本节将探讨流执行器配置选项。
### 10.5.1.附加流执行侦听器
使用`flow-execution-listeners`元素来注册观察流执行生命周期的侦听器。例如,在 XML 中:
```
<webflow:flow-execution-listeners>
<webflow:listener ref="securityListener"/>
<webflow:listener ref="persistenceListener"/>
</webflow:flow-execution-listeners>
```
在 Java 中:
```
@Bean
public FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(flowRegistry())
.addFlowExecutionListener(securityListener())
.addFlowExecutionListener(persistenceListener())
.build();
}
```
你也可以将监听器配置为只观察某些流。例如,在 XML 中:
```
<webflow:listener ref="securityListener" criteria="securedFlow1,securedFlow2"/>
```
在 Java 中:
```
@Bean
public FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(flowRegistry())
.addFlowExecutionListener(securityListener(), "securedFlow1,securedFlow2")
.build();
}
```
### 10.5.2.调优流执行持久性
使用`flow-execution-repository`元素调优流执行持久性设置。例如,在 XML 中:
```
<webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">
<webflow:flow-execution-repository max-executions="5" max-execution-snapshots="30" />
</webflow:flow-executor>
```
在 Java 中:
```
@Bean
public FlowExecutor flowExecutor() {
return getFlowExecutorBuilder(flowRegistry())
.setMaxFlowExecutions(5)
.setMaxFlowExecutionSnapshots(30)
.build();
}
```
#### 最大处决
调整`max-executions`属性,以限制每个用户可以创建的流执行数量会话。当超过最大执行次数时,删除最老的执行。
|[[note](images/note.png)|Note|
|:--------------------------------------------------------------------------------------------------------:|:---|
|`max-executions`属性是每个用户会话,即它在任何流定义的实例之间工作。| |
#### max-execution-snapshots
调整`max-execution-snapshots`属性,以限制每个流执行可以获取的历史快照的数量。要禁用快照,请将该值设置为 0.要启用无限数量的快照,请将该值设置为-1.
|[[note](images/note.png)|Note|
|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---|
|历史快照启用浏览器后退按钮支持。<br/>当快照被禁用时,按下浏览器后退按钮将不起作用。<br/>它将导致使用指向尚未记录的快照的执行键。| |
\ No newline at end of file
# 14. 测试流程
## 14.1.导言
本章向你展示了如何测试流。
## 14.2.扩展 AbstractXMLFlowExecutionTests
要测试基于 XML 的流定义的执行情况,请扩展`AbstractXmlFlowExecutionTests`:
```
public class BookingFlowExecutionTests extends AbstractXmlFlowExecutionTests {
}
```
## 14.3.指定要测试的流的路径
至少,你必须重写`getResource(FlowDefinitionResourceFactory)`才能返回你希望测试的流的路径:
```
@Override
protected FlowDefinitionResource getResource(FlowDefinitionResourceFactory resourceFactory) {
return resourceFactory.createFileResource("src/main/webapp/WEB-INF/hotels/booking/booking.xml");
}
```
## 14.4.注册流依赖项
如果你的流依赖于外部管理的服务,也可以覆盖`configureFlowBuilderContext(MockFlowBuilderContext)`以注册这些服务的存根或模拟:
```
@Override
protected void configureFlowBuilderContext(MockFlowBuilderContext builderContext) {
builderContext.registerBean("bookingService", new StubBookingService());
}
```
如果你的流从另一个流扩展,或者具有扩展其他状态的状态,那么也覆盖`getModelResources(FlowDefinitionResourceFactory)`以返回父流的路径。
```
@Override
protected FlowDefinitionResource[] getModelResources(FlowDefinitionResourceFactory resourceFactory) {
return new FlowDefinitionResource[] {
resourceFactory.createFileResource("src/main/webapp/WEB-INF/common/common.xml")
};
}
```
## 14.5.测试流启动
让你的第一个测试练习启动你的流:
```
public void testStartBookingFlow() {
Booking booking = createTestBooking();
MutableAttributeMap input = new LocalAttributeMap();
input.put("hotelId", "1");
MockExternalContext context = new MockExternalContext();
context.setCurrentUser("keith");
startFlow(input, context);
assertCurrentStateEquals("enterBookingDetails");
assertTrue(getRequiredFlowAttribute("booking") instanceof Booking);
}
```
断言通常会验证流处于你期望的正确状态。
## 14.6.测试流事件处理
定义额外的测试来执行流事件处理行为。你的目标应该是在流程中锻炼所有的路径。你可以使用方便的`setCurrentState(String)`方法跳转到你希望开始测试的流状态。
```
public void testEnterBookingDetails_Proceed() {
setCurrentState("enterBookingDetails");
getFlowScope().put("booking", createTestBooking());
MockExternalContext context = new MockExternalContext();
context.setEventId("proceed");
resumeFlow(context);
assertCurrentStateEquals("reviewBooking");
}
```
## 14.7.模拟一个子流
要测试调用一个子流,请注册一个子流的模拟实现,该实现断言输入被正确地传入,并为你的测试场景返回正确的结果。
```
public void testBookHotel() {
setCurrentState("reviewHotel");
Hotel hotel = new Hotel();
hotel.setId(1L);
hotel.setName("Jameson Inn");
getFlowScope().put("hotel", hotel);
getFlowDefinitionRegistry().registerFlowDefinition(createMockBookingSubflow());
MockExternalContext context = new MockExternalContext();
context.setEventId("book");
resumeFlow(context);
// verify flow ends on 'bookingConfirmed'
assertFlowExecutionEnded();
assertFlowExecutionOutcomeEquals("finish");
}
public Flow createMockBookingSubflow() {
Flow mockBookingFlow = new Flow("booking");
mockBookingFlow.setInputMapper(new Mapper() {
public MappingResults map(Object source, Object target) {
// assert that 1L was passed in as input
assertEquals(1L, ((AttributeMap) source).get("hotelId"));
return null;
}
});
// immediately return the bookingConfirmed outcome so the caller can respond
new EndState(mockBookingFlow, "bookingConfirmed");
return mockBookingFlow;
}
```
\ No newline at end of file
# 5. 呈现视图
## 5.1.导言
本章向你展示了如何使用`view-state`元素在流中呈现视图。
## 5.2.定义视图状态
使用`view-state`元素来定义流的一个步骤,该步骤呈现视图并等待用户事件恢复:
```
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
```
按照惯例,视图状态将其 ID 映射到流所在目录中的视图模板。例如,如果流本身位于`/WEB-INF/hotels/booking`目录中,则上面的状态可能呈现`/WEB-INF/hotels/booking/enterBookingDetails.xhtml`
下面是一个示例目录结构,显示了视图和其他资源(如消息包)与它们的流定义合用的位置:
<img src="images/flow-view-packaging.png" align="middle" />
流动包装
## 5.3.指定视图标识符
使用`view`属性指定要显式呈现的视图的 ID。
### 5.3.1.流相对视图 ID
视图 ID 可以是在流的工作目录中查看资源的相对路径:
```
<view-state id="enterBookingDetails" view="bookingDetails.xhtml">
```
### 5.3.2.绝对视图 ID
视图 ID 可以是指向 WebApp 根目录中某个视图资源的绝对路径:
```
<view-state id="enterBookingDetails" view="/WEB-INF/hotels/booking/bookingDetails.xhtml">
```
### 5.3.3.逻辑视图 ID
对于一些视图框架,例如 Spring MVC 的视图框架,视图 ID 也可以是由框架解析的逻辑标识符:
```
<view-state id="enterBookingDetails" view="bookingDetails">
```
有关如何与 MVC`ViewResolver`基础架构集成的更多信息,请参见 Spring MVC 集成部分。
## 5.4.视图作用域
视图状态在进入时分配一个新的`viewScope`。这个作用域可以在视图状态中被引用,以分配应该在状态持续期间有效的变量。这个作用域对于在来自同一视图的一系列请求(通常是 Ajax 请求)上操作对象非常有用。当视图状态退出时,它会破坏它的视图范围。
### 5.4.1.分配视图变量
使用`var`标记声明一个视图变量。与流变量一样,当视图状态恢复时,任何`@Autowired`引用都会自动恢复。
```
<var name="searchCriteria" class="com.mycompany.myapp.hotels.SearchCriteria" />
```
### 5.4.2.分配 ViewScope 变量
在视图呈现之前,使用`on-render`标记从操作结果中分配一个变量:
```
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
</on-render>
```
### 5.4.3.在视图范围内操作对象
视图作用域中的对象通常在来自同一视图的一系列请求上进行操作。下面的示例页面通过一个搜索结果列表。在每次呈现之前,都会在视图范围内更新该列表。异步事件处理程序修改当前数据页,然后请求重新呈现搜索结果片段。
```
<view-state id="searchResults">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" />
</on-render>
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
<render fragments="searchResultsFragment" />
</transition>
<transition on="previous">
<evaluate expression="searchCriteria.previousPage()" />
<render fragments="searchResultsFragment" />
</transition>
</view-state>
```
## 5.5.执行呈现动作
在视图呈现之前,使用`on-render`元素执行一个或多个操作。呈现操作在初始呈现以及任何后续刷新上执行,包括视图的任何部分重新呈现。
```
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
</on-render>
```
## 5.6.绑定到模型
使用`model`属性声明视图绑定到的模型对象。此属性通常与呈现窗体等数据控件的视图结合使用。它使表单数据绑定和验证行为能够由模型对象上的元数据驱动。
下面的示例声明一个`enterBookingDetails`状态操作`booking`模型:
```
<view-state id="enterBookingDetails" model="booking">
```
模型可以是任何可访问范围内的对象,例如`flowScope``viewScope`。当发生视图事件时,指定`model`将触发以下行为:
1. 视图到模型绑定。在视图回发时,用户输入值将绑定到为你建模的对象属性。
2. 模型验证。绑定之后,如果模型对象需要验证,则将调用验证逻辑。
要生成能够驱动视图状态转换的流事件,必须成功地完成模型绑定。如果模型绑定失败,将重新呈现视图,以允许用户修改其编辑。
## 5.7.执行类型转换
当使用请求参数填充模型(通常称为数据绑定)时,在设置目标模型属性之前,需要进行类型转换以解析基于字符串的请求参数值。默认类型转换可用于许多常见的 Java 类型,例如数字、原语、枚举和日期。用户还可以为用户定义的类型注册自己的类型转换逻辑,并覆盖默认的转换器。
### 5.7.1.类型转换选项
从版本 2.1 Spring 开始,Web 流使用 Spring 3 中引入的[类型转换](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/validation.html#core-convert)[formatting](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/validation.html#format)系统来满足几乎所有的类型转换需求。以前,Web 流应用程序使用的类型转换机制与 Spring MVC 中的机制不同,后者依赖于`java.beans.PropertyEditor`抽象。 Spring 3 提供了一种替代 PropertYeditor 的现代类型转换方法,而 PropertYeditor 实际上受到了 Web Flow 自己的类型转换系统的影响。因此,Web 流用户应该会发现使用新的 Spring 3 类型转换是很自然的。这种变化的另一个明显且非常重要的好处是,现在可以跨 Spring MVC 和 Spring Web 流使用单一类型转换机制。
### 5.7.2.升级到 Spring 3 类型转换和格式化
这对现有的应用程序实际上意味着什么?现有的应用程序很可能通过 Spring 绑定中可用的`DefaultConversionService`的子类来注册它们自己的类型`org.springframework.binding.convert.converters.Converter`的转换器。这些转换器可以像以前一样继续注册。它们将被改编为 Spring 3`GenericConverter`类型,并以 Spring 3`org.springframework.core.convert.ConversionService`实例注册。换句话说,现有的转换器将通过 Spring 的类型转换服务被调用。
此规则的唯一例外是命名转换器,它可以从`view-state`中的`binding`元素引用:
```
public class ApplicationConversionService extends DefaultConversionService {
public ApplicationConversionService() {
addDefaultConverters();
addDefaultAliases();
addConverter("customConverter", new CustomConverter());
}
}
```
```
<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="checkinDate" required="true" converter="customConverter" />
</binder>
</view-state>
```
命名转换器不受支持,并且不能与 Spring 3 中提供的类型转换服务一起使用。因此,这样的转换器将不会被适配并且将继续像以前那样工作,即将不涉及 Spring 3 类型的转换。然而,这种机制是不受欢迎的,并且鼓励应用程序支持 Spring 3 类型转换和格式化特性。
还要注意,现有的 Spring binding`DefaultConversionService`不再注册任何默认的转换器。相反,Web 流现在依赖于 Spring 3 中的默认类型转换器和格式化程序。
总之, Spring 3 类型转换和格式现在几乎只在 Web 流中使用。尽管现有的应用程序将在没有任何更改的情况下工作,但我们鼓励朝着统一 Spring MVC 和 Spring 应用程序的 Web 流部分的类型转换需求的方向发展。
### 5.7.3.配置类型转换和格式
在 Spring MVC 中,`FormattingConversionService`的实例是通过自定义 MVC 名称空间自动创建的:
```
<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<mvc:annotation-driven/>
```
在内部,这是在`FormattingConversionServiceFactoryBean`的帮助下完成的,它注册了一组默认的转换器和格式化程序。你可以通过`conversion-service`属性自定义 Spring MVC 中使用的转换服务实例:
```
<mvc:annotation-driven conversion-service="applicationConversionService" />
```
在 Web 流中,自动创建 Spring 绑定`DefaultConversionService`的实例,该实例不注册任何转换器。相反,它将所有类型转换需求委托给`FormattingConversionService`实例。默认情况下,这不是与 Spring 3 中使用的实例相同的`FormattingConversionService`实例。然而,在你开始注册自己的格式化程序之前,这不会产生实际的影响。
Web 流中使用的`DefaultConversionService`可以通过 flow-builder-services 元素进行定制:
```
<webflow:flow-builder-services id="flowBuilderServices" conversion-service="defaultConversionService" />
```
连接这些点以便注册你自己的格式化程序以便在 Spring MVC 和 Spring Web 流中使用,你可以执行以下操作。创建一个类来注册你的自定义格式化程序:
```
public class ApplicationConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {
@Override
protected void installFormatters(FormatterRegistry registry) {
// ...
}
}
```
将其配置为在 Spring MVC 中使用:
```
<?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"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<mvc:annotation-driven conversion-service="applicationConversionService" />
<!--
Alternatively if you prefer annotations for DI:
1. Add @Component to the factory bean.
2. Add a component-scan element (from the context custom namespace) here.
3. Remove XML bean declaration below.
-->
<bean id="applicationConversionService" class="somepackage.ApplicationConversionServiceFactoryBean">
```
将 Web 流`DefaultConversionService`连接到 Spring MVC 中使用的相同的“ApplicationConversionService” Bean:
```
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices" ... />
<webflow:flow-builder-services id="flowBuilderServices" conversion-service="defaultConversionService" ... />
<bean id="defaultConversionService" class="org.springframework.binding.convert.service.DefaultConversionService">
<constructor-arg ref="applicationConversionSevice"/>
</bean>
```
当然,混搭也是可以的。通过“ApplicationConversionService”注册新的 Spring 3<gtr="98"/>类型。通过“DefaultConversionService”注册现有的 Spring binding<gtr="99"/>类型。
### 5.7.4.使用 Spring 3 类型转换和格式化
要理解的一个重要概念是类型转换器和格式化程序之间的区别。
Spring 3 中的类型转换器,在`org.springframework.core`中提供,是用于在任意两个对象类型之间进行通用类型转换的。除了最简单的`Converter`类型外,还有两个接口`ConverterFactory``GenericConverter`
Spring 3 中的格式化程序(在`org.springframework.context`中提供)具有将对象表示为字符串的更专门的目的。`Formatter`接口扩展了`Printer``Parser`接口,用于将对象转换为字符串并将字符串转换为对象。
Web 开发人员将发现`Formatter`接口最相关,因为它适合 Web 应用程序对类型转换的需求。
|[[note](images/note.png)|Note|
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---|
|重要的一点是,对象到对象的转换是更具体的对象到字符串转换的推广。<br/>实际上在最后`Formatters`被重新定义为`GenericConverter`类型, Spring 的`GenericConversionService`使它们等于任何其他转换器。| |
### 5.7.5.格式化注释
新类型转换的最佳特性之一是能够使用注释以简洁的方式更好地控制格式。注释可以放在模型属性和映射到请求的 @Controller 方法的参数上。 Spring 开箱即用提供了两个注释`NumberFormat``DateTimeFormat`,但你可以创建自己的注释,并将它们与相关的格式设置逻辑一起注册。你可以在[Spring Travel](https://src.springframework.org/svn/spring-samples/travel)[Petcare](https://src.springframework.org/svn/spring-samples/petcare)中看到`DateTimeFormat`注释的示例,以及[Spring Samples](https://src.springframework.org/svn/spring-samples)存储库中的其他示例。
### 5.7.6.与日期合作
`DateTimeFormat`注释意味着使用[Joda Time](http://joda-time.sourceforge.net/)。如果在 Classpath 上存在该注释,则自动启用该注释的使用。默认情况下, Spring MVC 或 Web 流都不会注册任何其他的日期格式化程序或转换器。因此,对于应用程序来说,注册一个自定义格式化程序来指定打印和解析日期的默认方式是很重要的。另一方面,`DateTimeFormat`注释在需要偏离默认值的地方提供了更细粒度的控制。
有关使用 Spring 3 类型转换和格式的更多信息,请参阅[Spring documentation](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/index.html)的相关部分。
## 5.8.抑制结合
使用`bind`属性来抑制特定视图事件的模型绑定和验证。当`cancel`事件发生时,以下示例将取消绑定:
```
<view-state id="enterBookingDetails" model="booking">
<transition on="proceed" to="reviewBooking">
<transition on="cancel" to="bookingCancelled" bind="false" />
</view-state>
```
## 5.9.显式指定绑定
使用`binder`元素来配置要对其应用数据绑定的模型属性的精确集合。这对于限制每个视图的“允许字段”集是有用的。不使用此功能可能会导致安全问题,这取决于应用程序域和实际用户,因为默认情况下,如果未指定 Binder 元素,则模型的所有公共属性都有资格通过视图进行数据绑定。相比之下,当指定`binder`元素时,只允许显式配置绑定。下面是一个例子:
```
<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="creditCard" />
<binding property="creditCardName" />
<binding property="creditCardExpiryMonth" />
<binding property="creditCardExpiryYear" />
</binder>
<transition on="proceed" to="reviewBooking" />
<transition on="cancel" to="cancel" bind="false" />
</view-state>
```
每个绑定还可以应用转换器来格式化模型属性值,以便以自定义的方式显示。如果没有指定转换器,将使用模型属性类型的默认转换器。
```
<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="checkinDate" converter="shortDate" />
<binding property="checkoutDate" converter="shortDate" />
<binding property="creditCard" />
<binding property="creditCardName" />
<binding property="creditCardExpiryMonth" />
<binding property="creditCardExpiryYear" />
</binder>
<transition on="proceed" to="reviewBooking" />
<transition on="cancel" to="cancel" bind="false" />
</view-state>
```
在上面的示例中,`shortDate`转换器绑定到`checkinDate``checkoutDate`属性。定制的转换器可以注册到应用程序的 ConversionService 中。
每个绑定还可能应用所需的检查,如果用户提供的值在表单回发时为空,则该检查将生成验证错误:
```
<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="checkinDate" converter="shortDate" required="true" />
<binding property="checkoutDate" converter="shortDate" required="true" />
<binding property="creditCard" required="true" />
<binding property="creditCardName" required="true" />
<binding property="creditCardExpiryMonth" required="true" />
<binding property="creditCardExpiryYear" required="true" />
</binder>
<transition on="proceed" to="reviewBooking">
<transition on="cancel" to="bookingCancelled" bind="false" />
</view-state>
```
在上面的示例中,所有的绑定都是必需的。如果绑定了一个或多个空白输入值,将生成验证错误,视图将使用这些错误重新呈现。
## 5.10.验证模型
模型验证由针对模型对象指定的约束驱动。Web 流支持以编程的方式以及以声明的方式使用 JSR-303 Bean 验证注释来强制执行这些约束。
### 5.1 0.1.JSR-303 Bean 验证
Web Flow 提供了对 JSR-303 Bean 验证 API 的内置支持,构建在 Spring MVC 中提供的等效支持之上。要启用 JSR-303 验证,请使用 Spring MVC 的`LocalValidatorFactoryBean`配置 flow-builder-services:
```
<webflow:flow-registry flow-builder-services="flowBuilderServices" />
<webflow:flow-builder-services id="flowBuilderServices" validator="validator" />
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
```
有了上述功能,配置的验证器将应用于数据绑定后的所有模型属性。
请注意,JSR-303 Bean 验证和约定验证(在下一节中进行解释)并不相互排斥。换句话说,WebFlow 将应用所有可用的验证机制。
#### 部分验证
JSR-303 Bean 验证支持通过验证组进行部分验证。例如:
```
@NotNull
@Size(min = 2, max = 30, groups = State1.class)
private String name;
```
在流定义中,你可以在视图状态或转换中指定验证提示,这些提示将解析为验证组。例如:
```
<view-state id="state1" model="myModel" validation-hints="'group1,group2'">
```
*验证-提示*属性是一个表达式,在上面的示例中,它解析为一个逗号分隔的字符串,该字符串由提示“group1”和“group2”组成。a`ValidationHintResolver`用于解析这些提示。默认情况下使用的`BeanValidationHintResolver`尝试将这些字符串解析为基于类的 Bean 验证组。为此,它在模型或其父模型中寻找匹配的内部类型。
例如,给出带有内部类型`org.example.MyModel``Group2``org.example.MyModel``Group2`就足以提供简单的类型名,即“group1”和“group2”。你还可以提供完全限定的类型名。
值为“default”的提示具有特殊的含义,并在 Bean Validation`javax.validation.groups.Default`中转换为默认验证组。
如果需要,可以通过 flow-builder-services 元素的 ValidationHintResolver 属性配置自定义`ValidationHintResolver`:
```
<webflow:flow-registry flow-builder-services="flowBuilderServices" />
<webflow:flow-builder-services id="flowBuilderServices" validator=".." validation-hint-resolver=".." />
```
### 5.1 0.2.程序验证
有两种方法来执行模型验证程序。第一种是在模型对象中实现验证逻辑。二是实现外部`Validator`。这两种方式都为你提供了`ValidationContext`,以记录错误消息并访问有关当前用户的信息。
#### 实现一种模型验证方法
在模型对象中定义验证逻辑是验证其状态的最简单方法。一旦这种逻辑根据 Web 流约定进行了结构化,那么 Web 流将在视图状态回发生命周期期间自动调用该逻辑。Web 流约定让你通过视图状态来构造模型验证逻辑,从而使你能够轻松地验证在该视图上可编辑的模型属性的子集。要做到这一点,只需创建一个名为`validate${state}`的公共方法,其中`${state}`是你希望在其中运行验证的视图状态的 ID。例如:
```
public class Booking {
private Date checkinDate;
private Date checkoutDate;
...
public void validateEnterBookingDetails(ValidationContext context) {
MessageContext messages = context.getMessageContext();
if (checkinDate.before(today())) {
messages.addMessage(new MessageBuilder().error().source("checkinDate").
defaultText("Check in date must be a future date").build());
} else if (!checkinDate.before(checkoutDate)) {
messages.addMessage(new MessageBuilder().error().source("checkoutDate").
defaultText("Check out date must be later than check in date").build());
}
}
}
```
在上面的示例中,当在编辑`Booking`模型的`enterBookingDetails`视图状态中触发转换时,Web 流将自动调用`validateEnterBookingDetails(ValidationContext)`方法,除非该转换的验证已被抑制。这种视图状态的一个示例如下所示:
```
<view-state id="enterBookingDetails" model="booking">
<transition on="proceed" to="reviewBooking">
</view-state>
```
定义了任意数量的验证方法。通常,流通过一系列视图编辑模型。在这种情况下,将为需要运行验证的每个视图状态定义一个验证方法。
#### 实现验证器
第二种方法是定义一个单独的对象,称为*验证器*,它验证你的模型对象。要做到这一点,首先创建一个类,其名称具有模式 ${model}validator,其中`${model}`是模型表达式的头化形式,例如`booking`。然后定义一个名为`validate${state}`的公共方法,其中`${state}`是你的视图状态的 ID,例如`enterBookingDetails`。然后将类部署为 Spring Bean。可以定义任意数量的验证方法。例如:
```
@Component
public class BookingValidator {
public void validateEnterBookingDetails(Booking booking, ValidationContext context) {
MessageContext messages = context.getMessageContext();
if (booking.getCheckinDate().before(today())) {
messages.addMessage(new MessageBuilder().error().source("checkinDate").
defaultText("Check in date must be a future date").build());
} else if (!booking.getCheckinDate().before(booking.getCheckoutDate())) {
messages.addMessage(new MessageBuilder().error().source("checkoutDate").
defaultText("Check out date must be later than check in date").build());
}
}
}
```
在上面的示例中,当在编辑`Booking`模型的`enterBookingDetails`视图状态中触发转换时,Web 流将自动调用`validateEnterBookingDetails(Booking, ValidationContext)`方法,除非该转换的验证已被抑制。
验证器还可以接受 Spring MVC对象,这是调用现有 Spring 验证器所需的。
必须使用命名约定`${model}Validator`将验证器注册为 Spring bean,才能自动检测和调用。在上面的示例中, Spring 2.5 Classpath-扫描将检测到`@Component`并自动将其注册为名称为`bookingValidator`的 Bean。然后,每当需要验证`booking`模型时,将为你调用这个`bookingValidator`实例。
#### 默认验证方法
一个*验证器*类还可以定义一个名为`validate`的方法,该方法与任何特定的视图状态都没有关联(根据约定)。
```
@Component
public class BookingValidator {
public void validate(Booking booking, ValidationContext context) {
//...
}
}
```
在上面的代码示例中,每当验证类型`Booking`的模型时,将调用方法`validate`(除非该转换抑制了验证)。如果需要,除了现有的特定于状态的方法之外,还可以调用缺省方法。考虑以下示例:
```
@Component
public class BookingValidator {
public void validate(Booking booking, ValidationContext context) {
//...
}
public void validateEnterBookingDetails(Booking booking, ValidationContext context) {
//...
}
}
```
在上面的代码示例中,将首先调用方法`validateEnterBookingDetails`。下一步将调用默认的`validate`方法。
### 5.1 0.3.ValidationContext
ValidationContext 允许你获得`MessageContext`以在验证期间记录消息。它还公开了关于当前用户的信息,例如已信号的`userEvent`和当前用户的`Principal`标识。该信息可用于根据 UI 中激活了什么按钮或链接,或者验证了谁,定制验证逻辑。有关更多信息,请参见`ValidationContext`的 API Javadocs。
## 5.11.抑制验证
使用`validate`属性禁止对特定视图事件进行模型验证:
```
<view-state id="chooseAmenities" model="booking">
<transition on="proceed" to="reviewBooking">
<transition on="back" to="enterBookingDetails" validate="false" />
</view-state>
```
在本例中,数据绑定仍将发生在`back`上,但验证将被抑制。
## 5.12.执行视图转换
定义一个或多个`transition`元素来处理视图上可能发生的用户事件。转换可以将用户带到另一个视图,也可以只执行一个操作并重新呈现当前视图。在处理 Ajax 事件时,转换还可能请求呈现视图中称为“碎片”的部分。最后,还可以定义在所有视图中共享的“全局”转换。
实现视图转换将在下面的小节中进行说明。
### 5.1 2.1.过渡行动
视图-状态转换可以在执行之前执行一个或多个操作。这些操作可能会返回错误结果,以防止转换退出当前视图状态。如果出现错误结果,视图将重新呈现,并应向用户显示适当的消息。
如果转换操作调用一个普通的 Java 方法,那么被调用的方法可能返回一个布尔值,该值是 true 还是 false,指示是否应该进行转换或阻止执行转换。方法还可以返回一个字符串,其中文字值“Success”、“Yes”或“True”表示应该发生转换,而任何其他值则表示相反的值。此技术可用于处理服务层方法引发的异常。下面的示例调用一个调用服务并处理异常情况的操作:
```
<transition on="submit" to="bookingConfirmed">
<evaluate expression="bookingAction.makeBooking(booking, messageContext)" />
</transition>
```
```
public class BookingAction {
public boolean makeBooking(Booking booking, MessageContext context) {
try {
bookingService.make(booking);
return true;
} catch (RoomNotAvailableException e) {
context.addMessage(new MessageBuilder().error().
.defaultText("No room is available at this hotel").build());
return false;
}
}
}
```
当在转换过程中定义了多个动作时,如果一个动作返回一个错误结果,则集合中的其余动作将执行*不是*。如果你需要确保一个转换操作的结果不会影响另一个转换操作的执行,那么可以定义一个单独的转换操作,调用一个封装了所有操作逻辑的方法。
### 5.1 2.2.全球转型
使用流的`global-transitions`元素创建适用于所有视图的转换。全局转换通常用于处理作为布局一部分的全局菜单链接。
```
<global-transitions>
<transition on="login" to="login" />
<transition on="logout" to="logout" />
</global-transitions>
```
### 5.1 2.3.事件处理程序
从视图状态,也可以定义没有目标的转换。这样的转换被称为“事件处理程序”:
```
<transition on="event">
<!-- Handle event -->
</transition>
```
这些事件处理程序不会改变流的状态。它们只需执行动作并重新呈现当前视图或当前视图的一个或多个片段。
### 5.1 2.4.呈现片段
在处理事件后,在转换中使用`render`元素请求部分重新呈现当前视图:
```
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
<render fragments="searchResultsFragment" />
</transition>
```
Fragments 属性应该引用你希望重新呈现的视图元素的 ID。通过用逗号分隔符分隔要重新呈现的多个元素,指定它们。
这种部分呈现通常与 Ajax 发出信号的事件一起使用,以更新视图的特定区域。
## 5.13.处理消息
Spring Web 流的`MessageContext`是用于在流执行过程中记录消息的 API。可以将纯文本消息添加到上下文中,以及由 Spring `MessageSource`解析的国际化消息。消息可以通过视图进行呈现,并在流执行重定向后自动保留。提供了三个不同的消息严重性:`info``warning``error`。此外,还存在一个方便的`MessageBuilder`用于流畅地构造消息。
### 5.1 3.1.添加纯文本消息
```
MessageContext context = ...
MessageBuilder builder = new MessageBuilder();
context.addMessage(builder.error().source("checkinDate")
.defaultText("Check in date must be a future date").build());
context.addMessage(builder.warn().source("smoking")
.defaultText("Smoking is bad for your health").build());
context.addMessage(builder.info()
.defaultText("We have processed your reservation - thank you and enjoy your stay").build());
```
### 5.1 3.2.添加国际化消息
```
MessageContext context = ...
MessageBuilder builder = new MessageBuilder();
context.addMessage(builder.error().source("checkinDate").code("checkinDate.notFuture").build());
context.addMessage(builder.warn().source("smoking").code("notHealthy")
.resolvableArg("smoking").build());
context.addMessage(builder.info().code("reservationConfirmation").build());
```
### 5.1 3.使用消息包
国际化消息是在由 Spring `MessageSource`访问的消息包中定义的。要创建特定于流的消息包,只需在流的目录中定义`messages.properties`文件。为每个需要支持的额外`Locale`创建一个默认的`messages.properties`文件和一个.properties 文件。
```
#messages.properties
checkinDate=Check in date must be a future date
notHealthy={0} is bad for your health
reservationConfirmation=We have processed your reservation - thank you and enjoy your stay
```
从视图或流中,你还可以使用`resourceBundle`EL 变量访问消息资源:
```
<h:outputText value="#{resourceBundle.reservationConfirmation}" />
```
### 5.1 3.4.理解系统生成的消息
在几个地方,Web Flow 本身将生成要显示给用户的消息。发生这种情况的一个重要位置是在视图到模型的数据绑定过程中。当出现绑定错误(例如类型转换错误)时,WebFlow 将自动将该错误映射到从资源包中检索到的消息。要查找要显示的消息,WebFlow 将尝试包含绑定错误代码和目标属性名称的资源键。
例如,考虑绑定到`Booking`对象的`checkinDate`属性。假设用户输入了一个字母字符串。在这种情况下,将引发类型转换错误。WebFlow 将通过使用以下键首先查询资源包中的一条消息,将“Type 错配”错误代码映射到一条消息:
```
booking.checkinDate.typeMismatch
```
关键的第一部分是模型类的简称。密钥的第二部分是属性名称。第三部分是错误代码。这允许查找在模型属性上绑定失败时向用户显示的唯一消息。这样的信息可能会说:
```
booking.checkinDate.typeMismatch=The check in date must be in the format yyyy-mm-dd.
```
如果在该表单中找不到这样的资源密钥,将尝试使用更通用的密钥。这个键就是错误代码。属性的字段名称作为消息参数提供。
```
typeMismatch=The {0} field is of the wrong type.
```
## 5.14.显示弹出窗口
使用`popup`属性在模态弹出对话框中呈现视图:
```
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
```
当使用 Spring JavaScript 的 Web 流时,弹出窗口不需要客户端代码即可显示。WebFlow 将向客户机发送响应,请求从弹出窗口重定向到视图,客户机将执行该请求。
## 5.15.视图回溯
默认情况下,当你退出一个视图状态并转换到一个新的视图状态时,你可以使用浏览器 Back 按钮返回到以前的状态。通过使用`history`属性,可以在每次转换的基础上配置这些视图状态历史策略。
### 5.15.1.抛弃历史
将 history 属性设置为`discard`,以防止回溯到视图:
```
<transition on="cancel" to="bookingCancelled" history="discard">
```
### 5.15.2.使历史失效
将历史属性设置为`invalidate`,以防止回溯到视图以及以前显示的所有视图:
```
<transition on="confirm" to="bookingConfirmed" history="invalidate">
```
\ No newline at end of file
# 2. 最新更新
## 2.1. Spring Web 流 2.5
这个版本提供了一条到 Spring Framework5 的升级路径,而这条路径又需要 Java8+、 Servlet 3.1、 Hibernate 5、Tiles3.有关更多详细信息,请参见[Spring Framework wiki](https://github.com/spring-projects/spring-framework/wiki/What%27s-New-in-Spring-Framework-5.x)[样品库](https://github.com/spring-projects/spring-webflow-samples)已升级到 Spring Web 流 2.5.
从 2.5 开始,不再有*spring-js*模块。该模块中的类已被保留,但已转移到*spring-webflow*模块中的新包中。*spring-js-resources*模块作为一个可选模块是可用的,必须显式地包含该模块。
此版本需要 JSF2.2 或更高版本。
## 2.2. Spring Web 流 2.4
这个版本需要 JDK1.6.
### 2.2.1.基于 Java 的配置
Web Flow 现在支持基于 Java 的系统配置替代方案。参见更新的[第 10 章,* 系统设置 *](system-setup.html)
还请参见[Booking-MVC](https://github.com/spring-projects/spring-webflow-samples/tree/master/booking-mvc)[预订脸](https://github.com/spring-projects/spring-webflow-samples/tree/master/booking-faces)示例,这些示例已更新为使用所有 Java 配置。
### 2.2.2. Spring MVC 闪存范围集成
当一个流结束时,在将属性保存在 Spring MVC 的闪存范围中供控制器访问后,它现在可以重定向到 Spring MVC 控制器。
[第 11.8 节,“将流输出保存到 MVC 闪存范围”](spring-mvc.html#spring-mvc-flash-output)
### 2.2.3.部分 JSR-303 Bean 验证
流定义可以通过 View State 和 Transition 元素支持的 Validation-Hints 属性在模型上应用部分验证。
[该部分被称为“部分验证”。](views.html#view-validation-jsr303-partial)
### 2.2.4. Hibernate 支持
除了 Hibernate 3 之外,`HibernateFlowExecutionListener`现在还支持 Hibernate 4.
截至 2.4.4,`HibernateFlowExecutionListener`也与 Hibernate 5 起作用。
### 2.2.5.磁贴 3 支持
现在`AjaxTilesView`除了支持 tile2.2 外,还支持 tile3.
### 2.2.6.最低 JSF2.0 要求
Spring Web Flow 不再支持 Java ServerFaces 版本 1.2 和更早版本,如果你还没有这样做,你将需要升级到 JSF2.0 或更高版本。此外,在这个版本中,已经删除了以前通过 JSF1.2 提供的用于渐进 Ajax 增强的 Spring 面组件。
[???]()。
### 2.2.7.Portlet API2.0 和 JSF2.0 支持
Spring Web Flow2.2 中引入的内部 Portlet 集成已经进行了升级,以实现 JSF2.0 的兼容性。Portlet 环境不支持一些更高级的 JSF2.0 特性,例如部分状态保存,但是,现有的应用程序现在可以升级到所需的最低 JSF 版本。升级后的项目将需要确保`<faces:resources>`元素被包括在其 Spring 配置中。
### 2.2.8.异议
此版本删除*Spring.js*。该版本包括整个 * Spring-js-resources* 模块,包括*Spring.js** Spring-dojo.js* 以及捆绑的 Dojo 和 CSS 框架。同样不推荐的是来自*spring-js*模块的`SpringJavascriptAjaxHandler`*spring-js*的其余部分,例如`AjaxHandler``AjaxTilesView`,将在未来的版本中折叠成*spring-webflow*
OGNL 支持现在已被弃用。
## 2.3. Spring Web 流 2.3
### 2.3.1.在页面上嵌入流
默认情况下,WebFlow 在进入每个视图状态时都会进行客户端重定向。这使得不可能在页面或模态对话框中嵌入流并执行多个视图状态,而不会导致整页刷新。Web Flow 现在支持以“嵌入式”模式启动一个流。在这种模式下,在 Ajax 请求期间,流可以转换到其他视图状态,而无需客户端重定向。见[第 11.7 节,“在页面上嵌入流”](spring-mvc.html#spring-mvc-embedded-flow)[第 13.6 节,“在页面上嵌入流”](spring-faces.html#spring-faces-embedded-mode)
### 2.3.2.支持 JSR-303 Bean 验证
对 JSR-303 Bean 验证 API 的支持现在可以在 Spring MVC 中提供的等效支持的基础上获得。有关更多详细信息,请参见[第 5.10 节,“验证模型”](views.html#view-validate)
### 2.3.3.流管理的持久性上下文传播
从 Web 流 2.3 开始,管理的流`PersistenceContext`将自动扩展(传播)到子流,假设子流也具有启用的特性。见[第 7.3 节,“流管理的持久性和子流”](flow-managed-persistence.html#flow-managed-persistence-propagation)
### 2.3.4.Portlet2.0 资源请求
现在已经添加了对 Portlet2.0 资源请求的支持,从而支持部分呈现的 Ajax 请求。可以在 JSP 页面中使用`<portlet:resourceURL>`标记来准备此类请求的 URL。服务器端处理类似于一个动作和一个呈现请求的组合,但合并在一个请求中。与呈现请求不同,来自资源请求的响应仅包括来自目标 Portlet 的内容。
### 2.3.5.自定义对话管理器
现在,`<flow-execution-repository>`元素提供了一个 conversation-manager 属性,该属性接受对 Conversationmanager 实例的引用。
### 2.3.6.在相同的状态下重定向
默认情况下,只要当前请求不是 Ajax 请求,在保持相同视图状态时,Web 流就会执行客户端重定向。这在表单验证失败后很有用。点击刷新或返回不会导致浏览器警告。因此,这种行为通常是可取的。然而,一个新的流执行属性使得禁用它成为可能,这在某些特定于 JSF 应用程序的情况下也是必要的。见[第 13.7 节,“在同一状态下重定向”](spring-faces.html#spring-faces-redirect-in-same-state)
### 2.3.7.样本
建立分布所包括的样本的过程已经简化。 Maven 可用于在一个步骤中构建所有样本。Eclipse 设置包含源代码引用以简化调试。
可按以下方式获取更多样本:
```
mkdir spring-samples
cd spring-samples
svn co https://src.springframework.org/svn/spring-samples/webflow-primefaces-showcase
cd webflow-primefaces-showcase
mvn package
# import into Eclipse
```
```
mkdir spring-samples
cd spring-samples
svn co https://src.springframework.org/svn/spring-samples/webflow-showcase
cd webflow-showcase
mvn package
# import into Eclipse
```
## 2.4. Spring Web 流 2.2
### 2.4.1.JSF2 支持
#### 全面的 JSF2 支持
Spring Web Flow2.2 版本以 2.1 为基础,增加了对核心 JSF2 特性的支持,2.1 中不支持的以下特性现在可用:部分状态保存、JSF2 资源请求、处理和 JSF2Ajax 请求。在这一点上,对 JSF2 的支持被认为是全面的,尽管并不涵盖所有的 JSF2 特性--排除的主要是与 Web 流提供的核心价值重叠的特性,例如与导航和状态管理有关的特性。
有关重要的配置更改,请参见[第 13.3 节,“配置用于 JSF 的 Web 流”](spring-faces.html#spring-faces-webflow-config)。请注意,只支持 Sun Mojarra2.0.3 或更高版本的部分状态保存。它还没有得到 MyFaces 的支持。这是因为 MyFaces 在如何存储组件状态方面不那么容易定制。我们将与 MyFaces 合作提供这种支持。同时,你将需要在`web.xml`中使用`javax.faces.PARTIAL_STATE_SAVING`上下文参数来禁用 Apache MyFaces 的部分状态保存。
#### 带原始表面组件的旅行样本
演示 Spring Web 流和 JSF 支持的主要 Spring 旅行示例现在构建在 JSF2 和来自 PrimeFaces 组件库的组件上。请核对一下分配中的订货面样品。
可以在 Spring WebFlow-Prime Faces[Showcase](https://src.springframework.org/svn/spring-samples/webflow-primefaces-showcase)处找到其他示例,这是[spring-samples](https://src.springframework.org/svn/spring-samples)存储库中的一个 SVN 存储库。使用以下命令检查并构建:
```
svn co https://src.springframework.org/svn/spring-samples/webflow-primefaces-showcase
cd webflow-primefaces-showcase
mvn package
```
### 2.4.2. Spring 安全 Facelets 标记库
Spring 新的安全标记库可用于 JSF2.0 或 JSF1.2Facelets 视图。它提供了一个 \<authorize\>标记以及几个 EL 函数。有关更多详细信息,请参见[Section 13.9, “Using the Spring Security Facelets Tag Library”](spring-faces.html#spring-faces-security-taglib)
### 2.4.3. Spring JavaScript 更新
#### 弃用资源 servlet
从 Spring 3.0.4 开始, Spring 框架包括对 ResourcesServlet 的替换。请参阅 Spring 框架文档了解定制 MVC 名称空间的详细信息,特别是新的[“资源”](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-static-resources)元素。
#### Dojo1.5 和 Dojox
捆绑的定制 Dojo 版本升级到了 1.5 版本。它现在包括 Dojox。
请注意,通常鼓励应用程序准备自己的定制 Dojo 构建以优化性能,这取决于 Dojo 的哪些部分通常一起使用。有关示例,请参见 Spring Web 流用于准备自己的定制 Dojo 构建的[scripts](https://src.springframework.org/svn/spring-webflow/branches/spring-webflow-2.2-maintenance/spring-js-resources/scripts/dojo)
#### 两个 Spring JS 工件
`spring-js`工件被一分为二--新工件(`spring-js-resources`)包含客户端资源(.js、.css 等),而现有工件(`spring-js`)仅包含服务器端 Java 代码。
准备自己的定制 Dojo 构建的应用程序现在有一个选项,可以避免将`spring-js-resources``Spring.js`直接放在其 Web 应用程序的根下面。
#### 客户端资源移至 meta-inf/web-resources
捆绑的客户机资源(.js、.css 等)已从其先前位于`META-INF`下的位置移到`META-INF/web-resources`。这种变化对于应用程序是透明的,但是当使用 Spring 3.0.4 中提供的新的资源处理机制时,将导致更简单和更安全的配置。
### 2.4.4.JSF Portlet 支持
#### Portlet API2.0 和 JSF1.2 支持
在 Spring 以前的版本中,对 JSF Portlet 的 Web 流支持依赖于用于 JSF 实现的 Portlet 桥,并且被认为是实验性的。 Spring Web Flow2.2 基于其自身的内部 Portlet 集成,增加了对 JSF Portlet 的支持,该集成以 Portlet API2.0 和 JSF1.2 环境为目标。有关更多详细信息,请参见[???]()。 Spring Web Flow Travel JSF Portlet 示例已经在 Apache Pluto Portal 容器上成功地进行了测试。
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册