[翻译] JSF for Nonbelievers: Clearing the FUD about JSF

[翻译] JSF for Nonbelievers: Clearing the FUD about JSF
JSF for Nonbelievers: Clearing the FUD about JSF -- Richard Hightower
来自:http://www-128.ibm.com/developerworks/library/j-jsf1/
翻译:zhouy
--------------------------------------------------------------------------------
恐惧、不确定和怀疑
JSF 的观念环绕着 JSF 技术已经有一段时间了,我觉得是阻止这种观念继续曼延的时候了——或者至少在两者这间取得平衡点。关于 JSF ,首当其充的误解,就是离不开拖拽式所见即所得工具进行 JSF 开发。其二, JSF 不支持类似 Struts MVC Model 2 框架。而最后一点,也是困扰 JSF 开发最大的一点,就是关于其开发困难度的说法。

在这个四小节的讨论中,我将尽我所能,通过教你如何利用JSF进行开发这种最实际的方法,来消除上述所有三种误解。事实上,如果你觉得JSF开发是件困难的事,那么很有可能是因为你没有正确的使用它——幸运的是要修正它并不难。我会让你对JSF有一个大体的观念,并且利用一个可工作的示例展示MVC和JSF的基本原理。在我们开始所有介绍之前,先花一分钟时间将事实不符的FUD区分清楚。

不要相信FUD

如前面提到的,关于JSF有三个大的误解,其一便是JSF要求所见即所得工具进行开发工作。这是错误的,就像许多Swing开发人员不使用所见即所得工具构建Swing应用程序一样,你不必需要所见即所得编辑器来构建JSF应用。事实上,不使用所见即所得工具来开发 JSF 会比传统的Model 2框架如Struts和WebWork开发来得简单。在稍后的章节中我会有详细的解释,但在这里请记住:JSF开发比Struts来得简单,即使不使用所见即所得工具!

关于JSF的另一个误解是,它不支持Model 2模型。现在,这种说法只是部分正确。事实是Model 2是一种建立在Servlets基础上的MVC瀑布模型版本。与Model 2面向无状态协议(HTTP)所不同的是,JSF支持富MVC模型——一种与传统的GUI应用更加相似的模型。虽然MVC的基础概念使得JSF框架的实现比其它框架困难,但在上层结构上许多实现JSF的工作已经为你做好,得到的结果是,付出减少,却能够获得更多的回报。

流传最广的关于JSF的误解是在于JSF的开发难度。我经常从人们口中听到这样的说法,他们阅读了大量的技术文档,但是并没有实际动手去尝试这项技术,所以我认为要消除这个虑虑很简单。如果你是通过 JSF 浩瀚无边的规格说明来建立你对JSF的理解,那么这些东西肯定会使你受惊吓。要明确的是,所有的这些规格本质上是给实现工具使用的,而不是应用程序开发者。如前所述,JSF是为方便应用程序开发者而设计的。

虽然从某种意义上说,JSF的组件基础和事件驱动的GUI式的开发模型对于Java世界来说是一种新鲜的事物,但是它其实早已在某些领域中存在了。Apple的WebObjects和ASP.net与JSF就十分的相似。Tapestry是一项开源的Web组件框架,它与JSF所采用的方法不同,但也是一种基于Web GUI组件框型的技术。

无论如何,关于FUD的讨论或许可以终止。解决关于JSF的编见最快捷的方法便是正确地研究这门技术,这正是我们稍后要做的事情。考虑到这或许是你第一次了解JSF,我将为你介绍它的总体概念。

给JSF初学者

类似于Swing和AWT,JSF提供了一套标准和可重用的GUI组件。JSF被用来构建Web应用表层,它提供如下开发优势:

  • 动作逻辑与表现逻辑的明显区分
  • 有状态事务的组件级别控制
  • 事件与服务端代码的轻松绑定
  • UI组件及Web层观念
  • 提供多样的、标准的开发商实现规则
一个典型的JSF应用包括以下几个部分:
  • 供管理应用状态和动作的JavaBeans组件
  • 事件驱动开发(通过与传统GUI开发类似的监听器来实现)
  • 展示MVC样式的页面,通过JSF组件树引用视图
虽然你将需要克服使用JSF过程中的观念上的困难,但是这些努力都将是值得的。JSF的组件状态管理,方便的用户输入校验,小粒度,组件基础的事件处理及方便的可扩展框架将使你的Web开发便得简单。我将在接下来的几章中用最详细的解释说明这些最重要的特征。

组件基础的框架模式

JSF为标准HTML中每个可用的输入域提供了组件标签,你也可以为你应用中的特殊目的定义自己的组件,或者也可以将多项HTML组件组合起来成为一个新的组合——例如一个数据采集组件可以包含三个下拉菜单。JSF组件是有状态的,组件的状态由JSF框架来提供,JSF用组件来处理HTML响应。

JSF的组件集包含事件发布模型、一个轻量级的IoC容量、拥有其它普遍的GUI特征的组件,包括可插入的渲染、服务器端校验、数据转换、页面导航控制等等。作为一种基于组件的框架,JSF极具可配置性和可护展性。大部分的JSF功能,比如导航和受管bean查询,可以被可插入组件所替换。这样的插入程度给开发人员构建Web应用GUI时提供了极大的弹性,允许开发人员将其它基于组件的技术应用于JSF的开发过程中来。比如说,你可以将JSF的内嵌IoC框架替换为更加全能的Spring的IoC/AOP以供受管bean查询。

JSF和JSP技术

一个JSF应用的用户表层被包含在JSP(JavaServer Pages)页面中。每个JSP页面包含有JSF组件,这些组件描绘了GUI功能。你可以在JSP页面里使用 JSF 定制标签库来渲染UI组件,注册事件句柄,实现组件与校验器的交互及组件与数据转换器的交互等等。

那就是说,JSF不是固定与JSP技术相关联。事实上,JSF标签仅仅引用组件以实现显示的目的。你会发现当你第一次修改一个JSP页面中某一JSF组件的属性,在重新载入页面的时候并没有发生应有的变化。这是因为标签在它自己的当前状态中进行查询,如果组件已经存在,标签就不会改变它的状态。组件模型允许控制代码改变一个组件的状态(例如将一个输入域置为不可用),当视图被展现的时候,组件树的当前状态也随着一览无余。

一个典型的JSF应用在UI层无需任何的Java代码,只需要很少的一部份JSTL EL( JSP 标准标签库,表达式语言)。前面已经提及,有非常多IDE工具可以帮助我们构建JSF应用,并且有越来越多的第三方JSF GUI组件市场。不使用所见即所得工具来编写JSF也是可行的。

JSF和MVC

JSF技术是在多年Java平台上的Web开发技术的总结的产物。这种趋势起源于JSP。JSP是一种很好的表现层,但同时它容易将Java代码与HTML页面混淆起来。另一个不好的原因与Model 1架构有关,它使得开发人员通过使用 <jsp:useBean> 标签将许多原本应该在后端处理的代码引入到Web页面中来。这种方法在简单的Web应用中可以运行得很好,但许多Java开发者不喜欢这种类似C++的静态引入组合方式,所以Model 2架构随之被引进。

本质上,Model 2架构是MVC Web应用的一种瀑布模型版本。在Model 2架构中控制器通过Servlets来表现,显示层则通过JSP页面来展现。Struts是一种最简单的Model 2实现,它使用Actions取代了Servlets。在Struts中应用的控制逻辑与数据层(通过ActionForms来展现)相分离。反对Struts的主要的不满在于,Struts更多偏向程序化,而非面向对象。WebWork和Spring MVC是Model 2的另外两种框架,它们比起Struts更大的改进在于尽量减少程序化,但是两者都没有Struts普及。另外两者也不像JSF般提供组件模型。

大多数Model 2框架真正的因素在于它们的事件模型显得过于单薄,留下了太多的工作需要开发人员自己来处理。一个富事件模型使多数用户所希望的交互变得简单。许多Model 2技术就像JPS一样,很容易将HTML布局及格式与GUI标签相混合,在表现上不像真正的组件。而一些Model 2的架构(如Struts)错误地将表现与状态分离,便得许多Java开发人员感觉他们像是在编写COBOL程序。

富MVC环境

JSF提供一种组件模型及比其它Model 2实现更为丰富的MVC环境。本质上说,JSF比Model 2架构更接近真正的MVC开发环境,虽然它仍然属于无状态协议。JSF能够构建比起其它Model 2框架更精彩的事件驱动 GUI 。JSF提供一系列事件选项如menu选项的selected事件,button的clicked事件等等,这与在其它Model 2框架中更多地依赖简单的“request received”不同。

JSF的易于翻转的事件模型允许应用程序更少地依赖HTTP实现细节,简化你的开发量。JSF改善了传统Model 2架构,它更容易将表现层和业务层移出控制器,并将业务逻辑从JSP页面中移出。事实上,简单的控制器类根本无需与JSF相关联,方便了对控制器类的测试。与真正的MVC架构不同,JSF不会在多于一个视点上发出多条事件,我们仍然是在处理一个无状态的协议,这使得这一点变得无关紧要。系统事件对一个视图的变动或更新通常来自于用户的请求。

JSF的MVC实现细节

在JSF的MVC实现中,作为映射的backing bean在视图和模型间扮演着协调的作用。正因如此,在backing bean里限制业务逻辑和持久层逻辑就显得犹为重要。一种普遍的做法是将业务逻辑置入应用模型中。这样的话backing bean仍会映射模型对象以供视图显示。另一种选择是将业务逻辑放入业务代理——一种与模型相作用的表层。

与JSP技术不同,JSF的视图实现是一种有状态组件模型。JSF的视图由两部分组成:根视图和JSP页面。根视图是UI组件的集合,它负责维护UI的状态。就如像Swing和AWT,JSF组件使用组合式设计模式来管理组件树,用按钮来管理事件句柄及动作方法。

Figure 1 通过MVC角度来透析的示例


 

一个 JSF 例子

在文章剩余的章节里,我将专注于构建一个 JSF 应用的过程。这个例子是 JSF 技术的一个非常简单的展现,主要表现在以下几个方面:

  • 如何设置 JSF 应用程序的格局
  • 如何为 JSF 配置 web.xml 文件
  • 如何为程序配置 faces-config.xml 文件
  • 编写模型(即 backing bean
  • 利用 JSP 技术构建视图
  • 利用传统标签库在根视图框建组件树
  • Form 的默认校验规则

例子是一个简单的计算器程序,使目标用户能够输入两个数并计算。因此页面上有两个文本域,两个标签,两处错误提示和一个提交按钮。文本域用来输入数字,标签用来标示输入的文本域,错误提示用来显示文本域中的输入在校验或数据转换时出现的错误。总共有三个 JSP 页面: index.jsp 用来重定向到 calculator.jsp 页面; calculator.jsp 用来显示上面提及的 GUI result.jsp 用来显示最后结果。一个受管 bean CalculatorController 作为 calculator.jsp result.jsp backing bean

  Figure 2 示例程序的第二张 MVC 视图


构建应用
 
为了构建计算器程序,你需要如下步骤:

  1.   收集 web.xml faces-config.xml 文件,可以在下面的示例源代码中找到。
  2. web.xml 中声明 Faces Servlet Faces Servlet 映射。
  3. web.xml 中指定 faces-config.xml
  4. faces-config.xml 中声明受管于 JSF bean
  5. faces-config.xml 中声明导航规则。
  6. 构建模型对象 Calculator
  7. CalculatorController Calculator 交互。
  8. 创建 index.jsp 页面。  
  9. 创建 calculator.jsp 页面。
  10. 创建 results.jsp 页面。

声明 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描述文件类似,所不同的是将request的掌握权转向JSF Servlet,而非自定义的Servlet。所有请求引用了f:viewJSP文件的request都将通过此Servlet。因此需要增加相应的映射,并且将允许使用JSFJSP技术通过映射装载进来。

<!-- 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配置文件命名为faces-config.xml并将其放置在WEB-INF目录下,Faces Servlet会查找到它并自动使用它。或者你也可以通过web.xml 中的一个初始化参数装载一个或多个应用配置文件——javax.faces.application.CONFIG_FILES——通过逗号将作为参数的文件列表分隔开来。或许你会使用第二种方法为所有的JSF应用程序作配置。

声明bean管理

接着是声明哪个beanJSF 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.CalculatorController
</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
</faces-config>

上面的配置告知JSF要往JSF环境中增加一个叫做CalcBeanbean,你也可以把受管bean取任何名字。Bean已经声明了,然后你要做的就是为应用定义导航规则。

定义导航规则

这样一个简单的应用,你只需使导航规则从calculator.jspresult.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”,那么它将用户转向/result.jsp页面。

  视计模型对象

  由于我们的目标是介绍如何开始JSF,所以我将模型对象设计得非常简单。这个模型包含在一个对象里,如Listing 1所示。

  Listing 1. The Calculator app's model object

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 表现层上。

  结合模型和视图

  控制器的目的就是将模型与视图结合在一起。控制器对象的一个作用就是保持模型与视图的不可知论。(?)如同你在下面所看到的,控制器定义了三个JavaBeans属性,通过这些属些控制输入和输出结果。这些属性是:results(作为输出)、firstNumber(作为输入)、secondNumber(作为输入)。Listing 2CalculatorController的代码。

  Listing 2. The CalculatorController

package com.arcmind.jsfquickstart.controller;

import com.arcmind.jsfquickstart.model.Calculator;

/**
 * Calculator Controller
 *
 * @author $author$
 * @version $Revision$
 */
public class CalculatorController {
    //~ 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 CalculatorController object.
     */
    public CalculatorController() {
        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";
    }
}

请注意,在
Listing 2 中乘法和加法都返回“ success ”。“ success ”表明一个逻辑输出,它不是保留字,它是与 faces-config.xml 的导航规则相结合的,通过增加或定义操作执行,页面将转向 result.jsp

  这样,你已经完成了后台代码工作。接下来我们将定义JSP页面及组件树以展示应用视图。

  创建index.jsp页面

  Index.jsp的目的是保证/calculator.jsp页面被正确的裁入JSF内容中,使得页面可以找到正确的根视图。

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

  这个页面全部所做的工作便是将用户重定向到/calc/calculator.jsp页面。这样便将calculator.jsp页面导向JSF内容中,使得JSF可以找到根视图。

  创建calculator.jsp页面

  Calculator.jsp是整个计算器应用程序的中心视图。这个页面获取用户输入的两个数字,如Figure 3所示。

Figure 3. The Calculator page
Calculator page

 

这一页较为复杂,我将一步一步进行讲解。首先你需要声明供JSF使用的标签库:

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

上述代码告诉
JSP 引擎你将使用两个 JSF 标签库: html core Html 标签库包含所有与 forms 和其它 HTML 规范有关的标签。 Core 标签库包含所有逻辑、校验、控制等 JSF 标签。

  一旦将页面展示在普通的HTML中,你会告诉JSF系统你将使用JSF来管理你的组件。通过使用<f:view>标签来实现这点,它会告知容器你将使用JSF来管理所有包含在它里面的组件。

  少了<f:view>JSF将无法构建组件树,稍后也无法在已经创建起来的组件树中进行查询。通过以下代码使用<f:view>

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

上面的第一行是<f:view>的声明,告知容器它受管于JSF。下一行是<h:form>标签,它告知JSF你需要在此建一个HTML FORM。在位于FORM组件内的组件容器的语法渲染期间,所有的组件将会被问询自动渲染,这样它们将会生成标准的HTML代码。

接下来,你告知JSFFORM里所需的其它组件。在<h:form>中定义了一个panelGridpanelGrid是一个组合组件——也就是一个组件里包含有其它的组件。panelGrid定义其它组件的布局,Listing 3显示如何定义panelGrid的代码:

Listing 3. Declaring the 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>

属性columns被定义为3表明所有组件将被布置在拥有3列空间的格局中。我们在panelGrid中加入了6个组件,共占2行。每一行包含一个outputLabel,一个inputText和一条messageLabelmessage都和inputText组件关联,因此当一个校验错误或或误信息关联到textField时,信息将会显示在message组件上。两个文本输入域都要求有,如果在提交的时候检测到无值,错误信息将会被创建,控制也将会转到这个视图来。

注意到两个inputFields都使用一条JSF表达式来做数值绑定。乍一看这很像一条JSTL表达式。但是,JSF表达式确实与绑定着后台代码相应字段的输入域相关联。这种关联是反向的,如果firstNumber100,那么在form显示的时候100也会被显示。同样,如果用户提交一个有效的值,例如200,那么200也将作为firstNumber的新值。

一个更加实用的目的是,后台代码通过绑定模型对象的属性的值到输入域中,从而将模型对象展现出来。你将在此节稍后看到关于此目的的例子。

除了输入域,calForm通过panelGroup中的两个commandButton与两个动作关联:

<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}”将按钮与后台动作绑定在一起。因此当这个form通过这个按钮提交的时候,相关联的方法便开始执行。

创建results.jsp页面

Results.jsp页面是用来显示最后计算结果。如Listing 4所示:

Listing 4. The results.jsp page

<%@ 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
是一个相对简单的页面,将加法运算的结果显示给用户。通过 <h:outputText> 标签来完成这一功能。 <h:outputText> 标签带有 id value 属性, value 属性输出值,它在渲染的时候将被当作 string 看待。 Value 属性通过 JSF 将要输出的值与后台代码的属性绑定在一起。

  运行程序!

运行war文件所映射的程序。Index.jsp页面将调用calculator.jsp,如果你在firstNumber域或secondNumber域中输入非法文本(如“abc”)提交,你将返回到/calculator.jsp视图并且相应的错误信息将会显示在页面上。如果你将firstNumbersecondNumber放空提交你也将会获得相应的错误结果。由此可以看出在JSF许多校验几乎是自动定义,只要你将输入域定义为required并且绑定相应的int属性的字段即可。

Figure 4显示应用程序如何处理校验和数据转换数据。

Figure 4. Validation and data conversion errors


 

总结

在这篇关于JSF的介绍中是否使你有些头晕?不用担心,你已经跨过了最坏的一道坎。了解JSF的框架概念是这场战役的一半,有过之而无不及,而且你将会很快意识到它的价值。

如果在阅读的过程中你想象使用Struts实现上述代码会更加简单,我估计它会耗费至少两倍精力。用Struts构建同样的应用,你需要为两个按钮创建两个action类,各自需要自己的对应的动作映射。而且需要一个动作映射来装载首页(假设你遵守Model 2的建议)。另外,模仿JSF默认的错误获取和校验机制,你需要为Struts配置校验框架或者实现在ActionForm中实现等值的validate方法。你还必须在Struts配置中定义DynaValidatorForm或者建立一个ActionForm重写validate方法,或者使用ValidatorForm的子类。最终,你或许(必须)需要为所有的action定义forward或者全局forward

不止双倍的代码工作,Struts对于初学者来说意味着需要花费更多的精力。我之所以知道这点,是因为我写过关于StrutsJSF课程的教材并且为我的学员们上过课。开发人员通常非常容易掌握JSF,但在学习Struts的过程中却倍受折磨。我相信更多有远见的人选择JSF,而非Struts。直觉上说,JSF更加合理。Struts已经被搁置,JSF被列入技术清单。在我的书中,JSF开发过程是简便的,并且比Struts更具有生产率。

这是JSF系列的第一篇文章的总结。在下一篇文章里我会继续这篇文章的话题,内容覆盖JSFrequest处理生命周期,指明生命周期中同一应用程序的不同部分。我还将介绍immediate event handling的概念,并且让你对JSF的组件事件模型有更全面了解,包括关于许多内嵌组件的讨论。我还将谈谈有关JSFJavaScript相结合的话题,请关注下个月的文章。

你可能感兴趣的:([翻译] JSF for Nonbelievers: Clearing the FUD about JSF)