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
视图
构建应用
为了构建计算器程序,你需要如下步骤:
-
收集
web.xml
和
faces-config.xml
文件,可以在下面的示例源代码中找到。
-
在
web.xml
中声明
Faces Servlet
和
Faces Servlet
映射。
-
在
web.xml
中指定
faces-config.xml
。
-
在
faces-config.xml
中声明受管于
JSF
的
bean
。
-
在
faces-config.xml
中声明导航规则。
-
构建模型对象
Calculator
。
-
用
CalculatorController
与
Calculator
交互。
-
创建
index.jsp
页面。
-
创建
calculator.jsp
页面。
-
创建
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:view的JSP文件的request都将通过此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配置文件命名为faces-config.xml并将其放置在WEB-INF目录下,Faces Servlet会查找到它并自动使用它。或者你也可以通过web.xml 中的一个初始化参数装载一个或多个应用配置文件——javax.faces.application.CONFIG_FILES——通过逗号将作为参数的文件列表分隔开来。或许你会使用第二种方法为所有的JSF应用程序作配置。
声明bean管理
接着是声明哪个bean被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.CalculatorController
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
</faces-config>
上面的配置告知JSF要往JSF环境中增加一个叫做CalcBean的bean,你也可以把受管bean取任何名字。Bean已经声明了,然后你要做的就是为应用定义导航规则。
定义导航规则
这样一个简单的应用,你只需使导航规则从calculator.jsp到result.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 2是CalculatorController的代码。
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
这一页较为复杂,我将一步一步进行讲解。首先你需要声明供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代码。
接下来,你告知JSF在FORM里所需的其它组件。在<h:form>中定义了一个panelGrid。panelGrid是一个组合组件——也就是一个组件里包含有其它的组件。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和一条message。Label和message都和inputText组件关联,因此当一个校验错误或或误信息关联到textField时,信息将会显示在message组件上。两个文本输入域都要求有,如果在提交的时候检测到无值,错误信息将会被创建,控制也将会转到这个视图来。
注意到两个inputFields都使用一条JSF表达式来做数值绑定。乍一看这很像一条JSTL表达式。但是,JSF表达式确实与绑定着后台代码相应字段的输入域相关联。这种关联是反向的,如果firstNumber是100,那么在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视图并且相应的错误信息将会显示在页面上。如果你将firstNumber或secondNumber放空提交你也将会获得相应的错误结果。由此可以看出在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对于初学者来说意味着需要花费更多的精力。我之所以知道这点,是因为我写过关于Struts和JSF课程的教材并且为我的学员们上过课。开发人员通常非常容易掌握JSF,但在学习Struts的过程中却倍受折磨。我相信更多有远见的人选择JSF,而非Struts。直觉上说,JSF更加合理。Struts已经被搁置,JSF被列入技术清单。在我的书中,JSF开发过程是简便的,并且比Struts更具有生产率。
这是JSF系列的第一篇文章的总结。在下一篇文章里我会继续这篇文章的话题,内容覆盖JSF的request处理生命周期,指明生命周期中同一应用程序的不同部分。我还将介绍immediate
event handling的概念,并且让你对JSF的组件事件模型有更全面了解,包括关于许多内嵌组件的讨论。我还将谈谈有关JSF与JavaScript相结合的话题,请关注下个月的文章。