本月,Rizon Software 的 CTO Paul Tabor 应邀与我一道解除针对 JSF 的 FUD。在本文中,我们将介绍 JSF 转换和验证框架的概念,它比您所想的要容易使用得多,也灵活得多。
首先我们将介绍应用于 JSF 生命周期的转换和验证过程,然后展示一个简单的 JSF 应用程序中 的默认转换和验证过程。接着将展示如何创建和插入自定义的实现,以应对要求更高的场景。正如 Rick 在以前的文章中所说的,我们会理论与实践并重,先介绍概念,再用一个实际 例子说明这些概念的应用。示例应用程序将涵盖大多数转换和验证用例,虽然只是初级的。
注意,示例应用程序的默认编译环境是 Maven,不过 , 还提供了一个 Ant 脚本。 可以单击本页顶部或者底部的 Code图标下载示例源代码。为了简便起见,您会发现,该例子的设置与上一篇文章中的一样。 关于构建环境配置的更多说明,包括在 Ant 环境中而不是在 Maven 环境中编译和运行 示例应用程序的说明,请参阅 参考资料。
转换和验证
虽然在 JSF Web 应用程序中使用转换和验证不一定要理解 JavaServer Faces 生命周期的基础知识,但是在深入转换和验证内容之前,最好对一些基本知识做一回顾。 此外,掌握一点 JSF 生命周期技巧可以极大地帮助简化 Web 应用程序的开发 工作。还有助于更好地理解 JSF 的可插入能力。
图 1 描绘了我们所说的“基本 JSF 生命周期”。 基本是在暗示这只是一个典型的处理所提交表单值的请求 - 响应(request-and-response)场景。
图 1. 基本 JSF 生命周期
![JSF 基本生命周期图](https://i-blog.csdnimg.cn/blog_migrate/a864c860e08ac7b1c514309a9932cdcc.png)
显然,不同的场景对这里重点描述的生命周期有不同的影响。我们将在 本文稍后介绍其中一些场景。现在,只需要注意转换和验证过程发生在应用请求值、处理验证和 呈现响应阶段即可。
我们将在稍后介绍为什么转换和验证会在这些阶段出现,但是首先让我们 澄清一个更基本的问题:转换是什么?简单地说,转换是确保数据拥有正确的对象或者类型的过程。下面是两个典型的转换:
- 字符串值可以转换为
java.util.Date
。 - 字符串值可以转换为 Float。
至于 验证,它用于确保数据包含所期望的内容。下面是两个 典型的验证:
- java.util.Date 的格式为 MM/yyyy。
- Float 在 1.0 和 100.0 之间。
关注生命周期阶段
转换和验证的主要目的是确保在更新模型数据之前已经经过了正确的 无害处理。之后,当需要调用应用程序方法用这些些数据实际 做一些事情时, 就可以有把握地假定模型的某些状态。转换和验证使您可以侧重于业务逻辑,而不是侧重于对输入数据进行繁琐的资格认定,比如 null 检验、长度限定、范围边界,等等。
因此,在 更新模型数据生命周期阶段中,在组件数据被绑定到 backing bean 模型 之前 进行转换和验证处理是有道理的。正如图 1 所示,转换发生在应用请求值阶段,而验证发生在 处理验证阶段。图 2 突出显示了这些阶段。
图 2. 要关注的转换和验证阶段
![转换和验证阶段的 JSF 生命周期图](https://i-blog.csdnimg.cn/blog_migrate/c4ff9ff66eb4d6ba85c54c853eae374a.png)
关于 immediate 属性
注意,图 2 中描绘的转换和验证过程表示了将 UIInput
组件的 immediate
属性设置为 false
时的应用程序流程。 如果这个属性设置为true
,那么转换和验证会发生在生命周期更早的时期,即应用请求值阶段(参见图 3)。对使用 immediate 属性的详细讨论超出了本文的范围,但是在某些情况下,比如管理动态清单(可能您还记得,本系列的上一篇文章中曾介绍过),它很有用,它甚至可以绕过验证(在与UICommand
组件结合使用时)。能想像一个 需要完全绕过验证的应用程序吗?
图 3 展示了当 immediate
属性设置为 true
时, 在 JSF 应用程序生命周期中的哪些地方进行转换和验证。
图 3. 将 immediate 属性设置为 true
![将 immediate 属性设置为 true 时的 JSF 生命周期图](https://i-blog.csdnimg.cn/blog_migrate/66e1d8299082ee1d58c0f52af493d0c6.png)
实际的例子
下面,我们将用一个示例应用程序展示所讨论的概念。本月的示例应用程序 将展示 JSF 的转换和验证能力。记住,这个示例应用程序非常简单,没有追求一些不必要的面面俱到: 无论如何,我们的目的不是构建一个在真实世界中使用的应用程序!这个示例应用程序 将展示以下几点:
- 使用标准 JSF 转换器转换表单字段数据。
- 使用标准 JSF 验证组件验证表单字段数据。
- 如何编写自定义转换器和验证器。
- 如何在 faces-config.xml 文件中注册自定义转换器和验证器。
- 如何定制默认错误消息。
这个示例应用程序是一个简单的用户注册表单。我们的目标是收集用户数据,比如 姓名、年龄、电子邮箱地址和电话号码。然后,我们将展示如何利用 JSF 转换和验证 确保收集的数据对于模型是适合的。
这个应用程序使用了三个 JSP 页:
- index.jsp 将用户定向到 UserRegistration.jsp。
- UserRegistration.jsp 包含应用程序的表单字段。
- results.jsp 通知应用程序用户已经注册。
我们将首先分析编写 JSF 转换过程的选择。
JSF 转换
如前所述,转换是确保数据对象或者类型正确的一个过程,因此,我们将字符串值 转换为其他类型,比如 Date
对象、基本浮点型或者 Float
对象。可以使用自带的转换器,也可以编写 自定义的转换器。
JSF 提供了许多标准数据转换器。也可以通过实现 Converter
接口插入 自定义转换器,但是这些将在后面进行介绍。下表显示了 JSF 进行简单数据转换所使用的转换器 id及其对应的实现类。大多数数据转换是自动发生的。
javax.faces.BigDecimal | javax.faces.convert.BigDecimalConverter |
---|---|
javax.faces.BigInteger | javax.faces.convert.BigIntegerConverter |
javax.faces.Boolean | javax.faces.convert.BooleanConverter |
javax.faces.Byte | javax.faces.convert.ByteConverter |
javax.faces.Character | javax.faces.convert.CharacterConverter |
javax.faces.DateTime | javax.faces.convert.DateTimeConverter |
javax.faces.Double | javax.faces.convert.DoubleConverter |
javax.faces.Float | javax.faces.convert.FloatConverter |
图 4 展示了用户年龄的默认转换。JSF 标签配置如下:
<!-- UserRegistration.jsp --> <h:inputText id="age" value="#{UserRegistration.user.age}"/>
图 4. 用户注册:年龄的默认转换
![默认转换](https://i-blog.csdnimg.cn/blog_migrate/449ee7632e5ed2a2e12757fb6fdd3180.png)
各种情况的转换器
UserRegistration.user.age表示一个值绑定属性,它的类型为 int
。对于基本型或者 BigInteger
/ BigDecimal
的绑定,JSF 选择了标准转换器。不过,还可以通过 <f:converter/>
标签,利用一个特定的转换器来增加粒度,如下所示。
<!-- UserRegistration.jsp --> <h:inputText id="age" value="#{UserRegistration.user.age}"> <f:converter id="javax.faces.Short"/> </h:inputText>
在图 5 中,可以看到 JSF 使用标准转换器的场景。在这种情况下,虽然 年龄实际上是一个有效的整数,但转换仍然会失败,因为该值不是短整型的。
图 5. 使用 f:converter 标签
![使用 f:converter 标签](https://i-blog.csdnimg.cn/blog_migrate/58658f3c8aceceb4fb407ef066d10775.png)
选择日期格式样式
尽管在默认情况下,JSF 可以很好地处理基本型及类似的类型,但是在处理 日期数据时,必须指定转换标签 <f:convertDateTime/>
。 这个标签基于 java.text
包,并使用短、长和自定义样式。下面是一 个例子:
<!-- UserRegistration.jsp --> <h:inputText id="birthDate" value="#{UserRegistration.user.birthDate}"> <f:convertDateTime pattern="MM/yyyy"/> </h:inputText>
这个例子展示了如何用 <f:convertDateTime/>
确保用户的生日可以转换为 格式为 MM/yyyy(月 / 年)的日期对象。请参阅 JSF 的java.text.SimpleDataFormat
(在 参考资料中),以获取模式列表。
其他样式
除了可以转换日期和时间格式外,JSF 还 提供了处理像百分数或者货币数据这类值的特殊转换器。 这个转换器处理分组(如逗号)、小数、货币符号等。例如, 以下 <f:convertNumber/>
的用法就是处理货币的一种技巧:
<!-- UserRegistration.jsp --> <h:inputText id="salary" value="#{UserRegistration.user.salary}"> <f:convertNumber maxFractionDigits="2" groupingUsed="true" currencySymbol="$" maxIntegerDigits="7" type="currency"/> </h:inputText>
在图 6 中,可以看到一些格式编排不正确的货币数据,以及所导致的转换错误。
图 6. 使用 f:convertNumber 标签
![使用 f:convertNumber 标签](https://i-blog.csdnimg.cn/blog_migrate/e0312e9da1873572abdc1d1f30ea6455.png)
自定义转换
如果需要将字段数据转换为特定于应用程序的值对象,则需要自定义数据转换, 如下面例子所示:
- String 转换为 PhoneNumber 对象 (PhoneNumber.areaCode、PhoneNumber.prefix、 ...)。
- String 转换为 Name 对象 (Name.first、Name.last)。
- String 转换为 ProductCode 对象 (ProductCode.partNum、ProductCode.rev、 ...)。
要创建自定义转换器,必须完成以步骤:
- 实现
Converter
接口(也就是javax.faxes.convert.Converter
)。 - 实现
getAsObject
方法,它将一个字段(字符串) 转换为一个对象(例如,PhoneNumber
)。 - 实现
getAsString
方法,它将一个对象 (如PhoneNumber
)转换为一个字符串。 - 在
Faces
上下文中注册自定义转换器。 - 用
<f:converter/>
标签在 JSP 中插入这个转换器。
您可以自己看到如何在 JSF 应用程序生命周期中加入这些步骤。 在图 7 中,JSF 在应用请求值阶段调用自定义转换器的 getAsObject
方法。 转换器必须在这里将请求字符串转换为所需的对象类型,然后返回这个对象,将它存储在相应的 JSF 组件中。 如果该值被返回呈现在视图中,那么 JSF 将在呈现响应阶段调用 getAsString
方法。 这意味着转换器还要负责将对象数据转换回字符串表示形式。
图 7. 自定义转换器 getAsObject 和 getAsString 方法
![getAsObject、getAsString 自定义转换器方法](https://i-blog.csdnimg.cn/blog_migrate/8b89ba082723fd23e8c2d6e05efa98dc.png)
创建自定义转换器
我们将使用一个案例分析来展示 Converter
接口、getAsObject
和 getAsString
方法的实现,同时还将展示如何在 Faces
上下文中注册这个转换器。
这个案例分析的目的是将一个单字段字符串值转换为一个 PhoneNumber
对象。我们将一步一步地完成这个转换过程。
第 1 步:实现 Converter 接口
这一步实现 Converter
接口。
import javax.faces.convert.Converter; import org.apache.commons.lang.StringUtils; ... public class PhoneConverter implements Converter { ... }
第 2 步:实现 getAsObject 方法
这一步将一个字段值转换为一个 PhoneNumber
对象。
public class PhoneConverter implements Converter { ... public Object getAsObject(FacesContext context, UIComponent component, String value) { if (StringUtils.isEmpty(value)){ return null;} PhoneNumber phone = new PhoneNumber(); String [] phoneComps = StringUtils.split(value," ,()-"); String countryCode = phoneComps[0]; phone.setCountryCode(countryCode); if ("1".equals(countryCode)){ String areaCode = phoneComps[1]; String prefix = phoneComps[2]; String number = phoneComps[3]; phone.setAreaCode(areaCode); phone.setPrefix(prefix); phone.setNumber(number); }else { phone.setNumber(value); } return phone; } }
第 3 步:实现 getAsString 方法
这一步将一个 PhoneNumber
对象转换为一个字符串。
public class PhoneConverter implements Converter { ... public String getAsString(FacesContext context, UIComponent component, Object value) { return value.toString(); } } public class PhoneNumber implements Serializable { ... public String toString(){ if (countryCode.equals("1")){ return countryCode + " " + areaCode + " " + prefix + " " + number; }else{ return number; } } }
第 4 步:在 faces 上下文中注册自定义转换器
第 4 步可以以两种方式执行。第一种选择使用(比如)arcmind.PhoneConverter
的 id 来注册 PhoneConverter
类。JSP 页中的<f:converter/>
标签会使用这个 id。下面是 第 4 步的选项 1 的代码:
<converter> <converter-id>arcmind.PhoneConverter</converter-id> <converter-class>com.arcmind.converters.PhoneConverter</converter-class> </converter>
另一种方法是注册 PhoneConverter
类来自动处理所有 PhoneNumber
对象,如下所示。
<converter> <converter-for-class>com.arcmind.value.PhoneNumber</converter-for-class> <converter-class>com.arcmind.converters.PhoneConverter</converter-class> </converter>
第 5 步:在 JSP 中使用转换器标签?
自然,下一步的执行取决于所选的注册方法。如果选择使用 arcmind.PhoneConverter
的 id 来注册 PhoneConverter
类,那么就使用<f:converter/>
标签,如下所示。
<h:inputText id="phone" value="#{UserRegistration.user.phone}"> <f:converter converterId="arcmind.PhoneConverter" /> </h:inputText>
如果选择注册 PhoneConverter
类来 自动 处理所有 PhoneNumber
,那么就不需要在 JSP 页 中使用 <f:converter/>
标签。下面是第 5 步的不带转换器标签的代码。
<h:inputText id="phone" value="#{UserRegistration.user.phone}"> [Look mom no converter!] </h:inputText>
这样,我们已经完成了这个示例应用程序的转换处理代码!到目前为止完成的应用程序如下所示。
图 8. 带有转换处理的示例应用程序
![带有转换处理的示例应用程序](https://i-blog.csdnimg.cn/blog_migrate/c62a8c89c8d567f53c378a573df7ee49.png)
JSF 验证
如前所述,JSF 验证可以确保应用程序数据包含预期的内容,例如:
- java.util.Date 为 MM/yyyy 格式。
- Float 在 1.0 和 100.0 之间。
在 JSF 中有 4 种验证:
- 自带验证组件。
- 应用程序级验证。
- 自定义验证组件(它实现了
Validator
接口)。 - 在 backing bean 中的验证方法(内联)。
我们将在下面的讨论中介绍并展示每一种形式。
JSF 验证生命周期和组件
图 9 显示了用户注册表单中名字字段的生命周期案例分析。代码引用被有意解释为 伪代码(pseudo-code)。
图 9. JSF 生命周期中的验证
![JSF 生命周期中的验证过程展示](https://i-blog.csdnimg.cn/blog_migrate/a7c3e6553e8aab3337affb52c3a62e26.png)
下面是 JSF 提供的一组标准验证组件:
-
DoubleRangeValidator
:组件的本地值必须为数字类型,必须 在由最小和 / 或最大值所指定的范围内。 -
LongRangeValidator
:组件的本地值必须为数字类型,并且可以转换 为长整型,必须在由最小和 / 或最大值所指定的范围内。 -
LengthValidator
:类型必须为字符串,长度必须在由最小和 / 或 最大值所指定的范围内。
标准验证
在我们的示例应用程序中,用户的年龄可以是任意有效的整数(byte、short、int)。 因为将年龄设置为(比如说)-2是无意义的,所以可能要对这个字段添加一些验证。 下面是一些简单的验证代码,用以确保年龄字段中的数据模型完整性:
<h:inputText id="age" value="#{UserRegistration.user.age}"> <f:validateLongRange maximum="150" minimum="0"/> </h:inputText>
完成年龄字段后,可能希望指定对名字字段的长度加以限制。可以像这样编写这个验证:
<h:inputText id="firstName" value="#{UserRegistration.user.firstName}"> <f:validateLength minimum="2" maximum="25" /> </h:inputText>
图 10 显示了由上面标准验证示例所生成的默认详细验证消息。
图 10. 标准验证错误消息
![带有标准验证错误消息的示例应用程序的屏幕快照](https://i-blog.csdnimg.cn/blog_migrate/4e2838e924d2c7a0247976a719af82eb.png)
尽管 JSF 自带的验证在许多情况下都可以满足,但是它有一些局限性。 在处理电子邮件验证、电话号码、URL、日期等数据时,有时编写自己的验证 器会更好一些,不过我们将在稍后对此进行讨论。
应用程序级验证
在概念上,应用程序级验证实际上是业务逻辑验证。JSF 将表单和 / 或字段级 验证与业务逻辑验证分离开。应用程序级验证主要需要在 backing bean 中添加代码,用这个模型确定绑定到模型中的数据是否合格。对于购物车,表单级验证 可以验证输入的数量是否有效,但是需要使用业务逻辑验证检查用户是否超出了他或者 她的信用额度。这是在 JSF 中分离关注点的另一个例子。
例如,假定用户单击了绑定到某个操作方法的按钮,那么就会在调用应用程序阶段调用 这个方法(有关的细节,请参见上面的 图 1)。假定在更新模型阶段进 行了更新,那么在对模型数据执行任何操纵之前,可以添加一些验证代码,根据应用程序的业务规则检查输入的数据是否有效。
例如,在这个示例应用程序中,用户单击了 Register按钮,这个按钮被绑定到应用程序控制器的 register()
方法。 我们可以在register()
方法中添加验证代码,以确定名字字段是否为 null。如果该字段为 null,那么还可以在 FacesContext
中添加一条消息,指示相关组件返回到当前页。
其实它现在并不是业务规则逻辑的一个好例子。更好的例子是检查用户是否 超出了她或者她的信用额度。在该例中,不是检查字段是否为空,我们可以调用模型对象的方法来确保当前用户已经不在系统中。
图 11 描绘了这个过程。
图 11. 应用程序级验证
![应用程序级验证图](https://i-blog.csdnimg.cn/blog_migrate/9e487b92a1fa6162412f83b388b6da8c.png)
注意在 register()
方法中,消息是如何以 ${formId}:${fieldId}
的形式添加到 FacesContext
中的。 图 12 显示了消息与组件 id 之间的关系。
图 12. 验证消息
![验证消息](https://i-blog.csdnimg.cn/blog_migrate/6b9ccc0b882b6200794e261fb557ffcd.png)
应用程序级验证的优缺点
应用级验证非常直观并且容易实现。不过,这种形式的验证是在其他形式的验证 (标准、自定义、组件)之后发生的。
应用程序级验证的优点如下:
- 容易实现。
- 不需要单独的类(自定义验证器)。
- 不需要页编写者指定验证器。
应用程序级验证的缺点如下:
- 在其他形式的验证(标准、自定义)之后发生。
- 验证逻辑局限于 backing bean 方法,使得重用性很有限。
- 在大型应用程序和 / 或团队环境中可能难于管理。
最终,应用程序级验证只应该用于那些需要业务逻辑验证的环境中。
自定义验证组件
对于标准 JSF 验证器不支持的数据类型,则需要建立自己的自定义验证组件,其中包括电子邮件地址和邮政编码。如果需要明确控制显示给最终用户的消息, 那么还需要建立自己的验证器。在 JSF 中,可以创建可在整个 Web 应用程序中重复使用 的可插入验证组件。
创建自定义验证器的步骤如下,我们将一步步地分析:
- 创建一个实现了
Validator
接口的类 (javax.faces.validator.Validator
)。 - 实现
validate
方法。 - 在 faces-confix.xml 文件中注册自定义验证。
- 在 JSP 页中使用
<f:validator/>
标签。
下面是创建自定义验证器的分步示例代码。
第 1:实现 Validator 接口
第一步是实现 Validator
接口。
import javax.faces.validator.Validator; import javax.faces.validator.ValidatorException; ... public class ZipCodeValidator implements Validator{ private boolean plus4Required; private boolean plus4Optional; /** Accepts zip codes like 85710 */ private static final String ZIP_REGEX = "[0-9]{5}"; /** Accepts zip code plus 4 extensions like "-1119" or " 1119" */ private static final String PLUS4_REQUIRED_REGEX = "[ |-]{1}[0-9]{4}"; /** Optionally accepts a plus 4 */ private static final String PLUS4_OPTIONAL_REGEX = "([ |-]{1}[0-9]{4})?"; ... }
第 2 步:实现验证方法
接下来,需要实现 validate
方法。
public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException { /* Create the correct mask */ Pattern mask = null; /* more on this method later */ initProps(component); if (plus4Required){ mask = Pattern.compile(ZIP_REGEX + PLUS4_REQUIRED_REGEX); } else if (plus4Optional){ mask = Pattern.compile(ZIP_REGEX + PLUS4_OPTIONAL_REGEX); } else if (plus4Required && plus4Optional){ throw new IllegalStateException("Plus 4 is either optional or required"); } else { mask = Pattern.compile(ZIP_REGEX); } /* Get the string value of the current field */ String zipField = (String)value; /* Check to see if the value is a zip code */ Matcher matcher = mask.matcher(zipField); if (!matcher.matches()){ FacesMessage message = new FacesMessage(); message.setDetail("Zip code not valid"); message.setSummary("Zip code not valid"); message.setSeverity(FacesMessage.SEVERITY_ERROR); throw new ValidatorException(message); } }
第 3 步:在 FacesContext 中注册自定义验证器
您现在应该熟悉在 FacesContext
中注册自定义验证器的代码了。
<validator> <validator-id>arcmind.zipCodeValidator</validator-id> <validator-class> com.arcmind.jsfquickstart.validation.ZipCodeValidator </validator-class> </validator>
第 4 步:在 JSP 中使用 <f:validator/> 标签
<f:validator/>
标签声明使用 zipCodeValidator
。<f:attribute/>
标签将 plus4Optional
属性设置为 true
。 注意,它定义了inputText
组件的属性,而 不是验证器的属性!
<h:inputText id="zipCode" value="#{UserRegistration.user.zipCode}"> <f:validator validatorId="armind.zipCodeValidator"/> <f:attribute name="plus4Optional" value="true"/> </h:inputText>
为了读取 zipCode
inputText
组件的 plus4Optional
属性,请完成以下步骤::
private void initProps(UIComponent component) { Boolean optional = Boolean.valueOf((String) component.getAttributes(). get("plus4Optional")); Boolean required = Boolean.valueOf((String) component.getAttributes(). get("plus4Required")); plus4Optional = optional==null ? plus4Optional : optional.booleanValue(); plus4Required = required==null ? plus4Optional : required.booleanValue(); }
总体而言,创建自定义验证器是相当直观的,并且可以使该验证在许多应用程序中重复使用。缺点是必须创建一个类,并在 faces 上下文中管理验证器注册。 不过,通过创建一个使用这个验证器的自定义标签,使其看上去像是一个自带的验证,可以进一步实现自定义验证器。对于常见的验证问题,如电子邮件 验证,这种方法可以支持这样一种设计理念,即代码重用和一致的应用程序行为是 最重要的。
backing bean 中的验证方法
作为创建单独的验证器类的替代方法,可以只在 backing bean 的方法中实现自定义 验证,只要这个方法符合 Validator
接口的 validate
方法的参数签名即可。例如,可以编写以下方法:
[SomeBackingBean.java] public void validateEmail(FacesContext context, UIComponent toValidate, Object value) { String email = (String) value; if (email.indexOf('@') == -1) { ((UIInput)toValidate).setValid(false); FacesMessage message = new FacesMessage("Invalid Email"); context.addMessage(toValidate.getClientId(context), message); } }
之后,可通过如下所示的 validator
属性在 JSF 中使用这个方法:
<h:inputText id="email" value="#{UserRegistration.user.email}" validator="#{UserRegistration.validateEmail}" required="true"> </h:inputText>
JSF 用 validateEmail
方法对绑定到 user.email
模型属性的 inputText
组件值进行自定义验证。如果电子邮件格式无效,那么就在相关组件的 faces 上下文中添加 消息。考虑到这种验证方法实际上是 backing bean 的一部分,为什么通常必须用某个值与相关组件的关联来评估该值,而不是直接检查本地 bean 属性呢?线索就在前面的生命周期图中。如果现在不能马上找到 答案,也不要担心,我们将在本文的最后对此加以说明。
默认验证
注意上面 email
标签的 required
属性。 利用 required
属性是一种 默认验证形式。如果这个属性是 true
,那么相应的组件必须有一个值。一个重要的 说明:如果 required
属性为 false
, 那么就不用对这个标签 / 组件指派验证,这样,JSF 将跳过对这个组件的验证,并让值和组件的状态保持不变。
图 13 概述了我们讨论过的验证形式。
图 13. 验证视图
![JSF 中验证形式的一览图](https://i-blog.csdnimg.cn/blog_migrate/3c0431a91445bc3a25babbb1d91164f9.png)
自定义消息
您可能注意到了,JSF 提供的默认转换和验证消息非常长,这会让那些总是输入无效表单数据的最终用户感到困惑和恼火。幸运的是,您可以通过 创建自己的消息资源绑定来改变 JSF 提供的默认消息。jsf-impl.jar (或类似的文件中)中包含了一个 message.properties 文件,该文件包含图 14 所示的默认消息。
图 14. 默认 JSF 转换和验证消息
![JSF 的默认转换和验证消息](https://i-blog.csdnimg.cn/blog_migrate/f8f230fc907d42d28c462a59a91019eb.png)
通过创建自己的 message.properties 文件并断开指定场所的 faces 上下文中绑定 的消息资源,您可以更改默认消息,如图 15 所示。
图 15. 取消消息资源绑定
![消息绑定](https://i-blog.csdnimg.cn/blog_migrate/08dff9dd2d32c5f80bb2a080c971bc94.png)
关于在 JSF 中创建自定义转换和验证消息的更多内容请参前阅 参考资料。
处理 JSF 生命周期
我们在本文前面留下了一些问题让您考虑,现在可以解决它们了! 我们提到的一件事是对 UICommand
按钮使用 immediate 属性,比如commandLink
或者 commandButtons
。现在请您考虑希望在什么样的场景中跳过验证。
基本上只要用户需要输入数据,就需要对这个数据进行验证。不过,如果整个数据 项是可选的,那么就不需要进行验证。一种避免 JSF 生命周期的验证阶段的方法是利用 UICommand
组件的 immediate
属性,该属性可以在处理验证阶段 之前的应用请求值阶段期间(而不是在处理验证阶段 之后的调用应用程序阶段) 强制调用这个操作。
immediate
属性允许您通过标准浏览规则控制 页流程,并绕过验证。可以针对特定的场景实现这项技术,比如带有可选步骤和 / 或表单的在线 向导(如当用户单击 Skip按钮以进入下一视图),或者在用户因为某种原因而取消某个表单的情况下。
我们在本文中留下的第二个问题是:既然验证方法实际上是 backing bean 的 一部分,那么为什么通常必须利用组件关联来判断它的值。请参阅前面的 JSF 应用程序生命周期,看看您能否找到答案。
这里的密诀是:尽管 validateEmail
嵌入 验证方法是实际的 backing bean 的一部分,但是该方法必须通过组件关联来引用这,而不是直接访问本地属性来引用值。由于验证发生在组件值绑定到模型 之前(在更新模型值阶段),所以模型处于未知状态。 因此,必须编写嵌入自定义验证逻辑,就像使用一个自定义 Validator
对象处理验证一样。这也解释了维护相同方法签名的需求。
这些尚待解决的枝节问题有什么意义呢,当然,它们最终将我们带回 JSF 应用程序生命周期。将这些问题汇总在一起,就能体现充分理解生命周期的重要性 —— 向后、向前或由内向外,这样您就可以在需要的时候操纵它。
结束语
在本文中我们讨论了相当多的 JSF 转换和验证的基本内容。事实上, 我们讨论了在自己的应用程序中使用这些过程需要知道的大部分内容 (至少对这个版本的 JSF 而言)!
当然,我们不可能讨论到 所有内容。例如,您可能想要了解 MyFaces (请参阅 参考资料)中 JSF 没有提供、或者这里没有讨论到的验证器组件。 此外,虽然我们讨论了大多数常用的转换和验证技术,但还有一些没有包含在内。 例如,在编写自定义组件时,可以在组件的解码 / 编码过程中直接处理转换和 / 或验证 (取决于组件的类型及其功能),但是我们只能将对自定义组件开发的更深入讨论留 到以后进行了。
其他要牢记的是转换和验证不一定会很好地协同工作。转换将字符串转换 为对象,而大多数标准验证是对字符串进行的。因此,在同时使用自定义转换 和验证必须格外小心。例如,PhoneNumber
对象不能与长度验证器一起使用。在这种情况下,要么编写自定义验证器,要么在自定义转换器中添加一个特别的验证逻辑。我们偏向后一种方法,因为 它让我们可以将自定义转换器(自带验证逻辑)与特定的对象类型相关联,并让 JSF 处理这种对象类型。JSF 自动为我们做这项工作,不需要在 JSP 中包含任何 特定的转换器 id。(当然,有人会称它为懒惰编程,它也不是对所有用例都适用的最佳解决方案。)
我们认为本月文章中的讨论再次声明了以下这点,即 JSF 提供了一种灵活的、强大的可插入式 Web 应用程序开发框架。除了标准转换器和验证器之外,JSF 还可以促进同时满足应用程序和框架开发人员的要求的自定义实现。最终,要由您来确定选择何种转换和验证策略。JSF 使您能够在原型制造阶段很快、很容易地上手(标准转换器、验证器、内部验证等),并在以后的开发阶段移植到更 复杂的生产解决方案中(自定义对象、自定义消息等)。JSF 生命周期在所有阶段都提供了 可靠的基础设施,始终如一地保证数据模型的完整性。
在下个月中,我们将深入分析如何用 JSF 编写自已的自定义组件,并结束这一系列。
---------------------------------------------
转自:http://www.ibm.com/developerworks/cn/java/j-jsf3/