# 5. 呈现视图

## 5.1.导言

本章向你展示了如何使用`view-state`元素在流中呈现视图。

## 5.2.定义视图状态

使用`view-state`元素来定义流的一个步骤，该步骤呈现视图并等待用户事件恢复：

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

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

下面是一个示例目录结构，显示了视图和其他资源（如消息包）与它们的流定义合用的位置：

<img src="images/flow-view-packaging.png" align="middle" />

流动包装

## 5.3.指定视图标识符

使用`view`属性指定要显式呈现的视图的 ID。

### 5.3.1.流相对视图 ID

视图 ID 可以是在流的工作目录中查看资源的相对路径：

```
<view-state id="enterBookingDetails" view="bookingDetails.xhtml">
			
```

### 5.3.2.绝对视图 ID

视图 ID 可以是指向 WebApp 根目录中某个视图资源的绝对路径：

```
<view-state id="enterBookingDetails" view="/WEB-INF/hotels/booking/bookingDetails.xhtml">
			
```

### 5.3.3.逻辑视图 ID

对于一些视图框架，例如 Spring MVC 的视图框架，视图 ID 也可以是由框架解析的逻辑标识符：

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

有关如何与 MVC`ViewResolver`基础架构集成的更多信息，请参见 Spring MVC 集成部分。

## 5.4.视图作用域

视图状态在进入时分配一个新的`viewScope`。这个作用域可以在视图状态中被引用，以分配应该在状态持续期间有效的变量。这个作用域对于在来自同一视图的一系列请求（通常是 Ajax 请求）上操作对象非常有用。当视图状态退出时，它会破坏它的视图范围。

### 5.4.1.分配视图变量

使用`var`标记声明一个视图变量。与流变量一样，当视图状态恢复时，任何`@Autowired`引用都会自动恢复。

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

### 5.4.2.分配 ViewScope 变量

在视图呈现之前，使用`on-render`标记从操作结果中分配一个变量：

```
<on-render>
    <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
</on-render>
			
```

### 5.4.3.在视图范围内操作对象

视图作用域中的对象通常在来自同一视图的一系列请求上进行操作。下面的示例页面通过一个搜索结果列表。在每次呈现之前，都会在视图范围内更新该列表。异步事件处理程序修改当前数据页，然后请求重新呈现搜索结果片段。

```
<view-state id="searchResults">
    <on-render>
        <evaluate expression="bookingService.findHotels(searchCriteria)"
                  result="viewScope.hotels" />
    </on-render>
    <transition on="next">
        <evaluate expression="searchCriteria.nextPage()" />
        <render fragments="searchResultsFragment" />
    </transition>
    <transition on="previous">
        <evaluate expression="searchCriteria.previousPage()" />
        <render fragments="searchResultsFragment" />
    </transition>
</view-state>
			
```

## 5.5.执行呈现动作

在视图呈现之前，使用`on-render`元素执行一个或多个操作。呈现操作在初始呈现以及任何后续刷新上执行，包括视图的任何部分重新呈现。

```
<on-render>
    <evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
</on-render>
	    
```

## 5.6.绑定到模型

使用`model`属性声明视图绑定到的模型对象。此属性通常与呈现窗体等数据控件的视图结合使用。它使表单数据绑定和验证行为能够由模型对象上的元数据驱动。

下面的示例声明一个`enterBookingDetails`状态操作`booking`模型：

```
<view-state id="enterBookingDetails" model="booking">
	    
```

模型可以是任何可访问范围内的对象，例如`flowScope`或`viewScope`。当发生视图事件时，指定`model`将触发以下行为：

1. 视图到模型绑定。在视图回发时，用户输入值将绑定到为你建模的对象属性。

2. 模型验证。绑定之后，如果模型对象需要验证，则将调用验证逻辑。

要生成能够驱动视图状态转换的流事件，必须成功地完成模型绑定。如果模型绑定失败，将重新呈现视图，以允许用户修改其编辑。

## 5.7.执行类型转换

当使用请求参数填充模型（通常称为数据绑定）时，在设置目标模型属性之前，需要进行类型转换以解析基于字符串的请求参数值。默认类型转换可用于许多常见的 Java 类型，例如数字、原语、枚举和日期。用户还可以为用户定义的类型注册自己的类型转换逻辑，并覆盖默认的转换器。

### 5.7.1.类型转换选项

从版本 2.1 Spring 开始，Web 流使用 Spring 3 中引入的[类型转换](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/validation.html#core-convert)和[formatting](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/validation.html#format)系统来满足几乎所有的类型转换需求。以前，Web 流应用程序使用的类型转换机制与 Spring MVC 中的机制不同，后者依赖于`java.beans.PropertyEditor`抽象。 Spring 3 提供了一种替代 PropertYeditor 的现代类型转换方法，而 PropertYeditor 实际上受到了 Web Flow 自己的类型转换系统的影响。因此，Web 流用户应该会发现使用新的 Spring 3 类型转换是很自然的。这种变化的另一个明显且非常重要的好处是，现在可以跨 Spring MVC 和 Spring Web 流使用单一类型转换机制。

### 5.7.2.升级到 Spring 3 类型转换和格式化

这对现有的应用程序实际上意味着什么？现有的应用程序很可能通过 Spring 绑定中可用的`DefaultConversionService`的子类来注册它们自己的类型`org.springframework.binding.convert.converters.Converter`的转换器。这些转换器可以像以前一样继续注册。它们将被改编为 Spring 3`GenericConverter`类型，并以 Spring 3`org.springframework.core.convert.ConversionService`实例注册。换句话说，现有的转换器将通过 Spring 的类型转换服务被调用。

此规则的唯一例外是命名转换器，它可以从`view-state`中的`binding`元素引用：

```
public class ApplicationConversionService extends DefaultConversionService {
    public ApplicationConversionService() {
        addDefaultConverters();
        addDefaultAliases();
        addConverter("customConverter", new CustomConverter());
    }
}
				
```

```
<view-state id="enterBookingDetails" model="booking">
    <binder>
        <binding property="checkinDate" required="true" converter="customConverter" />
    </binder>
</view-state>
				
```

命名转换器不受支持，并且不能与 Spring 3 中提供的类型转换服务一起使用。因此，这样的转换器将不会被适配并且将继续像以前那样工作，即将不涉及 Spring 3 类型的转换。然而，这种机制是不受欢迎的，并且鼓励应用程序支持 Spring 3 类型转换和格式化特性。

还要注意，现有的 Spring binding`DefaultConversionService`不再注册任何默认的转换器。相反，Web 流现在依赖于 Spring 3 中的默认类型转换器和格式化程序。

总之， Spring 3 类型转换和格式现在几乎只在 Web 流中使用。尽管现有的应用程序将在没有任何更改的情况下工作，但我们鼓励朝着统一 Spring MVC 和 Spring 应用程序的 Web 流部分的类型转换需求的方向发展。

### 5.7.3.配置类型转换和格式

在 Spring MVC 中，`FormattingConversionService`的实例是通过自定义 MVC 名称空间自动创建的：

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

	<mvc:annotation-driven/>

				
```

在内部，这是在`FormattingConversionServiceFactoryBean`的帮助下完成的，它注册了一组默认的转换器和格式化程序。你可以通过`conversion-service`属性自定义 Spring MVC 中使用的转换服务实例：

```
<mvc:annotation-driven conversion-service="applicationConversionService" />
				
```

在 Web 流中，自动创建 Spring 绑定`DefaultConversionService`的实例，该实例不注册任何转换器。相反，它将所有类型转换需求委托给`FormattingConversionService`实例。默认情况下，这不是与 Spring 3 中使用的实例相同的`FormattingConversionService`实例。然而，在你开始注册自己的格式化程序之前，这不会产生实际的影响。

Web 流中使用的`DefaultConversionService`可以通过 flow-builder-services 元素进行定制：

```
<webflow:flow-builder-services id="flowBuilderServices" conversion-service="defaultConversionService" />
				
```

连接这些点以便注册你自己的格式化程序以便在 Spring MVC 和 Spring Web 流中使用，你可以执行以下操作。创建一个类来注册你的自定义格式化程序：

```
public class ApplicationConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {

    @Override
    protected void installFormatters(FormatterRegistry registry) {
        // ...
    }

}

				
```

将其配置为在 Spring MVC 中使用：

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

    <mvc:annotation-driven conversion-service="applicationConversionService" />

    <!--
    	Alternatively if you prefer annotations for DI:
    	  1. Add @Component to the factory bean.
    	  2. Add a component-scan element (from the context custom namespace) here.
    	  3. Remove XML bean declaration below.
      -->

    <bean id="applicationConversionService" class="somepackage.ApplicationConversionServiceFactoryBean">

				
```

将 Web 流`DefaultConversionService`连接到 Spring MVC 中使用的相同的“ApplicationConversionService” Bean:

```
    <webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices" ... />

    <webflow:flow-builder-services id="flowBuilderServices" conversion-service="defaultConversionService" ... />

    <bean id="defaultConversionService" class="org.springframework.binding.convert.service.DefaultConversionService">
    	<constructor-arg ref="applicationConversionSevice"/>
    </bean>
				
```

当然，混搭也是可以的。通过“ApplicationConversionService”注册新的 Spring 3<gtr="98"/>类型。通过“DefaultConversionService”注册现有的 Spring binding<gtr="99"/>类型。

### 5.7.4.使用 Spring 3 类型转换和格式化

要理解的一个重要概念是类型转换器和格式化程序之间的区别。

Spring 3 中的类型转换器，在`org.springframework.core`中提供，是用于在任意两个对象类型之间进行通用类型转换的。除了最简单的`Converter`类型外，还有两个接口`ConverterFactory`和`GenericConverter`。

Spring 3 中的格式化程序（在`org.springframework.context`中提供）具有将对象表示为字符串的更专门的目的。`Formatter`接口扩展了`Printer`和`Parser`接口，用于将对象转换为字符串并将字符串转换为对象。

Web 开发人员将发现`Formatter`接口最相关，因为它适合 Web 应用程序对类型转换的需求。

|[[note]（images/note.png）|Note|
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---|
|重要的一点是，对象到对象的转换是更具体的对象到字符串转换的推广。<br/>实际上在最后`Formatters`被重新定义为`GenericConverter`类型， Spring 的`GenericConversionService`使它们等于任何其他转换器。|    |

### 5.7.5.格式化注释

新类型转换的最佳特性之一是能够使用注释以简洁的方式更好地控制格式。注释可以放在模型属性和映射到请求的 @Controller 方法的参数上。 Spring 开箱即用提供了两个注释`NumberFormat`和`DateTimeFormat`，但你可以创建自己的注释，并将它们与相关的格式设置逻辑一起注册。你可以在[Spring Travel](https://src.springframework.org/svn/spring-samples/travel)和[Petcare](https://src.springframework.org/svn/spring-samples/petcare)中看到`DateTimeFormat`注释的示例，以及[Spring Samples](https://src.springframework.org/svn/spring-samples)存储库中的其他示例。

### 5.7.6.与日期合作

`DateTimeFormat`注释意味着使用[Joda Time](http://joda-time.sourceforge.net/)。如果在 Classpath 上存在该注释，则自动启用该注释的使用。默认情况下， Spring MVC 或 Web 流都不会注册任何其他的日期格式化程序或转换器。因此，对于应用程序来说，注册一个自定义格式化程序来指定打印和解析日期的默认方式是很重要的。另一方面，`DateTimeFormat`注释在需要偏离默认值的地方提供了更细粒度的控制。

有关使用 Spring 3 类型转换和格式的更多信息，请参阅[Spring documentation](http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/index.html)的相关部分。

## 5.8.抑制结合

使用`bind`属性来抑制特定视图事件的模型绑定和验证。当`cancel`事件发生时，以下示例将取消绑定：

```
<view-state id="enterBookingDetails" model="booking">
    <transition on="proceed" to="reviewBooking">
    <transition on="cancel" to="bookingCancelled" bind="false" />
</view-state>
		
```

## 5.9.显式指定绑定

使用`binder`元素来配置要对其应用数据绑定的模型属性的精确集合。这对于限制每个视图的“允许字段”集是有用的。不使用此功能可能会导致安全问题，这取决于应用程序域和实际用户，因为默认情况下，如果未指定 Binder 元素，则模型的所有公共属性都有资格通过视图进行数据绑定。相比之下，当指定`binder`元素时，只允许显式配置绑定。下面是一个例子：

```
<view-state id="enterBookingDetails" model="booking">
    <binder>
        <binding property="creditCard" />
        <binding property="creditCardName" />
        <binding property="creditCardExpiryMonth" />
        <binding property="creditCardExpiryYear" />
    </binder>
    <transition on="proceed" to="reviewBooking" />
    <transition on="cancel" to="cancel" bind="false" />
</view-state>

		
```

每个绑定还可以应用转换器来格式化模型属性值，以便以自定义的方式显示。如果没有指定转换器，将使用模型属性类型的默认转换器。

```
<view-state id="enterBookingDetails" model="booking">
    <binder>
        <binding property="checkinDate" converter="shortDate" />
        <binding property="checkoutDate" converter="shortDate" />
        <binding property="creditCard" />
        <binding property="creditCardName" />
        <binding property="creditCardExpiryMonth" />
        <binding property="creditCardExpiryYear" />
    </binder>
    <transition on="proceed" to="reviewBooking" />
    <transition on="cancel" to="cancel" bind="false" />
</view-state>

		
```

在上面的示例中，`shortDate`转换器绑定到`checkinDate`和`checkoutDate`属性。定制的转换器可以注册到应用程序的 ConversionService 中。

每个绑定还可能应用所需的检查，如果用户提供的值在表单回发时为空，则该检查将生成验证错误：

```
<view-state id="enterBookingDetails" model="booking">
    <binder>
        <binding property="checkinDate" converter="shortDate" required="true" />
        <binding property="checkoutDate" converter="shortDate" required="true" />
        <binding property="creditCard" required="true" />
        <binding property="creditCardName" required="true" />
        <binding property="creditCardExpiryMonth" required="true" />
        <binding property="creditCardExpiryYear" required="true" />
    </binder>
    <transition on="proceed" to="reviewBooking">
    <transition on="cancel" to="bookingCancelled" bind="false" />
</view-state>
		
```

在上面的示例中，所有的绑定都是必需的。如果绑定了一个或多个空白输入值，将生成验证错误，视图将使用这些错误重新呈现。

## 5.10.验证模型

模型验证由针对模型对象指定的约束驱动。Web 流支持以编程的方式以及以声明的方式使用 JSR-303 Bean 验证注释来强制执行这些约束。

### 5.1 0.1.JSR-303 Bean 验证

Web Flow 提供了对 JSR-303 Bean 验证 API 的内置支持，构建在 Spring MVC 中提供的等效支持之上。要启用 JSR-303 验证，请使用 Spring MVC 的`LocalValidatorFactoryBean`配置 flow-builder-services:

```
<webflow:flow-registry flow-builder-services="flowBuilderServices" />

<webflow:flow-builder-services id="flowBuilderServices" validator="validator" />

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
			
```

有了上述功能，配置的验证器将应用于数据绑定后的所有模型属性。

请注意，JSR-303 Bean 验证和约定验证（在下一节中进行解释）并不相互排斥。换句话说，WebFlow 将应用所有可用的验证机制。

#### 部分验证

JSR-303 Bean 验证支持通过验证组进行部分验证。例如：

```
@NotNull
@Size(min = 2, max = 30, groups = State1.class)
private String name;

```

在流定义中，你可以在视图状态或转换中指定验证提示，这些提示将解析为验证组。例如：

```
<view-state id="state1" model="myModel" validation-hints="'group1,group2'">

```

*验证-提示*属性是一个表达式，在上面的示例中，它解析为一个逗号分隔的字符串，该字符串由提示“group1”和“group2”组成。a`ValidationHintResolver`用于解析这些提示。默认情况下使用的`BeanValidationHintResolver`尝试将这些字符串解析为基于类的 Bean 验证组。为此，它在模型或其父模型中寻找匹配的内部类型。

例如，给出带有内部类型`org.example.MyModel`和`Group2`的`org.example.MyModel`的`Group2`就足以提供简单的类型名，即“group1”和“group2”。你还可以提供完全限定的类型名。

值为“default”的提示具有特殊的含义，并在 Bean Validation`javax.validation.groups.Default`中转换为默认验证组。

如果需要，可以通过 flow-builder-services 元素的 ValidationHintResolver 属性配置自定义`ValidationHintResolver`:

```
<webflow:flow-registry flow-builder-services="flowBuilderServices" />

<webflow:flow-builder-services id="flowBuilderServices" validator=".." validation-hint-resolver=".." />

```

### 5.1 0.2.程序验证

有两种方法来执行模型验证程序。第一种是在模型对象中实现验证逻辑。二是实现外部`Validator`。这两种方式都为你提供了`ValidationContext`，以记录错误消息并访问有关当前用户的信息。

#### 实现一种模型验证方法

在模型对象中定义验证逻辑是验证其状态的最简单方法。一旦这种逻辑根据 Web 流约定进行了结构化，那么 Web 流将在视图状态回发生命周期期间自动调用该逻辑。Web 流约定让你通过视图状态来构造模型验证逻辑，从而使你能够轻松地验证在该视图上可编辑的模型属性的子集。要做到这一点，只需创建一个名为`validate${state}`的公共方法，其中`${state}`是你希望在其中运行验证的视图状态的 ID。例如：

```
public class Booking {
    private Date checkinDate;
    private Date checkoutDate;
    ...

    public void validateEnterBookingDetails(ValidationContext context) {
        MessageContext messages = context.getMessageContext();
        if (checkinDate.before(today())) {
            messages.addMessage(new MessageBuilder().error().source("checkinDate").
                defaultText("Check in date must be a future date").build());
        } else if (!checkinDate.before(checkoutDate)) {
            messages.addMessage(new MessageBuilder().error().source("checkoutDate").
                defaultText("Check out date must be later than check in date").build());
        }
    }
}

					
```

在上面的示例中，当在编辑`Booking`模型的`enterBookingDetails`视图状态中触发转换时，Web 流将自动调用`validateEnterBookingDetails(ValidationContext)`方法，除非该转换的验证已被抑制。这种视图状态的一个示例如下所示：

```
<view-state id="enterBookingDetails" model="booking">
    <transition on="proceed" to="reviewBooking">
</view-state>
					
```

定义了任意数量的验证方法。通常，流通过一系列视图编辑模型。在这种情况下，将为需要运行验证的每个视图状态定义一个验证方法。

#### 实现验证器

第二种方法是定义一个单独的对象，称为*验证器*，它验证你的模型对象。要做到这一点，首先创建一个类，其名称具有模式 ${model}validator，其中`${model}`是模型表达式的头化形式，例如`booking`。然后定义一个名为`validate${state}`的公共方法，其中`${state}`是你的视图状态的 ID，例如`enterBookingDetails`。然后将类部署为 Spring  Bean。可以定义任意数量的验证方法。例如：

```
@Component
public class BookingValidator {
    public void validateEnterBookingDetails(Booking booking, ValidationContext context) {
        MessageContext messages = context.getMessageContext();
        if (booking.getCheckinDate().before(today())) {
            messages.addMessage(new MessageBuilder().error().source("checkinDate").
                defaultText("Check in date must be a future date").build());
        } else if (!booking.getCheckinDate().before(booking.getCheckoutDate())) {
            messages.addMessage(new MessageBuilder().error().source("checkoutDate").
                defaultText("Check out date must be later than check in date").build());
        }
    }
}
				
```

在上面的示例中，当在编辑`Booking`模型的`enterBookingDetails`视图状态中触发转换时，Web 流将自动调用`validateEnterBookingDetails(Booking, ValidationContext)`方法，除非该转换的验证已被抑制。

验证器还可以接受 Spring MVC对象，这是调用现有 Spring 验证器所需的。

必须使用命名约定`${model}Validator`将验证器注册为 Spring bean，才能自动检测和调用。在上面的示例中， Spring 2.5 Classpath-扫描将检测到`@Component`并自动将其注册为名称为`bookingValidator`的 Bean。然后，每当需要验证`booking`模型时，将为你调用这个`bookingValidator`实例。

#### 默认验证方法

一个*验证器*类还可以定义一个名为`validate`的方法，该方法与任何特定的视图状态都没有关联（根据约定）。

```
@Component
public class BookingValidator {
    public void validate(Booking booking, ValidationContext context) {
        //...
    }
}
				
```

在上面的代码示例中，每当验证类型`Booking`的模型时，将调用方法`validate`（除非该转换抑制了验证）。如果需要，除了现有的特定于状态的方法之外，还可以调用缺省方法。考虑以下示例：

```
@Component
public class BookingValidator {
    public void validate(Booking booking, ValidationContext context) {
        //...
    }
    public void validateEnterBookingDetails(Booking booking, ValidationContext context) {
        //...
    }
}
				
```

在上面的代码示例中，将首先调用方法`validateEnterBookingDetails`。下一步将调用默认的`validate`方法。

### 5.1 0.3.ValidationContext

ValidationContext 允许你获得`MessageContext`以在验证期间记录消息。它还公开了关于当前用户的信息，例如已信号的`userEvent`和当前用户的`Principal`标识。该信息可用于根据 UI 中激活了什么按钮或链接，或者验证了谁，定制验证逻辑。有关更多信息，请参见`ValidationContext`的 API Javadocs。

## 5.11.抑制验证

使用`validate`属性禁止对特定视图事件进行模型验证：

```
<view-state id="chooseAmenities" model="booking">
    <transition on="proceed" to="reviewBooking">
    <transition on="back" to="enterBookingDetails" validate="false" />
</view-state>
		
```

在本例中，数据绑定仍将发生在`back`上，但验证将被抑制。

## 5.12.执行视图转换

定义一个或多个`transition`元素来处理视图上可能发生的用户事件。转换可以将用户带到另一个视图，也可以只执行一个操作并重新呈现当前视图。在处理 Ajax 事件时，转换还可能请求呈现视图中称为“碎片”的部分。最后，还可以定义在所有视图中共享的“全局”转换。

实现视图转换将在下面的小节中进行说明。

### 5.1 2.1.过渡行动

视图-状态转换可以在执行之前执行一个或多个操作。这些操作可能会返回错误结果，以防止转换退出当前视图状态。如果出现错误结果，视图将重新呈现，并应向用户显示适当的消息。

如果转换操作调用一个普通的 Java 方法，那么被调用的方法可能返回一个布尔值，该值是 true 还是 false，指示是否应该进行转换或阻止执行转换。方法还可以返回一个字符串，其中文字值“Success”、“Yes”或“True”表示应该发生转换，而任何其他值则表示相反的值。此技术可用于处理服务层方法引发的异常。下面的示例调用一个调用服务并处理异常情况的操作：

```
<transition on="submit" to="bookingConfirmed">
    <evaluate expression="bookingAction.makeBooking(booking, messageContext)" />
</transition>
			
```

```
public class BookingAction {
   public boolean makeBooking(Booking booking, MessageContext context) {
       try {
           bookingService.make(booking);
           return true;
       } catch (RoomNotAvailableException e) {
           context.addMessage(new MessageBuilder().error().
               .defaultText("No room is available at this hotel").build());
           return false;
       }
   }
}
			
```

当在转换过程中定义了多个动作时，如果一个动作返回一个错误结果，则集合中的其余动作将执行*不是*。如果你需要确保一个转换操作的结果不会影响另一个转换操作的执行，那么可以定义一个单独的转换操作，调用一个封装了所有操作逻辑的方法。

### 5.1 2.2.全球转型

使用流的`global-transitions`元素创建适用于所有视图的转换。全局转换通常用于处理作为布局一部分的全局菜单链接。

```
<global-transitions>
    <transition on="login" to="login" />
    <transition on="logout" to="logout" />
</global-transitions>
			
```

### 5.1 2.3.事件处理程序

从视图状态，也可以定义没有目标的转换。这样的转换被称为“事件处理程序”：

```
<transition on="event">
    <!-- Handle event -->
</transition>
			
```

这些事件处理程序不会改变流的状态。它们只需执行动作并重新呈现当前视图或当前视图的一个或多个片段。

### 5.1 2.4.呈现片段

在处理事件后，在转换中使用`render`元素请求部分重新呈现当前视图：

```
<transition on="next">
    <evaluate expression="searchCriteria.nextPage()" />
    <render fragments="searchResultsFragment" />
</transition>
			
```

Fragments 属性应该引用你希望重新呈现的视图元素的 ID。通过用逗号分隔符分隔要重新呈现的多个元素，指定它们。

这种部分呈现通常与 Ajax 发出信号的事件一起使用，以更新视图的特定区域。

## 5.13.处理消息

Spring Web 流的`MessageContext`是用于在流执行过程中记录消息的 API。可以将纯文本消息添加到上下文中，以及由 Spring `MessageSource`解析的国际化消息。消息可以通过视图进行呈现，并在流执行重定向后自动保留。提供了三个不同的消息严重性：`info`、`warning`和`error`。此外，还存在一个方便的`MessageBuilder`用于流畅地构造消息。

### 5.1 3.1.添加纯文本消息

```
MessageContext context = ...
MessageBuilder builder = new MessageBuilder();
context.addMessage(builder.error().source("checkinDate")
    .defaultText("Check in date must be a future date").build());
context.addMessage(builder.warn().source("smoking")
    .defaultText("Smoking is bad for your health").build());
context.addMessage(builder.info()
    .defaultText("We have processed your reservation - thank you and enjoy your stay").build());
			
```

### 5.1 3.2.添加国际化消息

```
MessageContext context = ...
MessageBuilder builder = new MessageBuilder();
context.addMessage(builder.error().source("checkinDate").code("checkinDate.notFuture").build());
context.addMessage(builder.warn().source("smoking").code("notHealthy")
    .resolvableArg("smoking").build());
context.addMessage(builder.info().code("reservationConfirmation").build());
			
```

### 5.1 3.使用消息包

国际化消息是在由 Spring `MessageSource`访问的消息包中定义的。要创建特定于流的消息包，只需在流的目录中定义`messages.properties`文件。为每个需要支持的额外`Locale`创建一个默认的`messages.properties`文件和一个.properties 文件。

```
#messages.properties
checkinDate=Check in date must be a future date
notHealthy={0} is bad for your health
reservationConfirmation=We have processed your reservation - thank you and enjoy your stay
			
```

从视图或流中，你还可以使用`resourceBundle`EL 变量访问消息资源：

```
<h:outputText value="#{resourceBundle.reservationConfirmation}" />
			
```

### 5.1 3.4.理解系统生成的消息

在几个地方，Web Flow 本身将生成要显示给用户的消息。发生这种情况的一个重要位置是在视图到模型的数据绑定过程中。当出现绑定错误（例如类型转换错误）时，WebFlow 将自动将该错误映射到从资源包中检索到的消息。要查找要显示的消息，WebFlow 将尝试包含绑定错误代码和目标属性名称的资源键。

例如，考虑绑定到`Booking`对象的`checkinDate`属性。假设用户输入了一个字母字符串。在这种情况下，将引发类型转换错误。WebFlow 将通过使用以下键首先查询资源包中的一条消息，将“Type 错配”错误代码映射到一条消息：

```
booking.checkinDate.typeMismatch
			
```

关键的第一部分是模型类的简称。密钥的第二部分是属性名称。第三部分是错误代码。这允许查找在模型属性上绑定失败时向用户显示的唯一消息。这样的信息可能会说：

```
booking.checkinDate.typeMismatch=The check in date must be in the format yyyy-mm-dd.
			
```

如果在该表单中找不到这样的资源密钥，将尝试使用更通用的密钥。这个键就是错误代码。属性的字段名称作为消息参数提供。

```
typeMismatch=The {0} field is of the wrong type.
			
```

## 5.14.显示弹出窗口

使用`popup`属性在模态弹出对话框中呈现视图：

```
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
		
```

当使用 Spring JavaScript 的 Web 流时，弹出窗口不需要客户端代码即可显示。WebFlow 将向客户机发送响应，请求从弹出窗口重定向到视图，客户机将执行该请求。

## 5.15.视图回溯

默认情况下，当你退出一个视图状态并转换到一个新的视图状态时，你可以使用浏览器 Back 按钮返回到以前的状态。通过使用`history`属性，可以在每次转换的基础上配置这些视图状态历史策略。

### 5.15.1.抛弃历史

将 history 属性设置为`discard`，以防止回溯到视图：

```
<transition on="cancel" to="bookingCancelled" history="discard">
			
```

### 5.15.2.使历史失效

将历史属性设置为`invalidate`，以防止回溯到视图以及以前显示的所有视图：

```
<transition on="confirm" to="bookingConfirmed" history="invalidate">
			
```