`
zhangziyangup
  • 浏览: 1079277 次
文章分类
社区版块
存档分类
最新评论

JavaServer Faces 1.2 入门,第 1 部分: 构建基本应用程序(1)

 
阅读更多

JSF 基础

与 Swing 和 AWT 一样,JSF 也是一种开发框架,它提供一套标准的、可重用的 GUI 组件,用来构建 Web 应用程序的界面。JSF 具有以下优点:

  • 完全地隔离行为和表示
  • 能够对有状态进行组件级控制
  • 能够轻松地将事件连接到服务器端代码
  • 使用大家熟悉的 UI 组件和 Web 层概念
  • 提供多种标准的供应商实现
  • 出色的 IDE 支持

典型的 JSF 应用程序由以下部分组成:

  • 用来管理应用程序状态和行为的 JavaBean
  • 有状态 GUI 组件
  • 事件驱动的开发(像传统 GUI 开发一样通过监听器)
  • 表示 Model-View-Controller(MVC)视图的页面;页面通过 JSF 组件树引用视图根(view root)

为了使用 JSF,可能需要克服一些概念方面的障碍,掌握这些概念是很值得的。JSF 的组件状态管理、易用的用户输入检验、细粒度的基于组件的事件处理以及可轻松扩展的体系结构,这些概念会大大简化 Web 开发。本节详细解释最重要的 JSF 特性。

基于组件的体系结构

有状态组件

在使用 JSF 时最大的障碍是,您可能会忘记 JSF 是一个有状态组件模型。如果您以前使用过 Struts,就应该牢牢记住:“JSF 不是 Struts。JSF 不是 Struts。” 我发现,与多年使用 Struts 和没有 GUI 组件开发经验的开发人员相比,具备 Swing、AWT、Visual Basic 或 Delphi GUI 背景的开发人员能够更快地掌握 JSF。本教程将帮助您了解有状态组件的概念。

JSF 为标准 HTML 中可用的每个输入字段都提供了组件标记。还可以针对应用程序特有的用途编写定制的组件,或者用多个 HTML 组件组合成一个复合组件 — 例如,用三个下拉菜单组成一个 Data Picker 组件。JSF 组件是有状态的。它们的状态是通过 JSF 框架提供的。JSF 使用组件生成 HTML 响应。还可以使用许多第三方 JSF GUI 组件。

JSF 包括:

  • 一个事件发布模型
  • 一个轻量型反转控制(inversion-of-control,IoC)容器
  • 用于几乎每种常用 GUI 特性的组件,包括(但不限于):
    • 可插入的显示
    • 服务器端检验
    • 数据转换
    • 页面导航管理

作为一种基于组件的体系结构,JSF 具有很强的可配置性和可扩展性。大多数 JSF 功能 — 比如导航和托管 bean 查找 — 都可以替换为可插入的组件。这种可插入性为构建 Web 应用程序 GUI 提供了很强的灵活性,并允许轻松地将其他基于组件的技术结合到开发工作中。例如,可以将 JSF 的内置 IoC 框架替换为更成熟的 IoC/面向方面编程(AOP)Spring 框架,以执行托管 bean 查找。我将在第 2 部分中讨论许多高级特性。

JSF 和 JSP 技术

JSF 应用程序的用户界面由 JavaServer Pages(JSP)页面组成。每个 JSP 页面包含提供 GUI 功能的 JSF 组件。在 JSP 页面中,可以使用 JSF 定制标记库来显示 UI 组件、注册事件处理函数、将组件和检验器关联起来、将组件和数据转换器关联起来等等。

JSF 不了解 JSP

JSF 本质上并不直接处理 JSP,它通过 JSF 标记库处理 JSP。但是,JSF 的生命周期与 JSP 的生命周期非常不同。与 JSP 相比,Facelets 能够更好地与 JSF 配合,因为 Facelets 就是针对 JSF 设计的,而集成 JSF 和 JSP 总是很别扭。您应该考虑使用 Facelets;Facelets 特性将成为 JSF 2.0 的组成部分。关于 Facelets 的更多信息请参见 参考资料

JSF 并不一定要使用 JSP 技术。实际上,JSP 页面使用的 JSF 标记仅仅引用组件,让它们可以显示。组件的生命周期与 JSP 页面很不一样。

您可以这样体会这一点:在 JSP 页面中修改 JSF 组件的属性并重新装载页面,这时什么也不会发生。这是因为标记在它的当前状态中查找组件。如果组件已经存在了,定制标记就不修改它的状态。组件模型允许控制器代码修改组件的状态(例如,禁用一个文本字段),当显示视图时,会显示组件树的当前状态。

在典型的 JSF 应用程序中,不需要 Java 代码,只需要非常少的统一 Expression Language(JSTL EL)UI 代码。正如前面提到的,有许多 IDE 工具可以用来构建和组装 JSF 应用程序,还有许多第三方 JSF GUI 组件。尽管 JSF 在设计时就考虑到了 WYSIWYG IDE 工具,但是也可以在不使用 WYSIWYG 工具的情况下编写 JSF 应用程序(本教程中就是这么做的)。

不需要利用 WYSIWYG IDE 支持

尽管 JSF 在设计时就考虑到了 WYSIWYG IDE 支持,但是不一定非要使用 WYSIWYG IDE 支持。实际上,即使手工编写代码,JSF 仍然比大多数 Java Web 框架容易使用。如果您用 Swing 进行编程并使用 WYSIWYG IDE,那么可以继续用那个工具进行 JSF 编程。如果您喜欢手工编写 Swing 代码,那么也会喜欢手工编写 JSF。纯粹的编程!

JSP 2.1 和 JSF 1.2 中的改进

JSP 2.1 添加了许多新特性来支持 JSF,包括统一 Expression Language(EL)API(JSF 1.2 也添加了这个特性)。现在可以使用标准的 JSTL 标记循环处理列表并显示 JSF 组件,这是用 JSF 1.1 和 JSP 2.0 无法实现的。关于 JSP 2.1 中的改进的更多信息参见 参考资料。(尽管有了这些改进,但是 Facelets 仍然更适合 JSF,而且 JSF 2.0 将融合许多来自 Facelets 的思想。)

JSF 和 MVC

JSF 是近几年 Java 平台上 Web 开发技术迅速发展的成果。Java Web 开发技术首先从 JSP 技术开始,JSP 的主要好处是很容易在 HTML(和类似 HTML 的)页面中混合 Java 代码。下一步是 Model 1 体系结构,它让开发人员将大多数后端代码放到 JavaBeans 组件中,然后用 <jsp:useBean> 标记将 JavaBeans 组件导入 Web 页面。这个体系结构对于简单的 Web 应用程序很合适,但是许多 Java 开发人员讨厌 JSP 技术中结合的 C++ 特性,比如静态包含。所以产生了 Model 2 体系结构。

从本质上说,Model 2 体系结构是一种用于 Web 应用程序的简化版 MVC。在 Model 2 体系结构中,控制器由 servlet(或 Actions)表示,显示由 JSP 页面负责。Apache Struts 是一个简化的 Model 2 实现,其中用 Actions 替代了 servlet。在 Struts 中,应用程序的控制器逻辑与它的数据(由 ActionForms 表示)分隔开。对 Struts 的主要批评意见是它太过程性了,不够面向对象(它被戏称为 “COBOL for the Web”)。WebWork 和 Spring MVC 是另两种 Model 2 体系结构实现,它们降低了过程性,但是它们没有像 Struts 那样得到广泛应用。另外,它们没有像 JSF 那样提供有状态组件模型。(Struts 2 是在 WebWork 上构建的,原来的 Struts 代码基已经被废弃了。即使 Struts 也对自己不满意。)

大多数 Model 2 框架的真正问题是事件模型太过简单(本质上是高度简化的 MVC),而且它没有提供有状态的 GUI 组件,因此把太多工作留给了开发人员。为了简便地创建大多数用户期望的交互方式,需要一个更丰富的组件和事件模型。与 JSP 技术一样,大多数 Model 2 框架允许非常容易地在 HTML 布局和格式化代码中混合 GUI 定制标记,这使代码具备组件那样的松散性,但它们不是有状态的。而且,一些 Model 2 体系结构(比如过去的 Struts)在行为和状态的隔离方面犯了错误,这使许多 Java 开发人员觉得他们是在使用 COBOL。

更丰富的 MVC 环境

JSF 不是 Struts;抛弃原来的观念,接受新的知识
JSF 不是一种 Model 2 框架。它比 Model 2 框架丰富得多。但是,因为 Struts 原来的作者领导了 JSF 规范的制订,所以许多人误认为在 JSF 项目中可以使用 Struts 技能。不要按照 Struts 的经验编写 JSF 应用程序。您必须抛弃一些 Struts 技能,学习 JSF 技能。

JSF 提供一个组件模型和一个更丰富的 MVC 环境 — 比 Model 2 框架丰富得多。与 Model 2 体系结构相比,JSF 更接近真正的 MVC 编程环境,尽管它仍然是在一个无状态协议上构建的。JSF 还有助于构建比 Model 2 框架更细粒度的事件驱动的 GUI。JSF 提供了大量事件 — 选择菜单项、单击按钮、展开树节点等等 — 而大多数 Model 2 框架依赖于简单的 “接收请求” 事件。

JSF 出色的事件模型让应用程序依赖的 HTTP 细节更少,简化了开发工作。JSF 还改进了传统的 Model 2 体系结构,可以更轻松地将表示和业务逻辑移出控制器以及将业务逻辑移出 JSP 页面。实际上,简单的控制器类根本不连接 JSF,这使它们更容易测试。与真正的 MVC 体系结构不同,JSF 模型层不太可能发出必须在多个视图中解析的许多事件(但是,Crank 利用 JBoss ajax4JSF 的支持尝试这么做;参见 参考资料)。另外,这也是不必要的,因为 JSF 使用的是无状态协议。用来修改或更新视图的系统事件几乎总是一个来自用户的请求。

JSF MVC 实现的细节

在 JSF 的 MVC 实现中,映射托管 bean 在视图和模型之间起协调作用。因此,一定要限制托管 bean 中与 JSF 连接的业务逻辑和持久性逻辑。一种常用的替代方法是将业务逻辑委托给应用程序模型。在这种情况下,托管 bean 还映射模型对象,让视图可以显示它们(作为托管 bean 的属性)。我喜欢将我的托管 bean 分为两类:连接 JSF 的托管 bean(控制器)和不连接 JSF 的托管 bean(模型对象)。

与 JSP 技术不同,JSF 的视图实现是一个有状态组件模型。JSF 视图由两部分组成:视图根 和 JSP 页面。视图根是一个 UI 组件集合,它维护 UI 的状态。与 Swing 和 AWT 一样,JSF 组件使用 Composite 设计模式管理一个组件树(简单地说:容器包含组件;容器也是组件)。JSP page 将 UI 组件绑定到 JSP 页面,允许将字段组件绑定到后端 bean 的属性(更可能是属性的属性),以及将按钮绑定到事件处理函数和动作方法。

图 1 从 MVC 的视角展示一个示例应用程序的结构(稍后将详细了解这个程序):


图 1. 示例应用程序的结构
示例应用程序的结构

如果第一节的内容让您有点儿困惑,也不必担心:最困难的部分已经过去了。了解了 JSF 的总体框架,实现这种技术的过程就完成了一大半儿 — 您不久就会发现克服这个障碍是值得的。现在,理论已经讨论够了:我们来进行纯粹的编程吧!

JSF 示例:逐步创建

本节讨论用 JSF 创建应用程序的步骤。这个示例应用程序是一个简单的计算器应用程序,它演示 JSF 技术的以下方面:

  • 如何安排 JSF 应用程序的结构
  • 如何配置 JSF 的 web.xml 文件
  • 如何配置应用程序的 faces-config.xml 文件
  • 编写托管 bean(也称为模型对象和控制器)
  • 使用 JSP 技术构造视图
  • 使用定制的标记库在视图根中构造组件树
  • 对表单字段的默认检验

在后面几节中,将通过几次迭代改进这个应用程序,让它具备更多 JSF 特性。图 2 给出最终的 Calculator 示例应用程序的注解视图。在 下载 中可以获得应用程序源代码。


图 2. 最终的 Calculator 应用程序
最终的应用程序

Calculator 应用程序

用 Maven 2 和 Eclipse WTP 执行构建
Calculator 示例应用程序的默认构建环境是 Apache Maven 2。在这些示例中,我使用 Maven Web 应用程序的默认布局。用 Eclipse JEE、Tomcat 和 Maven 2 运行本教程源代码的说明和 JAR 文件见 参考资料

最初的 Calculator 示例应用程序的目标是显示一个页面,让用户输入两个数字,然后将它们相加或相乘。

这个页面包含:

  • 一个表单
  • 两个文本字段
  • 两个标签
  • 两个错误消息位置
  • 两个 Submit 按钮
  • 一个结果面板

文本字段用来输入数字。标签表示文本字段的意义。错误消息位置用来显示文本字段的检验或数据转换错误消息。有两个 JSP 页面:calculator.jsp 和 index.jsp,后者仅仅重定向到 calculator.jsp。一个称为 Calculator 的托管 bean 作为 calculator.jsp 的模型。这个简单的示例没有控制器层。

创建应用程序:概述

为了用 JSF 构建最初的 Calculator 应用程序,需要:

  • 在 Web 应用程序部署描述符文件(web.xml)中声明 Faces Servlet 并添加 Faces Servlet 映射
  • 在 web.xml 文件中指定 faces-config.xml 文件
  • 创建 Calculator
  • 在 faces-config.xml 文件中声明 Calculator bean
  • 创建 index.jsp 页面
  • 创建 calculator.jsp 页面

这个应用程序使用以下目录结构:

+---src
+---main
+---java
+---webapp
+---pages
+---WEB-INF
+---lib

Java 代码放在 src/main/java/ 下面。web.xml 文件放在 src/main/webapp/WEB-INF 目录中。JSF 配置文件也放在 src/main/webapp/WEB-INF 下面。这个示例应用程序是用 Eclipse IDE for Java EE Developers(Eclipse JEE)创建的,这个 IDE 包含一个 JSF 项目创建向导,它会创建包含适当条目的 web.xml 文件和 faces-config.xml 文件。我假设您将使用支持 Java EE 5 的应用服务器,也就是说,它有 JSF 和 JSTL JAR 文件。关于设置 Tomcat 来运行 JSF、设置 Eclipse JEE 来运行 Tomcat 6 并在 Maven 2 中运行示例的说明,参见 参考资料

声明 Faces Servlet 和 servlet 映射

为了使用 Faces Servlet,首先需要在 web.xml 文件中声明它,见清单 1:


清单 1. web.xml 中的 Faces Servlet 声明

<servlet>
<servlet-name>FacesServlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

这与大多数 web.xml 描述符相似,但是要让 JSF servlet 处理请求,而不是指定自己的 servlet。对使用 <f:view> 标记(就像这个示例应用程序所做的)的 JSP 文件的所有请求必须通过这个 servlet。因此,需要添加一个映射并通过这个映射只装载启用 JSF 的 JSP 页面,见清单 2:


清单 2. web.xml 中的 Faces Servlet 路径映射

<servlet-mapping>
<servlet-name>FacesServlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>FacesServlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>

清单 2 让 Faces Servlet 容器将所有以 /faces/ 开头或以 *.jsf 结尾的请求发送到 Faces Servlet 进行处理。这让 JSF 在显示 JSF 页面之前初始化 Faces 上下文和视图根。视图根包含 JSF 组件树。Faces 上下文是与 JSF 进行交互的方式。

这意味着,要想装载 Calculator 应用程序,应该使用 http://localhost:8080/calculator/pages/calculator.jsf 或 http://localhost:8080/calculator/faces/pages/calculator.jsp — 而不是 http://localhost:8080/calculator/pages/calculator.jsp。如果在 JSF 上下文之外装载 JSP 页面,JSF 就没有机会初始化 Faces 上下文或视图根。

指定 faces-config.xml 文件

如果将 Faces 配置文件命名为 faces-config.xml 并把它放在 Web 应用程序的 WEB-INF 目录中,那么 Faces Servlet 会自动地找到并使用它(因为这是默认设置)。另外,也可以在 web.xml 文件中的初始化参数 javax.faces.application.CONFIG_FILES 中指定逗号分隔的文件列表,从而装载一个或多个应用程序配置文件。除了最简单的 JSF 应用程序之外,对于所有 JSF 应用程序,很可能会使用第二种方法。因为这个示例应用程序很简单,我们使用默认的 faces-config.xml 文件位置 /src/main/webapp/WEB-INF。

创建 Calculator

现在,创建一个称为 Calculator 的 POJO(普通 Java 对象),它根本不连接到 JSF。然后用方法绑定和属性绑定将它绑定到 JSF。这个简单的类有两个属性:firstNumbersecondNumber

我的目标是演示如何开始使用 JSF,所以让模型对象尽可能简单。这个应用程序的模型包含在一个模型对象中,见清单 3。后面将把它分割成两个类:控制器和模型。


清单 3. Calculator POJO

packagecom.arcmind.jsfquickstart.model;

/***//**
*Calculator.SimplePOJO.
*
*
@authorRickHightower
*/

publicclassCalculator...{

/***//**Firstnumberusedinoperation.*/
privateintfirstNumber=0;

/***//**Resultofoperationonfirstnumberandsecondnumber.*/
privateintresult=0;

/***//**Secondnumberusedinoperation.*/
privateintsecondNumber=0;

/***//**Addthetwonumbers.*/
publicvoidadd()...{
result
=firstNumber+secondNumber;
}


/***//**Multiplythetwonumbers.*/
publicvoidmultiply()...{
result
=firstNumber*secondNumber;
}


/***//**Cleartheresults.*/
publicvoidclear()...{
result
=0;
}


/**//*----------properties-------------*/

publicintgetFirstNumber()...{
returnfirstNumber;
}


publicvoidsetFirstNumber(intfirstNumber)...{
this.firstNumber=firstNumber;
}


publicintgetResult()...{
returnresult;
}


publicvoidsetResult(intresult)...{
this.result=result;
}


publicintgetSecondNumber()...{
returnsecondNumber;
}


publicvoidsetSecondNumber(intsecondNumber)...{
this.secondNumber=secondNumber;
}


}

清单 3 非常简单,不需要解释;您只需阅读代码。但是要记住,Calculator POJO 并不处理 JSF。

在 faces-config.xml 文件中声明 Calculator bean

清单 4 给出完整的 faces-config.xml 文件。可以看到,这个文件与 Java EE JSF XML 模式相关联。在 faces-config.xml 中,使用 <managed-bean> 元素声明一个 bean,JSF 可以绑定到这个 bean:


清单 4. 包含托管 bean 声明的 faces-config.xml 文件

<?xmlversion="1.0"encoding="UTF-8"?>

<faces-configxmlns="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_1_2.xsd"

version
="1.2">
<managed-bean>
<managed-bean-name>calculator</managed-bean-name>
<managed-bean-class>com.arcmind.jsfquickstart.model.Calculator</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>

</faces-config>

清单 4 中的 bean 声明用 <managed-bean-name> 元素指定 bean 的名称 calculator。还用 <managed-bean-class> 指定完全限定的类名。这个类必须有一个无参数构造函数。

<managed-bean> 元素的 <managed-bean-scope> 子元素指定 JSF 可以在哪里找到这个 bean:request 范围。如果将这个 bean 名称绑定到一个视图(本教程后面会这么做),而且 JSF 无法找到它,那么 JSF 就会创建它。这是通过 JSF 和统一 EL API 实现的。request 范围只针对一个请求。这是放置不需要在页面视图之间维持状态的 bean 的合适位置。

创建 index.jsp 页面

在 Calculator 应用程序中,index.jsp 页面的用途是确保 calculator.jsp 页面装载 JSF 上下文,让页面能够找到对应的视图根。清单 5 给出 index.jsp 页面:


清单 5. index 页面重定向到 calculator.jsp

<jsp:forwardpage="/faces/calculator.jsp"/>

这个页面仅仅把用户重定向到 faces Web 上下文中的 calculator.jsp。这将 calculator.jsp 页面放在 JSF 上下文路径下面,它可以在这里找到它的视图根。

创建 calculator.jsp 页面

calculator.jsp 页面是 Calculator 应用程序视图的核心。这个页面接受用户输入的两个数字,见图 3:


图 3. 在 Eclipse JEE/WTP 中运行的第一个 Calculator 应用程序
第一个 Calculator 应用程序

这个页面的完整代码见清单 6:


清单 6. /src/main/webapp/calculator.jsp

<?xmlversion="1.0"encoding="ISO-8859-1"?>
<%...@tagliburi="http://java.sun.com/jsf/html"prefix="h"%>
<%...@tagliburi="http://java.sun.com/jsf/core"prefix="f"%>

<!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<htmlxmlns="http://www.w3.org/1999/xhtml">
<head>
<title>CalculatorApplication</title>
</head>
<body>
<f:view>
<h:formid="calcForm">
<h4>Calculator</h4>
<table>
<tr>
<td><h:outputLabelvalue="FirstNumber"for="firstNumber"/></td>
<td><h:inputTextid="firstNumber"
value
="#{calculator.firstNumber}"required="true"/></td>
<td><h:messagefor="firstNumber"/></td>
</tr>

<tr>
<td><h:outputLabelvalue="SecondNumber"for="secondNumber"/>
</td>
<td><h:inputTextid="secondNumber"
value
="#{calculator.secondNumber}"required="true"/></td>
<td><h:messagefor="secondNumber"/></td>
</tr>
</table>
<div>

<h:commandButtonaction="#{calculator.add}"value="Add"/>
<h:commandButtonaction="#{calculator.multiply}"value="Multiply"/>
<h:commandButtonaction="#{calculator.clear}"value="Clear"immediate="true"/>
</div>

</h:form>

<h:panelGrouprendered="#{calculator.result!=0}">
<h4>Results</h4>
<table>
<tr><td>
FirstNumber${calculator.firstNumber}
</td></tr>
<tr><td>
SecondNumber${calculator.secondNumber}
</td></tr>
<tr><td>
Result${calculator.result}
</td></tr>
</table>
</h:panelGroup>
</f:view>

</body>
</html>

注意,这个文件中的大多数代码是普通的 HTML(准确地说,是 XHTML)。可以在 <f:view><h:form><h:panelGroup> 标记中使用 HTML。一种常见的误解是,不能在 JSF 标记中混合 HTML。实际上,在许多情况下都可以这么做。但是,不能在 <h:commandButton> 中使用 HTML,因为这个标记只接受其他组件作为子元素。

因为这个页面有点儿复杂,我来解释一下如何构建它。

声明标记库

首先声明 JSF 的标记库,见清单 7:


清单 7. 将标记库导入 calculator.jsp

<%...@tagliburi="http://java.sun.com/jsf/html"prefix="h"%>
<%...@tagliburi="http://java.sun.com/jsf/core"prefix="f"%>

清单 7 告诉 JSP 引擎您希望使用两个 JSF 标记库 htmlcorehtml 标记库包含用来处理表单和其他 HTML 相关元素的所有标记。core 标记库包含 JSF 特有的所有逻辑、检验、控制器和其他标记。

<f:view> 标记

在用一般的 HTML 布置页面之后,要告诉 JSF 系统您希望使用 JSF 管理组件。这需要使用 <f:view> 标记,这个标记告诉容器希望使用 JSF 管理其中的组件。

如果没有 <f:view>,JSF 就无法构建组件树,以后也无法搜索已经创建的组件树。使用 <f:view> 标记的方式见清单 8:


清单 8. calculator.jsp 的 <f:view> 标记

<f:view>
<h:formid="calcForm">
...
</h:form>
</f:view>

清单 8 中的第一行是 <f:view> 的声明,它告诉容器它由 JSF 管理。

<f:view> 标记中:<h:form> 标记

清单 8 中的第二行是 <h:form> 标记,这告诉 JSF 这里需要一个 HTML 表单。在显示阶段,会搜索这个表单组件中包含的组件并要求它们显示自己,这时它们会生成标准的 HTML。可以按照您喜欢的任何方式布置表单组件。清单 9 是 Calculator 应用程序的输入字段的布局:


清单 9. 在 calculator.jsp 的 <h:form> 标记中:输入字段

<table>
<tr>
<td><h:outputLabelvalue="FirstNumber"for="firstNumber"/></td>
<td><h:inputTextid="firstNumber"
value
="#{calculator.firstNumber}"required="true"/></td>
<td><h:messagefor="firstNumber"/></td>
</tr>

<tr>
<td><h:outputLabelvalue="SecondNumber"for="secondNumber"/>
</td>
<td><h:inputTextid="secondNumber"
value
="#{calculator.secondNumber}"required="true"/></td>
<td><h:messagefor="secondNumber"/></td>
</tr>
</table>

请再次注意,这里使用了大量 HTML。可以用 spandivtable 或其他元素布置应用程序的布局。JSF 对设计人员没什么限制。一些工具甚至允许在 Dreamweaver 中使用 JSF(参见 参考资料)。在结合使用 JSF 和 Facelets 时,JSF 甚至对设计人员更友好(可以在 JSF 1.1 和更高版本中使用 Facelets,而且它将成为 JSF 2.0 的组成部分)。

在清单 9 中还要注意,两个 inputTextvalue 属性都使用 JSF EL(JavaServer Faces Expression Language)值表达式(例如,value="#{calculator.firstNumber}")。初看上去这很像 JSTL EL。但是,统一 EL 代码实际上将字段与对应的后端 bean 的属性值关联起来。这种关联是双向的;也就是说,如果 firstNumber 的值是 100,那么在显示表单时会显示 100。同样,如果用户提交了一个有效的值,比如 200,那么 200 会成为 firstNumber 属性的新值(假设通过了转换和检验过程,稍后讨论这个问题)。

除了字段之外,calcForm 还通过三个 commandButton 与三个动作相关联,见清单 10:


清单 10. 在 calculator.jsp 的 <h:form> 标记中:按钮

<div>
<h:commandButtonaction="#{calculator.add}"value="Add"/>
<h:commandButtonaction="#{calculator.multiply}"value="Multiply"/>
<h:commandButtonaction="#{calculator.clear}"value="Clear"immediate="true"/>
</div>

清单 10 中的代码将三个按钮绑定到 calculator 类的 add()multiply()clear() 方法,所以当单击按钮时会调用对应的方法(假设成功执行了转换和检验)。

在默认情况下,在执行任何动作方法(比如 add()multiply())之前,JSF 会检验表单。但是,如果使用 immediate="true"(就像清单 10 中的 Clear 按钮那样),那么 JSF 会跳过检验阶段并直接执行方法(稍后会进一步讨论)。

查看结果:<h:panelGroup> 标记

最后,在 <f:view> 标记中,用 <h:panelGroup> 显示加法和乘法操作的结果,见清单 11:


清单 11. 显示结果

<h:panelGrouprendered="#{calculator.result!=0}">
<h4>Results</h4>
<table>
<tr><td>
FirstNumber${calculator.firstNumber}
</td></tr>
<tr><td>
SecondNumber${calculator.secondNumber}
</td></tr>
<tr><td>
Result${calculator.result}
</td></tr>
</table>
</h:panelGroup>

这里仍然主要使用 HTML。注意,可以在 <h:panelGroup> 的元素体中混合 JSP 样式表达式。与所有 JSF 组件一样,<h:panelGroup> 有一个呈现表达式。因此,只有当表达式 calculator.result != 0 为 true 时,才会显示结果部分。因此,当用户初次装载计算器页面时,不会显示这个部分。当他们输入值时,显示结果部分(只要结果不是零)。(这个表达式的问题在于,它把逻辑放在视图中。另外,如果希望允许用户输入 0 + 0(或 7 * 0)并显示结果,那么应该怎么办?本教程后面会纠正这个问题。)

关于样式表

所有 JSF 组件的外观和感觉都是通过样式表类声明的。每个组件有一个样式与行内样式相关联,有一个 styleClass 将组件与一个 styleSheet 类关联起来。<panelGrid>(将在下一节中使用)还有对行和列应用样式的样式属性。在下一节中,将使用 Cascading Style Sheets(CSS)控制 JSF 的样式。

运行应用程序

Struts 比 JSF 更容易吗?

如果要创建这个简单 JSF 应用程序的传统 Struts 版本,我估计至少需要两倍的工作量。如果使用 Struts,就需要为两个按钮创建两个动作类,每个类都需要自己的一套动作映射。如果按照 Model 2 的建议,还需要一个用来装载第一个页面的动作映射。为了模拟 JSF 的默认错误处理和检验,必须将 Struts 配置为使用检验器框架,或者在 ActionForm 上的检验方法中实现检验。还需要在 Struts 配置中声明 DynaValidatorForm,或者创建 ActionForm 并覆盖检验方法,或者使用 ValidatorForm 的子类连接检验器框架。最后,可能需要配置一些转发(每个动作可能需要两套转发)或所有动作使用的全局转发。所以,最好不要在新的应用程序中使用传统的 Struts。

为了运行这个应用程序,应该访问 WAR 文件映射到的页面(在我的机器上,这个页面是 http://localhost:8080/calculator0/)。这会导致 index.jsp 文件在 JSF 上下文中装载 calculator.jsp 页面。如果使用 Eclipse WTP 并设置了服务器,那么在 Navigator 中右键单击 calculator.jsp 页面并选择 Run As > Run On Server 选项。

如果在 First Number 字段或 Second Number 字段中输入无效的文本(例如,abc)并提交,那么会返回到 /calculator.jsp 视图,并在对应的字段旁边显示一个错误消息。因此,只需指定字段是必需的并将字段绑定到 int 属性,就会在 JSF 中看到一些自动的检验。图 4 显示这个应用程序如何处理检验和数据转换错误:


图 4. 检验和数据转换错误
检验和数据转换错误

请注意这些古怪的错误消息。转换和必需值的默认错误消息并不便于用户理解:

  • calcForm:firstNumber: 'abc' must be a number between -2147483648 and 2147483647 Example: 9346.
  • calcForm:secondNumber: Validation Error: Value is required.

在下一节中,将修改这些错误消息。

输入两个值(它们的和或乘积不是零)并提交之后,结果部分就会出现,见图 5:


图 5. 结果面板
结果面板

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics