基础
指定view-state的view属性的几种方式
- 按照默认名称在相对路径下查找view
1 | <!-- 在flow配置文件同目录下查找名为enterBookingDetails的视图 --> |
- 指明view名称,在相对路径下查找view
1 | <!-- 在flow配置文件同目录下查找名为bookingDetails.xhtml的视图 --> |
- 绝对路径查找view
1 | <!-- 直接查找/WEB-INF/hotels/booking/bookingDetails.xhtml的视图 --> |
- 按照逻辑ID定位view
这是结合Spring提供的其它viewResolver来定位到其他组件中的view,如Tiles等。在前面 配置 一章中有讲
1 | <!-- 结合viewResolver共同确定视图位 --> |
在viewScope中分配变量
- 直接分配
1 | <var name="searchCriteria" class="com.mycompany.myapp.hotels.SearchCriteria" /> |
- 依靠运算结果分配
1 | <on-render> |
在viewScope中操作对象
如下例子展示了如何在同一个view state的不同时机操作一系列对象
1 | <view-state id="searchResults"> |
<on-render>
在view渲染前可以执行一个或多个action,这些action将会在视图最开始渲染以及后续的任何刷新,甚至视图局部的重新渲染执行。以上面的代码为例,在重新渲染结果之前,还会先执行一次findHotels方法。
##数据绑定
在view-state上绑定model
使用model属性,可以将一个对象绑定到view中的表单中。web flow可以帮助完成对象属性和表单域的绑定和验证。
1 | <view-state id="enterBookingDetails" model="booking"> |
绑定时机是当view的event发生时
view-to-model
绑定:在view完成并回发时,用户的输入域会被绑定到指定对象的属性上model
验证:绑定后,如果需要验证,验证逻辑会被调用。- 需要指出的是:只有当验证成功后才能transition到别的state,验证失败时会重新渲染该view,要求用户重新输入。
##绑定model时的类型转换
####基础
由于客户端上传的表单数据都是字符串类型的, 因此需要进行类型转换
Web Flow类型转换和Spring MVC的类型转换的关系
在web-flow 2.1以前,Sprign MVC和web-flow使用不同的类型转换机制,但是2.1以后,二者使用相同的类型转换
以前,Web Flow使用spring-binding-2.4.6.RELEASE.jar
包提供的API进行类型转换,相关的类有org.springframework.binding.convert.service.DefaultConversionService
,org.springframework.binding.convert.converters.Converter
等,通过实现Converter
接口完成自定义转换器,再通过DefaultConversionService
进行注册,就像如下所示的方式1那样;而且这样还能够注册带命名ID的转换器,可以结合<bingding>
的converter
属性进行使用,但这种方式已经非常不建议了。
目前, Web Flow在执行conversionService时依然使用org.springframework.binding.convert.service.DefaultConversionService
,但该服务已经不会去注册任何默认的转换器和格式化器了,而是将转换和格式任务全部委托给来自spring-core-4.3.7.RELEASE.jar
包的org.springframework.core.convert.ConversionService
完成。DefaultConversionService
内部维护一个ConversionService
对象,对DefaultConversionService
中进行的大多数操作都被转变成对ConversionService
的操作(对带命名ID转换器的管理除外)。值得一提的是,命名ID的转换器非常没有必要,因为在检测到相应类型后,系统会自动调用合适的转换器。方式一 传统方式添加Converter(不建议使用)
1 | import org.springframework.binding.convert.converters.Converter; |
1 | // 注册自己的转换器 |
1 | <!-- 使用转换器,什么都不用指定,当检测到model中的checkinDate属性为Date类型时,DateConverter会自动被调用 --> |
- 方式二 添加带ID的Converter(已过时,不推荐使用)
1 | DateConverter的定义不变 |
1 | // 注册时指定id |
1 | <!-- 使用时指定id --> |
- 方式三 - 配置通用的Converter和Formatter(推荐使用,当前版本鼓励的方式)
1 | // 首先实现FormattingConversionServiceFactoryBean,添加自定义格式化器和转换器 |
1 | // 创建由ApplicationConversionServiceFactoryBean产生的ConversionService |
1 | // 下面三个步骤用于配置给Web Flow |
1 | <!-- 下面的步骤用于注册给Spring MVC --> |
- 针对上述三种方式的说明
- 方式一表面上使用的是旧的转换器添加方式,但内部实现还是按照新的方式进行,在添加Converter时,会通过一个适配器类转换将
org.springframework.binding.convert.converters.Converter
转换为org.springframework.core.convert.converter.GenericConverter
1 | // 源码片段 |
- 方式二已过时,这里仅展示
- 方式三推荐使用,但这里仅展示了我自己实验成功的java config配置方式,xml配置方式可以参见官网。
- 实际使用时,会发现方式一从视图提交到model时类型转换能够正常进行,但是从model回填到视图时并非自己最开始输入的数值,那是因为我们只设置了Converter,没有设置Formatter。
- 关于转换器的配置,只要认识到ConversionService是类型转换的核心,就会省事很多
Converter和Formatter的区别
Converter
: 是spring-core-4.3.7.RELEASE.jar
包提供的,用于Object to Object
的转换Formatter
: 是spring-context-4.3.7.RELEASE.jar
包提供的,用于Object to String
的转换。
格式化注解
新的类型转换提供两个有用的注解,可以放在model类的属性上,和被@Controller类的方法参数中。
NumberFormat
DateTimeFormat
: 该注解默认使用Joda Time
,需要在类路径中包含Joda Time的包。默认情况下Spring MVC和web flow都没有其他的日期相关的转换器和格式化器。因此定义我们自己的日期相关转换器和格式化器非常重要。- 此外,我们还可以参照上述两个注解定义自己的注解.
关于绑定的另外两点
- 取消绑定
可以使用bind属性在特定情况下取消绑定。如下当触发cancel事件时不会执行绑定操作
1 | <view-state id="enterBookingDetails" model="booking"> |
- 显式地指明绑定的字段
使用标签可以显式指明需要绑定的字段,同时可以指明需要使用到的转换器,和是否允许为空。
1 | <view-state id="enterBookingDetails" model="booking"> |
注意事项
- 没有显式指定绑定字段时,所有model对象的公共属性都会被绑定;指定绑定字段时,则只有显式指定的字段会被绑定
- 没有显式声明转换器时,会使用自动检测的转换器
- 声明不允许为空时,若出现空,则会产生验证错误,并会重新绘制视图并报错。
##绑定数据的验证
Web Flow支持自定义验证条件和JSR-303 Bean验证框架
####JSR-303 Bean Validation
- 基础配置
首先类路径中需要有一个validator的jar包,然后按照如下配置后,validator会应用到所有的添加了条件注解的model属性中
1 | <webflow:flow-registry flow-builder-services="flowBuilderServices" /> |
form中按如下方式配置
1 | @NotNull |
前端按如下配置,使用<sf:errors>
可将name属性的错误信息显示出来
1 | <sf:form action="${flowExecutionUrl}&_eventId=submit" commandName="searchForm" method="post"> |
效果如下
- 部分验证
JSR-303支持部分验证,通过验证组的方式,使用如下(我在验证该方式时是不行的,提示viwe-state标签不允许出现validation-hints属性)
1 | @NotNull |
1 | <view-state id="state1" model="myModel" validation-hints="'group1,group2'"> |
对该方法不做详细解释,最好还是参考一下JSR-303再来看
自定义验证
JSR-303仅支持对Bean的验证,如非空,字符串长度等。我们经常需要自定义验证逻辑,有如下两种方式进行自定义
- 一是在model类内部定义一个以
validate${stateId}(ValidationContext)
为签名的方法,在view提交时会自动调用该验证方法。stateId是view-state的id
1 | // 官方示例 |
- 二是单独定义一个类,类名为
${model}Validator
,在其内部定义一个以validate${stateId}(${model}, ValidationContext)
为签名的方法,然后将该类装载到Spring中。
1 | // 官方示例 |
针对第二种情况也可以定义一个validate(${model}, ValidationContext)
方法,这样无论在哪个view-state的view返回时,只要绑定了该moel,都会调用该验证方法。
当validate(${model}, ValidationContext)
和validate${stateId}(${model}, ValidationContext)
都存在时,会先调用后者,再调用前者。
多说两点
- 失能验证
通过如下方式可以在局部使得验证失效
1 | <view-state id="chooseAmenities" model="booking"> |
转移
在一个view-state中,可能发生各种转移
- 转移之前执行操作
可以在转移之前执行特定的操作,如一个方法, 当方法返回false或者发生错误时,转移不会继续进行下去。而是重新渲染相应的部分
1 | <transition on="submit" to="bookingConfirmed"> |
- 全局转移
定义全局有效的转移操作
1 | <global-transitions> |
- 事件处理器
可以利用transition标签只响应事件,而不做任何跳转操作,从而作为事件处理器
1 | <transition on="event"> |
- 局部渲染
利用<render>
标签可以进行局部渲染,一般用于Ajax的局部刷新操作
1 | <transition on="next"> |
如上,当发生next事件时,首先执行翻页操作,然后重新刷新查询结果区域。fragment属性应该引用想要刷新的view的id,当刷新多个区域时,使用逗号隔开。
view的回退控制
当我们从一个view跳转到另一个view时,通过浏览器的回退按钮,可以返回上一个view,我们可以对这个功能进行配置
- 失能一个回退view,即当前view不能再下一个view上回退,回退到的是前一个view
1 | <transition on="cancel" to="bookingCancelled" history="discard"> |
- 失能所有回退view,即当前及之前所有view都是不能够被回退的。
1 | <transition on="confirm" to="bookingConfirmed" history="invalidate"> |
##MessageContext
Spring web flow的MessageContext是用来记录在flow执行期间的信息的。而其中包含的信息都由MessageBuilder产生,可以手动添加,也可以由系统自动产生。
手动添加信息
- 手动添加普通文本
1 | MessageContext context = ...(这里一般是从上一级获取到context对象) |
- 添加Spring MessageSource得到的信息
1 | MessageContext context = ... |
- 添加message bundle获取的信息
可以直接在view或flow中使用resourceBundle
这个EL变量来获取资源文件中的内容。(这个需要在web-flow同目录下放置一个message.properties
文件)
1 | <input value="#{resourceBundle.reservationConfirmation}" /> |
系统自动产生信息
有多重情况下web-flow会自动生成message,其中一种重要的情况是当view-to-model验证失败时,生成规则是,首先到资源文件中查找key为${model}l.${property}.${errCode}
的资源,如果找不到,则直接查找key为${errCode}
的资源。
举例:有model名为booking,验证其中的checkDate属性,当出现类型不匹配时,系统给出的错误代码为typeMismatch
,则会在资源文件中查找如下key的资源
booking.checkDate.typeMismatch
或 typeMismatch
附
下面大致列举一下Spring MVC会自动注册的转换器和格式化器(调用ConversionService
的toString()
方法)
1 | @org.springframework.format.annotation.DateTimeFormat java.lang.Long -> java.lang.String: org.springframework.format.datetime.DateTimeFormatAnnotationFormatterFactory@3de4aaed,@org.springframework.format.annotation.NumberFormat java.lang.Long -> java.lang.String: org.springframework.format.number.NumberFormatAnnotationFormatterFactory@806d8b |