内容


怀疑论者的 JSF

消除关于 JSF 的 FUD

JavaServer Faces 比想像的要容易

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 怀疑论者的 JSF

敬请期待该系列的后续内容。

此内容是该系列的一部分:怀疑论者的 JSF

敬请期待该系列的后续内容。

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 的大量实际工作已经不用您自己完成了,所以您的净付出减少了,而您的净受益就显著增加了。

关于 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 开发。

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 依赖于更加简单的“请求接受”。

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 角度来看的示例应用程序
从 MVC 角度来看的示例应用程序

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

一个 JSF 例子

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

  • 如何为部署布局 JSF 应用程序。
  • 如何为 JSF 配置 web.xml 文件。
  • 如何为应用程序配置 faces-config.xml。
  • 编写 Model beans(也叫做 backing beans)。
  • 使用 JSP 技术构造视图。
  • 使用自定义标签库在视图根中构造组件树。
  • 表单字段的默认验证。

该例是一个简单的 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 视图
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 页面
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>

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 页面
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 进行组合,所以请务必关注下一期的文章!


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Web development
ArticleID=58287
ArticleTitle=怀疑论者的 JSF: 消除关于 JSF 的 FUD
publish-date=03172005