# 3. 定义流

## 3.1.导言

本章从用户部分开始。它展示了如何使用流定义语言实现流。在本章结束时，你应该已经对语言结构有了很好的了解，并且能够编写流定义。

## 3.2.什么是流动？

流封装了一个可重用的步骤序列，这些步骤可以在不同的上下文中执行。下面是[加勒特信息架构](http://www.jjg.net/ia/visvocab/)图，其中引用了一个流程，该流程封装了酒店预订过程的步骤：

<img src="images/hotels-site.png" align="middle" />

示出对流程的引用的站点地图

## 3.3.一个典型的流的构成是什么？

在 Spring Web 流中，流由一系列称为“状态”的步骤组成。输入一个状态通常会导致向用户显示一个视图。在该视图中，会发生由状态处理的用户事件。这些事件可以触发向其他状态的转换，从而导致视图导航。

下面的示例显示了上一个图中引用的预订酒店流程的结构：

<img src="images/hotels-site-bookhotel-flow.png" align="middle" />

流程图

## 3.4.流程是如何编写的？

流是由 Web 应用程序开发人员使用一种简单的基于 XML 的流定义语言编写的。本指南的下一步将向你介绍这种语言的元素。

## 3.5.基本语言要素

### 3.5.1.流量

每个流都以以下根元素开始：

```
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow.xsd">

</flow>
			
```

流的所有状态都在这个元素中定义。定义的第一个状态成为流的起点。

### 3.5.2.view-state

使用`view-state`元素定义呈现视图的流的一个步骤：

```
<view-state id="enterBookingDetails" />
			
```

按照惯例，视图状态将其 ID 映射到流所在目录中的视图模板。例如，如果流本身位于`/WEB-INF/hotels/booking`目录中，则上面的状态可能呈现`/WEB-INF/hotels/booking/enterBookingDetails.xhtml`。

### 3.5.3.过渡

使用`transition`元素来处理在一个状态中发生的事件：

```
<view-state id="enterBookingDetails">
    <transition on="submit" to="reviewBooking" />
</view-state>
			
```

这些转换驱动视图导航。

### 3.5.4.end-state

使用`end-state`元素来定义流结果：

```
<end-state id="bookingCancelled" />
			
```

当一个流转换到一个结束状态时，它就会终止，并返回结果。

### 3.5.5.检查点：基本语言元素

使用三个元素`view-state`、`transition`和`end-state`，你可以快速表示视图导航逻辑。团队通常在添加流行为之前就这样做了，这样他们就可以首先专注于与最终用户一起开发应用程序的用户界面。下面是一个样例流程，它使用以下元素实现了它的视图导航逻辑：

```
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow.xsd">

    <view-state id="enterBookingDetails">
        <transition on="submit" to="reviewBooking" />
    </view-state>

    <view-state id="reviewBooking">
        <transition on="confirm" to="bookingConfirmed" />
        <transition on="revise" to="enterBookingDetails" />
        <transition on="cancel" to="bookingCancelled" />
    </view-state>

    <end-state id="bookingConfirmed" />

    <end-state id="bookingCancelled" />

</flow>
			
```

## 3.6.行动

大多数流需要表达的不仅仅是查看导航逻辑。通常，它们还需要调用应用程序的业务服务或其他操作。

在一个流中，你可以在几个点上执行操作。这些要点是：

* 在流启动时

* 进入国家时

* On View 渲染

* 关于转换执行

* 在国家退出时

* 在流端

动作是用简洁的表达式语言定义的。 Spring 默认情况下，Web 流使用统一的 EL。接下来的几节将介绍用于定义操作的基本语言元素。

### 3.6.1.评估

你最常使用的动作元素是`evaluate`元素。使用`evaluate`元素在流中的某个点计算表达式。使用这个单一标记，你可以在 Spring bean 或任何其他流变量上调用方法。例如：

```
<evaluate expression="entityManager.persist(booking)" />
			
```

#### 分配评估结果

如果表达式返回一个值，该值可以保存在流的数据模型`flowScope`中：

```
<evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels" />
				
```

#### 转换求值结果

如果表达式返回一个可能需要转换的值，请使用`result-type`属性指定所需的类型：

```
<evaluate expression="bookingService.findHotels(searchCriteria)" result="flowScope.hotels"
          result-type="dataModel"/>
				
```

### 3.6.2.检查点：流操作

现在查看示例预订流程，并添加以下操作：

```
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow.xsd">

    <input name="hotelId" />

    <on-start>
        <evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
                  result="flowScope.booking" />
    </on-start>

    <view-state id="enterBookingDetails">
        <transition on="submit" to="reviewBooking" />
    </view-state>

    <view-state id="reviewBooking">
        <transition on="confirm" to="bookingConfirmed" />
        <transition on="revise" to="enterBookingDetails" />
        <transition on="cancel" to="bookingCancelled" />
    </view-state>

    <end-state id="bookingConfirmed" />

    <end-state id="bookingCancelled" />

</flow>
			
```

这个流现在开始时在流范围内创建一个 Booking 对象。要预订的酒店的 ID 是从一个流输入属性获得的。

## 3.7.输入/输出映射

每个流都有一个定义良好的输入/输出契约。流可以在开始时传递输入属性，在结束时返回输出属性。在这方面，调用流在概念上类似于调用具有以下签名的方法：

```
FlowOutcome flowId(Map<String, Object> inputAttributes);
		
```

...其中`FlowOutcome`具有以下签名：

```
public interface FlowOutcome {
   public String getName();
   public Map<String, Object> getOutputAttributes();
}
		
```

### 3.7.1.输入

使用`input`元素声明一个流输入属性：

```
<input name="hotelId" />
			
```

输入值以属性的名称保存在流作用域中。例如，上面的输入将以`hotelId`的名称保存。

#### 声明输入类型

使用`type`属性声明输入属性的类型：

```
<input name="hotelId" type="long" />
				
```

如果输入值与声明的类型不匹配，将尝试进行类型转换。

#### 分配一个输入值

使用`value`属性指定一个表达式，将输入值分配给：

```
<input name="hotelId" value="flowScope.myParameterObject.hotelId" />
				
```

如果表达式的值类型可以确定，那么如果没有指定`type`属性，则该元数据将用于类型强制。

#### 根据需要标记输入

使用`required`属性强制输入不为空或空：

```
<input name="hotelId" type="long" value="flowScope.hotelId" required="true" />
				
```

### 3.7.2.输出

使用`output`元素声明一个流输出属性。输出属性在表示特定流结果的结束状态中声明。

```
<end-state id="bookingConfirmed">
    <output name="bookingId" />
</end-state>
			
```

输出值是在属性的名称下从流作用域获得的。例如，上面的输出将被分配`bookingId`变量的值。

#### 指定输出值的源

使用`value`属性表示特定的输出值表达式：

```
<output name="confirmationNumber" value="booking.confirmationNumber" />
				
```

### 3.7.3.检查点：输入/输出映射

现在查看带有输入/输出映射的样例预订流程：

```
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow.xsd">

    <input name="hotelId" />

    <on-start>
        <evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
                  result="flowScope.booking" />
    </on-start>

    <view-state id="enterBookingDetails">
        <transition on="submit" to="reviewBooking" />
    </view-state>

    <view-state id="reviewBooking">
        <transition on="confirm" to="bookingConfirmed" />
        <transition on="revise" to="enterBookingDetails" />
        <transition on="cancel" to="bookingCancelled" />
    </view-state>

    <end-state id="bookingConfirmed" >
        <output name="bookingId" value="booking.id"/>
    </end-state>

    <end-state id="bookingCancelled" />

</flow>
			
```

流现在接受`hotelId`输入属性，并在确认新预订时返回`bookingId`输出属性。

## 3.8.变量

流可以声明一个或多个实例变量。这些变量在流开始时被分配。当流恢复时，变量持有的任何`@Autowired`瞬态引用也会重新布线。

### 3.8.1.var

使用`var`元素声明一个流变量：

```
<var name="searchCriteria" class="com.mycompany.myapp.hotels.search.SearchCriteria"/>
			
```

确保变量的类实现`java.io.Serializable`，因为实例状态在流请求之间被保存。

## 3.9.可变作用域

Web 流可以将变量存储在以下几个范围中的一个：

### 3.9.1.流动范围

流作用域在流开始时被分配，在流结束时被销毁。对于默认的实现，存储在流作用域中的任何对象都需要是可序列化的。

### 3.9.2.视图作用域

当`view-state`进入时，视图作用域被分配，而当状态退出时，视图作用域被销毁。视图作用域是*只有*可从`view-state`中引用的。对于默认实现，存储在视图作用域中的任何对象都需要是可序列化的。

### 3.9.3.请求范围

当一个流被调用时，请求作用域被分配，当流返回时，请求作用域被销毁。

### 3.9.4.闪光范围

当一个流开始时，flash 作用域被分配，在每个视图呈现后被清除，当该流结束时被销毁。对于默认的实现，存储在 Flash 作用域中的任何对象都需要是可序列化的。

### 3.9.5.会话范围

对话范围在顶级流启动时被分配，在顶级流结束时被销毁。会话范围由顶级流及其所有子流共享。对于默认的实现，对话范围的对象存储在 HTTP会话中，并且通常应该是可序列化的，以考虑典型的会话复制。

要使用的范围通常是在上下文中确定的，例如，取决于定义变量的位置--在流定义的开始（流范围），在视图状态（视图范围）内，等等。在其他情况下，例如在 EL 表达式和 Java 代码中，需要显式地指定它。随后的章节解释了如何做到这一点。

## 3.10.调用子流

一个流可以调用另一个流作为子流。流将等待直到子流返回，然后对子流结果做出响应。

### 3.10.1.子流-状态

使用`subflow-state`元素调用另一个流作为子流：

```
<subflow-state id="addGuest" subflow="createGuest">
    <transition on="guestCreated" to="reviewBooking">
        <evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
    </transition>
    <transition on="creationCancelled" to="reviewBooking" />
</subflow-state>
			
```

上面的示例调用`createGuest`流，然后等待它返回。当流返回`guestCreated`结果时，新的客人将被添加到预订的客人列表中。

#### 传递一个子流输入

使用`input`元素将输入传递给子流：

```
<subflow-state id="addGuest" subflow="createGuest">
    <input name="booking" />
    <transition to="reviewBooking" />
</subflow-state>
				
```

#### 映射子流输出

当一个子流完成时，它的结束状态 ID 将返回给调用流，作为用于继续导航的事件。

子流还可以在结果转换中创建调用流可以引用的输出属性，如下所示：

```
<transition on="guestCreated" to="reviewBooking">
    <evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
</transition>
				
```

在上面的示例中，`guest`是由`guestCreated`结果返回的输出属性的名称。

### 3.1 0.2.检查点：调用子流

现在查看调用子流的样例预订流程：

```
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
                          http://www.springframework.org/schema/webflow/spring-webflow.xsd">

    <input name="hotelId" />

    <on-start>
        <evaluate expression="bookingService.createBooking(hotelId, currentUser.name)"
                  result="flowScope.booking" />
    </on-start>

    <view-state id="enterBookingDetails">
        <transition on="submit" to="reviewBooking" />
    </view-state>

    <view-state id="reviewBooking">
        <transition on="addGuest" to="addGuest" />
        <transition on="confirm" to="bookingConfirmed" />
        <transition on="revise" to="enterBookingDetails" />
        <transition on="cancel" to="bookingCancelled" />
    </view-state>

    <subflow-state id="addGuest" subflow="createGuest">
        <transition on="guestCreated" to="reviewBooking">
            <evaluate expression="booking.guests.add(currentEvent.attributes.guest)" />
        </transition>
        <transition on="creationCancelled" to="reviewBooking" />
    </subflow-state>

    <end-state id="bookingConfirmed" >
        <output name="bookingId" value="booking.id" />
    </end-state>

    <end-state id="bookingCancelled" />

</flow>
			
```

该流现在调用`createGuest`子流，将一个新的来宾添加到来宾列表中。