HTML5 和 JSF 高级教程(一)
零、简介
对于打算用支持 HTML5 的 JSF 构建复杂的企业级 web 体验的 Java 开发人员来说,这本书是一个理想的资源。如今,Java web 开发人员拥有前所未有的各种库、框架和组件,可以用来创建丰富而有意义的应用。结果是一个生态系统,在个人层面上提供令人难以置信的生产力。
也就是说,大多数软件是在团队环境中构建的,具有共享的代码库、多个服务端点、复杂的视图和不同的功能区域。在团队环境中,大量的选择可能会适得其反,导致技术债务的增加和相似用例的多种实现模式。像 JavaServer Faces (JSF)这样的基于组件的框架旨在抑制团队环境中的这种行为,同时仍然给开发人员在用例需要时进行定制的自由。
在 Pro JSF 和 HTML5 中,我们向您展示了如何充分利用 JSF 的潜力,这是一个面向服务器端组件的 Java web 框架,为开发团队的生产力设定了标准。它为开发人员提供了丰富的标准和第三方界面组件,可以用来以直观、可重用和可重复的方式组成复杂的视图。最新的标准 JSF 2.2 允许在视图的组成中使用支持 HTML5 的组件。我们展示了开发人员如何将 HTML5 的功能和表达能力与 JSF 组件的规则和可重复性结合起来,以提供丰富的用户体验。
这本书为 JavaServer Faces(尤其是 JSF 2.2)的基础和高级主题提供了坚实的基础。前四章涵盖了 JSF 生命周期、架构、受管 beans 和与 CDI 的集成(上下文和依赖注入)、表达式语言、异常处理、转换和验证、JSF 事件(Faces 事件、Phase 事件和系统事件)以及视图参数。第五章涵盖了 JSF 2.2 的新特性,比如 faces flow、资源库契约、HTML5 友好标记、Ajax 文件上传等。
第六章、第七章和第八章详细介绍了在 JSF 2.2 世界中创建支持 HTML5 的组件的过程,并提供了一些交互示例。第九章介绍了两个流行的 JSF 组件库(PrimeFaces 和 RichFaces)的基础知识,并给出了两个交互示例。
在第十章中,开发了一个基本的 JSF 2.2 应用,该应用利用 Java EE 7 技术(CDI、JPA 2.1 和 EJB 3.2)来促进 JSF 应用中的 bean 管理、事务管理和持久性。第十一章涵盖了 JSF 的高级主题,比如应用设计考虑、单元测试和 Ajax 队列。
第十二章涵盖了与 JSF 应用安全相关的重要主题,包括认证、授权和数据保护。本章展示了如何在一个示例应用中应用容器管理的安全性。第十二章还介绍了 JSF 性能考虑因素,以帮助确保您的应用运行平稳、响应迅速。
最后,第十三章收集了书中涵盖的大部分主题,并以实现现实世界用例的高级应用的形式将它们付诸实践。
我们希望这本书成为面向组件框架初学者的渐进指南,并作为经验丰富的 JSF 开发者的参考,他们正在寻求最大化 JSF 2.2 显著升级的功能。我们祝您阅读愉快,编码高效!
一、JSF 简介
本章将解释什么是 JavaServer Faces (JSF) 框架,该框架如何随着时间的推移而发展,其架构的关键方面,以及关于其签名请求处理生命周期的细节。除了解释之外,我们将亲自动手,指导您使用 JSF 2.1 和 Maven 3 从头开始开发您的第一个 JSF 应用;您将学习如何在两个不同的 web 容器(GlassFish 和 Tomcat)上部署 JSF 2.1 应用。如果您已经在基本的组件级别上熟悉了 JSF,那么在处理更复杂的应用时,对请求生命周期的深入理解将会对您大有裨益。
什么是 JSF?
JSF 是一个服务器端的面向组件的 Java web 框架,它简化了开发富 Java 企业 web 应用的过程。JSF 擅长提供高度可定制的标准化方法来构建应用用户界面。用户界面层通常是任何应用中最具挑战性和最易变的部分。这也是被广泛采用并不断发展的成功应用与勉强采用并频繁更改以满足用户需求的应用之间的区别。
JSF 为解决 Java 企业 web 应用开发中经常出现的常见问题提供了一个强大的平台,例如验证、转换、导航、模板和页面流。JSF 为解决 web 应用开发的常见问题提供了一种标准的方法,这使得它成为一个优秀的框架,可以减少 web 应用的开发和维护时间。
当您的开发团队很大并且是分布式的时,尤其如此,这是企业中常见的情况。围绕一组标准化的 JSF 组件构建用户体验允许一定程度的定制和表达,但同时也为应用在不同实现间的外观、行为和响应建立了“共享 DNA”。
JSF 为以下应用提供 API 和标记库
- 为应用的快速开发提供 UI 组件。
- 将组件事件连接到 Java 服务器端代码中。
- 用 POJOs(普通旧 Java 对象)绑定 UI 组件。
- 提供了一组有用的内置验证器和转换器,并提供了一种创建自定义验证器和转换器的机制,以满足特定的需求。
- 处理异常。
- 处理应用页面之间的导航。
- 创建页面模板和应用模板。
- 以反映应用需求的方式定义页面流。
- 处理用户界面的本地化和国际化。
- 创建用来自后端和 API 的数据自动更新的“实时”页面。
- 通过扩展框架类并提供自定义组件视图、事件和状态的实现来创建自定义组件。
JSF 是一个大框架,允许开发者在不同的层次上使用它。根据应用的复杂程度和规模,可以将每个级别划分为不同的角色,由一个或多个开发人员来执行。图 1-1 显示了根据 JSF 规范的不同 JSF 角色。JSF 角色包括以下内容:
- 页面作者。
- 组件编写器。
- 应用开发人员。
- 工具提供商。
- JSF 实施者。
图 1-1 。JSF 角色
如图 1-1 所示,页面作者负责创建页面的用户界面。页面作者应该了解标记、样式和脚本语言,如 HTML5、CSS3 和 JavaScript。页面作者还应该了解 JavaServer Pages (JSP)等呈现技术。
与应用开发人员不同,页面作者专注于为应用开发令人愉快且有效的用户体验。JSF 的组件架构抽象出了大量的复杂性,使得页面作者即使不熟悉 Java 和 C#等编程语言也能高效工作。
组件编写者负责开发页面作者可以使用的可重用 JSF 组件。可重用的 JSF 组件可以收集到“组件库”中可以把它想象成一个组件面板,很容易发现、定制和集成到您的应用中。组件库是大型开发团队生产力倍增的一个重要因素。两个流行且全面的 JSF 组件库是 PrimeFaces 和 RichFaces。
可重复使用的 JSF 组件应支持以下功能:
- *编码:*将内部组件的属性和特性转换成使用该组件的页面中合适的标记(比如 HTML)。
- 解码:将带有相关头和参数的传入请求转换成组件的相关属性和特性。
- 除此之外,组件应该支持请求时事件、验证、转换和状态。转换是将传入的请求转换为适合组件的形式的过程。有状态性意味着组件必须为新请求保留其原始状态。这可以通过跨不同请求保存和恢复组件状态来完成。
应用开发人员负责开发 JSF 应用的服务器端功能。应用开发人员专注于开发 Java、EJB 或任何其他能够在 JVM (Java 虚拟机)上运行的语言。除此之外,应用开发人员可以定义 JSF 应用的持久性存储机制(包括数据和内容模型),并从 JSF 页面公开要使用的数据和业务逻辑对象。
工具提供商负责开发工具,帮助 JSF 开发人员构建 JSF 应用。这些工具包括 IDE(集成开发环境)插件和扩展以及页面生成器。JSF 实现者负责为所有前面的角色提供符合标准的运行时或 JSF 规范的实现。Oracle Mojarra(javaserverfaces.java.net/)和 Apache MyFaces(myfaces.apache.org/)是 JSF 规范的可用实现的例子。
JSF 进化:1.0–2.2
JSF 1.0 于 2004 年 3 月发布;它代表了 web 层实现方式的重大发展。但是伴随着这些优势而来的还有一些限制,为了让 JSF 在社区中得到广泛的接受,这些限制需要被规避。这些限制中的一些与部件性能有关,另一些与开放式缺陷有关。
专家组努力制定规范,并于 2004 年 5 月发布了 1.1 版,该版本消除了 JSF 1.0 的一些最大的性能问题,并修复了许多缺陷,使 JSF 框架可用于下一代 web 应用。有了 JSF 1.1,专家组已经实现了他们在 Java 规范请求(JSR) 127 中设定的大部分早期目标。这些目标与开发工具支持的标准 GUI 组件框架的创建有关,开发工具允许 JSF 开发人员通过扩展基础框架组件、定义用于输入验证和转换的 API 以及指定 GUI 本地化和国际化的模型来创建自定义组件。
注在
jcp.org/en/jsr/detail?id=127可以读到 JSR 127。
2006 年 5 月,JSF 1.2 和 Java 企业版 5.0 一起发布。JSF 1.2 对 JSF 1.1 进行了重大改进,解决了用户群体的一些现实问题。JSF 1.2 有许多特性,其中一些是
- JSF 和 JSP 之间的统一表达式语言。
- 解决 JSF 与 JSP 和 JSTL 的集成问题。
- 允许单个组件覆盖转换和验证消息。
- 增强客户端状态保存的安全性。
注 JSR 252 是 JSF 1.2 规格;你可以在 jcp.org/en/jsr/deta… 的看到。
2009 年 7 月,JSF 2.0 与 Java 企业版 6.0 一同发布。JSF 2.0 引入了大量的特性和增强功能。一些特性和增强功能包括
- 复合部件。
- 完全模板支持。
- 完整的 Ajax 支持。
- 增强 JSF 导航。
- 支持视图参数。
- 在应用中支持更多的作用域。
- 提供异常处理机制。
- 通过 JSR 303 集成改进验证。
- 标准化资源加载机制。
- 通过支持大多数配置的注释来最小化 XML 的使用。
2010 年 7 月,JSF 2.1 主要是 JSF 2.0 的维护版本。它包括错误修复和增强。其中一些是
- 允许 JSP 文档语法(。jspx 文件)被视为 Facelets 文件。
- 可插拔的 Facelet 缓存机制。
注 JSR 314 规范在其最终版本中集合了 JSF 2.0,在其维护版本中集合了 JSF 2.1;你可以在 jcp.org/en/jsr/deta… 阅读说明书。
截至本章撰写时,JSF 2.2 规范和实现仍在进行中,尚未发布。JSF 2.2 预计将与 Java 企业版 7.0 一起发布。JSF 2.2 的主要特点是
- 通过引入 FacesFlow 来标准化流 API。
- 多模板。
- 添加新的 JSF 元素和属性,这些元素和属性是特定于 HTML5 的。
注 JSR 344 是 JSF 2.2 规格;你可以在 jcp.org/en/jsr/deta… 的看到它的初稿评论。
在本书的所有章节中,我们将使用实际的、易于理解的例子来更详细地讨论这些特性。
JSF 建筑
JSF 架构基于 MVC(模型视图控制器)2 模式。与 MVC1 模式不同,MVC2 模式将视图从控制器和模型中分离出来。图 1-2 显示了 JSF 的 MVC2 架构。
图 1-2 。JSF MVC2 架构
在 JSF,MVC2 模式实现如下:
- 控制器,由 JSF Faces Servlet 表示。Faces Servlet 负责处理请求分发和页面导航。Faces Servlet 通过调用负责处理 JSF 请求处理生命周期的 JSF 生命周期对象来编排 JSF 生命周期。
- 模型,由 JSF 管理的 beans 和后端代码表示。JSF 托管 bean 只是一个符合 JavaBeans 命名约定的 POJO,可以从 JSF 应用(页面和其他托管 bean)访问。JSF 管理的 bean 必须有一个控制其生命周期的范围;它可以在请求、视图、流、会话、应用或无范围内。每个 JSF 托管 bean 都应该在 faces-config . XML(JSF 配置文件)中注册或使用注释注册(托管 bean 将在第二章中详细介绍)。
- 视图,这是 JSF 的渲染技术。呈现技术定义了页面布局和内容。从 2.0 版本开始,JSF 的默认呈现技术是 Facelets XHTML(但是,您仍然可以选择使用 JSP 作为 JSF 呈现技术,尽管不建议这样做)。
您可能想知道 JSF 运行时如何简化 JSF 开发人员的应用开发,以及控制器如何协调 JSF 的模型和视图。这些问题将在“JSF 生命周期”部分得到解答。
开发您的第一个 JSF 应用
现在,是时候暂时停止这个理论,开始用 JSF 框架工作了。让我们看看如何开发和运行您的第一个 JSF 2.1 应用。
所需软件
在详细介绍您的第一个 JSF 2.1 应用示例之前,我想提一下,本书的所有示例都基于 Apache Maven 3 软件,版本 3.0.4,用于执行编译,并将编译后的源代码组装到可部署的 Java EE WAR 文件中。Maven 3 可以从maven.apache.org/download.html下载。
Apache Maven 是一个强大的构建管理工具。每个 Maven 项目都有一个名为(pom.xml)的“项目对象模型”文件。(pom.xml)文件包括项目依赖项,用于将项目编译和构建到目标工件中。为了构建一个项目,Maven 从(pom.xml)文件中获取依赖项,然后如果在本地磁盘上没有找到这些依赖项,就将它们下载到本地磁盘上;之后,Maven 执行编译,并将编译后的源代码组装成目标工件。本书中所有示例的目标工件是 Java EE web 应用 WAR 文件。Maven 的强大特性之一是其应用的严格结构,如图图 1-3 所示。
图 1-3 。Maven 项目结构
如图所示,项目根包含两个主子文件夹(src 和 target)和(pom.xml)文件。src 目录包含应用的源代码,目标目录包含生成的工件。src 目录有许多子目录;这些目录中的每一个都有特定的用途:
- src/main/java: 包含应用的 java 源代码。
- src/main/resources: 包含应用需要的资源,比如资源包。
- src/main/filters: 它包括资源过滤器。
- src/main/config: 包含配置文件。
- src/main/webapp: 包含 JEE web 应用项目的文件。
- src/test: 包括应用的单元测试。
- src/site: 它包括用于生成 Maven 项目网站的文件。
除了 Apache Maven 3 软件,本书的所有示例都使用 Oracle jdk1.6.0_27(可以从www.oracle.com/technetwork…下载),这些示例可以运行在任何支持 JSF 2.1(JSF 2.2 示例使用 JSF 2.2)的运行时环境上。
注意 Oracle GlassFish v3.1(或更高版本)和 Apache Tomcat 7 能够运行 JSF 2.1 应用。我将向您展示如何在两个 Java web 容器上运行基于 JSF 2.1 的第一个应用。
开发第一个应用
第一个应用包含两个页面。在第一页中,您可以输入您的姓名和密码,如图 1-4 所示。
图 1-4 。登录页面
点击“登录”按钮后,将被重定向到欢迎页面,如图图 1-5 所示。
图 1-5 。欢迎页面
第一个应用具有以下 Maven 结构:
- first application/src/main/WEB app/we b-INF/faces-config . XML
- first application/src/main/WEB app/we b-INF/WEB . XML
- first application/src/main/WEB app/we b-INF/templates/simple . XHTML
- first application/src/main/web app/CSS/simple . CSS
- first application/src/main/web app/index . XHTML
- first application/src/main/web app/welcome . XHTML
- first application/src/main/Java/com/jsfprotml 5/first application/model/user . Java
- first application/src/main/resources/com/jsfprohtml 5/first application/messages . properties
图 1-6 显示了第一个应用的完整布局。
图 1-6 。第一个应用 Maven 结构
配置文件
第一个应用有两个配置文件,分别是 web.xml 和 Faces-config . XML。web . XML 文件是标准的 web 模块部署描述符,其中定义了 Faces Servlet。Faces Servlet 的主要目的是拦截对 JSF 页面的请求,以便在访问所请求的 JSF 页面之前准备好 JSF 上下文。清单 1-1 显示了第一个应用的 web.xml 文件。
清单 1-1。 第一个应用的 web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app FontName">http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
在清单 1-1 中,您需要知道两件主要的事情:第一件是 Faces Servlet 的定义及其使用< url-pattern >元素到(/faces/*) URL 的映射。第二个是 javax.faces.PROJECT_STAGE 上下文参数,它被设置为 Development(其他可能的值是 Production、SystemTest 和 UnitTest)。在开发模式中设置项目阶段使得 JSF 框架在发现常见的开发错误时在页面中生成额外的消息。这个特性可以帮助 JSF 开发人员在开发过程中提高工作效率。
最后,元素指定应用的欢迎页面,就是 index.xhtml 文件;这将把对localhost:8080/first application/的任何请求重定向到localhost:8080/first application/Faces/index.xhtml,这将触发 Faces Servlet 在转到 index . XHTML 页面之前准备 JSF 上下文。
注意在任何 Servlet 3.0 容器比如 GlassFish v3 中,web.xml 文件都是可选的。如果省略 web.xml,Faces Servlet 将自动映射到*。jsf,。faces 和/faces/ URL 模式。
现在,让我们转到 faces-config 文件,它包括相关的 JSF 配置。实际上,从 JSF 2.0 开始,faces-config 文件变成了可选的,因为大多数 JSF 配置都可以使用 Java 注释来定义。清单 1-2 显示了第一个 Application Faces 配置文件。
清单 1-2。 第一个应用面孔配置文件
<faces-config version="2.1"
FontName">http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd">
<application>
<resource-bundle>
<base-name>com.jsfprohtml5.firstapplication.messages</base-name>
<var>bundle</var>
</resource-bundle>
</application>
</faces-config>
清单 1-2 全局定义了应用资源包,以便从 JSF 表达式语言(#{...})使用捆绑变量(JSF 表达式语言将在第三章中详细说明)。
Facelets Pages
firstApplication 包含两个主页面:第一个页面(index.xhtml)表示主页,另一个页面是(welcome.xhtml)页面,表示欢迎页面。两个页面都引用了(first application/src/main/WEB app/we b-INF/templates/)下定义的模板(simple.xhtml)页面,如清单 1-3 所示。
清单 1-3。 简单. xhtml 模板文件
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title><ui:insert name="title">#{bundle['application.defaultpage.title']}</ui:insert></title>
<link href="#{request.contextPath}/css/simple.css" rel="stylesheet" type="text/css"/>
</h:head>
<h:body>
<div id="container">
<div id="header">
<ui:insert name="header">
<h1>#{bundle['application.defaultpage.header.content']}</h1>
</ui:insert>
</div>
<div id="content">
<ui:insert name="content">
#{bundle['application.defaultpage.body.content']}
</ui:insert>
</div>
<div id="footer">
<ui:insert name="footer">
#{bundle['application.defaultpage.footer.content']}
</ui:insert>
</div>
</div>
</h:body>
</html>
声明了模板 doctype:在 firstApplication 页面中,所有页面都使用这个 doctype,它代表 HTML5 doctype。为了包含 JSF HTML、core 和 Facelets UI 标签,使用了下面的声明 :
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
第一行是标准的 XHTML 实践,第二行声明 Facelets UI 标记,第三行声明 JSF 核心标记,最后一行声明模板页面中使用的 JSF HTML 标记。
和元素是 HTML 和元素的替代品。该模板包含一个容器元素,其 ID 为“container”。“容器”元素包含三个子元素:
- **header 元素:**页面的页眉定义在“header”div 元素里面。
- **内容元素:**页面的内容在“content”div 元素中定义。
- **页脚元素:**页面的页脚是在“footer”div 元素中定义的。
在 header、content 和 footer 元素中,有一个 Facelets 标记。Facelets 标签用于声明初始默认内容,该内容可以被使用该模板的页面覆盖。标签中的内容代表内容的初始值。#{...}表示 JSF 表达式语言。JSF 表达式语言可以用来绑定 JSF 模型和 JSF 页面;在模板页面中,它通过#{bundle['key name']}将资源包内容与页面绑定。bundle 变量在 faces-config.xml 中定义,如“配置文件”一节所示。清单 1-4 显示了 messages.properties 资源包文件。
***清单 1-4。***message . properties 文件
user.name = Your name
user.password = password
user.name.validation = You need to enter a username
user.password.validation = You need to enter a password
application.login = Login
application.loginpage.title = Login page
application.welcome = Welcome
application.welcomepage.title = Welcome page
application.welcomepage.return = Back to home
application.defaultpage.title = Default Title
application.defaultpage.header.content = Welcome to the first application
application.defaultpage.body.content = Your content here ...
application.defaultpage.footer.content = Thanks for using the application
资源包是一组键和值对。使用 JSF 表达式语言,在运行时解析包键的值。例如,在运行时,将# { bundle[' application . default page . header . content ']}表达式计算为“欢迎使用第一个应用”。
模板文件还包括一个 CSS(层叠样式表)文件,就是 simple.css 文件。simple.css 负责模板页面布局。清单 1-5 显示了 simple.css 文件。
***清单 1-5。***simple . CSS 文件
h1, p, body, html {
margin:0;
padding:0;
}
body {
background-color:#EEEEEE;
}
#container {
width:100%;
}
#header {
background-color:#FFA500;
}
#header h1 {
margin-bottom: 0px;
}
#content {
float:left;
width:100%;
}
#footer {
clear:both; /*No floating elements are allowed on left or right*/
background-color:#FFA500;
text-align:center;
font-weight: bold;
}
.errorMessage {
color: red;
}
为了在不改变 web 应用的 HTML 代码的情况下随时改变页面布局,建议使用 CSS。
注意从 JSF 2.0 开始,支持页面模板。在 JSF 2.0 之前,JSF 的开发者必须在 JSF 应用中下载并配置一个模板库(比如 Facelets 库)来定义页面的布局。
清单 1-6 显示了 index.xhtml 页面代码,它代表了应用的介绍性页面。
***清单 1-6。***index . XHTML 页面
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html">
<ui:composition template="/WEB-INF/templates/simple.xhtml">
<ui:define name="title">
#{bundle['application.loginpage.title']}
</ui:define>
<ui:define name="content">
<h:form>
<h:panelGrid columns="3">
<h:outputText value="#{bundle['user.name']}"></h:outputText>
<h:inputText id="userName"
value="#{user.name}"
required="true"
requiredMessage="#{bundle['user.name.validation']}">
</h:inputText>
<h:message for="userName" styleClass="errorMessage"/>
<h:outputText value="#{bundle['user.password']}"></h:outputText>
<h:inputSecret id="password"
value="#{user.password}"
required="true"
requiredMessage="#{bundle['user.password.validation']}">
</h:inputSecret>
<h:message for="password" styleClass="errorMessage"/>
</h:panelGrid>
<h:commandButton value="#{bundle['application.login']}" action="welcome">
</h:commandButton> <br/><br/>
</h:form>
</ui:define>
</ui:composition>
</html>
为了在 index.xhtml 页面(或任何其他 xhtml 页面)中包含 simple.xhtml 模板页面,使用了标记,用模板页面的相对路径指定其模板属性。标签包括标签。如果标签使用 name 属性匹配,标签用于覆盖由标签定义的模板内容。
在 index.xhtml 页面中,模板的“标题”和“内容”被页面标题和页面内容覆盖。页面内容包括一个
标签,所有参与表单提交的 JSF 输入组件都需要这个标签。为了制作表单的内部布局,使用了< h:panelGrid >标签。标签是一个布局容器,它在由行和列组成的网格中呈现 JSF 组件。< h:panelGrid >标签有一个 columns 属性,指定网格的列数(在本例中是“3”)。在 index.xhtml 页面中,< h:panelGrid >中的每一行都代表一个带有标签和消息的输入字段。第一行如下:
<h:outputText value="#{bundle['user.name']}"></h:outputText>
<h:inputText id="userName"
value="#{user.name}"
required="true"
requiredMessage="#{bundle['user.name.validation']}">
</h:inputText>
<h:message for="userName" styleClass="errorMessage"/>
定义用户名标签,定义“用户名”输入文本,最后定义“用户名”输入文本的消息,以便显示验证错误消息。使用指定输入文本 ID 的 for 属性将与输入文本链接起来(在本例中是“userName”)。
将标签的 required 属性设置为 true 会在输入文本上创建一个验证,以避免空值。如果在输入文本中输入一个空值,那么 requiredMessage 属性中的消息将显示在中。标记的 value 属性包含以下 JSF 值表达式,#{user.name},该表达式将用户管理的 bean 的 name 属性与用户输入的输入文本值相链接。用户管理的 bean 的代码在“管理的 bean”一节中列出。
的第二行代表“密码”秘密字段及其标签和消息。这与第一行的想法相同,不同之处在于它使用了标记来定义密码输入字段,并且标记的值通过#{user.password}值表达式与用户管理的 bean 的密码属性相链接。呈现一个 HTML 提交按钮。在页面中,登录命令按钮定义如下:
<h:commandButton value="#{bundle['application.login']}" action="welcome"></h:commandButton>
的 action 属性可以接受一个 JSF 方法绑定表达式,以便在单击命令按钮时调用受管 bean 操作方法。受管 bean 操作方法必须是公共方法,并且必须返回字符串值或 null。返回的字符串表示操作的逻辑结果,JSF 运行时通过查看配置文件中是否定义了匹配的导航规则来确定要显示的下一页。但是从 JSF 2.0 开始,支持隐式导航。这允许动作属性接受直接指向目标页面的字符串值,而不需要在配置文件中定义导航规则(例如:如果目标页面名称是“foo.xhtml ”,则动作字符串结果必须是“foo ”,并且 JSF 运行时将追加“.xhtml”扩展名和动作字符串值,以便正确导航到目标页面)。在 index.xhtml 页面中,action 属性设置为“welcome”,这意味着当单击 login 命令按钮时,应用将导航到 welcome.xhtml 页面。
注JSF 导航是一个有很多细节的话题,在本书的下一章会有更详细的说明。JSF 验证和转换将在第三章的中详细说明。
清单 1-7 显示了 welcome.xhtml 页面代码,它表示应用的欢迎页面。
***清单 1-7。***welcome . XHTML 页面代码
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<ui:composition template="/WEB-INF/templates/simple.xhtml">
<ui:define name="title">
#{bundle['application.welcomepage.title']}
</ui:define>
<ui:define name="content">
#{bundle['application.welcome']}, #{user.name}! <br/>
<h:link value="#{bundle['application.welcomepage.return']}"
outcome="index"></h:link> <br/><br/>
</ui:define>
</ui:composition>
</html>
welcome.xhtml 页面包含使用标签的 simple.xhtml 模板页面。与 index.xhtml 页面一样,在 welcome.xhtml 页面中,模板的“标题”和“内容”被页面标题和页面内容覆盖。页面内容显示一条欢迎消息,其中包含使用#{user.name}值表达式的用户管理的 bean 的 name 属性。页面内容包括一个使用标签的介绍性页面的链接。标签是从 JSF 2.0 开始引入的新标签;它呈现一个 HTML 锚元素。的值属性呈现为锚文本,其结果属性用于确定目标导航页面。在欢迎页面中,outcome 属性值被设置为“index ”,这使得隐式导航成为应用的“index.xhtml”页面。
被管理的 bean
如 Facelets 页面部分所示,有一个用户管理的 bean,它与索引和欢迎页面的输入和输出组件绑定在一起。清单 1-8 显示了用户管理的 bean。
清单 1-8。 用户托管 Bean
package com.jsfprohtml5.firstapplication.model;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class User implements Serializable {
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
用户管理的 bean 是一个简单的 Java bean,有两个用于 name 和 password 属性的 setters 和 getters。@ManagedBean 注释用于将用户类注册为 JSF 管理的 Bean。
注意@ managed Bean 注释有一个可选的 name 属性,它描述了要在 JSF 表达式中使用的受管 bean 的名称。在用户管理的 bean 中,name 属性被省略;这意味着受管 bean 名称将与第一个字符为小写的类名相同,也就是说,它将在像#{user}这样的 JSF 表达式中使用。
@SessionScoped 注释用于在会话范围内设置受管 bean。其他可能的值可以是(@RequestScoped、@ViewScoped、@ApplicationScoped、@ none scoped[或@FlowScoped,这在 JSF 2.2 中受支持])。
依赖性
现在,让我们转到第一个应用的(pom.xml)依赖项。清单 1-9 显示了 GlassFish 3.1.2 上第一个应用所需的依赖关系。
清单 1-9。 在 pom.xml 文件中配置 GlassFish 3.1.2
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.faces</groupId>
<artifactId>javax.faces-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
如清单所示,以下依赖项仅用于编译,不会包含在 web 应用的 lib 文件夹中,因为 GlassFish 3.1.2 应用服务器已经附带了这些依赖项:
- Servlet API 版本 2.5。
- JSP API 版本 2.0。
- JavaEE Web API 版本 6。
- JSF API 版本 2.1。
清单 1-10 显示了 Tomcat 7 上第一个应用所需的依赖关系。
清单 1-10。 雄猫 7 配置
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.faces</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
如清单所示,这是几乎相同的依赖项集,只有一个区别,即替换了 JSF API v2.1 依赖项,该依赖项在提供的范围内:
<dependency>
<groupId>javax.faces</groupId>
<artifactId>javax.faces-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
在编译范围内具有以下依赖关系:
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.faces</artifactId>
<version>2.1.6</version>
</dependency>
这个替换告诉 Maven 使用 JSF 2.1.6 jar 来编译第一个应用,并将这个 jar 包含在 web 应用 lib 文件夹中。我特别选择 2.1.6 版本,因为它在 Tomcat 7 上运行良好。
构建和部署第一个应用
第一个 Application Maven 项目可以从本书的网站下载:www.apress.com/97814302501…。为了构建和部署第一个应用,您需要在您的系统中安装 Maven 3。
注配置 Maven 3 的详细步骤在 Maven 网站有描述:
maven.apache.org/download.html#Installation。这些说明向您展示了如何在您的系统中安装 Maven,无论它是 Windows、Linux、Solaris 还是 Mac OS X。
在您的系统中安装了 Maven 3 之后,您可以通过从命令行执行以下 Maven 命令来构建第一个应用。这个命令应该从包含 pom.xml 文件的应用目录中执行:
mvn clean install
执行这个命令后,可以在目标文件夹中找到生成的 firstApplication-1.0.war 文件。让我们看看如何在 Apache Tomcat 7 和 Oracle GlassFish 3.1.2 上部署生成的 war 文件。
注意不要忘记使用“依赖项”一节中提到的适当的 pom.xml 依赖项来正确生成两个 war 文件(一个用于 Apache Tomcat 7,另一个用于 Oracle GlassFish 3.1.2)。
在 Tomcat 7 上部署应用
为了在 Apache Tomcat 7 上部署 firstApplication-1.0.war 文件,您需要执行以下操作:
- 将 firstApplication-1.0.war 文件复制到$ { Tomcat _ Installation _ Directory } \ web apps 目录。
- 从$ { Tomcat _ Installation _ Directory } \ bin 目录中执行以下命令,启动 Tomcat 7 服务器:
- startup.bat(适用于 Windows)
- startup.sh(适用于 Linux)
- 从以下 URL 访问第一个应用:
localhost:8080/first application-1.0/
在 GlassFish 3.1.2 上部署应用
为了在 Oracle GlassFish 3.1.2 上部署 firstApplication-1.0.war 文件,您需要执行以下操作:
- 将 firstApplication-1.0.war 文件复制到$ { GlassFish _ Installation _ Directory } \ domains **domain 1**\ auto deploy 目录中( domain1 可以改为任意域名)。
- 通过从${ GlassFish _ Installation _ Directory } \ bin 目录执行以下命令来启动 GlassFish 3.1.2 服务器:asadmin start-domain 域 1
- 从以下 URL 访问第一个应用:
localhost:8080/first application/
尽管第一个应用是一个简单的 JSF 应用,但它涵盖了 JSF 的许多基础知识。您现在知道了以下内容:
- 如何从头开始创建一个 JSF 应用?
- JSF 表达式的基础。
- JSF 管理的是如何创建和使用 JSF 应用。
- 如何创建一个 JSF 页面模板,并在应用页面中使用该模板。
- 如何从 JSF 应用中配置和使用资源包。
- 如何使用基本的 JSF HTML 组件标签?
- 如何使用 JSF 必填字段验证器来验证输入字段。
- 如何使用 Maven 来管理在不同的 JSF 2.1 web 容器上轻松部署 JSF 应用。
JSF 生命周期
现在,是时候了解 JSF 是如何在幕后工作的了。虽然开发 JSF 应用并不需要了解 JSF 生命周期的细节,但建议阅读本节,以便了解您开发的代码如何在 JSF 运行时容器中执行,并为高级 JSF 开发做好准备。JSF 请求处理生命周期有六个阶段,如图 1-7 所示。
图 1-7 。JSF 请求处理阶段
六个阶段如下:
- 恢复视图。
- 应用请求值。
- 流程验证。
- 更新模型值。
- 调用应用。
- 渲染响应。
恢复视图
每个 JSF 页面在服务器上都表示为一个 UI 组件树,它与客户机(浏览器)中的用户界面有一对一的映射。为了正确理解这一点,我们来看一个例子。清单 1-11 显示了一个 JSF XHTML 页面,允许用户输入他/她喜欢的食物、饮料和运动。
清单 1-11。 收藏夹. xhtml 页面
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>Your Favorites</title>
</h:head>
<h:body>
<h:form id="favForm">
<h:panelGrid columns="3">
<h:outputText value="Favorite Food"></h:outputText>
<h:inputText id="favoriteFood" value="#{favorite.food}" required="true">
</h:inputText>
<h:message for="favoriteFood"/>
<h:outputText value="Favorite Beverage"></h:outputText>
<h:inputText id="favoriteBeverge" value="#{favorite.beverage}" required="true">
</h:inputText>
<h:message for="favoriteBeverge"/>
<h:outputText value="Favorite Sport"></h:outputText>
<h:inputText id="favoriteSport" value="#{favorite.sport}" required="true">
</h:inputText>
<h:message for="favoriteSport"/>
</h:panelGrid>
<h:commandButton value="Save my favorites" action="#{favorite.save}"/><br/><br/>
</h:form>
</h:body>
</html>
favorites.xhtml 页面中的代码表现为 UI 组件的树,如图图 1-8 所示。
图 1-8 。收藏夹页面的 UI 组件树
在“恢复视图”阶段,有两种情况:
- “非回发”请求:“非回发”请求是指对页面的新请求。如果是这种情况,还原视图阶段会创建一个空的 UI 组件树,并将其存储在当前的 FacesContext 实例中。对于“非回发”请求,JSF 生命周期直接进入最后一个阶段,即“呈现响应”阶段。在“呈现响应”阶段,空的 UI 组件树由页面中的 JSF 组件填充。此外,UI 组件树状态保存在 JSF 视图状态中,以供下一个请求使用。
- “回发”请求:当使用 HTTP POST 方法将表单内容提交到同一页面时,会发生“回发”请求。在这种情况下,还原视图阶段从 JSF 视图状态还原 UI 组件树,该视图状态是由前一个页面请求生成的。
应用请求值
在 UI 组件树恢复后,调用“应用请求值”阶段。在这个阶段,UI 组件树中的每个节点都被分配了从表单提交的值。图 1-9 显示了如果表单提交了值,UI 组件树是如何填充请求值的,例如,“favoriteFood”的“鱼”,“favoriteBeverge”的“橙汁”,“favoriteSport”的“足球”。
图 1-9 。用请求值填充 UI 组件树
注意“应用请求值”应用于所有具有值属性的组件。在 JSF 中,具有 value 属性的组件必须实现 ValueHolder 接口。为了将请求值应用到 UI 组件树中的所有值容器节点,JSF 运行时调用 UIViewRoot 的 processDecodes()方法,这导致子组件的 processDecodes()方法也被调用,以便为所有组件应用请求值。
流程验证
“流程验证”阶段在“应用请求值”阶段之后调用。在此阶段,转换和验证按顺序执行。在 favorites.xhtml 中,通过将“required”属性设置为 true,对所有输入字段执行验证,以保证它们总是具有非空值。
转换是将 HTTP 请求参数转换成相应的 Java 类型,以消除开发人员为每个 web 应用实现这一功能所需的开销。JSF 有许多内置的转换器,它提供了一个接口,以便开发定制的转换器。清单 1-12 显示了一个 JSF 内置日期转换器的例子。
清单 1-12。 内置日期转换器的例子
<h:inputText id="birthDate" value="#{user.birthDate}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:inputText>
在本例中,“birthDate”输入字段确保具有以下格式的日期“dd/MM/yyyy ”,并被转换为用户管理的 bean 中的 birthDate(日期对象)属性(验证和转换的更多细节将在第三章的中说明)。
当组件验证(或转换)失败时,组件错误消息(FacesMessage)将在 FacesContext 实例中排队。如果出现验证(或转换)错误,JSF 生命周期将直接进入“呈现响应”阶段,排队的 Faces 消息将显示在或 UI 组件中。
注意为了在 UI 组件树中应用过程验证,JSF 运行时调用 UIViewRoot 的 processValidators()方法,这导致子组件的 processValidators()方法也被调用,以便将转换和验证应用于所有子组件。
更新模型值
在完成转换和验证 UI 组件树中的值之后,调用“更新模型值”阶段。在这个阶段,在 UI 组件树中的值和 JSF 模型(受管 beans)之间进行绑定。
图 1-10 显示了 JSF 管理的 bean 属性是如何用 UI 组件树的值更新的。
图 1-10 。JSF 管理的 bean 属性随 UI 组件树的值更新
注意为了执行模型值更新,JSF 运行时调用 UIViewRoot 的 processUpdates()方法,这导致子组件的 processUpdates()方法也被调用,以便将模型更新应用于所有子组件。但是,UIInput 组件覆盖 processUpdates()方法,以便调用 updateModel()将用户输入与受管 bean 属性绑定起来。
调用应用
在完成模型值更新之后,调用“调用应用”阶段。在这个阶段,执行动作代码。JSF 中的动作代码可以在动作方法和动作监听器中。
注意在 favorites.xhtml 中,动作代码在#{favorite.save}动作方法中表示。
在此阶段,导航是在执行自定义操作代码后由 JSF NavigationHandler 执行的。action 属性可以设置为文字值。在第一个应用中,您已经看到了将 action 属性设置为文字值的情况:
<h:commandButton value="#{bundle['application.login']}" action="welcome">
</h:commandButton>
在文本值的情况下,JSF 运行时直接将“welcome”文本值传递给 JSF NavigationHandler,以便导航到新页面。导航处理结果显示在“渲染响应”阶段。
如果操作结果没有隐式(结果匹配现有页面名称)或显式(通过匹配 faces-config 中定义的导航规则)匹配导航案例,NavigationHandler 将停留在同一页面上。
注意为了执行“调用应用”阶段,JSF 运行时调用 UIViewRoot 的 processApplication()方法,该方法通过调用 UIComponent 的 broadcast(FacesEvent event)方法,将排队的事件广播给 UICommand 组件(或任何其他实现 ActionSource 接口或 ActionSource2 接口[在 JSF 1.2 中引入]的 UIComponent)。broadcast 方法将操作事件广播给注册到该事件类型的所有操作侦听器进行处理。
渲染响应
最后,JSF 运行时调用“呈现响应”阶段,以便向用户呈现最终结果。通过在每个组件上调用 encodeXXX()方法,UI 组件树被呈现给客户端(encode 方法负责为每个组件生成合适的标记)。
除了呈现之外,“呈现响应”阶段还将 UI 组件树的状态存储在视图状态中,以便在下一个请求中恢复。
立即属性
有时,在您的 JSF 应用中,您可能希望跳过转换和验证,以便导航到另一个页面。例如,假设在 favorites.xhtml 页面中,您希望有一个导航到索引页面“index.xhtml”的“主页”按钮,如下所示:
<h:form id="favForm">
...
<h:inputText id="favoriteFood"
value="#{favorite.food}"
required="true">
</h:inputText>
... <!-- other required fields -->
<h:commandButton value="Save my favorites" action="#{favorite.save}"/>
<h:commandButton value="Go home" action="index"/>
</h:form>
如果您点击“回家”按钮,并将必填的输入字段留空,您将会看到如图图 1-11 所示的验证错误信息:
图 1-11 。由于没有“立即”为真而导致的验证错误
这是因为“回家”命令按钮会提交触发 JSF 生命周期的帖子,并且由于输入字段上的验证,“回家”操作无法完成。
JSF 框架提供了“立即”属性,允许跳过 JSF 生命周期的转换和验证。“immediate”属性实际上允许在“应用请求值”阶段执行动作事件。将 immediate 属性设置为 true 可以解决这个问题,如下所示:
<h:form id="favForm">
...
<h:inputText id="favoriteFood"
value="#{favorite.food}"
required="true">
</h:inputText>
... <!-- other required fields -->
<h:commandButton value="Save my favorites" action="#{favorite.save}"/>
<h:commandButton value="Go home" action="index" immediate="true"/>
</h:form>
注<h:链接>和< h:按钮>是从 JSF 2.0 开始引入的新部件;它们可以用于使用 outcome 属性实现到目标页面的 GET 导航(您已经在第一个应用中看到了一个< h:link >的例子)。因此,可以直接使用这些新组件来代替命令按钮和命令链接**,并将 immediate 属性设置为 true** 来进行导航,而无需执行转换和验证。
添加到 UICommand 组件中,immediate 属性可以应用于 EditableValueHolder 组件(如输入文本)。如果 EditableValueHolder 组件的 immediate 属性设置为 true,则 EditableValueHolder 组件的转换和验证将在“应用请求值”阶段(在“流程验证”阶段之前)执行。
注意 ValueHolder 组件是那些具有 value 属性如(标签和输出文本)的组件,它们实现 ValueHolder 接口。EditableValueHolder 组件是 ValueHolder 组件的一个子类型,可以由用户编辑其值,如(输入文本)。ActionSource 组件是那些可以进行诸如(命令按钮和命令链接)之类的操作的组件,它们实现 ActionSource 接口(或 JSF 1.2 以后的 ActionSource2)。
摘要
读完这一章,你就知道什么是 JSF,你也看到了 JSF 框架是如何随着时间的推移而演变的。您了解 JSF 架构,并学习了如何开发 JSF 应用,该应用涵盖了 JSF 世界中许多有趣的主题(基本 UI 组件、托管 beans、表达式语言、模板、资源包和验证)。最后,您知道了 JSF 请求处理生命周期在幕后是如何工作的。在接下来的章节中,本章中提到的所有 JSF 主题和其他高级主题都将有更详细的说明。
二、引擎盖下的 JSF——第一部分
本章阐述了 JSF 框架中的重要主题。在本章中,您将详细了解 JSF 管理的 beans 和表达式语言(EL)。您还将了解一些 JSF 导航。最后,您将了解如何在您的 JSF web 应用中利用 JSF 异常处理机制来增强应用的错误处理能力。
被管理的 bean
JSF 托管 bean 只是一个 POJO(普通旧 Java 对象),它符合 JavaBeans 命名约定,可以从 JSF 应用(页面和其他托管 bean)中访问。它被称为 managed ,因为它是由 JSF 框架管理的,当 JSF 应用需要使用它时,它会以一种懒惰的方式为您实例化 bean 类。下一节将详细介绍如何声明受管 bean,如何初始化受管 bean,如何管理不同受管 bean 之间的依赖关系,如何访问受管 bean,以及最后如何利用@Named 和@inject 注释来处理 JSF POJO 模型。
声明受管 Beans
在第一章中,我们在第一个应用中有一个托管 beans 用法的例子。清单 2-1 展示了用户管理的 bean。
清单 2-1。 用户托管 Bean
package com.jsfprohtml5.firstapplication.model;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class User implements Serializable {
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
用户管理的 bean 是一个 Java bean 类,有两个用于 name 和 password 属性的 setters 和 getters。@ManagedBean 注释用于将用户类注册为 JSF 管理的 Bean。@ManagedBean 注释有一个可选的 name 属性,它描述了将在 JSF 表达式中使用的受管 Bean 的名称。在用户管理的 bean 中,name 属性被省略;这意味着受管 bean 名称将与第一个字符为小写的类名相同,也就是说,它将在像#{user}这样的 JSF 表达式中使用。
一个 JSF 管理的 bean 必须有一个与之相关联的作用域,以控制它的生命周期。范围可以是:
- 请求范围(@RequestScoped),这意味着 bean 将被实例化,并且只要 HTTP 请求是活动的,它就将是活动的。
- 视图范围(@ViewScoped),这意味着只要用户停留在同一个视图中,bean 就会被实例化,并且是活动的。
- 会话范围(@SessionScoped),这意味着 bean 将被实例化,并且只要用户的 HTTP 会话是活动的,它就将是活动的。
- 应用范围(@ApplicationScoped),这意味着 bean 将被实例化,并将在应用的整个生命周期中保持活动状态。
- None scope (@NoneScoped),这意味着 bean 不会被实例化,也不会作为独立的实体存储在任何作用域中。无作用域的受管 bean 可以由另一个受管 bean 使用和实例化;在这种情况下,无作用域 bean 将具有实例化它的调用方受管 bean 的作用域(即,只要它们的调用方受管 bean 是活动的,无作用域受管 bean 就将是活动的)。在“管理受管 beans 的依赖性”一节中,您将看到这种情况的一个例子。
注在 JSF 2.2 中,有一个新的作用域叫做“流作用域”(@FlowScoped)。这个流程将在下一章详细说明。
除了注释之外,每个 JSF 管理的 bean 也可以在 faces-config . XML(JSF 配置文件)中注册。清单 2-2 展示了如何在 faces-config.xml 文件中定义用户管理的 bean,而不是使用注释。
***清单 2-2。***faces-config . XML 文件中的用户托管 Bean 定义
<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>com.jsfprohtml5.firstapplication.model.User</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
正在初始化受管 Beans
JSF 管理的 beans 可以从 faces 配置文件或使用注释进行初始化。清单 2-3 显示了如何从 faces 配置文件中用值“anonymous”初始化用户管理的 bean 的 name 属性。
清单 2-3。 初始化 faces-config.xml 中的 Name 属性
<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>com.jsfprohtml5.firstapplication.model.User</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>name</property-name>
<value>anonymous</value>
</managed-property>
</managed-bean>
元素可以用来初始化被管理的 bean 属性。它主要有两个子元素:
- 元素,它包括受管 bean 属性的名称。
- 元素,它包括受管 bean 属性的初始值。
初始化受管 bean 属性的另一种方法是使用注释。清单 2-4 展示了如何使用@ManagedProperty 注释用值“anonymous”初始化用户管理的 bean 的 name 属性。
清单 2-4。 使用@ManagedProperty 注释初始化 Name 属性
@ManagedBean
@SessionScoped
public class User implements Serializable {
@ManagedProperty(value="anonymous")
private String name;
private String password;
// The setters and the getters ...
}
使用注释,@ManagedProperty 注释用于使用 value 属性初始化用户管理的 bean 的 name 属性,其值为“anonymous”。
值得注意的是,JSF 管理的 beans 可以很好地与不同的 Java EE 注释一起工作。有两个与 JSF 受管 bean 生命周期相关的 Java EE 注释,可用于初始化和反初始化受管 bean:
- @PostConstruct
- @PreDestroy
清单 2-5 显示了用户管理 bean 中@PostConstruct 和@PostDestroy 注释的例子。
清单 2-5。@ post construct 和@ PostDestroy 注释
import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class User implements Serializable {
@ManagedProperty(value="anonymous")
private String name;
private String password;
// The setters and the getters ...
@PostConstruct
private void initialize() {
System.out.println("Bean is initialized with the following user name: " + name);
}
@PreDestroy
private void cleanUp() {
System.out.println("You can do the cleanup here");
}
}
当用户管理的 bean 被实例化和初始化时,调用@PostConstruct 方法。这意味着当初始化方法被调用时,输出控制台将显示以下消息:
"Bean is initialized with the following user name: anonymous"
注意正如你注意到的,@PostConstruct 和@PreDestroy 方法的返回类型是无效的,并且不带任何参数。这些方法也可以是私有的、公共的、受保护的或打包的。
在销毁用户管理的 bean 之前,调用@PreDestroy 方法。在这个方法中,您可以将受管 bean 所需的清理和反初始化代码。
还有许多其他与数据访问相关的 Java EE 注释,可以很好地与 JSF 管理的 beans 一起使用,例如@Resource、@PersistenceUnit、@PersistenceContext、@EJB 和@WebServiceRef。我们将在接下来的章节中讨论大部分注释。
除了初始化简单属性的能力之外,您还可以初始化受管 bean 的复杂类型,比如列表和映射。清单 2-6 为用户管理的 bean 引入了两个新属性:收藏夹体育列表和发言人语言映射。
清单 2-6。 用户用两个新属性管理 Bean
import java.util.List;
import java.util.Map;
//...
public class User implements Serializable {
private String name;
private String password;
private List<String> favoriteSports;
private Map<String, String> spokenLanguages;
// setters and getters ...
}
为了用一些初始值初始化 favoriteSports 列表,可以在元素中使用元素,如清单 2-7 所示。
清单 2-7。Faces 配置文件中 List 属性的 初始化
<managed-property>
<property-name>favoriteSports</property-name>
<list-entries>
<value>Handball</value>
<value>Football</value>
<value>Basketball</value>
</list-entries>
</managed-property>
在 XHTML 页面中,可以使用< ui:repeat >迭代 favoriteSportlist,如清单 2-8 所示。
清单 2-8。 在 XHTML 页面中显示喜爱的运动项目
<b>You have the following initial list of favorite sports:</b>
<ul>
<ui:repeat value="#{user.favoriteSport}" var="sport">
<li>#{sport}</li>
</ui:repeat>
</ul>
如果要在收藏夹体育列表中显示特定项目,可以使用[]运算符。例如,下面的#{user.favoriteSport[0]}将显示 favoriteSport 数组中的第一项,即“手球”。为了用一些初始值初始化 spokenLanguages 映射,可以在元素中使用元素,如清单 2-9 所示。
清单 2-9。 初始化 Faces 配置文件中的地图属性
<managed-property>
<property-name>spokenLanguages</property-name>
<map-entries>
<map-entry>
<key>EN</key>
<value>English</value>
</map-entry>
<map-entry>
<key>FR</key>
<value>French</value>
</map-entry>
</map-entries>
</managed-property>
在 XHTML 页面中,您可以使用#{user.spokenLanguages}来显示 spokenLanguages。如果想要显示特定的映射条目值,也可以使用[]操作符从它的键中获得它。例如,使用#{user.spokenLanguages['EN']}将显示“英语”。
注意对于<列表条目>和<地图条目>元素没有等价的 JSF 注释,所以初始化地图和列表的唯一方法是使用 faces 配置文件。
管理受管 Beans 依赖性
JSF 支持受管 bean 的 IoC(控制反转),这意味着受管 bean 可以在运行时耦合,而不需要从应用代码处理这种耦合。让我们看看如何在我们的 JSF 应用中利用 IoC。清单 2-10 为用户管理的 bean 引入了一个新的属性——职业属性。
清单 2-10。 用户管理 Bean 中的新职业属性
public class User implements Serializable {
...
private Profession profession;
...
public Profession getProfession() {
return profession;
}
public void setProfession(Profession profession) {
this.profession = profession;
}
}
清单 2-11 显示了职业管理 bean 类的属性。
清单 2-11。 职业管理豆
public class Profession implements Serializable {
private String title;
private String industry;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getIndustry() {
return industry;
}
public void setIndustry(String industry) {
this.industry = industry;
}
}
可以使用 JSF 配置文件或 JSF 注释将专业管理的 bean 实例与用户管理的 bean 实例关联起来。清单 2-12 显示了 JSF 配置文件的一部分,它配置了在实例化时被注入到用户管理 bean 中的专业管理 bean。
清单 2-12。 配置要注入到用户实例中的职业实例
<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>com.jsfprohtml5.firstapplication.model.User</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
< managed-property>
<property-name>profession</property-name>
<value>#{profession}</value>
</managed-property>
...
</managed-bean>
<managed-bean>
<managed-bean-name>profession</managed-bean-name>
<managed-bean-class>com.jsfprohtml5.firstapplication.model.Profession</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>title</property-name>
<value>Software Engineer</value>
</managed-property>
<managed-property>
<property-name>industry</property-name>
<value>IT</value>
</managed-property>
</managed-bean>
Profession 受管 bean 在 none 作用域中声明,以便拥有实例化它的调用者受管 bean 的作用域(在本例中是用户受管 bean)。在用户管理的 bean 中,为 profession 属性定义了属性初始化,并且通过使用元素将#{profession}表达式用作 profession 属性的值。
完成此配置后,当用户管理的 bean 被 JSF 框架实例化时,专业管理的 bean 将被实例化并设置在会话范围中。这意味着# { user . professional . title }表达式将返回职业头衔的初始值,即“软件工程师”。
从 Java 代码访问受管 Beans
了解如何从 Java 代码中访问受管 beans 是很重要的。如果您想要从另一个不直接引用 bean1 的受管 bean(例如 bean2)获取特定受管 bean(例如 bean1)的信息,这将非常有用。为了从 Java 代码中访问受管 beans,您需要使用 ValueExpression 类。清单 2-13 展示了如何从 Java 代码中获取用户管理的 bean 的信息。
清单 2-13。 从 Java 代码中获取用户管理的 Bean 信息
FacesContext context = FacesContext.getCurrentInstance();
Application application = context.getApplication();
ELContext elContext = context.getELContext();
ExpressionFactory expressionFactory = application.getExpressionFactory();
ValueExpression valueExpression = expressionFactory.createValueExpression
(elContext, "#{user}", User.class);
User user = (User) valueExpression.getValue(elContext);
为了检索用户管理的 bean 信息,使用 ExpressionFactory 类的 createValueExpression API 创建一个 ValueExpression 对象。createValueExpression API 采用以下参数:
- ELContext,指用于解析表达式的 EL 上下文。
- 要分析的表达式字符串。
- 在表达式求值之后,表达式的结果将被强制转换为的类型。
使用 ValueExpression 对象,可以使用 getValue(ELContext) API 获取表达式的值,还可以使用 ValueExpression 对象在运行时使用 setValue(ELContext,value) API 为表达式设置值。
@Named 和@inject 批注
在未来的 JSF 规范中,@ManagedBean 将被弃用,因此建议使用@Named CDI(上下文和依赖注入)批注,而不是@ManagedBean。您应该知道,@ManagedBean 是由 JSF 框架管理的,而@Named 批注不是由 JSF 框架管理的。@Named 注释由支持 CDI 的 JEE 应用服务器管理。CDI 优于 JSF 依赖注入的一个优点是,为了使用 CDI @inject 注释注入实例,该实例不需要用任何特定的注释进行注释,而在 JSF 中,@ManagedProperty 要求要注入的 bean 用@ManagedBean 注释进行注释。
让我们看看如何为用户管理的 bean 使用@Named 注释,而不是@ManagedBean。清单 2-14 显示了使用@Named 注释的用户管理 bean 的修改版本。
清单 2-14。@命名用户托管 Bean
import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;
@Named
@SessionScoped
public class User implements Serializable {
private String name;
private String password;
@Inject
private Profession profession;
// ...
public Profession getProfession() {
return profession;
}
public void setProfession(Profession profession) {
this.profession = profession;
}
//...
}
为了将 CDI 用于我们的用户管理 bean,我们需要注意一些应该进行的更改:
- @Named 批注替换了@ManagedBean 批注。
- 我们使用了 javax . enterprise . context . session scope,而不是 javax . faces . bean . session scope(在 CDI 中,不能使用 javax.faces.bean.xxx 包来指定 bean 作用域;而是要用 javax.enterprise.context.xxx 包)。
- 我们使用@inject 注释在用户管理的 bean 实例中注入专业管理的 bean 实例,而不是使用@ManagedProperty 注释。
注意使用 CDI 时,需要在 WEB 应用的 WEB-INF 文件夹下创建一个空的 beans.xml。
表达语言
有两套不同的El:
- JSP EL。
- JSF 埃尔。
这两种语言有许多不同之处。JSP EL 表达式 以美元符号($)开始,然后是左花括号({),然后是 JSP 表达式,最后以右花括号(})结束。当呈现页面时,JSP EL 立即执行(在页面编译时)。另一方面,JSF EL 表达式以散列(#)开始,然后是左花括号({),然后是 JSF 表达式,最后以右花括号(})结束。JSF EL 表达式的执行被延迟,这意味着表达式的计算基于 JSF 生命周期上下文。
更具体地说,JSF 延迟表达式在页面回发和页面初始呈现期间可用,这意味着 JSF 延迟表达式可以在 JSF 框架的请求处理和响应呈现阶段进行计算,而 JSP 即时表达式仅在页面呈现期间(而不是页面回发)可用,因为它们是在第一次呈现页面时计算的。这意味着 JSP EL 总是只读的值表达式,而 JSF EL 可以在读写模式下工作。
统一表达语言
多亏了 JSP 2.1,它是 Java EE 5 的一部分,这个不匹配的问题通过一个统一的 EL 解决了,这个 EL 本质上代表了 JSP 和 JSF EL 的联合。统一 EL 具有以下特征 :
- 表达式的延迟求值。
- 表达式可以在读写模式下工作。
- JSTL 标签可以处理延迟表达式。
清单 2-15 展示了一个例子,展示了< c:forEach > JSTL 标签如何与 JSF 延迟表达式#一起工作..}.
清单 2-15。 带有< c:forEach >标签的 JSF 延迟表达式
<b>You have the following initial list of favorite sports:</b>
<ul>
<c:forEach items="#{user.favoriteSport}" var="sport">
<li>#{sport}</li>
</c:forEach>
</ul>
如清单所示,这段代码可以替代清单 2-16 中的代码,它是第一个应用中 welcome.xhtml 页面的一部分。
清单 2-16。 原 JSF 延期表达式用< ui:重复>标签
<b>You have the following initial list of favorite sports:</b>
<ul>
<ui:repeat value="#{user.favoriteSport}" var="sport">
<li>#{sport}</li>
</ui:repeat>
</ul>
除此之外,您还可以选择将其他 JSTL 标签与 JSF 延迟表达式组合,如清单 2-17 中的所示。
清单 2-17。
<b>You have the following initial list of favorite sports:</b>
<ul>
<c:forEach items="#{user.favoriteSport}" var="sport">
<c:choose>
<c:when test="#{sport == 'Football'}">
<li><b><u>Popular in Africa:</u></b> #{sport}</li>
</c:when>
<c:otherwise>
<li>#{sport}</li>
</c:otherwise>
</c:choose>
</c:forEach>
</ul>
在示例中,以下 JSTL 标记与 JSF 延迟表达式一起使用:
- 用于执行迭代。
- 用于定义一组互斥的选择。它类似于 Java switch 语句。
- ,类似于 Java 的 case 语句。
- ,类似 Java 默认语句。
在这个例子中,在#{user.favoriteSport}列表上迭代,当前运动项目由#{sport}表达式表示。< c:当>标签检查当前的 sport #{sport}值是否为‘Football’时,下面的句子“Popular in Africa:”被突出显示并加下划线,并附加到列表项的开头。如果当前运动不等于“足球”,则使用< c:否则> JSTL 元素正常显示#{sport}。图 2-1 显示了列表项的显示方式。
图 2-1 。高亮下划线的列表项
请注意,为了与 JSTL 合作,您需要将 JSTL·URI 包含在声明中,如下面的粗体文本所示:
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="[`java.sun.com/jsp/jstl/core`](http://java.sun.com/jsp/jstl/core)">
在《JSF 埃尔》中,有两种表达方式:
- 价值表达。
- 方法表达式。
在接下来的部分中,我们将深入研究值和方法表达式的细节。
值表达式
值表达式可用于设置受管 bean 属性的值,或显示受管 bean 属性或任何动态评估对象的值。到目前为止,我们已经看到了几个值表达式的例子。例如,firstApplication 的 welcome.xhtml 页面中的以下#{user.name}表达式表示显示用户管理的 bean 的名称的值表达式:
Welcome, #{user.name}!
而 firstApplication 的登录页面(index.xhtml)中的同一个#{user.name}表达式表示设置用户管理的 bean 的名称的值表达式:
<h:inputText id="userName" value="#{user.name}" required="true"/>
注从第一章我们知道,EditableValueHolder(比如 inputText 和 selectOneMenu)的值和 JSF EL 表达式(模型)之间的绑定是在 JSF 请求处理生命周期的更新模型值阶段执行的。
值表达式也可以访问隐式对象。表 2-1 显示了根据 JSF 规范可用于 JSF EL 的 JSF 隐式对象。
表 2-1 。JSF EL 可用隐式对象
|
隐含对象
|
表示
|
| --- | --- |
| Application | ServletContext如果您正在处理 Servlet 容器,或者PortletContext如果您正在处理 Portlet 容器。 |
| applicationScope | 应用范围的映射(应用属性映射)。 |
| Cookie | HTTP cookie 映射。 |
| facesContext | 当前FacesContext实例。 |
| Component | UIComponent实例(将在第六章 - 第八章的“创建自定义 JSF 组件”中详细说明)。 |
| Cc | 父复合组件(将在第六章 - 和第八章中的“创建定制 JSF 组件”中详述)。 |
| Flash | flash 范围图(将在 flash 范围部分详细说明)。 |
| Header | 请求 HTTP 头映射。 |
| headerValues | 请求 HTTP 头映射。然而,map 的每个值都是一个代表头键值的String[]。 |
| initParam | 初始化应用的参数映射。 |
| Param | 请求查询参数映射。 |
| paramValues | 请求查询参数映射。但是,map 的每个值都是一个代表参数键值的String[]。 |
| Request | ServletRequest如果您正在处理 Servlet 容器,或者PortletRequest如果您正在处理 Portlet 容器。 |
| requestScope | 请求范围映射(请求属性映射)。 |
| Resource | 资源参考。 |
| Session | HttpSession如果您正在处理 Servlet 容器,或者PortletSession如果您正在处理 Portlet 容器。 |
| sessionScope | 会话范围映射(会话属性映射)。 |
| View | UIViewRoot为当前视图。 |
| viewScope | 视图范围映射(视图属性映射)。 |
清单 2-18 中的示例显示了如何使用#{header}值表达式显示请求头信息。
清单 2-18。 使用表头隐式对象显示请求表头信息
<table border="1">
<th>Key</th>
<th>Value</th>
<c:forEach items="#{header}" var="header">
<tr>
<td>#{header.key}</td>
<td>#{header.value}</td>
</tr>
</c:forEach>
</table>
#{header}表达式返回一个表示 HTTP 标头的映射,并使用 JSTL 标签显示标头键和值。图 2-2 显示了清单 2-18 的输出。
图 2-2 。HTTP 头信息
注意也可以用方括号([ ])代替(。)放在值表达式中。除了访问受管 bean 属性之外,方括号还可以用于访问映射和数组,正如我们在“受管 bean”一节的示例中看到的那样。
方法表达式
方法表达式可用于执行受管 beans 的公共非静态方法。方法表达式可以从
- ActionSource(或 ActionSource2)组件的 action 和 actionListener 属性,如(commandButton 和 commandLink)。
- EditableValueHolder 组件的 valueChangeListener 属性,如(inputText 和 selectOneMenu)。
- 标签的 beforePhase 和 afterPhase 属性。
在第一个应用中,我们可以修改 login commandButton 的 action 属性,用一个方法表达式代替登录页面 (index.xhtml)中的“welcome”字符串,如清单 2-19 中的粗体行所示。
清单 2-19。 修改登录页面
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html">
<ui:composition template="/WEB-INF/templates/simple.xhtml">
<ui:define name="title">
#{bundle['application.loginpage.title']}
</ui:define>
<ui:define name="content">
...
<h:commandButton value="#{bundle['application.login']}" action="#{user.login}"/>
<br/><br/>
</h:form>
</ui:define>
</ui:composition>
</html>
用户管理 bean 中的 login()方法很简单,如清单 2-20 所示。
清单 2-20。 用户管理 Bean 中的登录动作方法
public class User implements Serializable {
...
public String login() {
return "welcome";
}
...
}
如清单 2-20 所示,action 属性的方法有如下签名:
- 返回用于确定导航的字符串(结果)。
- 不需要争论。
此外,如果使用 EditableValueHolders,可以从 valueChangeListener 属性调用方法表达式。让我们看一个例子来解释这一点。清单 2-21 显示了饮料页面,该页面包含一个 selectOneMenu 组件,该组件包含一个饮料列表和一个显示所选饮料价格的输出文本。
清单 2-21。 饮料页面
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Beverages</title>
</h:head>
<h:body>
<h:form>
<h:outputText value="Select a beverage: " />
<h:selectOneMenu value="#{beverage.name}"
valueChangeListener="#{beverage.beverageSelected}"
onchange="submit()">
<f:selectItem itemLabel="---" itemValue="---"/>
<f:selectItem itemLabel="Tea" itemValue="tea" />
<f:selectItem itemLabel="Coffee" itemValue="coffee" />
<f:selectItem itemLabel="Coca-Cola" itemValue="cocacola" />
</h:selectOneMenu> <br/><br/>
<h:outputText value="You will have to pay: #{beverage.price} USD"
rendered="#{beverage.price ne null}"/>
</h:form>
</h:body>
</html>
selectOneMenu 组件呈现一个带有饮料项目列表(茶、咖啡、可口可乐)的组合框;当用户选择一种可用的饮料时,提交表单并执行 valueChangeListener 方法(饮料管理 bean 的 beverageSelected 方法)来计算所选饮料的价格。您可能会注意到,使用 rendered 属性时,如果没有饮料价格,则不会呈现 outputText。
注 ne 代表(不等于),它也相当于!=由 EL 支持。EL 还支持以下关系运算符:
- eq 代表(等于),它相当于==。
- lt 代表(小于),它相当于
- le 代表(小于或等于),它相当于< =。
- gt 代表(大于),它相当于>。
- ge 代表(大于或等于),它相当于> =。
清单 2-22 显示了饮料管理豆。
清单 2-22。 饮料管理豆
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.event.ValueChangeEvent;
@ManagedBean
@RequestScoped
public class Beverage {
private String name;
private Double price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public void beverageSelected(ValueChangeEvent event) {
String selectedBeverage = event.getNewValue().toString();
if ("tea".equals(selectedBeverage)) {
price = 2.0;
} else if ("coffee".equals(selectedBeverage)) {
price = 2.5;
} else if ("cocacola".equals(selectedBeverage)) {
price = 3.0;
}
}
}
如清单 2-22 所示,valueChangeListener 属性的方法具有以下签名:
- 返回 void。
- 接受作为 ValueChangeEvent 对象的单个参数。
ValueChangeEvent 对象保存旧值和新选择的值。可以使用 getNewValue()检索新选择的值。在 beverageSelected 方法中,检索新的选定值,并根据选定的饮料项目设置价格。图 2-3 显示了饮料示例输出。
图 2-3 。饮料示例输出
到目前为止,我们已经看到了 JSF 方法表达式的两个例子;在接下来的章节中,我们会看到许多其他的例子。
注意您不必提交整个页面来执行 EditableValueHolder 的 valueChangeListener 方法;否则,您可以使用< f:ajax >标签以 ajax 化的方式调用 valueChangeListener 方法(第五章将详细讨论< f:ajax >标签)。
需要注意的是,如果您正在使用包含 Unified EL 2.1 的 Java EE 6 容器(或更高版本),那么您可以使用参数调用任意方法。让我们看一个例子来解释这一点。清单 2-23 显示了一个定制的数学管理 bean 的 calculateAverage 方法。
清单 2-23。 自定义数学管理豆的计算平均法
@ManagedBean
@RequestScoped
public class Maths {
public Double calculateAverage (Double number1, Double number2) {
return (number1 + number2) / 2;
}
}
在 XHTML 页面中,可以使用#{maths.calculateAverage(10.5,12.3)}调用 calculateAverage 方法,输出 11.4。
闪光灯范围
闪光灯范围是自 JSF 2.0 以来引入的新范围。flash scope 概念的灵感来自 RoR (Ruby on Rails)。flash 作用域意味着“任何放置在 flash 作用域中的内容都将暴露给同一用户会话遇到的下一个视图,然后被清除。”换句话说,flash 范围内的对象对于同一浏览器窗口的下一个请求来说只可用。
如果只想为下一个请求保留短时间的信息,无论下一个请求是由 HTTP 重定向、JSF 窗体回发还是新页面的 HTTP GET 产生的,flash 范围都很有用。让我们看一个例子来理解 flash 的作用域。
调查应用是一个简单的应用,由三个页面组成:
- input.xhtml,它要求用户提供一些对调查有用的信息。
- confirm.xhtml,显示用户输入的信息,并要求用户确认或修改。
- final.xhtml,这是一个“感谢”页面。
清单 2-24 显示了 input.xhtml 页面。
***清单 2-24。***input . XHTML 页面
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<ui:composition template="/WEB-INF/templates/simple.xhtml">
<ui:define name="title">
#{bundle['survey.input.page']}
</ui:define>
<ui:define name="content">
<h:form>
<h:panelGrid columns="3">
<h:outputText value="#{bundle['survey.user.name']} "></h:outputText>
<h:inputText id="userName"
value="#{flash.userName}"
required="true">
</h:inputText>
<h:message for="userName" styleClass="errorMessage"/>
<h:outputText value="#{bundle['survey.user.age']}"></h:outputText>
<h:inputText id="age"
value="#{flash.age}"
required="true">
<f:convertNumber />
</h:inputText>
<h:message for="age" styleClass="errorMessage"/>
<h:outputText value="#{bundle['survey.user.sex']}"></h:outputText>
<h:selectOneMenu id="sex"
value="#{flash.sex}">
<f:selectItem itemLabel="Male" itemValue="male"/>
<f:selectItem itemLabel="Female" itemValue="female"/>
</h:selectOneMenu>
<h:message for="sex" styleClass="errorMessage"/>
<h:outputText value="#{bundle['survey.user.monthyIncome']}"></h:outputText>
<h:inputText id="monthlyIncome"
value="#{flash.monthlyIncome}"
required="true">
<f:convertNumber />
</h:inputText>
<h:message for="monthlyIncome" styleClass="errorMessage"/>
<h:outputText value="#{bundle['survey.user.yearlyTravelAbroad']}"></h:outputText>
<h:inputText id="yearlyTravelsAbroad"
value="#{flash.travelsAbroad}"
required="true">
<f:convertNumber />
</h:inputText>
<h:message for="yearlyTravelsAbroad" styleClass="errorMessage"/>
<h:outputText value="#{bundle['survey.user.oftenTravelBy']}"></h:outputText>
<h:selectOneMenu id="travelBy"
value="#{flash.travelBy}">
<f:selectItem itemLabel="#{bundle['survey.travelby.plane']}" itemValue="plane"/>
<f:selectItem itemLabel="#{bundle['survey.travelby.car']}" itemValue="car"/>
</h:selectOneMenu>
<h:message for="travelBy" styleClass="errorMessage"/>
</h:panelGrid>
<h:commandButton value="#{bundle['survey.actions.next']}"
action="confirm?faces-redirect=true"/>
<br/><br/>
</h:form>
</ui:define>
</ui:composition>
</html>
在 input.xhtml 页面中,要求用户输入姓名、年龄、性别、月收入、每年出国的次数以及用户旅行的频率。本页中最值得注意的是粗体行,其中#{flash} EL 用于在 EditableValueHolders 和 flash 范围之间进行值绑定。当表单数据有效,用户点击“下一步”命令按钮时,EditableValueHolder 值和 flash 属性之间的绑定完成,页面重定向到 confirm.xhtml 页面。清单 2-25 显示了 confirm.xhtml 页面的初始代码。
清单 2-25。??【confirm . XHTML 页面的初始代码
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html">
<ui:composition template="/WEB-INF/templates/simple.xhtml">
<ui:define name="title">
#{bundle['survey.confirm.page']}
</ui:define>
<ui:define name="content">
<h:form>
<h:outputText value="#{bundle['survey.information.confirm']}"/>
<h:panelGrid columns="2">
<h:outputText value="#{bundle['survey.user.name']}"/>
<h:outputText value="#{flash.userName}"/>
<h:outputText value="#{bundle['survey.user.age']}"/>
<h:outputText value="#{flash.age}"/>
<h:outputText value="#{bundle['survey.user.sex']}"/>
<h:outputText value="#{flash.sex}"/>
<h:outputText value="#{bundle['survey.user.monthyIncome']}"/>
<h:outputText value="#{flash.monthlyIncome}"/>
<h:outputText value="#{bundle['survey.user.yearlyTravelAbroad']}"/>
<h:outputText value="#{flash.travelsAbroad}"/>
<h:outputText value="#{bundle['survey.user.oftenTravelBy']}"></h:outputText>
<h:outputText value="#{flash.travelBy}"/>
</h:panelGrid>
<h:commandButton value="#{bundle['survey.actions.save']}"
action="#{survey.save}"/>
<h:commandButton value="#{bundle['survey.actions.modify']}"
action="input?faces-redirect=true"/>
<br/><br/>
</h:form>
</ui:define>
</ui:composition>
</html>
确认页面使用#{flash.attributeName}显示所有输入的调查信息。在这个页面中,用户可以保存输入的信息并导航到 final.xhtml 页面,也可以修改 input.xhtml 页面中的信息。
但是,如果您单击“修改”命令按钮来修改输入的信息,您会发现 input.xhtml 字段为空(换句话说,flash 信息丢失),这是合乎逻辑的,因为一旦呈现 confirm.xhtml 页面,flash 作用域的生命就结束了。为了解决这个问题,您只需使用 keep 关键字来为下一个请求保留 flash 信息,如下所示:#{flash.keep.attributeName}。清单 2-26 显示了修改后的 confirm.xhtml 页面。
清单 2-26。 修改确认. xhtml 页面
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html">
<ui:composition template="/WEB-INF/templates/simple.xhtml">
<ui:define name="title">
#{bundle['survey.confirm.page']}
</ui:define>
<ui:define name="content">
<h:form>
<h:outputText value="#{bundle['survey.information.confirm']}"/>
<h:panelGrid columns="2">
<h:outputText value="#{bundle['survey.user.name']}"/>
<h:outputText value="#{flash.keep.userName}"/>
<h:outputText value="#{bundle['survey.user.age']}"/>
<h:outputText value="#{flash.keep.age}"/>
<h:outputText value="#{bundle['survey.user.sex']}"/>
<h:outputText value="#{flash.keep.sex}"/>
<h:outputText value="#{bundle['survey.user.monthyIncome']}"/>
<h:outputText value="#{flash.keep.monthlyIncome}"/>
<h:outputText value="#{bundle['survey.user.yearlyTravelAbroad']}"/>
<h:outputText value="#{flash.keep.travelsAbroad}"/>
<h:outputText value="#{bundle['survey.user.oftenTravelBy']}"></h:outputText>
<h:outputText value="#{flash.keep.travelBy}"/>
</h:panelGrid>
<h:commandButton value="#{bundle['survey.actions.save']}"
action="#{survey.save}"/>
<h:commandButton value="#{bundle['survey.actions.modify']}"
action="input?faces-redirect=true"/>
<br/><br/>
</h:form>
</ui:define>
</ui:composition>
</html>
使用#{flash.keep.attributeName}将确保为下一个请求保留闪存属性。
注意你不能为了保留 HTTP 重定向后的信息而使用请求作用域,因为 HTTP 重定向创建了一个新的请求,这意味着当前的请求信息将会丢失。
清单 2-27 显示了调查管理 bean 的代码,它包括一个单独的方法 save(),该方法检索 flash 属性,然后在控制台中打印它们。
清单 2-27。 调查托管豆
package com.jsfprohtml5.survey.model;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.context.FacesContext;
import javax.faces.context.Flash;
@ManagedBean
public class Survey implements Serializable {
public String save() {
Flash flash = FacesContext.getCurrentInstance().getExternalContext().getFlash();
// Read the information from the flash
String userName = (String) flash.get("userName");
Number age = (Number) flash.get("age");
String sex = (String) flash.get("sex");
Number monthlyIncome = (Number) flash.get("monthlyIncome");
Number travelsAbroad = (Number) flash.get("travelsAbroad");
String travelBy = (String) flash.get("travelBy");
System.out.println("Flash information are: \n{\n" +
"Name: " + userName + ", \n" +
"Age: " + age + ", \n" +
"Sex: " + sex + ", \n" +
"monthlyIncome: " + monthlyIncome + ", \n" +
"travelsAbroad: " + travelsAbroad + ", \n" +
"travelBy: " + travelBy + "\n" +
"}");
// Save the information in the survey database ...
// ...
return "final?faces-redirect=true";
}
}
可以使用 ExternalContext 的 getFlash() API 获取 Flash 对象。之后,您可以使用 flash 对象的 get()方法检索 Flash 属性。最后,您可以对检索到的 flash 属性做任何您想做的事情(例如将它们保存在结构化数据库中,或者使用它们来启动工作流...).
注意调查应用的完整源代码可在本书网站www.apress.com/97814302501…获得(附在第二章源代码 zip 文件中)。
航行
在 JSF 框架中,有两种类型的导航:
- 隐式导航。
- 基于规则的导航。
接下来的部分举例说明了这两种导航类型,以及何时使用它们。
隐式导航
在第一个应用中,我们已经有了两个页面导航的例子。第一个是关于,它从 index.xhtml 页面导航到 welcome.xhtml 页面:
<h:commandButton value="#{bundle['application.login']}" action="welcome"/>
第二个例子是关于,它从 welcome.xhtml 页面导航到 index.xhtml 页面:
<h:link value="#{bundle['application.welcomepage.return']}" outcome="index"/>
上面提到的两个例子代表了第一种类型的 JSF 导航,称为“隐式导航隐式导航是在 JSF 框架的 2.0 版本中引入的。之所以称之为隐式,是因为使用它,您不必在 JSF 配置文件(faces-config.xml)中定义导航规则;您所需要做的就是在动作或结果属性中指定目标页面的相对路径(您不需要提到。xhtml 扩展,因为 JSF 导航系统会为您添加它并导航到目标页面)。
使用隐式导航,您还可以将 HTTP 重定向到目标页面,而不是将 HTTP 请求转发到目标页面(这是默认行为)。这可以通过使用 faces-redirect 参数来执行,如下所示:
<h:commandButton value="#{bundle['application.login']}" action="welcome?faces-redirect=true"/>
将 faces-redirect 参数设置为 true 会告诉 JSF 导航系统进行 HTTP 重定向,而不是 HTTP 请求转发。
JSF 隐式导航最重要的优点是它的简单性;然而,它的缺点之一是不灵活,当您在 JSF 应用中有一个复杂的导航时,您需要重命名其中一个目标导航页面。在这种情况下,您必须重新访问包含目标导航页面的所有页面,以便将旧页面名称更改为新页面名称。因此,建议将 JSF 隐式导航用于小型应用、原型开发或概念验证。
基于规则的导航
在基于规则的导航 中,导航规则在 Faces 配置文件(faces-config.xml)中定义。基于规则的导航由一组导航规则组成。每个导航规则可以有一个或多个导航案例。清单 2-28 显示了一个基于规则的导航示例。
清单 2-28。 基于规则的导航示例 1
<faces-config ...>
<navigation-rule>
<from-view-id>/index.xhtml</from-view-id>
<navigation-case>
<from-action>#{exampleBean.doAction}</from-action>
<from-outcome>success</from-outcome>
<to-view-id>/welcome.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-action>#{exampleBean.doAction}</from-action>
<from-outcome>fail</from-outcome>
<to-view-id>/invalid.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
如示例所示,导航规则可以包含以下元素:
- (可选),表示导航开始的视图。
- ,可以是导航规则内的 1 到 N。
导航案例可以包含以下元素:
- (可选),保存 EL 表达式,引用返回字符串(outcome)的动作方法。
- ,表示字符串文字结果。在存在元素的情况下,值与返回的结果进行比较,如果两个值匹配,则导航进行到。如果元素不存在,则将与动作源组件的动作属性进行比较,如果两个值匹配,则导航进行到(将在下一个导航示例中说明)。
- ,代表目标视图。
回头看示例,从 index.xhtml 页面开始,当执行#{exampleBean.doAction}操作方法并返回“success”结果时,第一个导航案例触发;在这种情况下,页面将被转发到 welcome.xhtml 页面。另一个导航案例在执行#{exampleBean.doAction}操作方法并返回“失败”结果时触发;在这种情况下,页面将被转发到 invalid.xhtml 页面。清单 2-29 显示了 ExampleBean 管理的 Bean 的重要部分。
清单 2-29。 比恩比恩
@ManagedBean
@RequestScoped
public class ExampleBean implements Serializable {
public String doAction() {
if (validateInformation()) {
return "success";
} else {
return "fail";
}
}
private boolean validateInformation() {
/* Some calls can be performed to the business services here ... */
}
}
如前所述,元素是可选的,这意味着 JSF 导航可以只通过指定元素来工作。清单 2-30 显示了两个没有<从动作>元素的导航案例。
清单 2-30。 基于规则的导航示例 2
<faces-config ...>
<navigation-rule>
<from-view-id>/index.xhtml</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/welcome.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>fail</from-outcome>
<to-view-id>/invalid.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
当省略元素时,字符串文字与动作源组件(commandButton 或 commandLink)的动作属性进行比较。这意味着,如果我们有任何 commandButton 或 commandLink 的 action 值(或计算表达式值)为“success ”,那么第一个导航案例将被触发,当它为“fail”时,第二个导航案例将被触发。
除了动作源组件之外,字符串还与和组件的结果属性进行比较。
注意JSF(隐式和基于规则的)导航可以使用
- 通过动作属性的和。
- 和通过结果属性。
action 和 outcome 属性都可以接受字符串文字或 EL 表达式,后者是指返回字符串(在 action 属性的情况下)或计算结果为字符串(在 outcome 属性的情况下)的 EL 表达式的方法。
元素也可以省略,如清单 2-31 中的所示。
清单 2-31。 基于规则的导航示例 3
<navigation-rule>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/welcome.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>fail</from-outcome>
<to-view-id>/invalid.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
当元素被省略时,这意味着导航用例将可用于应用的所有页面,这意味着对于清单 2-31 ,当任何页面中组件的动作或结果属性的返回结果与< from-outcome >值匹配时,相应的导航用例将被触发。
注意重要的是要知道导航案例在<导航规则>中的执行顺序。
JSF 导航的一个很好的特性是支持元素中的通配符。如果您想在 JSF 应用的一组页面上应用导航规则,这可能会很有用。清单 2-32 显示了一个例子。
清单 2-32。 基于规则的导航示例 4
<navigation-rule>
<from-view-id>/common/*</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/welcome.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>fail</from-outcome>
<to-view-id>/invalid.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
前面的例子意味着,通过使用/common/*模式,导航案例将只应用于 common 文件夹下的所有页面。
基于规则的导航的默认行为是将请求转发到目标页面;但是,也可以通过使用 HTTP 重定向来改变这种行为。清单 2-33 展示了如何使用 HTTP 重定向进行导航。
清单 2-33。 基于规则的导航示例 5
<navigation-rule>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/welcome.xhtml</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>fail</from-outcome>
<to-view-id>/invalid.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
如前面的清单所示,将导航案例行为从 HTTP 请求转发更改为 HTTP 重定向非常简单。使用中的元素,当匹配时,HTTP 重定向将应用于这个导航案例。
高级导航
我们想在 JSF 导航中介绍的最后一个特性是条件导航特性,它在处理复杂的导航情况时非常有用。为了理解这个特性,我们来看一个详细的例子。在这个例子中,用户输入(他/她的)每天的平均睡眠时间,以便知道(他/她的)睡眠时间是否是:
- 低于平均值(< 7 小时)。
- 正常(7 至 9 小时)。
- 高于平均值(> 9 小时)。
图 2-4 显示了示例流程。该流程由四页组成:
- xhtml 表示开始输入页面。在该页面中,用户输入(他/她)的睡眠时间,然后点击“检查我的睡眠时间”按钮进入下一页面。
- normal.xhtml 表示当用户输入 7 到 9 之间的数字时将显示的页面。
- xhtml 表示当用户输入大于 9 的数字时将显示的页面。
- xhtml 表示当用户输入小于 7 的数字时将显示的页面。
图 2-4 。该示例流程
清单 2-34 显示了开始输入页面的代码(input.xhtml)。
清单 2-34。 输入. xhtml 页面
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Sleeping hours checker</title>
</h:head>
<h:body>
<h:form>
<h1>Sleeping hours checker</h1>
<h:outputText value="Enter your sleeping hours: "/>
<h:inputText id="sleepingHours"
value="#{sleeping.hours}"
required="true">
<f:convertNumber integerOnly="true" maxIntegerDigits="2" />
</h:inputText>
<br/>
<h:commandButton action="proceed" value="Check my sleeping hours"/>
</h:form>
</h:body>
</html>
该表单包含一个输入文本 sleepingHours,这是必需的,通过使用转换器只接受两位数的整数。
注意从第一章可知,JSF 转换是关于将 HTTP 请求参数转换成相应的 Java 类型,以便消除开发人员为每个 web 应用实现该功能所需的开销。在本例中,< f:convertNumber/ >转换器向其父输入文本组件(sleepingHours)注册了一个数字转换器,通过将 integerOnly 属性设置为 true,只对输入文本值的整数部分进行格式化和解析。将 maxIntegerDigits 设置为 2 意味着要解析和格式化的整数的最大位数将为 2(转换和验证将在第三章的中详细介绍)。
当点击“检查我的睡眠时间”命令按钮时,它产生“继续”结果,根据用户输入,我们希望显示正确的结果页面。多亏了 JSF 条件导航,这可以从 Faces 配置文件中完成,如清单 2-35 所示。
***清单 2-35。***faces-config . XML 文件中的条件导航
<faces-config ...>
<navigation-rule>
<from-view-id>/input.xhtml</from-view-id>
<navigation-case>
<if>#{sleeping.hours le 9 and sleeping.hours ge 7}</if>
<from-outcome>proceed</from-outcome>
<to-view-id>/normal.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<if>#{sleeping.hours lt 7}</if>
<from-outcome>proceed</from-outcome>
<to-view-id>/below.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<if>#{sleeping.hours gt 9}</if>
<from-outcome>proceed</from-outcome>
<to-view-id>/above.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
元素是自 JSF 2.0 以来引入的新元素(条件导航特性最初来自 JBoss Seam,是 JSF 2.0 的一部分)。为了匹配包含一个元素的特定导航案例,需要将元素的内容评估为真。正如您在前面的清单中看到的,元素内容可以是一个完整的 JSF EL 表达式;在第一个导航案例中,元素检查#{sleeping.hours}值是否小于 9 且大于 7。如果第一个导航案例匹配,那么目标导航页面将是 normal.xhtml。第二个导航案例中的第二个元素检查#{sleeping.hours}值是否小于 7。如果第二个导航案例匹配,那么目标导航页面将是 below.xhtml。最后,第三个导航案例中的第三个元素检查#{sleeping.hours}值是否大于 9。如果第三个导航案例匹配,那么导航目标页面将是 above.xhtml。
提示重要的是要知道< to-view-id >可以接受 JSF EL 表达式,JSF 导航处理程序将对该表达式进行求值,以便获得视图标识符。
在后台,JSF 导航由 NavigationHandler 类处理。如果 NavigationHandler 类匹配一个导航事例,它将通过用新视图调用 facescontext . setviewroot(UIViewRoot)API 来更改当前视图。了解如何使用 NavigationHandler 类是很有用的,因为您可能需要在 JSF 应用的许多地方直接使用它(例如阶段侦听器和异常处理程序[在“异常处理”一节中有一个这样的示例])。清单 2-36 显示了一个使用 NavigationHandler 的例子。
清单 2-36。 使用 NavigationHandler 类的例子
FacesContext context = FacesContext.getCurrentInstance();
NavigationHandler navigationHandler = context.getApplication().getNavigationHandler();
navigationHandler.handleNavigation(context, "#{myBean.handleFlow1}", "flow1");
NavigationHandler 类有一个 handleNavigation 方法,该方法带有以下参数:
- 表示当前的 JSF FacesContext。
- fromAction:一个字符串,表示产生指定结果的操作方法表达式(" #{myBean.handleFlow1} ")。它可以为空。
- outcome:表示结果的字符串(“flow1”)。它可以为空。
注意当一个特定动作的结果为 null 时,NavigationHandler 不做任何事情,这意味着当前视图将被重新显示。
异常处理
异常处理 是 Java EE web 应用中必须注意的最重要的问题之一。异常处理有许多好处:当出现应用错误时,它可以为应用最终用户显示友好的消息,从最终用户的角度来看,这增加了使用应用的信任;除此之外,异常处理允许应用开发人员轻松地排除和调试应用缺陷。从 JSF 2.0 开始,框架支持异常处理机制,以便在 JSF 应用中有一个集中的位置来处理异常。
让我们为第一个应用创建一个定制的 JSF 异常处理程序,以便理解如何在 JSF 应用中处理异常。为了给 JSF 应用创建一个定制的异常处理程序,我们需要做三件事:
- 创建处理应用异常的自定义异常处理程序类。这个处理程序类应该扩展一个异常处理包装类(比如 ExceptionHandlerWrapper 类)。
- 创建自定义异常处理程序工厂类,该类负责创建异常处理程序类的实例。自定义异常处理程序类应扩展 JSF ExceptionHandlerFactory 类。
最后,在 faces-config.xml 文件中注册自定义异常处理程序工厂类,该文件被添加到第一个应用示例中。清单 2-37 显示了自定义异常处理程序类,它扩展了添加到第一个应用例子中的 ExceptionHandlerWrapper 类。
清单 2-37。??【CustomExceptionHandler】类
package com.jsfprohtml5.firstapplication.exceptions;
import java.util.Iterator;
import javax.faces.FacesException;
import javax.faces.application.NavigationHandler;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.context.FacesContext;
import javax.faces.event.ExceptionQueuedEvent;
import javax.faces.event.ExceptionQueuedEventContext;
public class CustomExceptionHandler extends ExceptionHandlerWrapper {
private ExceptionHandler wrapped;
public CustomExceptionHandler(ExceptionHandler wrapped) {
this.wrapped = wrapped;
}
@Override
public ExceptionHandler getWrapped() {
return wrapped;
}
@Override
public void handle() throws FacesException {
Iterator i = getUnhandledExceptionQueuedEvents().iterator();
while (i.hasNext()) {
ExceptionQueuedEvent event = (ExceptionQueuedEvent) i.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
Throwable t = context.getException();
FacesContext fc = FacesContext.getCurrentInstance();
try {
/* Here you can use the Throwable object in order to verify the exceptions you want to handle in the application */
NavigationHandler navigationHandler = fc.getApplication().getNavigationHandler();
navigationHandler.handleNavigation(fc, null, "error?faces-redirect=true");
fc.renderResponse();
} finally {
i.remove();
}
}
// Call the parent exception handler’s handle() method
getWrapped().handle();
}
}
CustomExceptionHandler 类的核心方法是 handle()方法,它负责处理 JSF 应用中的异常。值得注意的是,getUnhandledExceptionQueuedEvents()方法可用于获取 JSF 应用中所有未处理的异常。返回的 Iterable 对象中的每一项都代表一个 ExceptionQueuedEvent 对象。从 ExceptionQueuedEvent 对象中,可以获得 ExceptionQueuedEventContext 对象,从中可以检索 Throwable 对象。使用 Throwable 对象,您可以验证您想要在应用中处理的异常。最后,使用 NavigationHandler 导航到应用错误页面(error.xhtml),并从 Iterable 对象中删除 ExceptionQueuedEvent。清单 2-38 显示了 error.xhtml 页面。
清单 2-38。 申请错误页面
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>Error</title>
<link href="#{request.contextPath}/css/simple.css" rel="stylesheet" type="text/css"/>
</h:head>
<h:body>
<h2 class="errorMessage">
An error occurs. return to <a href="index.xhtml">login</a> page.
</h2>
</h:body>
</html>
其次,我们需要创建自定义异常处理程序工厂类,该类负责创建 custom exception handler 类的实例。清单 2-39 显示了 CustomExceptionHandlerFactory 类。
***清单 2-39。***CustomExceptionHandlerFactory 类
package com.jsfprohtml5.firstapplication.exceptions;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerFactory;
public class CustomExceptionHandlerFactory extends ExceptionHandlerFactory {
private ExceptionHandlerFactory parent;
public CustomExceptionHandlerFactory(ExceptionHandlerFactory parent) {
this.parent = parent;
}
@Override
public ExceptionHandler getExceptionHandler() {
ExceptionHandler result = new CustomExceptionHandler(parent.getExceptionHandler());
return result;
}
}
最后,我们需要在 faces-config.xml 中注册自定义异常处理程序工厂类,如清单 2-40 所示。
清单 2-40。 在 JSF 配置文件中注册自定义异常处理程序工厂类
<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.1"
FontName">http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd">
<factory>
<exception-handler-factory>
com.jsfprohtml5.firstapplication.exceptions.MyExceptionHandlerFactory
</exception-handler-factory>
</factory>
<!-- ... -->
</faces-config>
在 firstApplication 中设置了这个异常处理机制后,如果 firstApplication 因为某种原因抛出异常,那么 error.xhtml 页面就会显示为图 2-5 。
图 2-5 。错误处理页面(error.xhtml)
注更新后的 firstApplication 示例的完整源代码可在 www.apress.com/97814302501… 的图书网站上获得(附在第二章源代码 zip 文件中)。
摘要
在本章中,您详细了解了声明、初始化和管理 JSF 管理的 beans 的依赖项的不同方法。您还详细了解了如何在您的 JSF 应用中使用 EL。现在,您还详细了解了如何有效地使用 JSF 导航系统,以及如何利用 JSF 异常处理机制来增强您的 JSF 应用的错误处理能力。