IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Java technology | Web development  >

怀疑论者的 JSF: 消除关于 JSF 的 FUD

JavaServer Faces 比想像的要容易

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Rick Hightower (rhightower@arc-mind.com), CTO, ArcMind

2005 年 3 月 17 日

对于 Java Server Faces (JSF) 这样一种不可或缺的技术,产生了很多不适当的 FUD(“Fear, uncertainty, and doubt”的缩写,意思是“恐惧、不确定、怀疑” )。盛传着这样一些谣言:JSF 开发很难,比一些主流方法的要求更苛刻,并且完全依赖于 WYSIWYG 工具。在这个新的由 4 部分组成的系列文章中,经常为 developerWorks 撰稿的作者 Rick Hightower,划清了 FUD 与事实真相,向您展示了:JSF 实际上比诸如 Struts 这样的 MVC Model 2 框架更加容易使用。如果您知道自己在做什么,那么确实是这样的。

FUD 已经围绕 J2EE 的 JavaServer Faces (JSF) 技术多时了,我觉得现在该让它停止了,或者至少给出一种公允的说法。关于 JSF 的第一个谣传是,需要一个 WYSIWYG 拖放工具来进行 JSF 开发。第二个谣传是,JSF 不支持诸如 Struts 这样的 MVC Model 2 框架。最后一个,也是最致命的谣传是,JSF 开发就是难。

在这个 4 部分的系列文章中,我将尽量以最实际的方式消除所有这三个谣传,这种方式就是教您利用 JSF 进行开发。实际上,如果您认为 JSF 开发很难,可能是您没有用对,幸运的是,这很容易改正。本期一开始,我将给出 JSF 的一个结构上的概述和一个实际的例子,演示了 MVC 和 JSF 的基础。但是在开始之前,我将花一点时间来划清 JSF FUD 与事实真相。

千万别相信 FUD!

正如前面提到的,关于 JSF 存在三个谣传,第一个谣传是,进行 JSF 开发需要 WYSIWYG 工具。简直是胡说。就像很多 Swing 开发人员不使用 WYSIWYG 来构建 Swing 应用程序一样,您也不需要用 WYSIWYG 编辑器来构建 JSF 应用程序。事实上,不用 WYSIWYG 工具进行的 JSF 开发比利用诸如 Struts 和 WebWork 这样的传统 Model 2 框架进行的开发要容易得多。本文后面我将详细解释具体原因,但是现在您只要记住:JSF 开发比 Struts 要容易得多,即使不使用 WYSIWYG 工具

关于 JSF 的下一个谣传是,不支持 Model 2 架构。目前来说,这实际上说得有点对。但事实是,Model 2 是针对建立在 Servlets 之上的 Web 开发的 MVC (Model-View-Controller) 的打了折扣的版本。尽管 Model 2 连接到一个无状态协议 (HTTP),但是 JSF 支持更加丰富的 MVC 模型(这是传统 GUI 应用程序更加紧密的近似)。尽管 MVC 的基础使得 JSF 框架实现比其他框架更难构建,但是有利的是,实现 JSF 的大量实际工作已经不用您自己完成了,所以您的净付出减少了,而您的净受益就显著增加了。

关于本系列

这个 4 部分的系列文章旨在消除关于 JavaServer Faces (JSF) 技术的 FUD,主要方法是,让您有机会以一种逐步的、容易跟随的方式,自己尝试 JSF 开发。在这 4 篇文章中,我要提供一系列例子,向您介绍 JSF 的基本架构、特性和功能。一旦您熟悉了 JSF 方式的开发,我想您就很难回到 Struts Model 2 风格的开发了。毕竟,在体验了 JSF 的事件驱动的 GUI 组件模型之后,谁还愿意回到 XML 配置的深渊中去呢?

为了从本系列获得最大的受益,您应该熟悉 Java 编程、JavaBeans 组件(即事件模型和属性)、JavaServer Pages 技术、JSP Standard Tag Library Expression Language 和所有基本的 Web 开发概念。

关于 JSF 开发最主要、流传最广的谣传是,JSF 开发太难了。我经常从那些阅读过该技术的大量资料却没有亲自体验过的人那里听到这种说法,所以我认为我可以轻易澄清这一点。事实是,如果您将您对 JSF 的观点建立在它无可否认的广泛规范上 —— 以及它的所有生命周期图表和图片 —— 那么该技术很容易让您发怵。但是请记住这样一件事情,规范是针对工具实现者的,而不是针对应用程序开发人员本身。正如前面提到的,JSF 框架设计成对应用程序开发人员来说非常容易。

事实上,尽管 JSF 的基于组件的、事件驱动的 GUI 开发模型对于 Java 世界来说还有点新,但是在别处已经存在很长一段时间了。ASP.net 和 Apple 的 WebObjects 都是类似于 JSF 的架构。Tapestry 是一种开放源代码的、基于 Java 的 Web 组件框架,它采用的方法有些不同于 JSF 的方法,但是也建立在 Web GUI 组件模型之上。

就现在来说,对 FUD 的谈论也许已经足够了。消除您对 JSF 的偏见的最好方法是,适当地钻研这种技术,我们马上就来做这件事。但是为了避免这成为您对 JSF 的第一印象,我一开始将给出一个结构上的概述。





回页首


JSF 初学者

像 Swing 和 AWT 一样,JSF 是一个可以提供一组标准的、可重用的 GUI 组件的开发框架。JSF 用于构建 Web 应用程序接口。JSF 提供以下开发优势:

  • 行为与表示的完全分离。

  • 对状态的组件级控制。

  • 事件容易捆绑到服务器端代码。

  • 利用熟悉的 UI 组件和 Web 层概念。

  • 提供多个标准化的供应商实现。

典型的 JSF 应用程序包含以下部分:

  • 用于管理应用程序状态和行为的 JavaBeans 组件。

  • 事件驱动的开发(像传统 GUI 开发中一样通过侦听器)。

  • 呈现 MVC 样式视图的页面;页面通过 JSF 组件树引用视图根(view root)。

尽管使用 JSF 需要跨越一些概念上的障碍,但是这样做是值得的。JSF 的组件状态管理、容易使用用户输入验证、细粒度、基于组件的事件处理和容易扩展的架构,都将大大简化 Web 开发。在接下来的几小节中,我将更加详细地解释这些特性中最重要的特性。





回页首


基于组件的架构

JSF 为标准 HTML 中可用的每个输入字段提供了组件标签。您也可以为应用程序的特定目的,或者为了将多个 HTML 组件组合在一起形成一个复合体 —— 例如一个包含三个下拉菜单的 Data Picker 组件,而编写自己的自定义组件。JSF 组件是有状态的。组件的无状态是通过 JSF 框架提供的。JSF 使用组件来生成 HTML 响应。

JSF 的组件集包含一个事件发布模型、一个轻量级的 IOC 容器和很多用于几乎所有其他公共 GUI 特性的组件,这些特性包括可插入呈现、服务器端验证、数据转换、页面导航管理,等等。作为基于组件的架构,JSF 是相当可配置和可扩展的。大多数 JSF 功能,比如导航和托管 bean 查看,都可以用可插入的组件替换。这种程度的可插入性给予您在构建 Web 应用程序 GUI 方面相当大的灵活性,并允许您容易地将其他基于组件的技术融入到 JSF 开发中。例如,对于托管 bean 查看,您可以用更加全功能的 IOC/AOP Spring 框架来取代 JSF 的内置 IOC 框架。





回页首


JSF 和 JSP 技术

JSF 应用程序的用户界面包含 JSP (JavaServer Pages) 页面。每个 JSP 页面包含呈现 GUI 功能的 JSF 组件。可以在 JSP 页面中使用 JSF 自定义标签库来做以下事情:呈现 UI 组件、注册事件处理器、关联组件与验证器、关联组件与数据转换器,等等。

这就是说,JSF 并不内在地绑定到 JSP 技术。事实上,JSP 页面使用的 JSF 标签只是引用组件,以便显示组件。当您第一次修改 JSP 页面以更改 JSF 组件的属性,并重新加载该页面,看到没有任何事情发生时,您就会认识到这一点。这是因为标签以其当前状态查看组件。因此,如果组件已经存在,自定义标签将不会修改它的状态。组件模型允许控制器代码更改组件的状态(例如,禁用一个文本字段),并且当显示该视图时,将会显示组件树的当前状态。

典型的 JSF 应用程序在 UI 中不需要 Java 代码,需要很少的 JSTL EL (JSP Standard Tag Library,一种表示语言) 代码。正如前面提到的,JSF 中有很多用于构建和装配应用程序的 IDE 工具,并且 JSF GUI 组件似乎还有一个正在增长的第三方市场。不使用 WYSIWYG 工具也可以进行 JSF 开发。

关于 MVC

模型-视图-控制器(model-view-controller,MVC)架构提供一组设计模式,有助于区分构建和运行基于 GUI 的应用程序时涉及的一些领域。模型(model) 封装应用程序的业务逻辑和持久性代码。模型应该尽量是视图技术不可知的(view-technology-agnostic)。例如,同一模型应该可用于 Swing 应用程序、Struts 应用程序或者 JSF 应用程序。视图(view) 应该显示模型对象,并且只包含表示逻辑。视图中应该没有业务逻辑或控制器逻辑。控制器(controller) (加上它的主要逻辑)充当视图和模型之间的中介者。控制器与模型进行交流,并将模型对象交付给视图进行显示。在 MVC 架构中,控制器总是选择下一个视图。





回页首


JSF 和 MVC

JSF 是几年前学过的在 Java 平台上改进 Web 开发技术的课程的结果。这一趋势开始于 JSP 技术,这一技术很好,只是很容易在 HTML(和类 HTML)页面中混合 Java 代码。下一次提高是 Model 1 架构,它让开发人员将大多数后端代码放入 JavaBeans 组件中,然后用 <jsp:useBean> 标签将 JavaBeans 组件导入 Web 页面。这对于简单的 Web 应用程序工作得很好,但是许多 Java 开发人员不喜欢 JSP 技术这种与 C++ 特性(比如静态包含)的协作。所以引入了 Model 2 架构。

本质上,Model 2 架构是用于 Web 应用程序的 MVC 的打了折扣的版本(请参阅“关于 MVC”)。在 Model 2 架构中,控制器是由 Servlets 来表示的,而显示则委派给 JSP 页面。Struts 是一种简化的 Model 2 实现,其中的 Actions 代替了 Servlets。在 Struts 中,应用程序的控制器逻辑是与它的数据(由 ActionForms 表示)相分离的。对于 Struts 的主要抱怨是,它感觉上更像过程化的,而不像面向对象的。WebWork 和 Spring MVC 是另外两个 Model 2 架构,它们通过更加不像过程化的,在 Struts 的基础上有所改进,但是它们仍然没有 Struts 那样被广泛接受(或者没有那么成熟,有人可能对此有争议)。并且也不提供像 JSF 提供的那些组件模型。

关于大多数 Model 2 框架的实际问题是,事件模型太简单了(本质上是一个非常缩小的 MVC),这就给开发人员留下了太多的工作。更丰富的事件模型使得创建大多数用户期望的交互更加容易。像 JSP 技术一样,大多数 Model 2 也很容易利用 GUI 自定义标签来混合 HTML 布局和格式化,这些标签有点类似于组件。而有些 Model 架构(比如 Struts)出现分离行为与状态的错误,这让许多 Java 开发人员感觉自己是在进行 COBOL 编程。

更丰富的 MVC 环境

JSF 提供一个组件模型和一个比大多数 Model 2 实现更丰富的 MVC 环境。本质上,JSF 比 Model 2 架构更加接近于真正的 MVC 编程环境,尽管它仍然是一种无状态的协议。JSF 也比 Model 2 架构更方便构建更加细致的事件驱动 GUI。尽管 JSF 给了您很多事件选项(菜单项选择、按钮单击,等等),但是大多数 Model 2 依赖于更加简单的“请求接受”。

Struts 和 JSF

Struts 框架是 Java 平台上 Web 框架发展过程中的一个必要演变。Struts 推动了 Model 2 框架的局限性。Struts 中使用的思想演变成了 JSTL 和 JSF。产生了许多用于 Model 2 开发模型的项目,其中用到了 Struts 最初探索过的思想(通常有了本质的改进)。此外,在 JSF 项目中仍然可以看到许多 Struts DNA,尽管它们在架构上是分道扬镳的。例如,仍然可以看到 Tiles with JSF,或者使用 Struts 验证器框架来生成客户端 JavaScript。甚至有一些集成 Struts 和 JSF 的趋势。所有这些表明,我的观点是,JSF 几乎取代了对 Struts 的需要(或者至少当前是这样)。Struts 的下一主要版本叫做 Shale,对 JSF 遗弃了一些 Struts 核心。请继续阅读本系列文章,并自己确定 JSF 对于您的项目是不是 Struts 的可行替代。

JSF 的良好调优的事件模型,允许您的应用程序与 HTTP 细节的联系更少,并简化了开发。通过使得更加容易将表示和业务逻辑移出控制器,以及将业务逻辑移出 JSP 页面,JSF 也在传统的 Model 2 架构上有了一些改进。事实上,简单的控制器类根本与 JSF 没有联系,这使得它们更加容易测试。与真正的 MVC 架构不一样,JSF 模型层不可能发出许多必须在多个视窗(viewport)中解决的事件;此外,我们仍然在处理无状态的协议,所以这是没必要的。用于更改或更新视图的系统事件几乎总是(为什么我敢说总是呢?)用户请求。

JSF 的 MVC 实现细节

在 JSF 的 MVC 实现中,mapping backing beans(映射支持 beans)在视图和模型之间调停。因此,限制 backing beans 中的业务逻辑和持久性逻辑很重要。一个常见的替代方法是,将业务逻辑委派给应用程序模型。在这种情况下,backing beans 也映射模型对象,其中视图可以显示它们。另一种选项是,将业务逻辑放在 Business 代表中,后者充当模型。

与 JSP 技术不一样,JSF 的视图实现是一个有状态的组件模型。JSF 视图包含两个部分:视图根和 JSP 页面。视图根是 UI 组件集合,这些组件维护 UI 的状态。与 Swing 和 AWT 一样,JSF 组件使用 Composite 设计模式来管理组件树(简单地说,容器包含组件,容器也是一个组件)。JSP 页面将 UI 组件绑定到 JSP 页面,并允许您将字段组件绑定到 backing beans 的属性(或者属性的属性),以及将按钮绑定到事件处理器和操作方法。

下面是一个从 MVC 角度来看的示例应用程序(后面会详细介绍)。


图 1. 从 MVC 角度来看的示例应用程序
从 MVC 角度来看的示例应用程序

这已足够小了:我们来看 JSF!





回页首


一个 JSF 例子

对于本文的其余部分,我把重点放在用 JSF 实际创建应用程序的详细步骤上。该示例应用程序是 JavaServer Faces 技术的一个非常简单的演示。演示了以下几个方面:

  • 如何为部署布局 JSF 应用程序。

  • 如何为 JSF 配置 web.xml 文件。

  • 如何为应用程序配置 faces-config.xml。

  • 编写 Model beans(也叫做 backing beans)。

  • 使用 JSP 技术构造视图。

  • 使用自定义标签库在视图根中构造组件树。

  • 表单字段的默认验证。
利用 Maven 进行构建

示例 Calculator 应用程序的默认构建环境是 Maven。Maven 构建系统类似于 Ant。在这些例子中,我使用了 Maven Web 应用程序的默认布局。因此,应用程序的 Java 类文件是在 src/java 下的项目根中。Web 应用程序文件包含在 src/webapp 下,我在这里放了特定于我的 Web 应用程序的文件,包括 JSP 页面、faces-config.xml 和 web.xml。项目文件夹的根中的 project.xml 文件描述了,对于 Maven 构建和打包 war 文件,我需要什么,以及我的文件放在哪里。project.properties 文件具有附加的属性,这些属性告诉 Maven 做什么,以及到哪里去寻找特定于本地环境的东西。我也为不想尝试 Maven 的 Ant 用户生成了一个 Ant build.xml。如果您选择使用 Ant,您将会在 build.xml 文件和 参考资料 一节中,找到关于构建示例和设置环境的更多指令。

该例是一个简单的 Calculator 应用程序。创建该应用程序的目标是向终端用户呈现一个页面,让他/她输入两个数值。因此,该页面具有两个文本字段、两个标签、两个错误消息位置和一个 Submit 按钮。文本字段用于输入数值。标签用于标注字段。错误消息位置用于显示针对文本字段的验证或数据转换错误消息。存在三个 JSP 页面:index.jsp,它只是重定向到 calculator.jsp;calculator.jsp,它呈现前面提到的 GUI;results.jsp,它显示结果。 一个叫做 CalculatorController 的托管 bean 充当 calculator.jsp 和 results.jsp 的 backing bean。

图 2 展示了示例 Calculator 应用程序的第二个 MVC 视图。通过单击本页顶部或底部的 Code 图标,可以下载该应用程序的源代码。


图 2. 示例应用程序的第二个 MVC 视图
MVC 视图




回页首


构建应用程序

要用 JSF 构建 Calculator 应用程序,需要做以下事情:

  1. 收集 web.xml 和 faces-config.xml 文件,建立在示例应用程序的 src/webapp/WEB-INF 目录下。

  2. 在 web.xml 文件中声明 Faces Servlet 和 Faces Servlet 映射。

  3. 在 web.xml 文件中指定 faces-config.xml 文件。

  4. 在 faces-config.xml 文件中声明哪些 beans 由 JSF 托管。

  5. 在 faces-config.xml 文件中声明导航规则。

  6. 查看模型对象 Calculator

  7. 使用 CalculatorControllerCalculator 模型交谈。

  8. 创建 index.jsp 页面。

  9. 创建 calculator.jsp 页面。

  10. 创建 results.jsp 页面。

忽略第 1 步,因为这实际上只是设置,我将详细介绍每一步。

声明 Faces Servlet 和 Servlet 映射

为了使用 Faces,首先需要在 web.xml 文件中安装 Faces Servlet,如下所示:

<!-- Faces Servlet -->
<servlet>
    <servlet-name>Faces Servlet</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 技术,如下所示。

<!-- Faces Servlet Mapping -->
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>/calc/*</url-pattern>
</servlet-mapping>

上面的代码告诉 Faces Servlet 容器,将映射到 /calc/ 的所有请求发送到 Faces Servlet 进行处理。这允许 JSF 初始化 JSF 上下文和视图根。

指定 faces-config.xml 文件

如果您将外观配置文件命名为 faces-config.xml,并放在您的 Web 应用程序的 WEB-INF 目录中,那么 Faces Servlet 将自动找到并使用它(因为它是默认的)。另外,您也可以通过 web.xml 文件中的一个初始化参数 —— javax.faces.application.CONFIG_FILES —— 用一个以逗号分隔的文件列表作为参数,下载一个或多个应用程序配置文件。您可能愿意对除最简单的之外的所有 JSF Web 应用程序使用第二种方法。

声明 bean 托管

接下来,您将声明哪些 beans 由 JSF GUI 组件使用。该示例应用程序只有一个映射 bean。它配置在 faces-config.xml 中,如下所示:

<faces-config>
	...
  <managed-bean>
    <description>
      The "backing file" bean that backs up the calculator webapp
    </description>
    <managed-bean-name>CalcBean</managed-bean-name>
    <managed-bean-class>com.arcmind.jsfquickstart.controller.CalculatorConroller</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
</faces-config>

上面的配置告诉 JSF,您想要将一个 bean 添加到叫做 CalcBean 的 JSF 上下文。您可以向自己的托管 bean 调用任何事情。声明了 beans 之后,下一步是为应用程序指出高级别的导航规则。

声明导航规则

对于这个简单的应用程序,您只需要建立从 calculator.jsp 页面到 results.jsp 页面的导航规则,如下所示。

<navigation-rule>
  <from-view-id>/calculator.jsp</from-view-id>
  <navigation-case>
    <from-outcome>success</from-outcome>
    <to-view-id>/results.jsp</to-view-id>
  </navigation-case>
</navigation-rule>

上面的导航规则指出,如果一个操作从 /calculator.jsp 视图返回逻辑结果“success”,那么就会将用户转向 /results.jsp 视图。

查看模型对象

由于我的目标是演示如何开始进行 JSF 开发,所以我让模型对象保持非常简单。该应用程序的模型包含在一个模型对象中,如清单 1 所示。


清单 1. Calculator 应用程序的模型对象
package com.arcmind.jsfquickstart.model;
/**
 * Calculator
 *
 * @author Rick Hightower
 * @version 0.1
 */
public class Calculator {
    //~ Methods ----------------------------------------------------------------
    /**
     * add numbers.
     *
     * @param a first number
     * @param b second number
     *
     * @return result
     */
    public int add(int a, int b) {
        return a + b;
    }
    /**
     * multiply numbers.
     *
     * @param a first number
     * @param b second number
     *
     * @return result
     */
    public int multiply(int a, int b) {
        return a + b;
    }
    
}

随即,业务逻辑都设置好了。下一步是将业务逻辑粘接到 Web 应用程序接口中。

粘接模型和视图

控制器的目标是充当从模型到视图的粘合剂。Controller 对象的其中一个功能是保持模型对于视图技术不可知。正如从下面可以看到的,控制器指定三个 JavaBeans 属性,这些属性将用于收集输入和显示结果。这三个属性是:results(输出)、firstNumber(输入)和 secondNumber(输入)。Controller 也呈现两个操作,它们委派给 Calculator 对象中相同名称的操作。清单 2 展示了 CalculatorController 的代码。


清单 2. CalculatorController
package com.arcmind.jsfquickstart.controller;
import com.arcmind.jsfquickstart.model.Calculator;
/**
 * Calculator Controller
 *
 * @author $author$
 * @version $Revision$
 */
public class CalculatorConroller {
    //~ Instance fields --------------------------------------------------------
    /**
     * Represent the model object.
     */
    private Calculator calculator = new Calculator();
    /** First number used in operation. */
    private int firstNumber = 0;
    /** Result of operation on first number and second number. */
    private int result = 0;
    /** Second number used in operation. */
    private int secondNumber = 0;
    //~ Constructors -----------------------------------------------------------
    /**
     * Creates a new CalculatorConroller object.
     */
    public CalculatorConroller() {
        super();
    }
    //~ Methods ----------------------------------------------------------------
    /**
     * Calculator, this class represent the model.
     *
     * @param aCalculator The calculator to set.
     */
    public void setCalculator(Calculator aCalculator) {
        this.calculator = aCalculator;
    }
    /**
     * First Number property
     *
     * @param aFirstNumber first number
     */
    public void setFirstNumber(int aFirstNumber) {
        this.firstNumber = aFirstNumber;
    }
    /**
     * First number property
     *
     * @return First number.
     */
    public int getFirstNumber() {
        return firstNumber;
    }
    /**
     * Result of the operation on the first two numbers.
     *
     * @return Second Number.
     */
    public int getResult() {
        return result;
    }
    /**
     * Second number property
     *
     * @param aSecondNumber Second number.
     */
    public void setSecondNumber(int aSecondNumber) {
        this.secondNumber = aSecondNumber;
    }
    /**
     * Get second number.
     *
     * @return Second number.
     */
    public int getSecondNumber() {
        return secondNumber;
    }
    /**
     * Adds the first number and second number together.
     *
     * @return next logical outcome.
     */
    public String add() {
        
        result = calculator.add(firstNumber, secondNumber);
        return "success";
    }
    /**
     * Multiplies the first number and second number together.
     *
     * @return next logical outcome.
     */
    public String multiply() {
        result = calculator.multiply(firstNumber, secondNumber);
    	
        return "success";
    }
}

注意,在清单 2 中,multiplyadd 方法返回“success”。字符串 success 表示一个逻辑结果。注意它不是关键字。您在 faces-config.xml 中指定导航规则时,使用过字符串 success,因此,在 add(加) 或 multiply(乘)操作执行之后,应用程序将把用户转向到 results.jsp 页面。

随即,您就完成了 backing 代码。接下来指定呈现应用程序视图的 JSP 页面和组件树。

创建 index.jsp 页面

该应用程序中 index.jsp 页面的用途是,确保 /calculator.jsp 页面加载到 JSF 上下文中,以便该页面可以找到相应的视图根。index.jsp 页面看起来像下面这样:

<jsp:forward page="/calc/calculator.jsp" />

该页面所做的所有事情就是将用户重定向到 “calc” Web 上下文下的 calculator.jsp。这将 calculator.jsp 页面置于 JSF 上下文之下,在这里可以找到它的视图根。

创建 calculator.jsp 页面

calculator.jsp 页面是 Calculator 应用程序的视图的内容。该页面接受来自用户的两个数值,如图 3 所示。


图 3. Calculator 页面
Calculator 页面

因为该页面很复杂,所以我要向您一步一步地展示如何构建它。一开始是声明 JSF 标签库( taglib),如下所示:

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

上面的代码告诉 JSP 引擎,您想要使用两个 JSF 标签库,即 htmlcorehtml 标签库包含用于处理表单和其他特定于 HTML 的事情的所有标签。core 标签库包含所有的逻辑、验证、控制器和其他特定于 JSF 的标签。

一旦将该页面布局到普通 HTML 中,您就想要告诉 JSF 系统,您将使用 JSF 来管理组件。这可以通过使用 <f:view> 标签来做到,该标签通知容器,您将使用 JSF 来管理其中的组件。(这个标签应该读作“f 冒号 view”,特别强调这个冒号很重要,以免您出错!)

没有 <f:view>,JSF 就无法构建组件树,并且以后也无法查看已经创建好的组件树。像下面这样使用 <f:view> 标签:

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

上面的第一行是 <f:view> 的声明,告诉容器它是由 JSF 托管的。下一行是 <h:form> 标签,告诉 JSF 您在这里想要一个 HTML 表单。在呈现阶段,包含在表单组件中的组件将被查看到,并被要求呈现它们自己,因此它们将向输出产生标准的 HTML。

接下来,您告诉 JSF 您想在该表单中包含其他哪些组件。在 <h:form> 中,您声明一个 panelGridpanelGrid 是一个复合组件 —— 也就是说,一个包含其他组件的组件。panelGrid 指定其他组件的布局。panelGrid 的声明如清单 3 中所示。


清单 3. 声明 panelGrid
<h:panelGrid columns="3"> 
    <h:outputLabel value="First Number" for="firstNumber" />
    <h:inputText id="firstNumber" value="#{CalcBean.firstNumber}" required="true" />
        <h:message for="firstNumber" />	
    <h:outputLabel value="Second Number" for="secondNumber" />
    <h:inputText id="secondNumber" value="#{CalcBean.secondNumber}" required="true" />
        <h:message for="secondNumber" />
</h:panelGrid>

属性 column 被设置为 3,表明组件将被布局到一个具有 3 列的网格中。您添加 6 个组件到 panelGrid 中,也就是说 2 行。每行包含一个 outputLabel、一个 inputText 和一个 message。标签和消息被关联到 inputText 组件,因此,当一个验证错误或错误消息被关联到 textField 时,该消息就会展示在 message 组件中。这两个文本字段都是必需的,这意味着,如果在提交时没有它们的值,就会创建一条错误消息,而控制就会返回到该视图,即 /calculator.jsp。

注意,两个 inputFields 都对值属性使用 JSF EL (JavaServer Faces Expression Language) 值绑定(例如,value="#{CalcBean.firstNumber}")。乍一看,这很像 JSTL EL。但是 JSF EL 代码实际上将字段与 backing beans 属性的相应值相关联。该关联是反射性的,也就是说,如果 firstNumber 是 100,那么显示该表单时就会展示出 100。同样,如果用户提交了一个有效值,比如 200,那么 200 就成了 firstNumber 属性的新值。

一个更加常见(但也是更棘手)的方法是,用于 backing bean 通过属性暴露模型对象,并将这些模型对象属性绑定到字段。在本系列以后的文章中将会看到该方法的一个例子。

除了字段之外,通过在 panelGroup 中使用两个 commandButtoncalcForm 也与两个操作相关联,如下所示。

<h:panelGroup>
    <h:commandButton id="submitAdd" action="#{CalcBean.add}"  value="Add" />
    <h:commandButton id="submitMultiply" action="#{CalcBean.multiply}" value="Multiply" />
</h:panelGroup>

关于样式表

所有 JSF 组件的外观都是通过样式表类声明的:每个组件都有一个样式和一个 styleClass,前者用以与内联样式关联,后者用于将组件与 styleSheet 类关联。panelGrid 也有样式属性,用于将样式与行和列相关联。为了简单起见,在这第一篇文章中,我没有指定任何样式表。

panelGroup 在概念上类似于 panelGrid,只是它布局组件的方式不同。命令按钮使用 action="#{CalcBean.add}" 将按钮绑定到 backing bean 上的一个方法。因此,当用按钮提交表单时,关联的方法就会被调用(假设所有验证无误)。

至此,编写 JSF 应用程序的最艰难的工作已经完成了。最后两步将是微不足道的。

创建 results.jsp 页面

results.jsp 页面用于显示最终计算器操作的结果。它的定义如清单 4 中所示。

清单 4. results.jsp 页面

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
...
<f:view>
  First Number: <h:outputText id="firstNumber" value="#{CalcBean.firstNumber}"/> 
  <br />
  Second Number: <h:outputText id="secondNumber" value="#{CalcBean.secondNumber}"/>
  <br />
  Result: <h:outputText id="result" value="#{CalcBean.result}"/> 
  <br />
</f:view>

该 results.jsp 文件是一个相对简单的页面,用于将加法结果显示给用户。它是通过 <outputText> 标签来做到这一点。<outputText> 标签有一个 idvalue 属性。value 属性在呈现时将 bean 值输出为字符串。value 属性使用 JSF 将输出值绑定到 backing bean 属性(即 firstNumbersecondNumberresult)。

运行应用程序!

要运行该应用程序,请转到 war 文件被映射的页面。这导致 index.jsp 文件加载 calculator.jsp 页面。如果您在 firstNumber 字段或 secondNumber 字段输入一些有效的文本(例如,“abc”)并提交,那么您将被带回 /calculator.jsp 视图,并且会在相应的字段边上显示一条错误消息。如果您让 firstNumber 字段或 secondNumber 字段保持为空并提交,那么您将被带回 /calculator.jsp 视图,并且会在相应的字段边上显示一条错误消息。因此,您可以看到,在 JSF 中,一些验证几乎是自动的,只要指定字段是必需的,并将字段绑定到 int 属性即可。

图 4 展示了应用程序是如何处理验证和数据转换错误的。


图 4. 验证和数据转换错误
Calculator 页面




回页首


结束语

如果在阅读完这篇对 JSF 的介绍之后,您还有一点持怀疑态度,那么不要担心,您已经越过了最艰难的一道坎了。了解 JSF 的概念框架对于执行该技术来说已经成功了一大半,而且马上您就会认识到做这样的了解是值得的。

以防您还认为用 Struts 编写应用程序容易一些,我做了一个估计,创建本文的这个简单的 JSF 应用程序的 Struts 版本,至少要花费相当于这里所花费的两倍的精力。要用 Struts 构建这个相同的示例应用程序,需要两个操作类用于两个按钮,每个类又需要它自己的一组操作映射。您还需要一个操作映射用于加载第一个页面,这是在至少假设您遵循 Model 2 推荐的条件下的情况。要模仿 JSF 默认错误处理和验证,还必须配置 Struts 使用验证器框架,或者在 ActionForm 上的 validate 方法中实现等价的操作。您必须要么在 Struts 配中声明一个 DynaValidatorForm,要么创建一个 ActionForm 并覆盖 validate 方法,要么使用 ValidatorForm 的子类,并在验证器框架中放置钩子。最后,可能需要配置一些转向(可能是每个操作两组)或者一些由所有操作使用的全局转向。

除了加倍编码工作之外,Struts 要花费新开发人员更多的精力去学习它。因为我编写并讲授过 Struts 教程和 JSF 教程,所以我知道这一点。开发人员学习 JSF 容易,而学习 Struts 困难。我相信,JSF 设计中比 Struts 中考虑了更多的长远因素。JSF 只是更加逻辑性一些,但是它也是直观的。Struts 是被收集和演变的。JSF 是被指定和创建的。在我的书中,JSF 开发只是比 Struts 开发的生产效率更高。

这就结束了 JSF 系列中的第一篇文章。下一篇文章将接着本文进行介绍。我将介绍 JSF 请求处理生命周期的主要阶段,并将指出示例应用程序的不同部分适用于生命周期的哪个阶段。我还要介绍即时事件处理的概念,并试图让您更完整地了解 JSF 的组件事件模型,包括讨论了与该技术一同发布的许多内置组件。我还要谈论一点关于如何将 JavaScript 与 JSF 进行组合,所以请务必关注下一期的文章!



参考资料



关于作者

Rick Hightower 是 ArcMind 公司的 CTO。他是 Java Tools for Extreme Programming 一书的合著者之一,这是一本关于在 J2EE 开发中采用极限编程的书;他也是 Professional Struts 一书的合著者之一。他还在 Warner Onstine 出版了 JSF QuickStart 一书,本系列文章的一些素材就来自这本书中的一些例子。您可以通过 rhightower@arc-mind.com 与 Rick 联系。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款