提交 22398f74 编写于 作者: 茶陵後's avatar 茶陵後 👍

Merge branch 'structrue' of gitcode.net:dev-cloud/spring into structrue

此差异已折叠。
......@@ -2,7 +2,7 @@
## [](#messaging-transformation-chapter)消息转换
### [](#transformer)变压
### [](#transformer)转换
消息转换器在实现消息生产者和消息消费者之间的松耦合方面发挥着非常重要的作用。你可以在这些组件之间添加转换器,而不是要求每个产生消息的组件都知道下一个使用者期望的类型。通用的转换器,例如将`String`转换为 XML 文档的转换器,也是高度可重用的。
......@@ -36,13 +36,13 @@
| |如果`ref`属性引用扩展`AbstractMessageProducingHandler`的 Bean(例如框架本身提供的转换器),则通过将输出通道直接注入处理程序来优化配置。,在这种情况下,<br/>,每个`ref`必须是一个单独的 Bean 实例(或者`prototype`-作用域 Bean),或者使用内部的`<bean/>`配置类型。<br/>如果你不小心从多个 bean 引用了相同的消息处理程序,则会出现配置异常。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
当使用 POJO 时,用于转换的方法可能期望入站消息的`Message`类型或有效负载类型。它还可以通过分别使用`@Header``@Headers`参数注释来单独或作为完整映射接受消息头值。方法的返回值可以是任何类型。如果返回值本身是`Message`,则将其传递到变压器的输出通道。
当使用 POJO 时,用于转换的方法可能期望入站消息的`Message`类型或有效负载类型。它还可以通过分别使用`@Header``@Headers`参数注释来单独或作为完整映射接受消息头值。方法的返回值可以是任何类型。如果返回值本身是`Message`,则将其传递到转换器的输出通道。
在 Spring Integration2.0 中,消息转换器的转换方法不再能够返回`null`。返回`null`将导致一个异常,因为消息转换器应该始终被期望将每个源消息转换为有效的目标消息。换句话说,消息转换器不应该被用作消息过滤器,因为它有一个专用的`<filter>`选项。但是,如果你确实需要这种类型的行为(其中组件可能会返回`null`,这不应被视为错误),则可以使用服务激活器。其`requires-reply`值默认为`false`,但可以设置为`true`,以便为`null`抛出异常返回值,就像转换器一样。
#### [](#transformers-and-spring-expression-language-spel)变形金刚与 Spring 表达式语言
#### [](#transformers-and-spring-expression-language-spel)转换器与 Spring 表达式语言
与路由器、聚合器和其他组件一样,在 Spring Integration2.0 中,只要转换逻辑相对简单,变压器也可以从[SPEL 支持](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions)中受益。下面的示例展示了如何使用 SPEL 表达式:
与路由器、聚合器和其他组件一样,在 Spring Integration2.0 中,只要转换逻辑相对简单,转换器也可以从[SPEL 支持](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions)中受益。下面的示例展示了如何使用 SPEL 表达式:
```
<int:transformer input-channel="inChannel"
......@@ -52,9 +52,9 @@
前面的示例在不编写自定义转换器的情况下转换有效负载。我们的有效负载(假定为`String`)是上层的,与当前的时间戳连接在一起,并应用了一些格式。
#### [](#common-transformers)通用变压
#### [](#common-transformers)通用转换
Spring 集成提供了一些变压器实现方式。
Spring 集成提供了一些转换器实现方式。
##### [](#object-to-string-transformer)对象到字符串转换器
......@@ -84,7 +84,7 @@ Spring 集成提供了一些变压器实现方式。
| |当反序列化来自不受信任的源的数据时,你应该考虑添加一个`allow-list`的包和类模式。<br/>默认情况下,所有类都是反序列化的。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
##### [](#object-to-map-and-map-to-object-transformers)`Object`-to-`Map`和`Map`-to-`Object`变形金刚
##### [](#object-to-map-and-map-to-object-transformers)`Object`-to-`Map`和`Map`-to-`Object`转换器
Spring 集成还提供了`Object`-to-`Map``Map`-to-`Object`转换器,它们使用 JSON 序列化和反序列化对象图。对象层次结构被内省到最原始的类型(`String``int`,等等)。这种类型的路径是用 SPEL 描述的,它在转换的`Map`中成为`key`。原语类型成为值。
......@@ -184,7 +184,7 @@ Spring 集成为映射到对象提供了名称空间支持,如下例所示:
| |“ref”和“type”属性是互斥的。<br/>此外,如果使用“ref”属性,则必须指向 Bean 范围内的“prototype”。<br/>否则,将抛出一个`BeanCreationException`。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
从版本 5.0 开始,你可以为`ObjectToMapTransformer`提供自定义的`JsonObjectMapper`——当你需要为空集合的日期或空集合(以及其他用途)提供特殊格式时。有关`JsonObjectMapper`实现的更多信息,请参见[JSON 变形金刚](#json-transformers)
从版本 5.0 开始,你可以为`ObjectToMapTransformer`提供自定义的`JsonObjectMapper`——当你需要为空集合的日期或空集合(以及其他用途)提供特殊格式时。有关`JsonObjectMapper`实现的更多信息,请参见[JSON 转换器](#json-transformers)
##### [](#stream-transformer)流转换器
......@@ -215,7 +215,7 @@ public StreamTransformer streamToString() {
}
```
##### [](#json-transformers)JSON 变形金刚
##### [](#json-transformers)JSON 转换器
Spring 集成提供了对象到 JSON 和 JSON 到对象转换器。以下两个示例展示了如何用 XML 声明它们:
......@@ -341,7 +341,7 @@ Order generateOrder(String productId, @Header("customerName") String customer) {
正如你所看到的,头过滤器的配置非常简单。它是一个典型的端点,具有输入和输出通道以及`header-names`属性。该属性接受需要删除的标头的名称(如果有多个标头,则用逗号分隔)。因此,在前面的示例中,出站消息中不存在名为“lastname”和“state”的头。
#### [](#codec-based-transformers)基于编解码器的变压
#### [](#codec-based-transformers)基于编解码器的转换
[Codec](./codec.html#codec)
......@@ -549,7 +549,7 @@ Spring 集成 2.1 引入了有效负载 Enricher。有效负载 Enricher 定义
如果只需要用静态值来丰富有效负载,则不需要提供`request-channel`属性。
| |增强器是变压器的一种变体。<br/>在许多情况下,你可以使用有效负载 Enricher 或通用 Transformer 实现来向消息有效负载添加额外的数据。<br/>你应该熟悉 Spring Integration 提供的所有具有转换能力的组件,并仔细选择语义上最适合你的业务用例的实现。|
| |增强器是转换器的一种变体。<br/>在许多情况下,你可以使用有效负载 Enricher 或通用 Transformer 实现来向消息有效负载添加额外的数据。<br/>你应该熟悉 Spring Integration 提供的所有具有转换能力的组件,并仔细选择语义上最适合你的业务用例的实现。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
##### [](#payload-enricher-configuration)配置
......@@ -665,15 +665,15 @@ Spring 集成 2.1 引入了有效负载 Enricher。有效负载 Enricher 定义
[存储在图书馆中](https://www.enterpriseintegrationpatterns.com/StoreInLibrary.html)(或 ClaimCheck)模式描述了一种机制,该机制允许你将数据存储在一个众所周知的位置,同时仅维护指向该数据所在位置的指针(ClaimCheck)。你可以将该指针作为新消息的有效负载传递,从而让消息流中的任何组件在需要时立即获得实际数据。这种方法非常类似于认证邮件流程,即你在邮箱中收到一张索赔支票,然后必须去邮局领取你的实际包裹。这也是同样的想法后,在飞机上或在酒店提取行李。
Spring 集成提供了两种类型的索赔检查变压器:
Spring 集成提供了两种类型的索赔检查转换器:
* 进站索赔检查变压
* 进站索赔检查转换
* 出口索赔检查变压
* 出口索赔检查转换
可以使用方便的基于名称空间的机制来配置它们。
#### [](#claim-check-in)进件索赔检查变压
#### [](#claim-check-in)进件索赔检查转换
传入的索赔检查转换器通过将传入的消息存储在其`message-store`属性标识的消息存储区中来转换该消息。以下示例定义了传入的索赔检查转换器:
......@@ -688,7 +688,7 @@ Spring 集成提供了两种类型的索赔检查变压器:
现在,假设在某个时刻你确实需要访问实际的消息。你可以手动访问消息存储区并获取消息的内容,或者可以使用相同的方法(创建一个转换器),但现在你可以使用一个传出的 ClaimCheck 转换器将 ClaimCheck 转换为实际的消息。
下面的清单提供了传入的索赔检查变压器的所有可用参数的概述:
下面的清单提供了传入的索赔检查转换器的所有可用参数的概述:
```
<int:claim-check-in auto-startup="true" (1)
......@@ -712,7 +712,7 @@ Spring 集成提供了两种类型的索赔检查变压器:
|**7**|指定向输出通道发送回复消息时等待的最长时间(以毫秒为单位)。<br/>默认为`-1`—无限期地阻塞。<br/>此属性在`Chain`元素内不可用。<br/>可选。|
|**8**|定义一个 poller。<br/>这个元素在`Chain`元素中是不可用的。<br/>可选的。|
#### [](#claim-check-out)出口索赔检查变压
#### [](#claim-check-out)出口索赔检查转换
一个传出的索赔检查转换器允许你将具有索赔检查有效负载的消息转换为具有原始内容作为其有效负载的消息。
......@@ -725,7 +725,7 @@ Spring 集成提供了两种类型的索赔检查变压器:
在前面的配置中,在`input-channel`上收到的消息应该有一个索赔检查作为其有效负载。出站索赔检查转换器通过在消息存储中查询由提供的索赔检查标识的消息,将其转换为带有原始有效负载的消息。然后,它将新签出的消息发送到`output-channel`
下面的清单提供了即将发出的索赔检查变压器的所有可用参数的概述:
下面的清单提供了即将发出的索赔检查转换器的所有可用参数的概述:
```
<int:claim-check-out auto-startup="true" (1)
......@@ -807,7 +807,7 @@ Spring 集成的 4.2 版本引入了`Codec`抽象。编解码器对`byte[]`之
目前,这是`Codec`的唯一实现,并且它提供了两种`Codec`:
* `PojoCodec`:用于变压
* `PojoCodec`:用于转换
* `MessageCodec`:用于`CodecMessageConverter`
......
此差异已折叠。
# 6. 执行动作
## [](#actions-introduction)6.1。导言
本章向你展示了如何使用`action-state`元素来控制流中某个点上的动作的执行。它还将展示如何使用`decision-state`元素来做出流路由决策。最后,将讨论几个从流中可能的各个点调用操作的示例。
## [](#action-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 会检查结果,以查看是否与声明的向另一种状态的转换匹配。这意味着,如果配置了多个动作,它们将在有序的链中执行,直到其中一个返回一个结果事件,该结果事件与动作状态的状态转换相匹配,而其余的则被忽略。这是责任链模式的一种形式。
动作执行的结果通常是转换出此状态的标准。当前请求上下文中的附加信息也可以作为自定义转换标准的一部分进行测试,该标准允许根据上下文状态推理复杂的转换表达式。
还请注意,一个动作状态就像其他任何状态一样,可以有另外一个输入动作,这些动作从头到尾以列表的形式执行。
## [](#decision-state)6.3。决定状态的定义
使用`decision-state`元素作为 Action-State 的替代选项,以使用方便的 if/else 语法做出路由决策。下面的示例显示了上面的`moreAnswersNeeded`状态,该状态现在实现为一个决策状态,而不是一个动作状态:
```
<decision-state id="moreAnswersNeeded">
<if test="interview.moreAnswersNeeded()" then="answerQuestions" else="finish" />
</decision-state>
```
## [](#action-outcome-events)6.4。行动成果事件映射
动作经常调用普通 Java 对象上的方法。当从动作状态和决策状态调用时,可以使用这些方法返回值来驱动状态转换。由于转换是由事件触发的,因此方法返回值必须首先映射到事件对象。下表描述了如何将常见的返回值类型映射到事件对象:
[](#event-mapping-table)
**表 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>
```
## [](#action-implementations)6.5。动作实现
虽然将动作代码编写为 POJO 逻辑是最常见的,但还有其他几种动作实现选项。有时你需要编写需要访问流上下文的动作代码。你总是可以调用 POJO 并将 FlowRequestContext 作为 EL 变量传递给它。或者,你可以实现`Action`接口或从`MultiAction`基类进行扩展。当你的操作代码和 Spring Web 流 API 之间具有天然的耦合时,这些选项提供了更强的类型安全性。下面是这些方法的示例。
### [](#d5e1045)6.5.1。调用 POJO 动作
```
<evaluate expression="pojoAction.method(flowRequestContext)" />
```
```
public class PojoAction {
public String method(RequestContext context) {
...
}
}
```
### [](#d5e1049)6.5.2。调用自定义操作实现
```
<evaluate expression="customAction" />
```
```
public class CustomAction implements Action {
public Event execute(RequestContext context) {
...
}
}
```
### [](#d5e1053)6.5.3。调用多操作实现
```
<evaluate expression="multiAction.actionMethod1" />
```
```
public class CustomMultiAction extends MultiAction {
public Event actionMethod1(RequestContext context) {
...
}
public Event actionMethod2(RequestContext context) {
...
}
...
}
```
## [](#action-exceptions)6.6。动作例外
动作通常调用封装复杂业务逻辑的服务。这些服务可能会抛出操作代码应该处理的业务异常。
### [](#d5e1060)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";
}
}
}
```
### [](#d5e1065)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();
}
}
}
```
### [](#d5e1071)6.6.3。使用异常处理程序元素
通常,建议在操作中捕获异常并返回驱动标准转换的结果事件,也可以向具有`bean`属性的任何状态类型添加`exception-handler`子元素,该属性引用类型`FlowExecutionExceptionHandler`的 Bean。这是一个高级选项,如果使用不正确,可能会使流执行处于无效状态。将内建`TransitionExecutingFlowExecutionExceptionHandler`作为一个正确实现的示例。
## [](#action-examples)6.7。其他动作执行示例
### [](#action-on-start)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>
```
### [](#action-on-state-entry)6.7.2.进入
下面的示例显示了一个状态条目操作,该操作设置特殊的`fragments`变量,该变量使视图状态呈现其视图的部分片段:
```
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
<on-entry>
<render fragments="hotelSearchForm" />
</on-entry>
</view-state>
```
### [](#action-on-state-exit)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>
```
### [](#on-end)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>
```
### [](#action-on-render)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>
```
### [](#action-on-transition)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>
```
### [](#named-actions)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`
### [](#streaming-actions)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,然后将响应标记为已完成。
### [](#file-upload)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. 定义流
## [](#defining-flows-introduction)3.1。导言
本章从用户部分开始。它展示了如何使用流定义语言实现流。在本章结束时,你应该已经对语言结构有了很好的了解,并且能够编写流定义。
## [](#flow-overview)3.2。什么是流动?
流封装了一个可重用的步骤序列,这些步骤可以在不同的上下文中执行。下面是[加勒特信息架构](http://www.jjg.net/ia/visvocab/)图,其中引用了一个流程,该流程封装了酒店预订过程的步骤:
<img src="images/hotels-site.png" align="middle" />
示出对流程的引用的站点地图
## [](#flow-makeup)3.3。一个典型的流的构成是什么?
在 Spring Web 流中,流由一系列称为“状态”的步骤组成。输入一个状态通常会导致向用户显示一个视图。在该视图中,会发生由状态处理的用户事件。这些事件可以触发向其他状态的转换,从而导致视图导航。
下面的示例显示了上一个图中引用的预订酒店流程的结构:
<img src="images/hotels-site-bookhotel-flow.png" align="middle" />
流程图
## [](#flow-authoring)3.4。流程是如何编写的?
流是由 Web 应用程序开发人员使用一种简单的基于 XML 的流定义语言编写的。本指南的下一步将向你介绍这种语言的元素。
## [](#essential-flow-elements)3.5。基本语言要素
### [](#flow-element)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>
```
流的所有状态都在这个元素中定义。定义的第一个状态成为流的起点。
### [](#view-state-element)3.5.2.view-state
使用`view-state`元素定义呈现视图的流的一个步骤:
```
<view-state id="enterBookingDetails" />
```
按照惯例,视图状态将其 ID 映射到流所在目录中的视图模板。例如,如果流本身位于`/WEB-INF/hotels/booking`目录中,则上面的状态可能呈现`/WEB-INF/hotels/booking/enterBookingDetails.xhtml`
### [](#transition-element)3.5.3.过渡
使用`transition`元素来处理在一个状态中发生的事件:
```
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
```
这些转换驱动视图导航。
### [](#end-state-element)3.5.4.end-state
使用`end-state`元素来定义流结果:
```
<end-state id="bookingCancelled" />
```
当一个流转换到一个结束状态时,它就会终止,并返回结果。
### [](#checkpoint-essential-language-elements)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>
```
## [](#flow-actions)3.6。行动
大多数流需要表达的不仅仅是查看导航逻辑。通常,它们还需要调用应用程序的业务服务或其他操作。
在一个流中,你可以在几个点上执行操作。这些要点是:
* 在流启动时
* 进入国家时
* On View 渲染
* 关于转换执行
* 在国家退出时
* 在流端
动作是用简洁的表达式语言定义的。 Spring 默认情况下,Web 流使用统一的 EL。接下来的几节将介绍用于定义操作的基本语言元素。
### [](#evaluate-element)3.6.1.评估
你最常使用的动作元素是`evaluate`元素。使用`evaluate`元素在流中的某个点计算表达式。使用这个单一标记,你可以在 Spring bean 或任何其他流变量上调用方法。例如:
```
<evaluate expression="entityManager.persist(booking)" />
```
#### [](#evaluate-element-result)分配评估结果
如果表达式返回一个值,该值可以保存在流的数据模型`flowScope`中:
```
<evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels" />
```
#### [](#evaluate-element-result-type)转换求值结果
如果表达式返回一个可能需要转换的值,请使用`result-type`属性指定所需的类型:
```
<evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels"
result-type="dataModel"/>
```
### [](#checkpoint-actions)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 是从一个流输入属性获得的。
## [](#flow-inputoutput)3.7。输入/输出映射
每个流都有一个定义良好的输入/输出契约。流可以在开始时传递输入属性,在结束时返回输出属性。在这方面,调用流在概念上类似于调用具有以下签名的方法:
```
FlowOutcome flowId(Map<String, Object> inputAttributes);
```
...其中`FlowOutcome`具有以下签名:
```
public interface FlowOutcome {
public String getName();
public Map<String, Object> getOutputAttributes();
}
```
### [](#input-element)3.7.1.输入
使用`input`元素声明一个流输入属性:
```
<input name="hotelId" />
```
输入值以属性的名称保存在流作用域中。例如,上面的输入将以`hotelId`的名称保存。
#### [](#input-element-type)声明输入类型
使用`type`属性声明输入属性的类型:
```
<input name="hotelId" type="long" />
```
如果输入值与声明的类型不匹配,将尝试进行类型转换。
#### [](#input-element-value)分配一个输入值
使用`value`属性指定一个表达式,将输入值分配给:
```
<input name="hotelId" value="flowScope.myParameterObject.hotelId" />
```
如果表达式的值类型可以确定,那么如果没有指定`type`属性,则该元数据将用于类型强制。
#### [](#input-element-required)根据需要标记输入
使用`required`属性强制输入不为空或空:
```
<input name="hotelId" type="long" value="flowScope.hotelId" required="true" />
```
### [](#output-element)3.7.2.输出
使用`output`元素声明一个流输出属性。输出属性在表示特定流结果的结束状态中声明。
```
<end-state id="bookingConfirmed">
<output name="bookingId" />
</end-state>
```
输出值是在属性的名称下从流作用域获得的。例如,上面的输出将被分配`bookingId`变量的值。
#### [](#output-element-value)指定输出值的源
使用`value`属性表示特定的输出值表达式:
```
<output name="confirmationNumber" value="booking.confirmationNumber" />
```
### [](#checkpoint-input-output)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`输出属性。
## [](#flow-variables)3.8。变量
流可以声明一个或多个实例变量。这些变量在流开始时被分配。当流恢复时,变量持有的任何`@Autowired`瞬态引用也会重新布线。
### [](#var-element)3.8.1.var
使用`var`元素声明一个流变量:
```
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>
```
确保变量的类实现`java.io.Serializable`,因为实例状态在流请求之间被保存。
## [](#scopes)3.9。可变作用域
Web 流可以将变量存储在以下几个范围中的一个:
### [](#d5e398)3.9.1。流动范围
流作用域在流开始时被分配,在流结束时被销毁。对于默认的实现,存储在流作用域中的任何对象都需要是可序列化的。
### [](#d5e401)3.9.2。视图作用域
`view-state`进入时,视图作用域被分配,而当状态退出时,视图作用域被销毁。视图作用域是*只有*可从`view-state`中引用的。对于默认实现,存储在视图作用域中的任何对象都需要是可序列化的。
### [](#d5e407)3.9.3。请求范围
当一个流被调用时,请求作用域被分配,当流返回时,请求作用域被销毁。
### [](#d5e410)3.9.4。闪光范围
当一个流开始时,flash 作用域被分配,在每个视图呈现后被清除,当该流结束时被销毁。对于默认的实现,存储在 Flash 作用域中的任何对象都需要是可序列化的。
### [](#d5e413)3.9.5。会话范围
对话范围在顶级流启动时被分配,在顶级流结束时被销毁。会话范围由顶级流及其所有子流共享。对于默认的实现,对话范围的对象存储在 HTTP会话中,并且通常应该是可序列化的,以考虑典型的会话复制。
要使用的范围通常是在上下文中确定的,例如,取决于定义变量的位置--在流定义的开始(流范围),在视图状态(视图范围)内,等等。在其他情况下,例如在 EL 表达式和 Java 代码中,需要显式地指定它。随后的章节解释了如何做到这一点。
## [](#calling-subflows)3.10。调用子流
一个流可以调用另一个流作为子流。流将等待直到子流返回,然后对子流结果做出响应。
### [](#subflow-state-element)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`结果时,新的客人将被添加到预订的客人列表中。
#### [](#subflow-state-element-input)传递一个子流输入
使用`input`元素将输入传递给子流:
```
<subflow-state id="addGuest" subflow="createGuest">
<input name="booking" />
<transition to="reviewBooking" />
</subflow-state>
```
#### [](#subflow-state-element-output)映射子流输出
当一个子流完成时,它的结束状态 ID 将返回给调用流,作为用于继续导航的事件。
子流还可以在结果转换中创建调用流可以引用的输出属性,如下所示:
```
<transition on="guestCreated" to="reviewBooking">
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
</transition>
```
在上面的示例中,`guest`是由`guestCreated`结果返回的输出属性的名称。
### [](#checkpoint-subflow)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. 表达式语言
## [](#el-introduction)4.1。导言
Web Flow 使用 EL 访问其数据模型并调用操作。本章将让你熟悉 EL 语法、配置和你可以从流定义中引用的特殊 EL 变量。
EL 用于流中的许多事情,包括:
1. 访问客户端数据,例如声明流输入或引用请求参数。
2. 访问 Web 流中的`RequestContext`中的数据,例如`flowScope``currentEvent`
3. 通过操作在 Spring 管理的对象上调用方法。
4. 解析表达式,如状态转换条件、子流 ID 和视图名称。
EL 还用于将表单参数绑定到模型对象,并从模型对象的属性反向呈现格式化的表单字段。然而,当使用 Web 流与 JSF 一起使用时,这种方法并不适用,在这种情况下,标准的 JSF 组件 Lifecyle 就适用了。
### [](#el-types)4.1.1。表达式类型
要理解的一个重要概念是,在 Web 流中有两种类型的表达式:标准表达式和模板表达式。
#### [](#el-types-eval)标准表达式
第一种也是最常见的一种表达式是*标准表达式*。这样的表达式由 EL 直接求值,不需要用`#{}`这样的分隔符括起来。例如:
```
<evaluate expression="searchCriteria.nextPage()" />
```
上面的表达式是一个标准表达式,在计算`searchCriteria`变量时调用`nextPage`方法。如果你试图将这个表达式包含在一个特殊的分隔符中,比如`#{}`,你将得到一个`IllegalArgumentException`。在这种情况下,分隔符被视为多余的。`expression`属性唯一可接受的值是一个表达式字符串。
#### [](#el-types-template)模板表达式
第二种表达式是*模板表达式*。模板表达式允许将文本与一个或多个标准表达式混合在一起。每个标准表达式块都显式地被`#{}`分隔符包围。例如:
```
<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 中使用等效的快捷方式)在键入特定的流定义属性时访问可用的文档。| |
## [](#el-language-choices)4.2。EL 实现
### [](#el-spring-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`分发。
### [](#el-unified-el)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"/>
```
## [](#el-portability)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)部分。
## [](#el-variables)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>
```
以下是可以在流定义中引用的隐式变量列表:
### [](#el-variable-flowScope)4.4.1.flowscope
使用`flowScope`分配流变量。流作用域在流开始时被分配,在流结束时被销毁。对于默认的实现,存储在流作用域中的任何对象都需要是可序列化的。
```
<evaluate expression="searchService.findHotel(hotelId)" result="flowScope.hotel" />
```
### [](#el-variable-viewScope)4.4.2.viewscope
使用`viewScope`分配一个视图变量。当`view-state`进入时,视图作用域被分配,而当状态退出时,视图作用域被销毁。视图作用域是*只有*可从`view-state`中引用的。对于默认实现,存储在视图作用域中的任何对象都需要是可序列化的。
```
<on-render>
<evaluate expression="searchService.findHotels(searchCriteria)" result="viewScope.hotels"
result-type="dataModel" />
</on-render>
```
### [](#el-variable-requestScope)4.4.3.RequestScope
使用`requestScope`分配一个请求变量。当一个流被调用时,请求作用域被分配,当流返回时,请求作用域被销毁。
```
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
```
### [](#el-variable-flashScope)4.4.4.FlashScope
使用`flashScope`分配一个 flash 变量。当一个流开始时,flash 作用域被分配,在每个视图呈现后被清除,当该流结束时被销毁。对于默认的实现,存储在 Flash 作用域中的任何对象都需要是可序列化的。
```
<set name="flashScope.statusMessage" value="'Booking confirmed'" />
```
### [](#el-variable-conversationScope)4.4.5.ConversationScope
使用`conversationScope`分配一个会话变量。对话范围在顶级流启动时被分配,在顶级流结束时被销毁。会话范围由顶级流及其所有子流共享。对于默认的实现,对话范围的对象存储在 HTTP会话中,并且通常应该是可序列化的,以考虑典型的会话复制。
```
<evaluate expression="searchService.findHotel(hotelId)" result="conversationScope.hotel" />
```
### [](#el-variable-requestParameters)4.4.6.requestParameters
使用`requestParameters`访问客户端请求参数:
```
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
```
### [](#el-variable-currentEvent)4.4.7.currentEvent
使用`currentEvent`访问当前`Event`的属性:
```
<evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
```
### [](#el-variable-currentUser)4.4.8.currentuser
使用`currentUser`访问经过身份验证的`Principal`:
```
<evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
result="flowScope.booking" />
```
### [](#el-variable-messageContext)4.4.9.messagecontext
使用`messageContext`访问上下文以检索和创建流执行消息,包括错误和成功消息。有关更多信息,请参见`MessageContext`Javadocs。
```
<evaluate expression="bookingValidator.validate(booking, messageContext)" />
```
### [](#el-variable-resourceBundle)4.4.10.ResourceBundle
使用`resourceBundle`访问消息资源。
```
<set name="flashScope.successMessage" value="resourceBundle.successMessage" />
```
### [](#el-variable-requestContext)4.4.11.FlowRequestContext
使用`flowRequestContext`访问`RequestContext`API,这是当前流请求的表示。有关更多信息,请参见 API Javadocs。
### [](#el-variable-flowExecutionContext)4.4.12.FlowExecutionContext
使用`flowExecutionContext`访问`FlowExecutionContext`API,这是当前流状态的表示。有关更多信息,请参见 API Javadocs。
### [](#el-variable-flowExecutionUrl)4.4.13.flowexecutionurl
使用`flowExecutionUrl`访问当前流执行视图状态的上下文相关 URI。
### [](#el-variable-externalContext)4.4.14.ExternalContext
使用`externalContext`访问客户端环境,包括用户会话属性。有关更多信息,请参见`ExternalContext`API Javadocs。
```
<evaluate expression="searchService.suggestHotels(externalContext.sessionMap.userProfile)"
result="viewScope.hotels" />
```
## [](#el-scope-searching)4.5。范围搜索算法
正如本节前面提到的,在一个流作用域中分配变量时,需要引用该作用域。例如:
```
<set name="requestScope.hotelId" value="requestParameters.id" type="long" />
```
当只访问其中一个作用域中的变量时,引用该作用域是可选的。例如:
```
<evaluate expression="entityManager.persist(booking)" />
```
当没有指定作用域时,就像上面使用`booking`一样,使用作用域搜索算法。该算法将在请求、闪存、视图、流和会话范围中查找变量。如果没有找到这样的变量,将抛出一个`EvaluationException`
\ No newline at end of file
此差异已折叠。
# 9. 流继承
## [](#flow-inheritance-introduction)9.1。导言
流继承允许一个流继承另一个流的配置。继承可以在流和状态两个级别上发生。一个常见的用例是父流定义全局转换和异常处理程序,然后每个子流都可以继承这些设置。
为了找到父流,必须像其他任何流一样将其添加到`flow-registry`中。
## [](#flow-inheritance-java-comparison)9.2。流继承和 Java 继承一样吗?
流继承类似于 Java 继承,因为在父级中定义的元素是通过子级公开的,但是,有一些关键的区别。
子流不能覆盖父流中的元素。父流和子流之间的类似元素将被合并。父流中的唯一元素将被添加到子流中。
子流可以从多个父流继承。Java 继承仅限于一个类。
## [](#flow-inheritance-levels)9.3。流继承的类型
### [](#flow-inheritance-level-flow)9.3.1。流级继承
流级继承由`flow`元素上的`parent`属性定义。属性包含一个用逗号分隔的流标识符的列表。子流将按列出的顺序从每个父流继承,将元素和内容添加到结果流中。来自第一次合并的结果流将被认为是第二次合并中的子流,依此类推。
```
<flow parent="common-transitions, common-states">
```
### [](#flow-inheritance-level-state)9.3.2。状态级继承
状态级继承类似于流级继承,只是从父级继承了一个状态,而不是整个流。
与流继承不同,只允许有一个父代。此外,还必须定义要继承的流状态的标识符。流的标识符和该流中的状态由一个 # 分隔。
父状态和子状态必须是同一类型的。例如,一个视图状态不能从一个结束状态继承,只能从另一个视图状态继承。
```
<view-state id="child-state" parent="parent-flow#parent-view-state">
```
|[[note](images/note.png)|Note|
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---|
|流级继承的意图是将公共状态定义为<br/>添加到多个流定义中并在多个流定义之间共享,而状态级继承的意图<br/>是从单个<br/>父状态扩展并合并。流级继承非常适合组合<br/>和多重继承,但是在状态级,你仍然只能从单个父状态继承<br/>。| |
## [](#flow-inheritance-abstract)9.4。抽象流
通常,父流的设计不是为了直接执行。为了保护这些流不被运行,它们可以标记为`abstract`。如果一个抽象流试图运行,将抛出一个`FlowBuilderException`
```
<flow abstract="true">
```
## [](#flow-inheritance-algorithm)9.5。继承算法
当一个子流继承自它的父流时,本质上发生的情况是父流和子流合并在一起以创建一个新的流。对于 Web 流定义语言中的每个元素都有规则,这些规则控制该特定元素如何合并。
有两种类型的元素:*可合并**不可合并*。如果元素相似,可合并元素将始终尝试合并在一起。父流或子流中的不可合并元素将始终完整地包含在结果流中。它们不会作为合并过程的一部分进行修改。
|[[note](images/note.png)|Note|
|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---|
|到父流中的外部资源的路径应该是绝对的。<br/>当两个流合并时,相对路径将中断,除非父流和子流位于同一目录中。<br/>一旦合并,父流中的所有相对路径将变得相对于子流。| |
### [](#flow-inheritance-algorithm-mergeable)9.5.1。可合并元素
如果元素的类型相同,且其键控属性相同,则父元素的内容将与子元素合并。合并算法将继续合并合并父元素和子元素的每个子元素。否则,父元素将作为一个新元素添加到子元素中。
在大多数情况下,添加的父流中的元素将在子流中的元素之后添加。此规则的例外包括将在开始时添加的动作元素(评估、渲染 和 集)。这允许子操作使用父操作的结果。
可合并的要素是:
* 动作状态:ID
* 属性:Name
* 决策状态:ID
* 结束状态:ID
* 流程:总是合并
* 如果:测试
* 端:总是合并
* 入门:总是合并
* On-Exit:总是合并
* On-Render:总是合并
* 启动:总是合并
* 输入:Name
* 输出:Name
* 安全:属性
* 子流-状态:id
* 转换:ON 和 ON-Exception
* 视图-状态:id
### [](#flow-inheritance-nonmergeable)9.5.2。不可合并元素
不可合并的要素是:
* Bean-进口
* evaluate
* 异常处理程序
* 持久性-上下文
* render
* set
* VAR
\ No newline at end of file
# 7. 流管理的持久性
## [](#flow-managed-persistence-introduction)7.1。导言
大多数应用程序以某种方式访问数据。许多修改由多个用户共享的数据,因此需要事务性数据访问属性。它们通常将关系数据集转换成域对象,以支持应用程序处理。Web 流提供了“流管理的持久性”,其中流可以为你创建、提交和关闭对象持久性上下文。Web 流集成了 Hibernate 和 JPA 对象持久性技术。
除了流管理的持久性之外,还有一种模式是在应用程序的服务层中完全封装持久性 CECONTEXT 管理。在这种情况下,Web 层不涉及持久性,而是完全与传递给服务层并由服务层返回的分离对象一起工作。本章将重点讨论流管理的持久性,探讨如何以及何时使用该特性。
## [](#flowScopedPersistenceContext)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。这样的数据访问操作应该总是在非事务或只读事务中执行,以保持中间编辑的隔离。
## [](#flow-managed-persistence-propagation)7.3。流管理的持久性和子流
管理的流`PersistenceContext`被自动扩展(传播)到子流,假设该子流还具有`<perstistence-context/>`变量。当子流重新使用其父进程启动的`PersistenceContext`时,当达到结束状态时,它会忽略提交标志,从而将最终决定(提交或不提交)推迟到其父进程。
\ No newline at end of file
# 8. 保障资金流动
## [](#flow-security-introduction)8.1。导言
安全性对于任何应用程序都是一个重要的概念。最终用户不应该仅仅通过猜测 URL 就能够访问网站的任何部分。网站的敏感区域必须确保只处理授权的请求。 Spring 安全性是一种经过验证的安全平台,可以在多个级别上与你的应用程序集成。本节将重点讨论保护流执行的问题。
## [](#flow-security-how-to)8.2。我如何确保一个流程?
确保流执行的安全性是一个三步过程:
* 使用身份验证和授权规则配置 Spring 安全性
* 用受保护的元素注释流定义,以定义安全规则
* 添加 SecurityFlowExecutionListener 以处理安全规则。
这些步骤中的每一个都必须完成,否则将不应用流安全规则。
## [](#flow-security-secured-element)8.3。安全元件
安全元素指定其包含的元素在完全进入之前应用授权检查。这可能不会在每个被保护的流执行阶段中发生一次。
可以保证流执行的三个阶段:流、状态和转换。在每种情况下,安全元素的语法都是相同的。固定元件位于它所固定的元件内部。例如,要保护状态,受保护元素直接发生在该状态内:
```
<view-state id="secured-view">
<secured attributes="ROLE_USER" />
...
</view-state>
```
### [](#flow-security-secured-element-attributes)8.3.1。安全属性
`attributes`属性是 Spring 安全授权属性的逗号分隔列表。通常,这些都是特定的安全角色。 Spring 安全访问决策管理器将这些属性与用户授予的属性进行比较。
```
<secured attributes="ROLE_USER" />
```
默认情况下,基于角色的访问决策管理器用于确定是否允许用户访问。如果你的应用程序不使用授权角色,则需要重写此内容。
### [](#flow-security-secured-element-match)8.3.2。匹配类型
有两种类型的匹配可用:`any``all`。如果至少向用户授予了一个所需的安全属性,则允许访问。All,只有当每个所需的安全属性都被授予给用户时,才允许访问。
```
<secured attributes="ROLE_USER, ROLE_ANONYMOUS" match="any" />
```
这个属性是可选的。如果未定义,默认值为`any`
只有在使用默认的访问决策管理器时,才会尊重`match`属性。
## [](#flow-security-listener)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 捕获,并用于提示用户进行身份验证。重要的是,允许此异常在执行堆栈中不受约束地向上移动,否则可能不会提示最终用户进行身份验证。
### [](#flow-security-listener-adm)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>
```
## [](#flow-security-configuration)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 级别都需要配置。
### [](#flow-security-configuration-spring)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>
```
### [](#flow-security-configuration-web)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. 导言
## [](#manual-overview)1.1。这本指南涵盖的内容
本指南涵盖了 Spring Web 流程的所有方面。它涵盖了在最终用户应用程序中实现流以及使用功能集的工作。它还包括扩展框架和整体架构模型。
## [](#system-requirements)1.2。运行 Web 流需要什么
Java1.8 或更高。
Spring 5.0 或更高。
## [](#resources)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)
## [](#jars-mvn-central)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>
```
## [](#d5e66)1.5。如何访问夜间版本和里程碑版本
可以使用 Maven 获得 Web 流开发分支的夜间快照。这些快照构建对于在下一个版本发布之前测试你所依赖的修补程序非常有用,并且为你提供了一种方便的方式来提供有关修补程序是否满足你的需求的反馈。
### [](#d5e69)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 流。
此差异已折叠。
# 12. Spring JavaScript 快速引用
## [](#spring-js-introduction)12.1。导言
*spring-js-resources*模块是一个旧模块,不再推荐使用,但仍作为可选模块提供,以实现向后兼容。其最初的目标是提供一个客户端编程模型,用于通过行为和 Ajax 远程处理逐步增强 Web 页面。
Spring JS API 的使用在[样品库](https://github.com/spring-projects/spring-webflow-samples)中进行了演示。
## [](#spring-js-resource-servlet)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>
```
## [](#spring-js-includes)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" />" />
```
## [](#spring-js-decorations)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>
```
## [](#spring-js-ajax)12.5。处理 Ajax 请求
Spring JavaScript 的客户端 Ajax 响应处理建立在从服务器接收“碎片”的概念上。这些片段只是标准的 HTML,旨在替换现有页面的部分内容。服务器上所需的关键部分是一种确定需要将完整响应中的哪些部分拉出以进行部分呈现的方法。
为了能够呈现完整响应的部分片段,必须使用模板化技术构建完整响应,该模板化技术允许使用组合来构造响应,并且组合的成员部分可以被引用并单独呈现。 Spring JavaScript 提供了一些简单的 Spring MVC 扩展,它们利用磁贴来实现这一点。理论上,同样的技术可以用于任何支持合成的模板化系统。
Spring JavaScript 的 Ajax 远程处理功能是建立在这样一种理念之上的:Ajax 请求的核心处理代码不应与标准的浏览器请求不同,因此,在代码中不需要直接提供有关 Ajax 请求的特殊知识,并且可以对两种类型的请求使用相同的方法。
### [](#custom-ajax-handler)12.5.1。提供特定于库的 AjaxHandler
将各种 Ajax 库与 Web 流的 Ajax 感知行为集成在一起的关键接口是`org.springframework.js.AjaxHandler`。默认情况下配置了`SpringJavascriptAjaxHandler`,该配置能够检测通过 Spring JS 客户端 API 提交的 Ajax 请求,并且能够在需要重定向的情况下做出适当响应。为了集成不同的 Ajax 库(无论是纯 JavaScript 库,还是更高级别的抽象,例如支持 Ajax 的 JSF 组件库),可以将自定义的`AjaxHandler`注入到`FlowHandlerAdapter``FlowController`中。
### [](#spring-js-ajax-mvc)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”。
### [](#spring-js-ajax-mvc-webflow)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
此差异已折叠。
此差异已折叠。
# 14. 测试流程
## [](#testing-introduction)14.1。导言
本章向你展示了如何测试流。
## [](#extending-abstractflowexecutiontest)14.2。扩展 AbstractXMLFlowExecutionTests
要测试基于 XML 的流定义的执行情况,请扩展`AbstractXmlFlowExecutionTests`:
```
public class BookingFlowExecutionTests extends AbstractXmlFlowExecutionTests {
}
```
## [](#override-getResource)14.3。指定要测试的流的路径
至少,你必须重写`getResource(FlowDefinitionResourceFactory)`才能返回你希望测试的流的路径:
```
@Override
protected FlowDefinitionResource getResource(FlowDefinitionResourceFactory resourceFactory) {
return resourceFactory.createFileResource("src/main/webapp/WEB-INF/hotels/booking/booking.xml");
}
```
## [](#override-configureFlowBuilderContext)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")
};
}
```
## [](#testing-flowstartup)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);
}
```
断言通常会验证流处于你期望的正确状态。
## [](#testing-flowevents)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");
}
```
## [](#testing-mockingsubflows)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
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册