JSF 转换与验证
2006-02-12 21:49
417 查看
本月,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 生命周期
显然,不同的场景对这里重点描述的生命周期有不同的影响。我们将在本文稍后介绍其中一些场景。现在,只需要注意转换和验证过程发生在应用请求值、处理验证 和呈现响应 阶段即可。
我们将在稍后介绍为什么转换和验证会在这些阶段出现,但是首先让我们澄清一个更基本的问题:转换 是什么?简单地说,转换是确保数据拥有正确的对象或者类型的过程。下面是两个典型的转换:
字符串值可以转换为
字符串值可以转换为 Float。
至于验证,它用于确保数据包含所期望的内容。下面是两个典型的验证:
java.util.Date 的格式为 MM/yyyy。
Float 在 1.0 和 100.0 之间。
关注生命周期阶段
转换和验证的主要目的是确保在更新模型数据之前已经经过了正确的无害处理。之后,当需要调用应用程序方法用这些些数据实际做一些事情 时,就可以有把握地假定模型的某些状态。转换和验证使您可以侧重于业务逻辑,而不是侧重于对输入数据进行繁琐的资格认定,比如 null 检验、长度限定、范围边界,等等。
因此,在更新模型数据 生命周期阶段中,在组件数据被绑定到 backing bean 模型之前 进行转换和验证处理是有道理的。正如图 1 所示,转换发生在应用请求值阶段,而验证发生在处理验证阶段。图 2 突出显示了这些阶段。
图 2. 要关注的转换和验证阶段
关于 immediate 属性
注意,图 2 中描绘的转换和验证过程表示了将
图 3 展示了当
图 3. 将 immediate 属性设置为 true
实际的例子
下面,我们将用一个示例应用程序展示所讨论的概念。本月的示例应用程序将展示 JSF 的转换和验证能力。记住,这个示例应用程序非常简单,没有追求一些不必要的面面俱到:无论如何,我们的目的不是构建一个在真实世界中使用的应用程序!这个示例应用程序将展示以下几点:
使用标准 JSF 转换器转换表单字段数据。
使用标准 JSF 验证组件验证表单字段数据。
如何编写自定义转换器和验证器。
如何在 faces-config.xml 文件中注册自定义转换器和验证器。
如何定制默认错误消息。
这个示例应用程序是一个简单的用户注册表单。我们的目标是收集用户数据,比如姓名、年龄、电子邮箱地址和电话号码。然后,我们将展示如何利用 JSF 转换和验证确保收集的数据对于模型是适合的。
这个应用程序使用了三个 JSP 页:
index.jsp 将用户定向到 UserRegistration.jsp。
UserRegistration.jsp 包含应用程序的表单字段。
results.jsp 通知应用程序用户已经注册。
我们将首先分析编写 JSF 转换过程的选择。
JSF 转换
如前所述,转换是确保数据对象或者类型正确的一个过程,因此,我们将字符串值转换为其他类型,比如
JSF 提供了许多标准数据转换器。也可以通过实现
图 4 展示了用户年龄的默认转换。JSF 标签配置如下:
图 4. 用户注册:年龄的默认转换
各种情况的转换器
UserRegistration.user.age 表示一个值绑定属性,它的类型为
在图 5 中,可以看到 JSF 使用标准转换器的场景。在这种情况下,虽然年龄实际上是一个有效的整数,但转换仍然会失败,因为该值不是短整型的。
图 5. 使用 f:converter 标签
选择日期格式样式
尽管在默认情况下,JSF 可以很好地处理基本型及类似的类型,但是在处理日期数据时,必须指定转换标签
这个例子展示了如何用
其他样式
除了可以转换日期和时间格式外,JSF 还提供了处理像百分数或者货币数据这类值的特殊转换器。这个转换器处理分组(如逗号)、小数、货币符号等。例如,以下
在图 6 中,可以看到一些格式编排不正确的货币数据,以及所导致的转换错误。
图 6. 使用 f:convertNumber 标签
自定义转换
如果需要将字段数据转换为特定于应用程序的值对象,则需要自定义数据转换,如下面例子所示:
String 转换为 PhoneNumber 对象 (PhoneNumber.areaCode、PhoneNumber.prefix、 ...)。
String 转换为 Name 对象 (Name.first、Name.last)。
String 转换为 ProductCode 对象 (ProductCode.partNum、ProductCode.rev、 ...)。
要创建自定义转换器,必须完成以步骤:
实现
实现
实现
在
用
您可以自己看到如何在 JSF 应用程序生命周期中加入这些步骤。在图 7 中,JSF 在应用请求值阶段调用自定义转换器的
图 7. 自定义转换器 getAsObject 和 getAsString 方法
创建自定义转换器
我们将使用一个案例分析来展示
这个案例分析的目的是将一个单字段字符串值转换为一个
第 1 步:实现 Converter 接口
这一步实现
第 2 步:实现 getAsObject 方法
这一步将一个字段值转换为一个
第 3 步:实现 getAsString 方法
这一步将一个
第 4 步:在 faces 上下文中注册自定义转换器
第 4 步可以以两种方式执行。第一种选择使用(比如)
另一种方法是注册
第 5 步:在 JSP 中使用转换器标签?
自然,下一步的执行取决于所选的注册方法。如果选择使用
如果选择注册
这样,我们已经完成了这个示例应用程序的转换处理代码!到目前为止完成的应用程序如下所示。
图 8. 带有转换处理的示例应用程序
JSF 验证
如前所述,JSF 验证可以确保应用程序数据包含预期的内容,例如:
java.util.Date 为 MM/yyyy 格式。
Float 在 1.0 和 100.0 之间。
在 JSF 中有 4 种验证:
自带验证组件。
应用程序级验证。
自定义验证组件(它实现了
在 backing bean 中的验证方法(内联)。
我们将在下面的讨论中介绍并展示每一种形式。
JSF 验证生命周期和组件
图 9 显示了用户注册表单中名字字段的生命周期案例分析。代码引用被有意解释为伪代码(pseudo-code)。
图 9. JSF 生命周期中的验证
下面是 JSF 提供的一组标准验证组件:
标准验证
在我们的示例应用程序中,用户的年龄可以是任意有效的整数(byte、short、int)。因为将年龄设置为(比如说)-2 是无意义的,所以可能要对这个字段添加一些验证。下面是一些简单的验证代码,用以确保年龄字段中的数据模型完整性:
完成年龄字段后,可能希望指定对名字字段的长度加以限制。可以像这样编写这个验证:
图 10 显示了由上面标准验证示例所生成的默认详细验证消息。
图 10. 标准验证错误消息
尽管 JSF 自带的验证在许多情况下都可以满足,但是它有一些局限性。在处理电子邮件验证、电话号码、URL、日期等数据时,有时编写自己的验证器会更好一些,不过我们将在稍后对此进行讨论。
应用程序级验证
在概念上,应用程序级验证实际上是业务逻辑验证。JSF 将表单和/或字段级验证与业务逻辑验证分离开。应用程序级验证主要需要在 backing bean 中添加代码,用这个模型确定绑定到模型中的数据是否合格。对于购物车,表单级验证可以验证输入的数量是否有效,但是需要使用业务逻辑验证检查用户是否超出了他或者她的信用额度。这是在 JSF 中分离关注点的另一个例子。
例如,假定用户单击了绑定到某个操作方法的按钮,那么就会在调用应用程序阶段调用这个方法(有关的细节,请参见上面的图 1)。假定在更新模型阶段进行了更新,那么在对模型数据执行任何操纵之前,可以添加一些验证代码,根据应用程序的业务规则检查输入的数据是否有效。
例如,在这个示例应用程序中,用户单击了 Register 按钮,这个按钮被绑定到应用程序控制器的
其实它现在并不是业务规则逻辑的一个好例子。更好的例子是检查用户是否超出了她或者她的信用额度。在该例中,不是检查字段是否为空,我们可以调用模型对象的方法来确保当前用户已经不在系统中。
图 11 描绘了这个过程。
图 11. 应用程序级验证
注意在
图 12. 验证消息
应用程序级验证的优缺点
应用级验证非常直观并且容易实现。不过,这种形式的验证是在其他形式的验证(标准、自定义、组件)之后发生的。
应用程序级验证的优点如下:
容易实现。
不需要单独的类(自定义验证器)。
不需要页编写者指定验证器。
应用程序级验证的缺点如下:
在其他形式的验证(标准、自定义)之后发生。
验证逻辑局限于 backing bean 方法,使得重用性很有限。
在大型应用程序和/或团队环境中可能难于管理。
最终,应用程序级验证只应该用于那些需要业务逻辑验证的环境中。
自定义验证组件
对于标准 JSF 验证器不支持的数据类型,则需要建立自己的自定义验证组件,其中包括电子邮件地址和邮政编码。如果需要明确控制显示给最终用户的消息,那么还需要建立自己的验证器。在 JSF 中,可以创建可在整个 Web 应用程序中重复使用的可插入验证组件。
创建自定义验证器的步骤如下,我们将一步步地分析:
创建一个实现了
实现
在 faces-confix.xml 文件中注册自定义验证。
在 JSP 页中使用
下面是创建自定义验证器的分步示例代码。
第 1:实现 Validator 接口
第一步是实现
第 2 步:实现验证方法
接下来,需要实现
第 3 步:在 FacesContext 中注册自定义验证器
您现在应该熟悉在
第 4 步:在 JSP 中使用 <f:validator/> 标签
为了读取
总体而言,创建自定义验证器是相当直观的,并且可以使该验证在许多应用程序中重复使用。缺点是必须创建一个类,并在 faces 上下文中管理验证器注册。不过,通过创建一个使用这个验证器的自定义标签,使其看上去像是一个自带的验证,可以进一步实现自定义验证器。对于常见的验证问题,如电子邮件验证,这种方法可以支持这样一种设计理念,即代码重用和一致的应用程序行为是最重要的。
backing bean 中的验证方法
作为创建单独的验证器类的替代方法,可以只在 backing bean 的方法中实现自定义验证,只要这个方法符合
之后,可通过如下所示的
JSF 用
默认验证
注意上面
图 13 概述了我们讨论过的验证形式。
图 13. 验证视图
自定义消息
您可能注意到了,JSF 提供的默认转换和验证消息非常长,这会让那些总是输入无效表单数据的最终用户感到困惑和恼火。幸运的是,您可以通过创建自己的消息资源绑定来改变 JSF 提供的默认消息。jsf-impl.jar (或类似的文件中)中包含了一个 message.properties 文件,该文件包含图 14 所示的默认消息。
图 14. 默认 JSF 转换和验证消息
通过创建自己的 message.properties 文件并断开指定场所的 faces 上下文中绑定的消息资源,您可以更改默认消息,如图 15 所示。
图 15. 取消消息资源绑定
关于在 JSF 中创建自定义转换和验证消息的更多内容请参前阅 参考资料。
处理 JSF 生命周期
我们在本文前面留下了一些问题让您考虑,现在可以解决它们了!我们提到的一件事是对
基本上只要用户需要输入数据,就需要对这个数据进行验证。不过,如果整个数据项是可选的,那么就不需要进行验证。一种避免 JSF 生命周期的验证阶段的方法是利用
我们在本文中留下的第二个问题是:既然验证方法实际上是 backing bean 的一部分,那么为什么通常必须利用组件关联来判断它的值。请参阅前面的 JSF 应用程序生命周期,看看您能否找到答案。
这里的密诀是:尽管
这些尚待解决的枝节问题有什么意义呢,当然,它们最终将我们带回 JSF 应用程序生命周期。将这些问题汇总在一起,就能体现充分理解生命周期的重要性 —— 向后、向前或由内向外,这样您就可以在需要的时候操纵它。
结束语
在本文中我们讨论了相当多的 JSF 转换和验证的基本内容。事实上,我们讨论了在自己的应用程序中使用这些过程需要知道的大部分内容(至少对这个版本的 JSF 而言)!
当然,我们不可能讨论到 所有内容。例如,您可能想要了解 MyFaces (请参阅 参考资料)中 JSF 没有提供、或者这里没有讨论到的验证器组件。此外,虽然我们讨论了大多数常用的转换和验证技术,但还有一些没有包含在内。例如,在编写自定义组件时,可以在组件的解码/编码过程中直接处理转换和/或验证(取决于组件的类型及其功能),但是我们只能将对自定义组件开发的更深入讨论留到以后进行了。
其他要牢记的是转换和验证不一定会很好地协同工作。转换将字符串转换为对象,而大多数标准验证是对字符串进行的。因此,在同时使用自定义转换和验证必须格外小心。例如,
我们认为本月文章中的讨论再次声明了以下这点,即 JSF 提供了一种灵活的、强大的可插入式 Web 应用程序开发框架。除了标准转换器和验证器之外,JSF 还可以促进同时满足应用程序和框架开发人员的要求的自定义实现。最终,要由您来确定选择何种转换和验证策略。JSF 使您能够在原型制造阶段很快、很容易地上手(标准转换器、验证器、内部验证等),并在以后的开发阶段移植到更复杂的生产解决方案中(自定义对象、自定义消息等)。JSF 生命周期在所有阶段都提供了可靠的基础设施,始终如一地保证数据模型的完整性。
在下个月中,我们将深入分析如何用 JSF 编写自已的自定义组件,并结束这一系列。
参考资料
加入本文的 论坛. (您也可以通过点击文章顶部或者底部的论坛 链接参加讨论.)
您可以参阅本文在 developerWorks 全球站点上的 英文原文。
单击本页顶部或者底部的 Code 图标,以下载本文的源代码。
不要相信那些针对 JSF 的 FUD —— 也不要错过 怀疑论者的 JSF 系列中的任何一篇文章。
请参阅 JSF 主页,下载 JavaServer Faces API、自定义标签库和相关文档。
可以从 Apache Maven Project 页面 下载 Maven。
关于 Ant 和 Maven 的详细安装和编译指令,请参阅本系列的 JSF 参考资料。
请参阅 MyFaces 主页上介绍的、JSF 目前尚未提供的验证器组件。
Jackwind Li Guojie 撰写的“用 JavaServer Faces 进行 UI 开发”(developerWorks,2003 年 9 月)最先介绍了这项技术。
Roland Barcia 撰写的由 5 部分组成的“Developing JSF Applications using WebSphere Studio V5.1.1" 教程(developerWorks, 2004 年 1 月),是对使用 JSF 进行编程的一篇实用性介绍文章。
在前沿文章 “ 集成 Struts、Tiles 和 JavaServer Faces”(developerWorks,2003 年 9 月)中,Srikanth Shenoy 和 Nithin Mallya 向您介绍了如何集成 Struts、Tiles 和 JavaServer Faces 的特性。
David Geary 撰写的 Core JavaServer Faces 一书(Prentice Hall, 2004 年 6 月),是对 JavaServer Faces 技术进行的最好的书籍形式的介绍,其中包括关于创建自定义消息的讨论。
您可能还想查看 Sun Microsystems 的 详细 JSF 教程。
在 developerWorks 的 Java 技术专区 中,可以找到关于 Java 编程各个方面的文章。
请参阅 Developer Bookstore,以获得技术书籍的完整清单,其中包括数百本 Java 相关主题的书籍。
还请参阅 Java 技术专区教程页,以获得 developerWorks 的免费 Java 教程的完整清单。
下载
首先我们将介绍应用于 JSF 生命周期的转换和验证过程,然后展示一个简单的 JSF 应用程序中的默认转换和验证过程。接着将展示如何创建和插入自定义的实现,以应对要求更高的场景。正如 Rick 在以前的文章中所说的,我们会理论与实践并重,先介绍概念,再用一个实际例子说明这些概念的应用。示例应用程序将涵盖大多数转换和验证用例,虽然只是初级的。
注意,示例应用程序的默认编译环境是 Maven,不过, 还提供了一个 Ant 脚本。可以单击本页顶部或者底部的 Code 图标下载示例源代码。为了简便起见,您会发现,该例子的设置与上一篇文章中的一样。关于构建环境配置的更多说明,包括在 Ant 环境中而不是在 Maven 环境中编译和运行示例应用程序的说明,请参阅参考资料。
转换和验证
虽然在 JSF Web 应用程序中使用转换和验证不一定要理解 JavaServer Faces 生命周期的基础知识,但是在深入转换和验证内容之前,最好对一些基本知识做一回顾。此外,掌握一点 JSF 生命周期技巧可以极大地帮助简化 Web 应用程序的开发工作。还有助于更好地理解 JSF 的可插入能力。
图 1 描绘了我们所说的“基本 JSF 生命周期”。 基本 是在暗示这只是一个典型的处理所提交表单值的请求-响应(request-and-response)场景。
图 1. 基本 JSF 生命周期
显然,不同的场景对这里重点描述的生命周期有不同的影响。我们将在本文稍后介绍其中一些场景。现在,只需要注意转换和验证过程发生在应用请求值、处理验证 和呈现响应 阶段即可。
我们将在稍后介绍为什么转换和验证会在这些阶段出现,但是首先让我们澄清一个更基本的问题:转换 是什么?简单地说,转换是确保数据拥有正确的对象或者类型的过程。下面是两个典型的转换:
字符串值可以转换为
java.util.Date。
字符串值可以转换为 Float。
至于验证,它用于确保数据包含所期望的内容。下面是两个典型的验证:
java.util.Date 的格式为 MM/yyyy。
Float 在 1.0 和 100.0 之间。
关注生命周期阶段
转换和验证的主要目的是确保在更新模型数据之前已经经过了正确的无害处理。之后,当需要调用应用程序方法用这些些数据实际做一些事情 时,就可以有把握地假定模型的某些状态。转换和验证使您可以侧重于业务逻辑,而不是侧重于对输入数据进行繁琐的资格认定,比如 null 检验、长度限定、范围边界,等等。
因此,在更新模型数据 生命周期阶段中,在组件数据被绑定到 backing bean 模型之前 进行转换和验证处理是有道理的。正如图 1 所示,转换发生在应用请求值阶段,而验证发生在处理验证阶段。图 2 突出显示了这些阶段。
图 2. 要关注的转换和验证阶段
关于 immediate 属性
注意,图 2 中描绘的转换和验证过程表示了将
UIInput组件的
immediate属性设置为
false时的应用程序流程。如果这个属性设置为
true,那么转换和验证会发生在生命周期更早的时期,即应用请求值阶段(参见图 3)。对使用 immediate 属性的详细讨论超出了本文的范围,但是在某些情况下,比如管理动态清单(可能您还记得,本系列的上一篇文章中曾介绍过),它很有用,它甚至可以绕过验证(在与
UICommand组件结合使用时)。能想像一个需要完全绕过验证的应用程序吗?
图 3 展示了当
immediate属性设置为
true时,在 JSF 应用程序生命周期中的哪些地方进行转换和验证。
图 3. 将 immediate 属性设置为 true
实际的例子
下面,我们将用一个示例应用程序展示所讨论的概念。本月的示例应用程序将展示 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 |
<!-- UserRegistration.jsp --> <h:inputText id="age" value="#{UserRegistration.user.age}"/> |
各种情况的转换器
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. 使用 f:converter 标签
选择日期格式样式
尽管在默认情况下,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="___FCKpd___3quot; maxIntegerDigits="7" type="currency"/> </h:inputText> |
图 6. 使用 f:convertNumber 标签
自定义转换
如果需要将字段数据转换为特定于应用程序的值对象,则需要自定义数据转换,如下面例子所示:
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 方法
创建自定义转换器
我们将使用一个案例分析来展示
Converter接口、
getAsObject和
getAsString方法的实现,同时还将展示如何在
Faces上下文中注册这个转换器。
这个案例分析的目的是将一个单字段字符串值转换为一个
PhoneNumber对象。我们将一步一步地完成这个转换过程。
第 1 步:实现 Converter 接口
这一步实现
Converter接口。
import javax.faces.convert.Converter; import org.apache.commons.lang.StringUtils; ... public class PhoneConverter implements Converter { ... } |
这一步将一个字段值转换为一个
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; } } |
这一步将一个
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 步可以以两种方式执行。第一种选择使用(比如)
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> |
自然,下一步的执行取决于所选的注册方法。如果选择使用
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. 带有转换处理的示例应用程序
JSF 验证
如前所述,JSF 验证可以确保应用程序数据包含预期的内容,例如:
java.util.Date 为 MM/yyyy 格式。
Float 在 1.0 和 100.0 之间。
在 JSF 中有 4 种验证:
自带验证组件。
应用程序级验证。
自定义验证组件(它实现了
Validator接口)。
在 backing bean 中的验证方法(内联)。
我们将在下面的讨论中介绍并展示每一种形式。
JSF 验证生命周期和组件
图 9 显示了用户注册表单中名字字段的生命周期案例分析。代码引用被有意解释为伪代码(pseudo-code)。
图 9. JSF 生命周期中的验证
下面是 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. 标准验证错误消息
尽管 JSF 自带的验证在许多情况下都可以满足,但是它有一些局限性。在处理电子邮件验证、电话号码、URL、日期等数据时,有时编写自己的验证器会更好一些,不过我们将在稍后对此进行讨论。
应用程序级验证
在概念上,应用程序级验证实际上是业务逻辑验证。JSF 将表单和/或字段级验证与业务逻辑验证分离开。应用程序级验证主要需要在 backing bean 中添加代码,用这个模型确定绑定到模型中的数据是否合格。对于购物车,表单级验证可以验证输入的数量是否有效,但是需要使用业务逻辑验证检查用户是否超出了他或者她的信用额度。这是在 JSF 中分离关注点的另一个例子。
例如,假定用户单击了绑定到某个操作方法的按钮,那么就会在调用应用程序阶段调用这个方法(有关的细节,请参见上面的图 1)。假定在更新模型阶段进行了更新,那么在对模型数据执行任何操纵之前,可以添加一些验证代码,根据应用程序的业务规则检查输入的数据是否有效。
例如,在这个示例应用程序中,用户单击了 Register 按钮,这个按钮被绑定到应用程序控制器的
register()方法。我们可以在
register()方法中添加验证代码,以确定名字字段是否为 null。如果该字段为 null,那么还可以在
FacesContext中添加一条消息,指示相关组件返回到当前页。
其实它现在并不是业务规则逻辑的一个好例子。更好的例子是检查用户是否超出了她或者她的信用额度。在该例中,不是检查字段是否为空,我们可以调用模型对象的方法来确保当前用户已经不在系统中。
图 11 描绘了这个过程。
图 11. 应用程序级验证
注意在
register()方法中,消息是如何以
${formId}:${fieldId}的形式添加到
FacesContext中的。图 12 显示了消息与组件 id 之间的关系。
图 12. 验证消息
应用程序级验证的优缺点
应用级验证非常直观并且容易实现。不过,这种形式的验证是在其他形式的验证(标准、自定义、组件)之后发生的。
应用程序级验证的优点如下:
容易实现。
不需要单独的类(自定义验证器)。
不需要页编写者指定验证器。
应用程序级验证的缺点如下:
在其他形式的验证(标准、自定义)之后发生。
验证逻辑局限于 backing bean 方法,使得重用性很有限。
在大型应用程序和/或团队环境中可能难于管理。
最终,应用程序级验证只应该用于那些需要业务逻辑验证的环境中。
自定义验证组件
对于标准 JSF 验证器不支持的数据类型,则需要建立自己的自定义验证组件,其中包括电子邮件地址和邮政编码。如果需要明确控制显示给最终用户的消息,那么还需要建立自己的验证器。在 JSF 中,可以创建可在整个 Web 应用程序中重复使用的可插入验证组件。
MyFaces 是一个 JSF 的开放源代码实现,它提供了许多额外的验证器,其中包括一些 JSF 中所没有的验证器。要了解 MyFaces 的内容,请参阅参考资料。 |
创建一个实现了
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})?"; ... } |
接下来,需要实现
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); } } |
您现在应该熟悉在
FacesContext中注册自定义验证器的代码了。
<validator> <validator-id>arcmind.zipCodeValidator</validator-id> <validator-class>com.arcmind.jsfquickstart.validation.ZipCodeValidator</validator-class> </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(); } |
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> |
validateEmail方法对绑定到
user.email模型属性的
inputText组件值进行自定义验证。如果电子邮件格式无效,那么就在相关组件的 faces 上下文中添加消息。考虑到这种验证方法实际上是 backing bean 的一部分,为什么通常必须用某个值与相关组件的关联来评估该值,而不是直接检查本地 bean 属性呢?线索就在前面的生命周期图中。如果现在不能马上找到答案,也不要担心,我们将在本文的最后对此加以说明。
默认验证
注意上面
required属性。利用
required属性是一种默认 验证形式。如果这个属性是
true,那么相应的组件必须有一个值。一个重要的说明:如果
required属性为
false,那么就不用对这个标签/组件指派验证,这样,JSF 将跳过对这个组件的验证,并让值和组件的状态保持不变。
图 13 概述了我们讨论过的验证形式。
图 13. 验证视图
自定义消息
您可能注意到了,JSF 提供的默认转换和验证消息非常长,这会让那些总是输入无效表单数据的最终用户感到困惑和恼火。幸运的是,您可以通过创建自己的消息资源绑定来改变 JSF 提供的默认消息。jsf-impl.jar (或类似的文件中)中包含了一个 message.properties 文件,该文件包含图 14 所示的默认消息。
图 14. 默认 JSF 转换和验证消息
通过创建自己的 message.properties 文件并断开指定场所的 faces 上下文中绑定的消息资源,您可以更改默认消息,如图 15 所示。
图 15. 取消消息资源绑定
关于在 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 编写自已的自定义组件,并结束这一系列。
参考资料
加入本文的 论坛. (您也可以通过点击文章顶部或者底部的论坛 链接参加讨论.)
您可以参阅本文在 developerWorks 全球站点上的 英文原文。
单击本页顶部或者底部的 Code 图标,以下载本文的源代码。
不要相信那些针对 JSF 的 FUD —— 也不要错过 怀疑论者的 JSF 系列中的任何一篇文章。
请参阅 JSF 主页,下载 JavaServer Faces API、自定义标签库和相关文档。
可以从 Apache Maven Project 页面 下载 Maven。
关于 Ant 和 Maven 的详细安装和编译指令,请参阅本系列的 JSF 参考资料。
请参阅 MyFaces 主页上介绍的、JSF 目前尚未提供的验证器组件。
Jackwind Li Guojie 撰写的“用 JavaServer Faces 进行 UI 开发”(developerWorks,2003 年 9 月)最先介绍了这项技术。
Roland Barcia 撰写的由 5 部分组成的“Developing JSF Applications using WebSphere Studio V5.1.1" 教程(developerWorks, 2004 年 1 月),是对使用 JSF 进行编程的一篇实用性介绍文章。
在前沿文章 “ 集成 Struts、Tiles 和 JavaServer Faces”(developerWorks,2003 年 9 月)中,Srikanth Shenoy 和 Nithin Mallya 向您介绍了如何集成 Struts、Tiles 和 JavaServer Faces 的特性。
David Geary 撰写的 Core JavaServer Faces 一书(Prentice Hall, 2004 年 6 月),是对 JavaServer Faces 技术进行的最好的书籍形式的介绍,其中包括关于创建自定义消息的讨论。
您可能还想查看 Sun Microsystems 的 详细 JSF 教程。
在 developerWorks 的 Java 技术专区 中,可以找到关于 Java 编程各个方面的文章。
请参阅 Developer Bookstore,以获得技术书籍的完整清单,其中包括数百本 Java 相关主题的书籍。
还请参阅 Java 技术专区教程页,以获得 developerWorks 的免费 Java 教程的完整清单。
下载
描述 | Name | Size | Download method | ||||||||||||
example 2 zip file without JAR files | jsf-example2NoJars.zip | 91 KB | FTP | ||||||||||||
example 2 zip file with JAR files | jsf-example2WithJars.zip | 6413 KB | FTP | ||||||||||||
simple zip file without JAR files | jsf-simple-no-jars.zip | 33 KB | FTP | ||||||||||||
simple zip file with JAR files | jsf-simple.zip | 2051 KB | FTP | ||||||||||||
validation zip file without JAR files | jsf-validation-no-jars.zip | 32 KB | FTP | ||||||||||||
validation zip file with JAR files | jsf-validation.zip | 2170 KB | FTP | ||||||||||||
关于下载方法的信息 |
作者简介 Rick Hightower 是 ArcMind 公司的 CTO。他是 Java Tools for Extreme Programming 一书的合著者之一,这是一本关于在 J2EE 开发中采用极限编程的书;他也是 Professional Struts 一书的合著者之一。他还在 Warner Onstine 出版了 JSF QuickStart 一书,本系列文章的一些素材就来自这本书中的一些例子。您可以通过 rhightower@arc-mind.com 与 Rick 联系。 |
Paul Tabor 是 Rizon Software 的 CTO。在过去几年中,他与 Rick Hightower 合作开发了几个项目。Paul 和 Rick 在一些高技术应用程序中将 JSF 与 Spring 和 Hibernate 一起应用。您可以通过 ptabor@rizonsoftware.com 或者 ptabor@arc-mind.com 与 Paul 联系。 |
相关文章推荐
- JSF backing bean的转换和验证
- JSF 转换与验证
- Java Server Faces(JSF)历险(二),验证和转换
- JSF 转换与验证
- JSF 转换与验证
- jsf中的 国际化,验证和转换
- JSF入门十四(自定义转换, 验证标签)
- JSF 转换与验证 - 利用 JSF 的转换和验证框架来确保数据模型的完整性
- JSF 转换与验证 - 利用 JSF 的转换和验证框架来确保数据模型的完整性
- jQuery时间验证和转换为标准格式的时间格式
- 第七章 SpringMVC数据验证—注解式控制器的数据验证、类型转换及格式化
- springMVC学习笔记七(基于注解方式的控制器的数据验证,类型转换和格式化)
- jsf的初步使用(包括jsf框架的引入、用户登录、自定义表单验证、valueChangeEvent值变更事件处理做的级联下拉框)
- 新旧身份证合法性验证及相互转换算法(三):Java身份证号码验证及将15位转换18位
- JSF + Primefaces表单验证
- Java身份证号码验证及将15位转换18位
- 新旧身份证合法性验证及相互转换算法
- 常用正则表达式 新旧身份证合法性验证及相互转换算法
- JSF中进行验证的方法
- 新旧身份证合法性验证及相互转换算法