读书笔记《digital-java-ee-7-web-application-development》JSF验证和AJAX
“在完成之前似乎总是不可能的。” |
||
--Nelson Mandela |
至此,我们已经创建了一个完成常见的创建、检索、更新和删除的数字化客户旅程,也就是著名的 CRUD 需求。结果对利益相关者和产品所有者很有吸引力,但我们团队的用户成员对表格不满意,因为它缺乏验证公众成员输入的数据的能力。
当我们考虑它时,验证对用户来说很重要,因为他或她是在 Web 应用程序中输入数据的人。它为用户节省了时间和挫败感,因为他们在输入数据时就知道输入是错误的。它避免了数据库管理员为修复错误提交的数据而产生的成本。验证提高了在 Internet 上 24/7 全天候运行的 Web 应用程序的效率。随着我们越来越多的日常生活依赖于传统服务的数字化改造,电子商务现在已成为必需品;我们必须在正确的时间向公众提供正确的信息,即在销售点或捕获点。
在本章中,以 基本的JSF 表单为基础,我们将学习如何在服务器端和客户端-服务器端应用验证。这两种策略都有一定的优势;我们将了解这两种方法的优缺点。
表单验证可以在运行的Java EE应用程序的服务器端实现在应用程序服务器或 servlet 容器上。信息作为正常的 HTTP 表单提交从 Web 浏览器发送到 Web 应用程序。在这种模式下,表单作为传统的 HTML 表单元素提交。 Web 框架(在本例中为 Java EE)验证输入并将响应发送回客户端。如果表单验证失败,则重新显示包含 Web 表单的页面并显示错误消息。
服务器端快速验证是安全的,即使 JavaScript 在 Web 浏览器中被禁用或不可用,它也会保护数据库。另一方面,这种类型的验证需要从客户端到服务器端的往返。用户在提交 表单之前不会收到有关表单数据的反馈。
规则似乎总是有例外。如果服务器端表单验证是使用 AJAX 提交的,那么我们可以绕过响应缓慢的问题。 AJAX 验证是一个很好的折衷方案,因为可以在用户在表单上输入数据时验证表单。另一方面,AJAX 要求在 Web 浏览器中启用 JavaScript。
我们团队中的用户体验人员非常喜欢客户端验证,但是这种类型的 验证需要浏览器中存在 JavaScript(或等效的类型的动态脚本技术)。客户端验证提供了更灵敏和更丰富的用户与表单的交互。
客户端验证确保在允许用户提交表单之前表单始终正确。由于 JavaScript 是一种渐进式语言,有很多方法可以告知用户如何更好地与表单提交过程进行交互。 jQuery 等技术允许程序员在用户键入数据时添加提示和验证错误消息。
在某些情况下,JavaScript 在 Web 浏览器中被禁用或不可用。我很容易想到沙盒受到严格控制的政府安全或专家中心。当 JavaScript 被用户或设备管理员关闭时,客户端验证肯定会失败,用户可以绕过验证。
Tip
结合客户端和服务器端验证
在企业的专业应用程序中,我强烈建议您将这两种验证方法结合起来,以获得两全其美的效果。客户端验证提供更快、更丰富的体验,服务器端验证保护您的数据和数据库免受不良数据和黑客攻击。
在讨论验证的技术主题之前,我们必须了解消息在 JSF 中是如何表示的。
JSF 提供了两个自定义标记来显示错误消息。 <h:message>
标签显示绑定到特定组件的消息。 <h:messages>
标签显示未绑定到特定组件的消息。
我们在 Chapter 3 中看到了我们第一次使用 <h:message>
, 构建 JSF 表单。标签通常与表单控件相关联。我们可以使用以下内容向我们的 JSF 页面添加消息:
标签被 添加到内容的顶部。 globalStyle
属性是一个布尔值,它指定标签是否应该显示与组件无关的消息。在这里,我们再次使用 Bootstrap CSS 选择器。
以下是 JSF 标记之间共享的 属性的表格,<h:messages>
和 <h:message>
:
属性 |
描述 |
---|---|
|
指定唯一标识符 |
|
指定错误消息的 CSS 类选择器 |
|
指定错误消息的样式 |
|
指定信息消息的 CSS 类选择器 |
|
指定信息消息的 CSS 样式 |
|
指定消息关联的组件 |
|
设置一个布尔值来指定标签是否呈现到页面上 |
|
定义所有消息类型的 CSS 选择器 |
|
定义所有消息类型的 CSS 样式 |
在幕后,这些标签分别渲染 javax.faces.HtmlMessages
和 javax.faces.HtmlMessages
组件的内容,这反过来,依赖于 javax.faces.application.FacesMessage
元素的列表集合。作为一名 JSF 数字开发人员,我们不必过于担心日常的 HtmlMessage
和 HtmlMessages
组件,因为它们位于汽车引擎盖下。如果我们正在编写新的 JSF 渲染器或扩展,那么我们将不得不查看 Javadoc 和 JSF 规范。
在 第 3 章,构建 JSF 表单中,您被引入到应用程序中,FacesMessage
,用于创建 JSF CRUD 样式的表单。 在 Controller 中,我们可以创建与表单中的任何 UIComponent
无关的验证错误消息。因此,此验证错误只能通过全局错误消息访问。这是生成此类验证错误的代码:
The FacesMessage
object represents a validation message with a severity level. We add it to the FacesContext
object. The FacesMessage
constructor is of the form:
严重性可以是 FaceMessages
类中定义的四个静态常量,分别是 SEVERITY_INFO
、 SERVERITY_WARNING
、SEVERITY_ERROR
和 SEVERITY_FATAL
。这些值实际上是私有内部类 Severity
的实例化,不幸的是,在封闭类之外无法访问它,因此我们可以发明自己的严重性。
Faces 消息还需要消息的摘要,以及关于失效消息的详细信息(可选)。
javax.faces.context.FacesContext
是当前传入请求和潜在响应的聚合持有者。对象实例在初始 JSF 传入请求(Faces Request)上被实例化,它会一直保持活动状态,直到触发后续的 JSF release()
方法,该方法通常在框架。 FacesContext
是添加 FacesMessage
的地方以及可以从中检索消息列表集合的地方。
FacesContext
有几个有趣的方法,包括 isValidationFailed()
,这对于在 JSF 生命周期的早期检测任何验证失败很有用。稍后我们将通过 Bean Validation 看到此调用的示例。还有其他方法,例如使用 getViewRoot()
获取视图根,getCurrentPhaseId()
获取当前阶段JSF 生命周期,以及 getRenderKit()
来检索渲染工具包表单。使用 isPostback()
方法,我们可以确定请求是否为 HTML 表单以及 JSF 框架是否即将将数据发送回同一表单。上下文对象还有更多。
将 faces 消息添加到上下文的方法如下所示:
如果 clientId
属性为 null,则该消息是全局可用的消息,并且不与 任何视图关联零件。
现在我们已经了解了如何生成特定于 JSF 的消息,让我们深入研究 JSF 应用程序的验证。
在服务器端实现验证有两种主要方式。要遵循的一条路线是通过 使用 Java EE 7 规范中的 Bean Validation 版本 1.1,而另一条传统路线将带您通过 JSF 验证。
Bean Validation 是一种规范,它允许开发人员注释 POJO 并 实体 bean,然后调用自定义验证器实例来验证属性。验证框架与 Java 注释一起使用,因此,数字工程师可以明确地说明如何验证属性甚至方法。
我在 Java EE 7 开发人员手册 中用了一整章介绍 Bean 验证;不过,我将在这本 Digital Web Application 书中与您一起了解基础知识。 Bean Validation 1.1 标准中有几个注释可以直接使用。但是,如果您的平台允许或者您决定添加 Hibernate Validator,则可以使用更多有用的验证注释。开发人员还可以创建自定义验证。
让我们再次使用 ContactDetail
实体,但是这次我们在属性中添加了 Bean Validation 注解,如下所示:
我们添加了 注释@Pattern
, @Past
、@NotNull
和 @Size
到 ContactDetail
实体的属性.注释可以在 Java 包 javax.validation.constraints
中找到,为 Bean Validation 保留。
约束名称 |
描述 |
允许的类型 |
---|---|---|
|
任何 |
|
|
任何 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Bean Validation 注解通常接受消息 属性,即验证给用户的消息,也可以是括号内的值,触发验证框架从中搜索消息java.util.ResourceBundle
。某些注释,例如 @Min
、@Max
、@DecimalMin
和 < code class="literal">@DecimalMax 具有附加属性,例如 min
和 max
来指定明显的边界。
我们可以在带有验证消息的属性上定义 @NotNull
约束,如下所示:
这是一个很好的方法,可能用于制作网站原型;但正如我们从软件考古学的知识中知道的那样。这可能是维护的噩梦,因为我们正在将数字副本直接写入 Java 代码。最好在属性文件中编写文本副本,该文件可由 Bean Validation 使用的标准 ResourceBundle
获取。我们的常驻数字策略师和文案专家 Jenny 将感谢我们向她发送属性文件而不是 Java 源代码。
所以让我们重写这个属性的约束如下:
通过将消息放置在特定位置,可以将 Bean Validation 与 JSF 集成。程序员只需在WEB-INF/classes
文件夹中创建一个ValidationMessages.properties
文件。
通过 Bean Validation,我们可以添加用大括号表示的占位符,以丰富用户将看到的 消息。占位符是特定的,例如 {min}
和 {max}
。属性文件的另一个优点是 JDK 中的 ResourceBundle
已经处理了不同语言环境的国际化这个棘手的话题。
仅仅依靠使用 JSF 的 Bean Validation 有很大的缺点。它非常适合保护数据库免受输入不良数据的影响,并且使用数字开发人员几乎免费获得的 Java EE 7 应用程序服务器,可以在每条记录之前向数据库添加或修改验证。但是,Bean Validation 与 JSF 前端没有任何联系。该框架与页面作者的内容没有任何关联。在现代软件工程中,我们也不希望表示层和模型层之间存在这种依赖关系。面向对象编程的最佳实践之一是 SOLID 原则。我们当然希望这些层单独负责一个目的;对扩展开放,但对修改关闭,最重要的是,防止由于软件老化而导致软件技术债务的泄漏抽象。
仅依赖 Bean Validation 的另一个缺点是数据的验证完全取决于 Java 数字工程师的技能。这意味着页面作者或设计者在获得更好的以用户为中心的体验的道路上不能创新、编辑或删除验证。
Bean Validation 非常适合在应用程序逻辑中添加验证。如果这是业务要求,您可以确保联系人详细信息的标题永远不会为空。可以实现对属性的复杂和组验证。有关详细信息,请参阅我的书 Java EE 7 开发人员手册。
以下 屏幕截图显示了 bean-validation
/createContactDetail.xhtml
in action 来自本书的源代码。 屏幕截图显示了当 用户只提交表单而不填写表单时会发生什么:
<h:messages>
标签 和 设置 globalStyle=true
显示框架发现的验证消息的输出在 ContactDetail
实体中。
自 2004 年成立以来,JSF 一直有一个验证框架。当时将 JSF 与事实上的 Apache Struts Web 框架分开的特性,因为后者没有对验证的内置支持。
记住转换和验证发生在 JavaServer Faces 生命周期的不同阶段会很有帮助(查看 Chapter 2 中的图表,JavaServer Faces Lifecycle,执行和渲染生命周期部分)。提醒一下,JSF 将在应用请求值阶段设置组件中的值,然后根据需要使用各种转换将输入字符串值转换为目标对象。验证发生在流程验证阶段,并且此生命周期按照设计进行。为了转换来自 HTML 请求的输入数据,JSF 尝试并检查是否可以在支持 bean 中设置参数。 Update-Model-Values 阶段遵循前面的阶段。如果在生命周期中发生验证或转换错误,那么它会被有效地缩短。 JSF 直接进入 Render-Response 阶段,它将支持 bean 中的属性转换为字符串,以便 Web 客户端可以显示它们。
JSF 提供了一小组预构建的验证器标签,您可以将其应用于标记为页面作者,核心 JSF 自定义标签的表格已在 第 3 章,构建 JSF 表单。其中一些如下:<f:validateDoubleRange>
、<f:validateLength>
、<f:validateLongRange>
、 <f:validateRegex>
和 <f:validateRequired>
。
我们可以将这些标签应用于联系人详细信息 CRUD 示例。因此,让我们从 createContact.xhtml
页面开始。以下是该页面的简短摘录:
我们放置了 <f:validateRequired>
, <f :validateLength>
和 <f:validateLongRange>
标签在正文 的内容JSF HTML 呈现标签,如 <h:inputText>
和 <h:selectOneMenu>
。 validateLength
标签验证 String 属性的 长度。标签接受最大参数,但也可以采用最小属性。
我们还在其各自的 HTML 输入字段附近添加了一个 <h:message>
标记。 styleClass
属性指定了一个自定义 CSS 选择器,该选择器强制将验证消息放在单独的新行上。用于此的 CSS 如下所示:
为了避免像 <jsf_form>:<form_property_name>
这样的 JSF 糖化名称出现在验证错误消息中,这会给出类似 contactDetail 这样的结果:title -Validation Error: Value is required
,我们为每个 HTML 渲染标签指定 label 属性。标题输入字段有一个属性设置label="Title"
。
<f:validateLongRange>
标签检查字符串的实际内容是否是介于最小值和最大值属性之间的数值。我们在 date-of-birth
字段中使用此标签。
以下摘录是 DOB 组中日期字段的创作:
前面的 代码演示了 <f:validateLongRange>
标记如何强制执行日期表单中的字段。我们对其他 DOB 字段进行冲洗并重复 。
<f:validateRegex>
标记将输入属性字符串与正则表达式匹配。我们将 这个标签用于email
属性。以下是此验证检查的代码:
有趣的是,霸道的pattern属性值,正则表达式,和Bean Validation中的@Pattern
几乎一模一样。我们必须将双反斜杠字符转换为单反斜杠,因为我们不需要在普通正则表达式中转义文字,而不是在 Java 代码中设置。
以下是页面jsf-validation
/createContactDetail.xhtml
的截图:
如果您 使用过源代码并运行了示例,我敢打赌您会注意到 JSF 验证存在一些明显的问题。例如,当 email
字段的值不是有效的电子邮件地址时,您将看到如下验证消息:
很明显,这个非常详细的应用程序信息未能实现以用户为中心的设计和面向公众的简洁语言。我们可以用 JSF 做什么来避免这些消息?
可以将三个属性应用于输入字段的 HTML 呈现标签,例如 <h:inputText>
和 <h:selectOneMenu>
。下表显示了可帮助自定义 JSF 验证消息的属性:
属性 |
描述 |
---|---|
|
|
|
|
|
有了这些信息,我们可以通过将 requiredMessage
属性应用于我们的字段来轻松解决消息问题:
requiredMessage
、validatorMessage
和 conversionMessage
覆盖由服务器端的 JSF 验证器。请注意,这些属性可以接受值表达式。这对于页面作者来说非常有用,可以指定方法。然而,在栅栏的另一边,我们的电子邮件地址字段有两个验证约束,一个正则表达式检查和一个字段长度约束。该消息不适用于 validateLength
。因此,如果我们按照这种方法使用不止一种类型的验证器,就会出现问题。
我们可以采取另一种方法。在 JSF 框架中全局覆盖验证消息 怎么样?我们可以配置我们自己版本的 JSF 验证器消息。为了实现这一目标,首先,我们使用有关加载这些消息的位置的信息来配置框架。我们在WEB-INF/faces-config.xml
文件中设置了一个消息包,如下:
这个配置文件定义了应用程序的 Faces 资源,在这里我们可以配置一个引用属性文件的消息包。属性文件的路径其实是uk/co/xenonique/digital/JSFVMessages.properties
,可以在项目资源ch04/jsf中找到-crud-validation/src/main/resources
.
其次,我们提供自己的消息包文件。该属性文件 JSFVMessages.properties
的内容就是以下属性定义:
如您所见,我们重写了属性 RegexValidator.NOT_MATCHED
以提供新的 消息。原始定义可以在应用程序服务器的 JAR 文件中找到,或者作为第三方 JAR 捆绑在您的 servlet 容器中。定义可以在 JAR 的包中找到 (jsf-api-2.2.jar
) javax/faces/Messages.properties
。
正则表达式验证器的原始定义如下所示:
您可以在 http://svn.apache.org/repos/asf/myfaces/core/branches/2.0.x/api/src/main/resources/javax/面孔/Messages.properties。如您所见,它们非常技术性和用户不友好。消息包中的许多属性定义都接受参数的占位符。 NOT_MATCHED
接受两个参数:第一个参数 {0}
是模式,第二个参数 {1}
是输入字段的标签。
Note
在 Java EE 7 中,JSF 验证中的参数化占位符与 Bean Validation 框架中的占位符不同。 JSF 使用整数索引,而 Bean Validation 可以使用命名占位符。
在撰写本文时,JSF 验证器的参考实现中存在一个错误,这会阻止开发人员在消息属性中使用某些占位符。我们会喜欢这样的属性定义:
遗憾的是,Mojarra 中的 当前错误阻止了我们将其写为生产代码。
自定义 JSF 验证有另一种策略。我们可以定义自己的验证器来扩展框架的功能。
JSF 允许数字工程师在托管 bean 控制器中配置一个方法,该控制器将被调用以验证字段。在 HTML 渲染标签中添加属性验证器就完成了这个策略,它是一个值表达式。
以下是将自定义验证方法添加到联系人详细信息表单的 emailAddress
属性的方法:
属性验证器引用了修改后的 ContactDetailControllerJV
bean 中的方法 validateEmailAddress()
。此方法如下所示:
在前面的方法validateEmailAddress()
中,传入的参数是FacesContext
,被验证的组件是UIComponent类型,而要检查的挂起值是 Object 类型。此方法验证两个约束:它检查字段的 长度是否太长以及该字段是否为电子邮件地址。我们使用 JDK 标准库 javax.regex
包来实现这一点。为了断言验证错误(如果有),我们创建 FacesMessage
对象并将它们添加到当前的 FacesContext
实例中。
在控制器或名为 bean 的 CDI 中编写验证器是一种有用的策略。但是,缺点是 ,您的应用程序中总是需要间接 POJO。还有另一种策略,JSF 允许我们定义自定义验证器,这些验证器集成在框架中。开发人员可以选择编写使用注释 javax.faces.validator.FacesValidator
声明的 POJO。 POJO 必须实现接口javax.faces.validator.Validator
。
让我们将电子邮件地址检查代码移动到自定义验证器中。 FacesEmailAddressValidator
的代码如下:
这个类被注解了@FacesValidator
,单个参数标识页面中验证器的名称看法。 validate()
方法在验证器接口中通过约束实现设计。 JSF 传入 FacesContext
、与输入值关联的组件以及值本身。
我们将输入值检索为文本字符串。验证电子邮件地址的正则表达式代码与以前几乎相同,但消息键除外。错误键是 {application.emailAddress.pattern}
。
使用我们的 POJO 自定义验证器,我们可以重写页面视图上的 HTML 以使用它。以下是 login.xhtml
的提取视图:
唯一的区别是 <h:inputText>
元素中的验证器属性。此属性按名称将自定义验证器指定为 emailValidator
。正如我们所见,我们可以将自定义验证器与默认的标准验证器结合起来。还有一个 <f:validateRequired>
元素。
以下屏幕截图显示了 LoginControllerJV
的渲染输出:
现在我们已经 了解 JSF 自定义验证器,我们可以编写一个自定义验证器来验证组 Date-of-Birth 输入字段。我们可以实现这个目标,因为 FacesContext
已经通过了。可以独立于上下文单独查找 UI 组件。
我们将在页面视图中使用称为绑定的 JSF 技术。绑定有效地以 JSF 值发布 javax.faces.component.UIInput
的实例,并使其在页面的其他位置可用。 HTML 呈现 JSF 标记上的属性绑定将组件树中的组件引用绑定到范围变量。以下是相关的JSF代码。特别要注意 代码提取自 jsf-validation/createContact 开头的 JSF 隐藏输入元素.xhtml
。
我们利用了隐藏字段,它被标识为 aggregateDobHidden
并带有一个虚拟表单参数名称 hiddenField1
。它总是发送一个真值。 <f:attribute>
元素将 附加绑定信息附加到此 UI 组件。我们需要三个属性,名称分别为 dob_dotm
(月份中的某天)、dob_moty
(一年中的月份)和 dob_year
。这些属性是名称相似的页面范围变量 #{dob_dotm}
、#{dob_moty}
和 的值表达式class="literal">#{dob_year}
分别。
我们为每个 JSF 选择组件添加一个绑定属性。再次查看以下第一个字段:
属性绑定将组件与视图相关联,并使其在由文字字符串定义 #{dob_dotm}
定义的页面范围变量中可用。这是 javax.faces.component.UIInput
类的一个实例,它有一个 getSubmittedValue()
方法来获取提交价值。我们重复为其他两个属性添加绑定。在表单提交期间,hiddenField1
记录了每个单独属性的绑定值。此属性不同于单独的日、月和年属性。
这个绑定技巧允许我们将属性组合在一起以进行表单验证。以下源代码显示了服务器端的验证:
POJO FacesDateOfBirthValidator
验证三个 DOB 属性。它使用 JSF 中称为页面视图中的绑定的技术 来实现这一点,我们稍后会看到。绑定允许将 JSF 输入属性传播到另一个可以在页面上其他地方使用的命名范围变量。至于验证器,我们使用 HTML 隐藏元素作为工具来检索这些有界值作为属性。这就是将组件转换为 javax.faces.component.UIInput
然后提取值的代码的目的。
我们有一个辅助方法,parsePositiveInteger()
,用于将文本值转换为整数。在此之前,我们创建一个列表集合来存储任何错误消息。然后,我们验证从 1 到 31 的日期边界。对于 month 属性,逻辑几乎相同。
对于年财产,我们采取了不同的步骤。使用 JDK Calendar 和 Date 类,我们构建了两个 Date 实例:一个代表 100 年前的当前日期,另一个代表 18 年前的当前日期。然后我们可以比较用户的进入日期是否在这两个生日限制内。
如果方法 validate()
的末尾有任何错误,那么它会引发带有错误集合的 ValidatorException
异常。请注意,我们选择使用替代构造函数。
为了完成验证器,辅助方法 parsePositiveInteger()
可以写成如下:
以下 是联系人详细信息的屏幕截图,展示了正在使用的组验证器:
标准 JSF 验证器允许数字开发人员实现很多 功能。在某些情况下,要求超出了默认行为。转换器是在字符串和对象之间进行转换的 JSF 类。与注释定义自定义验证器的方式类似,JSF 允许注册自定义转换器。转换器与 JSF 组件相关联。
注释 @java.faces.convert.FacesConverter
表示 POJO 是自定义 JSF 转换器。该类型必须实现 javax.faces.convert.Converter
接口,该接口有以下方法:
getAsObject()
方法将字符串表示形式从客户端转换为目标对象。 其他方法 getAsString()
将对象转换为字符串 表示,在客户端浏览器中呈现。
我们将用一个将字符串转换为扑克牌花色的自定义JSF转换器来举例说明。我们可以使用一个简单的 Java 枚举类来编写它:
以下是自定义转换器 FrenchSuitConverter
类的完整列表:
POJO 是带注释的@FacesConverter
,其值成为页面视图中的标识符。
JSF 调用 getAsObject()
方法,其文本表示为 修剪并转换为大写,以便于比较。在方法开始时,新值可能是一个空白字符串。如果这是真的,那么我们从已经提交的值中检索文本表示。对于这个特定的转换器,空值的用例是可能的,所以我们添加了保护。如果进程出现问题,该方法会引发异常 javax.faces.convert.ConverterException
。
JSF 调用 getAsString()
方法来将对象表示转换为字符串。根据对象类型,该方法防御不同类型的输入。输入值可能只是一个字符串,也可能是 FrenchSuit
枚举的一个实例。如果输入值不是其中之一,则该方法引发 ConverterException
。
在现实世界中,我们知道一套扑克牌中总会有四种花色,因此,我们可以对枚举的可维护性充满信心。作为一个数字开发者,你可能没有这样的奢侈品,因此,在转换器和验证器中应用防御性编程原理可以走很长的路追踪错误的方法。
以下是练习自定义转换器的页面 /jsf-validation/french-suit.xhtml
的摘录:
在前面的视图中,我们使用了一个下拉菜单 <h:selectOneMenu>
,它允许用户选择卡片套装。这段代码你现在应该很熟悉了。不同之处在于每张牌的值表达式,它们都是带有字符串字面量参数的方法调用。表达式语言允许您使用参数调用方法。因此,表达式:#{frenchSuitController.suitEnumValue('HEARTS')}
转换为控制器上的方法调用。
在 <h:selectOneMenu>
的 body 内容中,我们明确引用了 通过标识符自定义转换器,并通过以下方式将其与 UI 组件关联:
JSF 然后调用自定义转换器,以便将单个 FrenchSuit
枚举从页面视图转换为字符串。这听起来像是一种显示值列表的迂回方式,但此示例演示了 FrenchSuitConverter
中的方法 getAsString()
是被调用。此外,它还说明了如何以稳健的方式在页面视图和控制器中引用 Java 枚举。
现在让我们检查一下控制器:
FrenchSuitController
的代码稍稍领先。首先,让我提请您注意 suitEnumValue()
方法,它将字符串文字转换为枚举类型 FrenchSuit
。这是在页面视图中获取枚举的一个非常方便的技巧,因为表达式语言不允许直接访问 Java 枚举。它特别适用于通过不同项目版本随时间变化的枚举。
doAction()
和 cancel()
方法返回带有特殊查询参数 redirect 的 URI =真
。这是 JSF 返回可收藏 URL 的指令;我们将在本章稍后部分讨论这个主题。
在 doAction()
中,我们第一次在 JSF 中使用 Flash 作用域。 Flash 范围是一个临时上下文,允许控制器将数据传递到下一个导航视图。请记住,视图范围仅对导航到同一页面视图的当前控制器有效。当 FacesContext
移动到下一个页面视图时,@ViewScoped
托管 bean 超出范围。这些方法在 javax.faces.context.Flash
实例中设置键值关联。
拼图的最后一块展示了我们如何在页面视图中使用 Flash 范围。此代码可以在文件 /jsf-validation/french-suit-complete.xhtml
中找到。以下代码是相同的摘录:
在此页面视图中,我们使用 map 表达式从 Flash 范围内检索值。表达式 #{flash['suit']}
是用户选择的西装。读者还可以查看默认 JSF 转换器 javax.faces.convert.EnumConverter
的文档。在同一个包中,还有BigDecimalConverter
、BigIntegerConverter
、DateTimeConverter< /code>、
ByteConverter
、LongConverter
和 DoubleConverter
。
我会给你留下表单视图的屏幕截图french-suit.xhtml
:
以下是显示 french-suit-complete.xhtml
的结束状态的屏幕截图。标记以 Bootstrap 的 CSS jumbotron 样式显示了漂亮的视觉效果。
异步 JavaScript 和 XML(AJAX)是一组技术它们共同解决了检索网页部分更新的限制,并提供了丰富的交互式用户体验。 AJAX 的关键是术语异步,它建立在万维网联盟 (W3C) 标准,即 XmlHttpRequest
对象。它于 2006 年在 Internet Explorer 中引入,现在所有现代 网络浏览器都支持这个
对象。异步模式允许浏览器在单独的连接上向服务器发出数据传输请求;企业后端响应以数据结果响应,通常是 JSON 或 XML。与每次重新加载整个页面相比,这些 AJAX 数据传输往往更小。
JSF 内置了对 AJAX 请求和响应的支持;开发人员无需了解 XmlHttpRequest
和 JavaScript 编程的更详细的细节即可获得即时响应的好处。数字开发人员可以从用于执行 AJAX 交互的默认 JavaScript 库开始。
很容易在JSF中使用AJAX ="literal"><f:ajax>
标签。这个 Core JSF 标记向 UI 组件注册 AJAX 行为,并用于对字段执行验证。开发人员只需将标签放在代表组件的 HTML JSF 标签的正文内容中,这需要验证。
以下代码显示了如何将此标记与联系详细信息应用程序一起使用:
tag
属性事件确定框架何时调用 AJAX 验证。模糊值表示它发生在用户从该组件字段移动到下一个输入字段时。因此,当用户在台式计算机上按下 Tab 键或在手机或平板电脑上导航 UI 时,会立即进行验证,因为 JavaScript 会触发对服务器的 AJAX 请求。第二个属性,render,通知框架有关特定 UI 组件的信息,以将错误消息呈现给(如果有)。 JSF 接收到 AJAX 响应,如果出现错误,它会知道 HTML 组件 ID 以使用验证消息进行更新。
我们来看项目ch04/jsf-crud-ajax-validation
,它是页面视图jsf-validation/createContactDetail的完整摘录。 xhtml
:
此页面视图举例说明了在 JSF 中向页面添加 AJAX 验证非常容易。 <f:ajax>
Core JSF 标签嵌入在对应的 HTML JSF 标签中,如您所见
用于名字和 姓氏字段。非 AJAX 和 AJAX 页面的联系详细信息的另一个区别是添加了标识符,例如 firstNameError
和 lastNameError
<h:message>
标签。我们需要添加 HTML 标识符元素,以允许 JavaScript 按 ID 从浏览器的 中查找 HTML 元素文档对象模型 (DOM)。
除了中间名和简报 HTML 复选框字段之外,该页面的所有属性都添加了 AJAX 验证。 AJAX 验证也适用于自定义验证器和转换器。
以下屏幕截图说明了单个属性 AJAX 验证:
到目前为止,我们已经看到了对单个输入字段实例的 JSF AJAX 验证。 <f:ajax>
标签也可以在一组组件上进行验证。我们可以将标签包含在一个或多个 JSF 输入字段周围,然后 <f:ajax>
标签成为 UI 组件的父级。这会导致 JSF 将 AJAX 验证应用于多个组件。
让我们使用以下页面视图将 组验证添加到联系人详细信息表单中的 Date-of-Birth 字段中:
如您所见,我们 用包含 <f:ajax>
标记包围 DOB 输入字段.事件属性仍设置为 blur
。 render 属性设置为特定验证消息的 HTML 元素 ID 列表,即 dobDayError
、dobMonthError
和 dobYearError
。
aggregationDobHidden
HTML 隐藏元素与非 AJAX 示例中的相同,以说明验证不会干扰自定义验证。
回顾一下,使用 <f:ajaxTag>
,并将其嵌入到任何 JSF 组件中。要验证一组多个组件,请用 <f:ajaxTag>
将组件括起来。
以下屏幕截图描述了 DOB 字段周围的多组件 AJAX 验证。一年中的月份组件最后是浏览器的焦点,因此,相应的验证消息描述了 onblur
DOM JavaScript 事件。类似地,在这组字段之间使用制表符会一一生成错误消息。
了解可应用于此 Core JSF 自定义标记的属性很有用。下表描述了 <f:ajax>
的属性。
从前面的表格中,您会注意到 execute 和 render 属性可能指示其他有意义的值。 execute 属性规定了要在服务器上执行的组件。当 AJAX 行为完成时,render 属性确定受影响的 UI 组件。下表列出了属性值:
JSF 生命周期实际上适用于所有 Faces 请求和响应,包括那些来自支持 AJAX 的组件的请求和响应。在幕后,JSF 为 AJAX 请求和响应实例化一个特殊对象 javax.faces.context.PartialViewContext
,并将其输入处理生命周期。此上下文对象包含允许 JSF 在服务器端更新组件模型的信息。基于 部分上下文,JSF 决定是否完成所选UI 组件的部分处理和/或UI 组件的部分呈现。部分处理对应于生命周期的 Apply-Requests-Values、Process-Validations 和 Update-Model-Values 阶段。部分渲染是指渲染-响应阶段。
在本章中,我们主要研究了使用 JSF 对用户输入的验证。我们忽略了一些关于导航的杂项概念。现在让我们谈谈处理视图和导航。
有几种方法可以从带有参数的 页面视图调用控制器。对于数字电子商务应用程序中的许多情况,开发人员需要检索特定的数据记录,触发服务器端动作,或者在后端从客户端保存某个状态。
JSF 允许 开发人员使用表达式语言将参数传递给页面视图中的方法。 Chapter 3, 构建 JSF 表单 称为方法表达式调用,它是在 JSF 2.0 中引入的。
以下是页面视图 /jsf-miscellany/examplar-methods.xhtml
的摘录:
上述代码描述了 <h:commandButton>
标记和一个动作值表达式,即 #{examplarController.methodThreeArgs('Obiwan', '本','克诺比')}
。这是一个带有三个文字字符串参数的方法调用。
参数也可以是对其他 JSF 范围实例的引用。以下是另一个调用,只有两个参数显示了这一点:
参数是从控制器 bean 属性动态设置的。现在让我们看一下控制器 ExamplarController
:
有三种方法,分别是,称为methodOneArg ()
、methodTwoArgs()
和 methodThreeArgs()
。这些名称对于可以传递的参数数量是不言自明的;每个人都在 JSF Flash 范围内保存一个输出结果,然后再转到下一个页面视图 /jsf-miscellany/examplar-methods-complete.xhtml
。
以下是最终状态 Facelet 视图的提取 ,示例方法完成.xhtml
:
在创建 JSF 2.0 规范之前,可以使用 <f 将参数发送到支持 bean 控制器:
标记, 或 <h:commandLink>
, <h:commandButton>
正文中的 param><h:link>
标签。尽管该技术已被 JSF 2.0 中的方法调用表达式所取代,但它仍然是一种向控制器发送越界通信的有用技术。
以下代码显示了配方,我们在 <h:commandButton>
中嵌入了两个 <f:param>
元素自定义标签:
此页面视图上的创作调用控制器的无参数方法,methodPassingParameters()
。 JSF 通过 Faces 请求将两个参数传递给目标方法,键名为 callToActionText
和 customerType
。
让我们看看处理这个调用的控制器方法:
在 方法 methodPassingParameters()
中,我们从 中检索参数FacesContext
实例通过使用嵌套调用 getRequestParameterMap()
。然后可以直接从 Map
以下屏幕截图显示了演示本节中描述的方法调用技术的页面:
处理视图的最后一种技术调用控制器中的动作侦听器。任何接受 参数的实例 方法>javax.faces.event.ActionEvent
可以是一个动作事件监听器。动作侦听器与页面标记中的 UI 组件相关联。 JSF 在调用动作之前调用动作侦听器,因此这种技术有助于挂接业务逻辑并为动作调用设置数据。
以下是实施此技术的方法调用页面的摘录。我们将在这段代码中省去 Bootstrap CSS 标记:
<h:commandButton>
标签有一个额外的 actionListener
属性设置为引用动作监听器方法的表达式,attributeListener()
。该标签还将 嵌入<f:attribute>
到定义一个传递的属性。 action 属性引用方法 performAction()
。
让我们检查我们的 ExamplarController
支持 bean 以查看代码:
在提交命令按钮时,JSF 首先使用 ActionEvent
实例调用方法 attributeListener()
。我们可以找到负责调用的组件并检索存储在其上的属性。在这种情况下,我们检索键入为 contactName
的属性的值。该值存储在控制器的实例变量中。 (如果我们的支持 bean 的范围设置为 @RequestScope
或 @ViewScope
,因为随着时间的推移,实例变量将在多个请求之间共享!)
在动作侦听器返回后,JSF 最终会调用动作方法 performAction()
。实例变量 contactName
可用,并且具有来自 页面的当前值。该方法继续 到下一个页面视图。
如果您一直按照本章中的示例进行操作,那么您一定注意到页面浏览量带有查询参数 redirect=true
(或 faces-redirect=true
,根据官方 JSF 规范)。这是对 JSF 的指令,用于将 HTTP 响应发送回 Web 客户端以重定向到 URL。为什么需要这个后缀?它允许用户为页面视图添加书签,因为 JSF 框架通过仅呈现输出有效地向用户隐藏当前页面视图。主要问题是内部页面转发,这使得使用数字应用程序的客户难以记住或收藏他们的位置。如果客户有一个深度嵌套的信息架构站点,那么提供页面重定向的能力是关键。第二个问题是,如果您的 Web 应用程序以线性方式执行流程,则 Web 浏览器的 URL 会更新,但始终显示流程中的前一页。
重定向在控制器方法 examplar-methods-complete?redirect=true
中起作用,这会导致 JSF 向浏览器发送 HTTP 响应重定向。 Web 浏览器将重定向解释为 URL 的另一个 HTTP GET 请求,例如 http://localhost:8080/jsf-crud-ajax-validation-1.0-SNAPSHOT/jsf-miscellany/examplar-方法.xhtml
。重定向的结果是每个页面导航或操作至少发生两个请求-响应事件。如果您还记得,@ViewScoped
或 @RequestScoped
bean 的范围仅在短时间内可用。当 JSF 处理来自重定向指令的 HTTP GET 时,原来的 bean 已经消失了。这就是示例使用 Flow 范围的原因;该范围保证来自控制器业务逻辑的数据在显示下一个页面视图之前仍然存在。
指定页面重定向的另一种方法是通过 faces-config.xml
用于特定的导航案例。我们可以定义一个案例如下:
这种配置风格在设置第三方 JSF 包时很有用。当然,它也 为库编写者提供了灵活性,并且不会污染Java 管理的bean 重定向字符串。我想这是一个以马换课程的情况,因此取决于项目的目的。
最后,开发者可以通过提交链接和按钮直接设置重定向到页面视图。以下代码展示了这种技术:
也许我应该早点介绍 JSF 的这个特性,因为学习使用 JSF 进行开发可能会让初学者感到困惑。如果您包含 <ui:debug/>
模板视图之一内的自定义标记元素。实际上,框架的 Facelet 视图渲染器输出负责此功能。
通过将 单个 <ui:debug>
嵌入 < ;ui:insert>
标记使 JSF 将一个特殊的 UI 组件添加到 UI 层次结构树中。此调试组件捕获 UI 层次结构的 Facelet 视图信息和当前状态,包括应用程序中的任何作用域变量。信息是在渲染时捕获的。如果用户按下键 Ctrl + Shift + D,JSF 会打开一个单独的浏览器窗口,显示可调试信息,这在困难的情况下非常有用。应用程序的主模板是添加 <ui:debug>
标签的最佳位置。
<ui:debug>
标签接受以下属性:
姓名 |
类型 |
描述 |
---|---|---|
|
|
|
|
|
以下屏幕截图显示了示例方法调用:
本章重点介绍 JSF 验证的不同形式,因为用户知道数据是否输入正确非常重要。我们检查了两种形式的验证方法:客户端和服务器端。我们查看了 FacesMessage
实例并学习了如何创建它们。之后,我们开始介绍服务器端的验证,特别是 Java EE 7 中的 Bean Validation 框架。然后,我们将开发人员的旅程延伸到 JSF 验证。我们学习了如何创建自定义验证器和转换器。我们还学习了如何使用 AJAX 执行即时模式验证并了解部分上下文生命周期。最后,我们花了很多时间处理视图并将信息从页面视图传递到控制器。在此过程中,我们处理了 JSF 流范围和页面重定向。
在下一章中,我们将把注意力转向会话范围,并开始将有用的流程流应用程序放在一起。在这一点上,我们为蓬勃发展的数字 JSF 应用程序添加了技巧和复杂性。我会让你看到那里。
<h:outputLink>
和<h:commandButton>
元素之间的根本区别是什么?如何使用 CSS 框架(如 Bootstrap)适当地设置控件元素的样式?在上一章中,有一些练习围绕着开发一个用于向当地的爱好者读书俱乐部注册新人的 Web 应用程序。您是否碰巧将内容写在单独的页面中而没有重复使用?
将 UI 模板合成应用到您的爱好读书俱乐部项目。将此版本称为第二版并保存第一个版本供您参考。使用模板合成标签
<ui:define>
、<ui:composition>
和<ui:insert>
仅在此阶段。添加
<ui:debug>
自定义标签来掌握模板页面。这个特殊标签对开发者有什么作用?一个恼怒的业务利益相关者来到您的办公室,并告诉您他们在使用欺骗数据方面遇到的问题。似乎网上有些顽皮的人在伪造数据录入,这给个案工作者造成了更大的负担。作为 JSF 的顾问,请解释如何使用验证来保护后端数据库中的数据。只有服务器端验证有效吗?只有客户端验证有效吗?
参考前面的 Hobby Book Club 应用程序,现在让我们向您创建的 JSF 表单元素添加验证。
将 Bean Validation 添加到 registrant 类(
Registrant.java
— 您可能在自己的项目中对此类进行了不同的命名)。您的用户会对验证输出感到满意吗?当您只向应用程序添加服务器端验证时会发生什么?
Bean Validation 和 JSF 验证有什么区别?
Bean Validation 和 JSF 验证有什么相似之处?
根据用户,Bean Validation 和 JSF 验证的错误消息有多合适?
从创建页面开始。根据注册人的姓名进行验证。您可以直接在页面视图上使用
<f:validateRequired>
和<f:validateLength>
进行验证。将适当的<h:messages>
添加到页面视图。一些注册人使用 Facebook、Twitter 和 Instagram 等社交网络。向 Registrant POJO 添加一些属性。添加 URL 验证器以验证社交网络属性是否正确。使用正则表达式验证器来验证 Twitter 帐户语法,或者,也许,编写您自己的自定义验证器。
下载本书的源代码示例并运行第 4 章的示例代码。研究如何从服务器端进行验证。
鉴于您已经开发了带有服务器端验证的项目,您必须将 Hobby Book Club Web 应用程序提升一个档次。使用 AJAX 为控件元素添加客户端验证。您需要将适当的
<f:ajax>
元素添加到 JSF 表单控制元素中。不要忘记每个控件都需要一个区域来呈现特定的错误消息;因此,您不会在页面上非常接近地添加相应的<h:message>
元素。下载 Chrome Developer Web Tools 或类似的网页检查开发工具,并检查 JSF 应用程序的 HTML 内容。对于各种 HTML 元素(尤其是表单)的命名,您有什么观察和注意事项?
稍作休息,将现代 CSS 样式添加到 Hobby Book Club 应用程序中。请同事或朋友评估您的应用程序的用户体验并收集反馈。根据反馈采取行动;改变周围的内容。
向您的 CRUD 应用程序添加取消操作;您需要什么来确保 JSF 不验证输入?