提交 e8ecbe51 编写于 作者: dallascao's avatar dallascao

yellow MT translation done

上级 9b44f1a3
此差异已折叠。
此差异已折叠。
# 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
# 11. Spring MVC 集成
## [](#spring-mvc-introduction)11.1。导言
本章展示了如何将 Web 流集成到 Spring MVC Web 应用程序中。对于具有 Web 流的 Spring MVC,`booking-mvc`示例应用程序是一个很好的参考。这个应用程序是一个简化的旅游网站,允许用户搜索和预订酒店房间。
## [](#spring-mvc-config-web.xml)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>
```
## [](#spring-mvc-config-spring-url-mapping)11.3。向流量调度
`DispatcherServlet`将对应用程序资源的请求映射到处理程序。流是处理程序的一种类型。
### [](#d5e1532)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>
```
### [](#d5e1537)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”响应。
### [](#d5e1545)11.3.3。流程处理工作流程
当找到一个有效的流映射时,`FlowHandlerAdapter`会根据 HTTP 请求提供的信息,计算出是启动该流的新执行,还是恢复现有的执行。有许多与适配器使用的流执行的启动和恢复相关的默认值:
* HTTP 请求参数在所有初始流执行的输入映射中都是可用的。
* 当流执行结束而未发送最终响应时,默认处理程序将尝试在同一请求中启动新的执行。
* 未处理的异常将传播到 Dispatcher,除非该异常是 NosuchFlowExecutionException 异常。默认处理程序将尝试通过重新执行 NosuchFlowExecutionException 来恢复 NosuchFlowExecutionException。
有关更多信息,请参阅`FlowHandlerAdapter`的 API 文档。你可以通过子类化或实现你自己的 Flowhandler(将在下一节中讨论)来覆盖这些默认值。
## [](#spring-mvc-config-flow-handlers)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 基础结构中。
### [](#spring-mvc-flow-handler-example)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`结果将导致重定向,以显示新的预订。任何其他结果将重定向回酒店索引页面。
### [](#d5e1600)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 将处理流程执行结果并将其重定向到适当的控制器。
### [](#spring-mvc-flow-handler-redirects)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"`
## [](#spring-mvc-config-spring-view-resolution)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。
## [](#spring-mvc-resuming-on-event)11.6。从视图发送事件信号
当流进入视图状态时,它会暂停,将用户重定向到其执行 URL,并等待用户事件恢复。事件通常通过激活按钮、链接或其他用户界面命令来发出信号。事件如何在服务器端进行解码取决于所使用的视图技术。本节展示了如何从基于 HTML 的视图中触发事件,这些视图是由 JSP、Velocity 或 Freemarker 等模板引擎生成的。
### [](#webflow-event-named-html-button)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`。当有几个不同的事件可以从相同的形式发出信号时,应该考虑这种样式。
### [](#webflow-event-hidden-parameter)11.6.2。使用隐藏的 HTML 表单参数来表示事件
下面的示例显示了一个表单,该表单在提交时表示`proceed`事件:
```
<input type="submit" value="Proceed" />
<input type="hidden" name="_eventId" value="proceed" />
```
在这里,WebFlow 只检测特殊的`_eventId`参数,并将其值用作事件 ID。只有当窗体上有一个事件可以用信号表示时,才应该考虑这种样式。
### [](#webflow-event-link)11.6.3。使用 HTML 链接发出事件信号
下面的示例显示了一个链接,该链接在激活时发出`cancel`事件的信号:
```
<a href="${flowExecutionUrl}&_eventId=cancel">Cancel</a>
```
触发一个事件会导致一个 HTTP 请求被发送回服务器。在服务器端,流处理从其当前视图状态中解码事件。这个解码过程的工作方式是特定于视图实现的。回想一下 Spring MVC 视图实现只是查找一个名为`_eventId`的请求参数。如果没有找到`_eventId`参数,则视图将查找一个以`_eventId_`开头的参数,并将剩余的子字符串用作事件 ID。如果两种情况都不存在,则不会触发流事件。
## [](#spring-mvc-embedded-flow)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 请求,并且只呈现更新显示该流的页面部分所需的内容。
### [](#spring-mvc-embedded-flow-alternatives)11.7.1。嵌入式模式与默认重定向行为
默认情况下,WebFlow 在进入每个视图状态时都会进行客户端重定向。但是,如果在 Ajax 请求期间保持相同的视图状态(例如,没有“to”属性的转换),那么就不会有客户端重定向。这种行为对于 Spring Web Flow2 用户应该非常熟悉。它适用于支持浏览器后退按钮的顶级流,同时还可以利用 Ajax 和部分呈现,用于在表单验证、分页槽搜索结果等视图中保持不变的用例。但是,在转换到新的视图状态之后,总是会进行客户端重定向。这使得不可能在页面或模态对话框中嵌入流并执行多个视图状态,而不会导致整页刷新。因此,如果你的用例需要嵌入一个流,你可以在“嵌入”模式中启动它。
### [](#spring-mvc-embedded-flow-examples)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
```
## [](#spring-mvc-flash-output)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
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册