13. 渲染视图

本章向您展示如何使用 view-state 元素在流程中渲染视图。spring-doc.cadn.net.cn

13.1. 定义视图状态

view-state 元素定义了流程中的一个步骤,该步骤渲染视图并等待用户事件以恢复,如下所示:spring-doc.cadn.net.cn

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

按照惯例,view-state 会将其 ID 映射到流程所在目录中的视图模板。 例如,如果流程本身位于 /WEB-INF/hotels/booking 目录中,前面示例中的状态可能会渲染 /WEB-INF/hotels/booking/enterBookingDetails.xhtmlspring-doc.cadn.net.cn

下图显示了一个示例目录结构,其中包含视图和其他资源(例如消息包),这些资源与其流定义位于同一位置:spring-doc.cadn.net.cn

flow view packaging

13.2. 指定视图标识符

您可以使用 view 属性显式指定要渲染的视图的 ID。spring-doc.cadn.net.cn

13.2.1. 流程相对视图ID

视图 ID 可能是相对于流工作目录中视图资源的相对路径,如下所示:spring-doc.cadn.net.cn

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

13.2.2. 绝对视图 ID

视图 ID 可能是 webapp 根目录中视图资源的绝对路径,如下所示:spring-doc.cadn.net.cn

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

13.2.3. 逻辑视图 ID

对于一些视图框架,例如 Spring MVC 的视图框架,视图 ID 也可能是一个由框架解析的逻辑标识符,如下所示:spring-doc.cadn.net.cn

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

有关如何与MVC ViewResolver 基础设施集成的更多信息,请参阅Spring MVC集成部分。spring-doc.cadn.net.cn

13.3. 视图作用域

A view-state 在进入时会分配一个新的 viewScope。 你可以在 view-state 中引用此作用域,以分配在状态持续期间应该存在的变量。 此作用域对于在来自同一视图的一系列请求(通常是 Ajax 请求)中操作对象非常有用。 A view-state 在退出时会销毁其 viewScopespring-doc.cadn.net.cn

13.3.1. 分配视图变量

你可以使用 var 标签来声明一个视图变量。 与流程变量一样,任何 @Autowired 引用都会在视图状态恢复时自动还原。 以下代码清单声明了一个视图变量:spring-doc.cadn.net.cn

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

13.3.2. 分配一个 viewScope变量

你可以使用 on-render 标签在视图渲染之前从操作结果分配一个变量,如下所示:spring-doc.cadn.net.cn

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

13.3.3. 在视图作用域中操作对象

视图作用域中的对象通常在来自同一视图的一系列请求中被操作。 在每次渲染之前,列表会在视图作用域中更新。 异步事件处理器修改当前数据页面,然后请求重新渲染搜索结果片段。 以下示例展示了如何对搜索结果列表进行分页:spring-doc.cadn.net.cn

<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>

13.4. 运行渲染操作

on-render 元素在视图渲染之前运行一个或多个操作。 渲染操作在初始渲染以及任何后续刷新(包括视图的任何部分重新渲染)时都会运行。 以下清单定义了一个 on-render 元素:spring-doc.cadn.net.cn

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

13.5. 绑定到模型

您可以使用 model 属性声明视图绑定的对象模型。 此属性通常与渲染数据控件(例如表单)的视图结合使用。 它允许通过模型对象上的元数据来驱动表单数据绑定和验证行为。spring-doc.cadn.net.cn

以下示例声明了一个操作 booking 模型的 enterBookingDetails 状态:spring-doc.cadn.net.cn

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

模型可以是任何可访问范围内的对象,例如 flowScopeviewScope。 指定 model 会在发生视图事件时触发以下行为:spring-doc.cadn.net.cn

  1. 视图到模型的绑定。在视图回发时,用户输入的值会自动绑定到模型对象的属性上。spring-doc.cadn.net.cn

  2. 模型验证。在绑定后,如果模型对象需要验证,则会调用该验证逻辑。spring-doc.cadn.net.cn

为了生成可以驱动视图状态转换的流事件,模型绑定必须成功完成。 如果模型绑定失败,视图将重新渲染,以便用户修订他们的编辑。spring-doc.cadn.net.cn

13.6. 执行类型转换

当请求参数用于填充模型(通常称为数据绑定)时,在设置目标模型属性之前需要进行类型转换,以解析基于字符串的请求参数值。 许多常见的 Java 类型(如数字、基本类型、枚举和日期)都提供了默认的类型转换。 用户还可以注册自己的类型转换逻辑,以用于用户定义的类型并覆盖默认的转换器。spring-doc.cadn.net.cn

13.6.1. 类型转换选项

从版本 2.1 开始,Spring Web Flow 几乎所有的类型转换需求都使用 类型转换格式化 系统。 以前,Web Flow 应用程序使用的类型转换机制与 Spring MVC 中的不同,后者依赖于 java.beans.PropertyEditor 抽象。 Spring 的类型转换实际上受到了 Web Flow 自己的类型转换系统的启发。 因此,Web Flow 用户会发现使用 Spring 类型转换非常自然。 这一变化的另一个明显且重要的好处是,您现在可以在 Spring MVC 和 Spring Web Flow 中使用统一的类型转换机制。spring-doc.cadn.net.cn

13.6.2. 升级到 Spring 3 类型转换与格式化

这对现有应用程序而言在实际操作中意味着什么呢?现有的应用程序可能会通过 Spring Binding 中提供的 DefaultConversionService 子类注册它们自己类型为 org.springframework.binding.convert.converters.Converter 的转换器。这些转换器可以像以前一样继续注册。它们已经被适配为 Spring 的 GenericConverter 类型,并注册到一个 Spring 的 org.springframework.core.convert.ConversionService 实例中。换句话说,现有的转换器会通过 Spring 的类型转换服务来调用。spring-doc.cadn.net.cn

此规则的唯一例外是命名转换器,您可以从 view-state 中的 binding 元素引用它们,如下所示:spring-doc.cadn.net.cn

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 框架中的类型转换服务一起使用。 因此,这些转换器未进行适配,仍然保持原有的工作方式。 也就是说,它们不涉及 Spring 的类型转换。 然而,此机制已被弃用,建议应用程序优先使用 Spring 的类型转换和格式化功能。spring-doc.cadn.net.cn

另请注意,现有的 Spring Binding DefaultConversionService 不再注册任何默认转换器。 相反,Web Flow 现在依赖于 Spring 中的默认类型转换器和格式化程序。spring-doc.cadn.net.cn

总之,Spring 类型转换和格式化现在几乎专门用于 Web Flow。 尽管现有应用程序应该可以无需任何更改即可运行,但我们鼓励朝着统一 Spring MVC 和 Spring Web Flow 部分的应用程序类型转换需求的方向发展。spring-doc.cadn.net.cn

13.6.3. 配置类型转换和格式化

在 Spring MVC 中,通过自定义的 MVC 命名空间会自动创建一个 FormattingConversionService 的实例,如下所示:spring-doc.cadn.net.cn

<?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
        https://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

	<mvc:annotation-driven/>

Internally, that is done with the help of FormattingConversionServiceFactoryBean, which registers a default set of converters and formatters. You can customize the conversion service instance used in Spring MVC through the conversion-service attribute, as follows:spring-doc.cadn.net.cn

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

在 Web Flow 中,会自动创建一个 Spring Binding 的实例 DefaultConversionService,该实例不会注册任何转换器。 而是将所有类型转换的需求委托给一个 FormattingConversionService 实例。 默认情况下,这个实例与 Spring 中使用的 FormattingConversionService 实例并不相同。 然而,这在您开始注册自己的格式化程序之前并不会产生实际的影响。spring-doc.cadn.net.cn

你可以通过 flow-builder-services 元素自定义 Web Flow 中使用的 DefaultConversionService,如下所示:spring-doc.cadn.net.cn

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

您可以执行以下操作来注册您自己的格式化程序,以在 Spring MVC 和 Spring Web Flow 中使用:spring-doc.cadn.net.cn

  1. 创建一个类来注册您的自定义格式化程序:spring-doc.cadn.net.cn

    public class ApplicationConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {
    
        @Override
        protected void installFormatters(FormatterRegistry registry) {
            // ...
        }
    
    }
    
  2. 配置此类以在 Spring MVC 中使用:spring-doc.cadn.net.cn

    <?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
            https://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://www.springframework.org/schema/beans
            https://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">
  3. 将 Web Flow 的 DefaultConversionService 连接到 Spring MVC 中使用的同一个 applicationConversionService bean:spring-doc.cadn.net.cn

        <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 Formatter 类型。 你可以通过 defaultConversionService 注册现有的 Spring Binding Converter 类型。spring-doc.cadn.net.cn

13.6.4. 使用 Spring 类型转换和格式化

`` 理解类型转换器和格式化器之间的区别是一个重要的概念。spring-doc.cadn.net.cn

Spring框架中的类型转换器,包含在org.springframework.core中,用于任何两种对象类型之间的通用类型转换。 除了最简单的Converter类型外,另外两个接口是ConverterFactoryGenericConverterspring-doc.cadn.net.cn

Spring框架中的格式化器,位于org.springframework.context中,具有将Object实例表示为String实例的更专门用途。 Formatter接口扩展了PrinterParser接口,用于将Object转换为String,并将String转换为Objectspring-doc.cadn.net.cn

Web 开发人员可能会发现 Formatter 接口最为相关,因为它满足了 Web 应用程序进行类型转换的需求。spring-doc.cadn.net.cn

对象到对象的转换是更具体的对象到字符串转换的一种泛化。实际上,Formatters 作为 GenericConverter 类型注册到了 Spring 的 GenericConversionService 中,使它们与其他任何转换器等效。

13.6.5. 格式化注解

类型转换的最佳特性之一是能够以简洁的方式使用注解来更好地控制格式化。 您可以将注解放置在模型属性上,也可以放置在映射到请求的@Controller方法的参数上。 Spring提供了两个注解(@NumberFormat@DateTimeFormat),但您还可以创建自己的注解,并将其与相关的格式化逻辑一起注册。spring-doc.cadn.net.cn

13.6.6. 处理日期

@DateTimeFormat 注解意味着使用 Joda Time。 如果它存在于类路径中,该注解的使用会自动启用。 默认情况下,Spring MVC 和 Web Flow 都不会注册任何其他日期格式化器或转换器。 因此,应用程序注册一个自定义格式化器以指定打印和解析日期的默认方式非常重要。 另一方面,@DateTimeFormat 注解在需要偏离默认行为时提供了更精细的控制。spring-doc.cadn.net.cn

有关使用 Spring 类型转换和格式化的更多信息,请参阅 Spring 文档 的相关部分。spring-doc.cadn.net.cn

13.7. 抑制绑定

您可以使用 bind 属性来抑制特定视图事件的模型绑定和验证。 以下示例在 cancel 事件发生时抑制绑定:spring-doc.cadn.net.cn

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

13.8. 明确指定绑定

您可以使用 binder 元素来配置要应用数据绑定的模型属性的确切集合。 这使您可以限制每个视图的“允许字段”集。 如果不使用此功能,可能会导致安全问题,具体取决于应用程序领域和实际用户,因为默认情况下,如果未指定绑定器元素,则模型的所有公共属性都可用于视图的数据绑定。 相比之下,当指定了 binder 元素时,只有显式配置的绑定是被允许的。 以下示例使用了一个 binder 元素:spring-doc.cadn.net.cn

<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>

每个绑定还可以应用一个转换器,以自定义格式化模型属性值以便以特定方式显示。如果没有指定转换器,则使用该模型属性类型的默认转换器。以下示例展示了两个带有 converter 属性的 binding 元素:spring-doc.cadn.net.cn

<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 转换器被绑定到 checkinDatecheckoutDate 属性。您可以使用应用程序的 ConversionService 注册自定义转换器。spring-doc.cadn.net.cn

每个绑定还可以应用一个必需的检查,以便在表单回发时,如果用户提供的值为 null,则生成验证错误,如下所示:spring-doc.cadn.net.cn

<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>

在前面的示例中,所有绑定都是必需的。 如果一个或多个空白输入值被绑定,将生成验证错误,并且视图会重新渲染并显示这些错误。spring-doc.cadn.net.cn

13.9. 验证模型

模型验证由针对模型对象指定的约束条件驱动。 Web Flow 支持以编程方式以及通过 JSR-303 Bean Validation 注解声明式地强制执行这些约束条件。spring-doc.cadn.net.cn

13.9.1. JSR-303 Bean 验证

Web Flow 提供了对 JSR-303 Bean Validation API 的内置支持,基于 Spring MVC 中等效的支持。 要启用 JSR-303 验证,请使用 Spring MVC 的 LocalValidatorFactoryBean 配置 flow-builder-services,如下所示:spring-doc.cadn.net.cn

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

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

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

在前面的示例就位后,配置的验证器会在数据绑定后应用于所有模型属性。spring-doc.cadn.net.cn

请注意,JSR-303 bean 验证和按约定验证(在下一节中解释)并不是互斥的。 换句话说,Web Flow 会应用所有可用的验证机制。spring-doc.cadn.net.cn

部分验证

JSR-303 Bean Validation 支持通过验证组进行部分验证。 以下示例定义了部分验证:spring-doc.cadn.net.cn

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

在流程定义中,您可以为视图状态或转换指定验证提示,这些提示会被解析为验证组。 以下示例定义了验证提示:spring-doc.cadn.net.cn

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

validation-hints 属性是一个表达式,在前面的例子中,它解析为一个逗号分隔的 String,包含两个提示: group1group2。使用 ValidationHintResolver 来解析这些提示。 默认使用的 BeanValidationHintResolver 会尝试将这些字符串解析为基于类的 bean 验证组。 为此,它会在模型或其父级中查找匹配的内部类型。spring-doc.cadn.net.cn

例如,给定 org.example.MyModel 的内部类型为 Group1Group2,提供简单类型名称即可,即 group1group2。 您还可以提供完全限定的类型名称。spring-doc.cadn.net.cn

值为 default 的提示具有特殊含义,并在 Bean Validation 中被转换为默认的验证组: jakarta.validation.groups.Defaultspring-doc.cadn.net.cn

你可以通过 validationHintResolver 元素的 flow-builder-services 属性(如果需要)来配置自定义的 ValidationHintResolver,如下所示:spring-doc.cadn.net.cn

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

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

13.9.2. 编程式验证

有两种方法可以以编程方式执行模型验证。 第一种是在模型对象中实现验证逻辑。 第二种是实现一个外部的 Validator。 这两种方法都为您提供了一个 ValidationContext 来记录错误消息并访问有关当前用户的信息。spring-doc.cadn.net.cn

实现模型验证方法

在模型对象中定义验证逻辑是验证其状态的最简单方法。 一旦根据 Web Flow 约定构建了此类逻辑,Web Flow 就会在 view-state 回发生命周期期间自动调用该逻辑。 Web Flow 约定要求您通过 view-state 来构建模型验证逻辑,从而使您可以验证该视图上可编辑的模型属性子集。 为此,请创建一个公共方法,其名称为 validate${state},其中 ${state} 是您希望运行验证的 view-state 的 ID。 以下示例执行模型验证:spring-doc.cadn.net.cn

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 view-state 中触发转换时,Web Flow 会自动调用 validateEnterBookingDetails(ValidationContext) 方法,除非该转换的验证已被禁止。 以下示例展示了这样的 view-statespring-doc.cadn.net.cn

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

您可以定义任意数量的验证方法。 通常,一个流程会在一系列视图中编辑模型。 在这种情况下,您需要为每个需要运行验证的view-state定义一个验证方法。spring-doc.cadn.net.cn

实现一个验证器

执行编程验证的第二种方法是定义一个单独的对象,称为验证器,它验证您的模型对象。 为此,首先创建一个类,其名称模式为${model}Validator,其中${model}是模型表达式的首字母大写形式,例如Booking。 然后定义一个公共方法,其名称为validate${state},其中${state}是您的view-state的ID,例如enterBookingDetails。 该类应作为Spring bean部署。 可以定义任意数量的验证方法。 以下示例定义了这样一个验证器:spring-doc.cadn.net.cn

@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 view-state 中触发转换时,Web Flow 会自动调用 validateEnterBookingDetails(Booking, ValidationContext) 方法,除非该转换的验证已被禁用。spring-doc.cadn.net.cn

验证器还可以接受一个 Spring MVC Errors 对象,这是调用现有的 Spring 验证器所必需的。spring-doc.cadn.net.cn

验证器必须作为 Spring 的 bean 注册,使用 ${model}Validator 命名约定,才能被自动检测并调用。 在前面的例子中,Spring 的类路径扫描会检测到 @Component 并自动将其注册为名为 bookingValidator 的 bean。 然后,任何时候当需要验证 booking 模型时,都会为您调用这个 bookingValidator 实例。spring-doc.cadn.net.cn

默认验证方法

一个验证器类还可以定义一个名为validate的方法,该方法(按照惯例)不与任何特定的view-state相关联。 以下示例定义了这样的方法:spring-doc.cadn.net.cn

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

在前面的代码示例中,每次验证类型为 Booking 的模型时都会调用 validate 方法(除非该转换的验证已被抑制)。如果需要,还可以在现有的特定状态方法之外调用默认方法。 请考虑以下示例:spring-doc.cadn.net.cn

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

在前面的代码示例中,首先调用 validateEnterBookingDetails 方法。 接下来调用默认的 validate 方法。spring-doc.cadn.net.cn

13.9.3. ValidationContext接口

A ValidationContext 让您能够获取一个 MessageContext,以在验证期间记录消息。 它还公开了当前用户的信息,例如发出的 userEvent 和当前用户的 Principal 身份。 您可以利用这些信息根据 UI 中激活的按钮或链接,或者根据已通过身份验证的用户来定制验证逻辑。 有关详细信息,请参阅 ValidationContext 的 API Javadoc。spring-doc.cadn.net.cn

13.10. 抑制验证

您可以使用 validate 属性来抑制特定视图事件的模型验证,如下所示:spring-doc.cadn.net.cn

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

在前面的示例中,数据绑定仍然发生在 back 上,但验证被抑制了。spring-doc.cadn.net.cn

13.11. 定义视图转换

你可以定义一个或多个transition元素来处理可能在视图上发生的用户事件。 转换可能会将用户带到另一个视图,或者它可能会执行某个操作并重新渲染当前视图。 在处理Ajax事件时,转换还可能请求渲染视图的某些部分(称为“片段”)。 最后,你还可以定义在所有视图之间共享的“全局”转换。spring-doc.cadn.net.cn

以下部分讨论如何实现视图转换。spring-doc.cadn.net.cn

13.11.1. 转换操作

A view-state 转换在运行之前可以调用一个或多个操作。 这些操作可能会返回一个错误结果,以防止转换离开当前的 view-state。 如果发生错误结果,视图将重新渲染,并应向用户显示适当的消息。spring-doc.cadn.net.cn

如果转换操作调用了一个普通的 Java 方法,被调用的方法可以返回一个布尔值,其值(truefalse)表示转换是否应该发生或被阻止运行。 方法还可以返回 String,其中 successyestrue 的字面值表示转换应该发生,而任何其他值则表示相反的含义。 您可以使用这种技术来处理服务层方法抛出的异常。 以下示例调用了一个调用服务并处理异常情况的操作:spring-doc.cadn.net.cn

<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;
       }
   }
}

当在一个转换上定义了多个操作时,如果其中一个返回错误结果,则集合中剩余的操作将不会被执行。 如果你需要确保一个转换操作的结果不会影响另一个操作的执行,请定义一个单独的转换操作,该操作调用封装了所有操作逻辑的方法。spring-doc.cadn.net.cn

13.11.2. 全局转换

您可以使用流的 global-transitions 元素来创建适用于所有视图的转换。 全局转换通常用于处理作为布局一部分的全局菜单链接。 以下示例定义了一个 global-transition 元素:spring-doc.cadn.net.cn

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

13.11.3. 事件处理器

从一个 view-state,你还可以定义没有目标的转换。 这样的转换被称为“事件处理器”。 以下示例定义了这样的转换:spring-doc.cadn.net.cn

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

这些事件处理器不会改变流程的状态。 它们执行其操作并重新渲染当前视图或当前视图的一个或多个片段。spring-doc.cadn.net.cn

13.11.4. 渲染片段

你可以使用 render 元素在转换过程中请求在处理事件后对当前视图进行部分重新渲染,如下所示:spring-doc.cadn.net.cn

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

fragments 属性应引用您希望重新渲染的视图元素的 ID。您可以通过使用逗号分隔符来指定多个要重新渲染的元素。spring-doc.cadn.net.cn

这种局部渲染通常与 Ajax 触发的事件一起使用,以更新视图中的特定区域。spring-doc.cadn.net.cn

13.12. 使用消息

Spring Web Flow的MessageContext 是一个用于在流执行过程中记录消息的API。 您可以将纯文本消息添加到上下文中,也可以添加由Spring MessageSource解析的国际化消息。 消息可以由视图渲染,并且在流执行重定向时会自动保留。 提供了三种不同的消息严重性级别:infowarningerror。 此外,还提供了一个便捷的MessageBuilder,用于流畅地构造消息。spring-doc.cadn.net.cn

13.12.1. 添加纯文本消息

您可以向上下文中添加纯文本消息。 以下示例展示了如何实现:spring-doc.cadn.net.cn

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());

13.12.2. 添加国际化消息

您可以向上下文中添加国际化的(即本地化的)消息。 以下示例展示了如何实现:spring-doc.cadn.net.cn

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());

13.12.3. 使用消息包

国际化消息定义在由 Spring MessageSource 访问的消息包中。 要创建特定流程的消息包,请在您的流程目录中定义 messages.properties 文件。 创建一个默认的 messages.properties 文件,并为每个需要支持的额外 Locale 创建一个 .properties 文件。 以下示例定义了一些消息:spring-doc.cadn.net.cn

#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 变量来访问消息资源,如下所示:spring-doc.cadn.net.cn

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

13.12.4. 理解系统生成的消息

Web Flow 本身在多个地方生成要向用户显示的消息。 一个重要的地方是在视图到模型的数据绑定过程中。 当发生绑定错误(例如类型转换错误)时,Web Flow 会将该错误映射到一条消息,这条消息会自动从您的资源包中检索。 为了查找要显示的消息,Web Flow 会尝试使用包含绑定错误代码和目标属性名称的资源键。spring-doc.cadn.net.cn

作为一个示例,考虑将绑定到 checkinDate 对象的 Booking 属性。 假设用户输入了一个字母字符串。 在这种情况下,会引发类型转换错误。 Web Flow 通过首先查询资源束中具有以下键的消息,将 typeMismatch 错误代码映射到一条消息:spring-doc.cadn.net.cn

booking.checkinDate.typeMismatch

键的第一部分是模型类的短名称。 键的第二部分是属性名称。 第三部分是错误代码。 这允许在模型属性绑定失败时查找要向用户显示的唯一消息。 这样的消息可能会说:spring-doc.cadn.net.cn

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

如果找不到该形式的资源键,则会尝试一个更通用的键。 此键是错误代码。 属性的字段名称作为消息参数提供,如下所示:spring-doc.cadn.net.cn

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

13.13. 显示弹出窗口

你可以使用 popup 属性在模态弹出对话框中渲染视图,如下所示:spring-doc.cadn.net.cn

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

当将 Web Flow 与 Spring Javascript 库一起使用时,弹出窗口显示不需要客户端代码。 Web Flow 会向客户端发送响应,请求从弹出窗口重定向到视图,客户端会遵守该请求。spring-doc.cadn.net.cn

13.14. 视图回溯

默认情况下,当您退出一个视图状态并过渡到一个新的视图状态时,可以使用浏览器的后退按钮返回到上一个状态。 这些视图状态历史记录策略可以通过使用 history 属性在每个过渡的基础上进行配置。spring-doc.cadn.net.cn

13.14.1. 丢弃历史记录

你可以将history属性设置为discard,以防止回溯到视图,如下所示:spring-doc.cadn.net.cn

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

13.14.2. 使历史记录无效

你可以将history属性设置为invalidate,以防止回溯到某个视图以及之前显示的所有视图,如下所示:spring-doc.cadn.net.cn

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