JSF入门基础教程


JSF 入门 如果您是从使用的角度来看 JSF,则您不用理会 HTTP、资料转换等细节,JSF 将细节都隐藏起来了, 无论您是网页设计人员或是应用程序设计人员,都可以使用自己熟悉的方式来看 JSF。 入门 藉由以下的几个主题,可以大致了解 JSF 的轮廓与特性,我们来看看网页设计人员与应用程序设计人 员各负责什麼。 简介 JSF 第一个 JSF 程序 简单的导航 Navigation 导航规则设置 JSF Expression Language 国际化信息 Managed Beans JSF 使用 Bean 来达到逻辑层与表现层分离的目的,Bean 的管理集中在组态档案中,您只要修改组态档 案,就可以修改 Bean 之间的相依关係。 Backing Beans Beans 的组态与设定 Beans 上的 List, Map 资料转换与验证 转换器(Converter)协助模型与视图之间的资料转换,验证器(Validator)协助进行语意检验(Semantic Validation)。 标准转换器 自订转换器 标准验证器 自订验证器 错误信息处理 自订转换, 验证标签 事件处理 JSF 的事件模型提供一个近似的桌面 GUI 事件模式,让熟悉 GUI 设计的人员也能快速上手 Web 程序设计。 动作事件 即时事件 值变事件 Phase 事件 JSF 标签 网页设计人员要作的就是了解 JSF 的标签的使用方式,这就像是学习进阶的 HTML 标签,另一件事就 是与程序设计人员沟通好各个 Bean 的名称绑定。 标签入门 标签的相关属性查询,您可以参考 Tag Library Documentation,这边的介绍只是一些简单的入门实例。 简介 JSF 标准标签 输出类标签 输入类标签 命令类标签 选择类标签 一 选择类标签 二 其它标签 表格处理 对于必须使用表格方式呈现的资料,JSF 的 标签协助您进行动态表格资料的输出。 简单的表格 表头, 表尾 TableModel 类别 自订元件 JSF 让您可以自订元件,每个元件都是可替换的,这使得元件在搭配时更有弹性,但相对的却使开发 元件的过程复杂的多,这边对自订 JSF 元件只是个入门砖,更多有关自订元件的细节可得要专书来说明。 JSF 生命周期与元件概述 要开发 JSF 元件,您需要更深入了解 JSF 的一些处理细节,包括了 JSF 生命周期以及 JSF 框架。 JSF 生命周期 概述自订元件 简单元件实例 在不考虑元件有子元件的情况下,这边以实际的一个例子来说明开发元件的过程,至于考虑子元件的 情况请参考专书介绍。 编码, 解码 元件标签 使用自订元件 自订 Renderer 第一部分 入门 1 JSF 简介 Web 应用程序的开发与传统的单机程序开发在本质上存在著太多的差异,Web 应用程序开发人员至今 不可避免的必须处理 HTTP 的细节,而 HTTP 无状态的(stateless)本质,与传统应用程序必须维持程序运 行过程中的信息有明显的违背,再则 Web 应用程序面对网站上不同的使用者同时的存取,其执行安全问题 以及资料验证、转换处理等问题,又是复杂且难以解决的。 另一方面,本质上是静态的 HTML 与本质上是动态的应用程序又是一项违背,这造成不可避免的,处 理网页设计的美术人员与程序设计人员,必须被彼此加入至视图元件中的逻辑互相干扰,即便一些视图呈 现逻辑以标签的方式呈现,试图展现对网页设计美术人员的亲切,但它终究必须牵涉到相关的流程逻辑。 有很多方案试著解决种种的困境,而各自的著眼点各不相同,有的从程序设计人员的角度来解决,有 的从网页设计人员的角度来解决,各种的框架被提出,所造成的是各种不统一的标签与框架,为了促进产 能的整合开发环境(IDE)难以整合这些标签与框架,另一方面,开发人员的学习负担也不断的加重,他 们必须一人了解多个角色的工作。 JavaServer Faces 的提出在试图解决这个问题,它试图在不同的角度上提供网页设计人员、应用程序 设计人员、元件开发人员解决方案,让不同技术的人员可以彼此合作又不互相干扰,它综合了各家厂商现 有的技术特点,由 Java Community Process(JCP)团队研拟出来的一套标准,并在 2004 年三月发表了 JavaServer Faces 1.0 完成成果。 从网页设计人员的角度来看,JavaServer Faces 提供了一套像是新版本的 HTML 标签,但它不是静态 的,而是动态的,可以与后端的动态程序结合,但网页设计人员不需要理会后端的动态部份,网页设计人 员甚至不太需要接触 JSTL 这类的标签,也可以动态的展现资料(像是动态的查询表格内容),JavaServer Faces 提供标准的标签,这可以与网页编辑程序结合在一起,另一方面,JavaServer Faces 也允许您自订标 签。 从应用程序设计人员的角度来看,JavaServer Faces 提供一个与传统应用程序开发相类似的模型(当然 因某些本质上的差异,模型还是稍有不同),他们可以基于事件驱动来开发程序,不必关切 HTTP 的处理 细节,如果必须处理一些视觉元件的属性的话,他们也可以直接在整合开发环境上拖拉这些元件,点选设 定元件的属性,JavaServer Faces 甚至还为应用程序设计人员处理了物件与字串(HTTP 传送本质上就是字 串)间不匹配的转换问题。 从UI 元件开发人员的角度来看,他们可以设计通用的 UI 元件,让应用程序的开发产能提高,就如同 在设计 Swing 元件等,UI 开发人员可以独立开发,只要定义好相关的属性选项来调整细节,而不用受到网 页设计人员或应用程序设计人员的干扰。 三个角色的知识领域原则上可以互不干扰,根据您的角色,您只要了解其中一个知识领域,就可以运 用 JavaServer Faces,其它角色的知识领域您可以不用了解太多细节。 当然,就其中一个角色单独来看,JavaServer Faces 隐藏了许多细节,若要全盘了解,其实 JavaServer Faces 是复杂的,每一个处理的环境都值得深入探讨,所以学习 JavaServer Faces 时,您要选择的是通盘了 解,还是从使用的角度来了解,这就决定了您学习时所要花费的心力。 要使用 JSF,首先您要先取得 JavaServer Faces 参考完成(JavaServer Faces Reference Implementation), 在将来,JSF 会与 Container 整合在一起,届时您只要下载支持的 Container,就可以使用 JSF 的功能。 请至 JSF 官方网站的 下载区 下载参考完成,在下载压缩档并解压缩之后,将其 lib 目录下的 jar 档 案复制至您的 Web 应用程序的/WEB-INF/lib 目录下,另外您还需要 jstl.jar 与 standard.jar 档案,这些档 案您可以在 sample 目录下,解压缩当中的一个范例,在它的/WEB-INF/lib 目录下找到,将之一併复制至 您的 Web 应用程序的/WEB-INF/lib 目录下,您总共需要以下的档案: * jsf-impl.jar * jsf-api.jar * commons-digester.jar * commons-collections.jar * commons-beanutils.jar * jstl.jar * standard.jar 接下来配置 Web 应用程序的 web.xml,使用 JSF 时,所有的请求都透过 FacesServlet 来处理,您可以 如下定义: web.xml web.xml JSF Demo JSF Demo Faces Servlet javax.faces.webapp.FacesServlet 1 Faces Servlet *.faces index.html 在上面的定义中,我们将所有.faces 的请求交由 FaceServlet 来处理,FaceServlet 会唤起相对的.jsp 网 页,例如请求是/index.faces 的话,则实际上会唤起/index.jsp 网页,完成以上的配置,您就可以开始使用 JSF 了。 2 第一个 JSF 程序 现在可以开发一个简单的程序了,我们将设计一个简单的登入程序,使用者送出名称,之后由程序显 示使用者名称及欢迎信息。 2.1 程序开发人员 先看看应用程序开发人员要作些什麼事,我们撰写一个简单的 JavaBean: UserBean.java package onlyfun.caterpillar; public class UserBean { private String name; public void setName(String name) { this.name = name; } public String getName() { return name; } } 这个Bean 将储存使用者的名称,编译好之后放置在/WEB-INF/classes 下。 接下来设计页面流程,我们将先显示一个登入网页/pages/index.jsp,使用者填入名称并送出表单,之 后在/pages/welcome.jsp 中显示 Bean 中的使用者名称与欢迎信息。 为了让 JSF 知道我们所设计的 Bean 以及页面流程,我们定义一个/WEB-INF/faces-config.xml: faces-config.xml /pages/index.jsp login /pages/welcome.jsp user onlyfun.caterpillar.UserBean session 中,我们定义了页面流程,当请求来自中指定的页面,并且指定了 中的为 login 时,则会将请求导向至所指定的页面。 在中我们可以统一管理我们的 Bean,我们设定 Bean 物件的存活范围是 session,也就 是使用者开启浏览器与程序互动过程中都存活。 接下来要告诉网页设计人员的资讯是,他们可以使用的 Bean 名称,即中设定的 名称,以及上面所定义的页面流程。 2.2 网页设计人员 首先网页设计人员撰写 index.jsp 网页: index.jsp <%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=Big5"%> 第一个 JSF 程序

请输入您的名称

名称:

我们使用了 JSF 的 core 与 html 标签库,core 是有关于 UI 元件的处理,而 html 则是有关于 HTML 的 进阶标签。 与有类似的作用,当您要开始使用 JSF 元件时,这些元件一定要在 之间,就如同使用 HTML 时,所有的标签一定要在与< /html>之间。 html 标签库中几乎都是与 HTML 标签相关的进阶标签,会产生一个表单,我们使用来显示 user 这个 Bean 物件的 name 属性,而会产生一个提交按钮,我们在 action 属性中指定将根据之前定义的 login 页面流程中前往 welcome.jsp 页面。 网页设计人员不必理会表单传送之后要作些什麼,他只要设计好欢迎页面就好了: welcome.jsp <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=Big5"%> 第一个 JSF 程序 您好!

欢迎使用 JavaServer Faces!

这个页面没什麼需要解释的了,如您所看到的,在网页上没有程序逻辑,网页设计人员所作的就是遵 照页面流程,使用相关名称取出资料,而不用担心实际上程序是如何运作的。 接下来启动 Container ,连接上您的应用程序网址,例如: http://localhost:8080/jsfDemo/pages/index.faces,填入名称并送出表单,您的欢迎页面就会显示了。 3 简单的导航 Navigation 在 第一个 JSF 程序 中,我们简单的定义了页面的流程由 index.jsp 到 welcome.jsp,接下来我们扩充 程序,让它可以根据使用者输入的名称与密码是否正确,决定要显示欢迎信息或是将使用者送回原页面进 行重新登入。 首先我们修改一下 UserBean: UserBean.java package onlyfun.caterpillar; public class UserBean { private String name; private String password; private String errMessage; public void setName(String name) { this.name = name; } public String getName() { return name; } public void setPassword(String password) { this.password = password; } public String getPassword() { return password; } public void setErrMessage(String errMessage) { this.errMessage = errMessage; } public String getErrMessage() { return errMessage; } public String verify() { if(!name.equals("justin") || !password.equals("123456")) { errMessage = "名称或密码错误"; return "failure"; } else { return "success"; } } } 在UserBean 中,我们增加了密码与错误信息属性,在 verify()方法中,我们检查使用者名称与密码, 它传回一个字串,"failure"表示登入错误,并会设定错误信息,而"success"表示登入正确,这个传回的字串 将决定页面的流程。 接下来我们修改一下 faces-config.xml 中的页面流程定义: faces-config.xml /pages/index.jsp success /pages/welcome.jsp failure /pages/index.jsp user onlyfun.caterpillar.UserBean session 根据上面的定义,当传回的字串是"success"时,将前往 welcome.jsp,如果是"failure"的话,将送回 index.jsp。 接下来告诉网页设计人员 Bean 名称与相关属性,以及决定页面流程的 verify 名称,我们修改 index.jsp 如下: index.jsp <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=Big5"%> 第一个 JSF 程序

请输入您的名称

名称:

密码:

当要根据 verify 运行结果来决定页面流程时,action 属性中使用 JSF Expression Language "#{user.verify}",如此 JSF 就知道必须根据 verify 传回的结果来导航页面。 可以取出指定的 Bean 之属性值,当使用者因验证错误而被送回原页面时,这个错误信 息就可以显示在页面上。 4 导航规则的设置 在JSF 中是根据 faces-config.xml 中设定,以决定在符合的条件成立时,该连结至哪 一个页面,一个基本的设定如下: .... /pages/index.jsp success /pages/welcome.jsp failure /pages/index.jsp .... 对于JSF,每一个视图(View)都有一个独特的识别(identifier),称之为 View ID,在 JSF 中的 View ID 是从 Web 应用程序的环境相对路径开始计算,设定时都是以/作为开头,如果您请求时的路径是 /pages/index.faces,则 JSF 会将副档名改为/pages/index.jsp,以此作为 view-id。 在 中的 是个选择性的定义,它规定了来源页面的条件, 中定义各种导览条件,定义当表单结果符合的条件时,各自改导向哪一个 目的页面,目的页面是在中定义。 您还可以在中加入,进一步规范表单结果必须根据哪一个动作方法 (action method),当中是使用 JSF Expression Language 来设定,例如: .... /pages/index.jsp #{user.verify} success /pages/welcome.jsp .... .... 在导航时,预设都是使用 forward 的方式,您可以在中加入一个,让 JSF 发出让浏览器重新导向(redirect)的 header,让浏览器主动要求新网页,例如: .... /pages/index.jsp success /pages/welcome.jsp .... .... 您的来源网页可能是某个特定模组,例如在/admin/下的页面,您可以在中使用 wildcards,也就是使用 * 字元,例如: .... /admin/* #{user.verify} success /pages/welcome.jsp .... .... 在上面的设定中,只要来源网页是从/admin 来的,都可以开始测试接下来的如果没有设定,表示来源网页不作限制,您也可以使用 * 显式的在定义档中表明,例 如: .... /* .... .... 或者是这样: .... * .... .... 5 JSF JSF Expression Language JSF Expression Language 搭配 JSF 标签来使用,是用来存取资料物件的一个简易语言。 JSF EL 是以#开始,将变量或运算符放置在 Unknown macro: { 与 } 之间,例如: #{someBeanName} 变量名称可以是 faces-config.xml 中定义的名称,如果是 Bean 的话,可以透过使用 '.' 运算符来存取 它的属性,例如: ... ... 在JSF 标签的属性上," 与 " (或'与')之间如果含有 EL,则会加以运算,您也可以这麼使用它: ... 名称, 年龄: ... 一个执行的结果可能是这样显示的: 名称, 年龄:Justin, 29 EL 的变量名也可以程序执行过程中所宣告的名称,或是 JSF EL 预设的隐含物件,例如下面的程序使 用 param 隐含物件来取得使用者输入的参数: <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html; charset=Big5"%> 您好, param 是 JSF EL 预设的隐含物件变量,它代表 request 所有参数的集合,实际是一个 java.util.Map 型 态物件,JSF 所提供的隐含物件,大致上对应于 JSP 隐含物件,不过 JSF 隐含物件移除了 pageScope 与 pageContext,而增加了 facesContext 与 view,它们分别对应于 javax.faces.context.FacesContext 与 javax.faces.component.UIViewRoot。 对于Map 型态物件,我们可以使用 '.' 运算子指定 key 值来取出对应的 value,也可以使用 [ 与 ] 来 指定,例如: ... 您好, ... 在 [ 与 ] 之间,也可以放置其它的变量值,例如: ... ... 如果变量是 List 型态或阵列的话,则可以在 [] 中指定索引,例如: .... .... 您也可以指定字面常数,对于 true、false、字串、数字,JSF EL 会尝试进行转换,例如: .... .... .... 如果要输出字串,必须以单引号 ' 或双引数 " 括住,如此才不会被认为是变量名称。 在声明变量名称时,要留意不可与 JSF 的保留字或关键字同名,例如不可取以下这些名称: true false null div mod and or not eq ne lt gt le ge instanceof empty 使用EL,您可以直接实行一些算术运算、逻辑运算与关係运算,其使用就如同在一般常见的程序语 言中之运算。 算术运算子有:加法 (+), 减法 (-), 乘法 (*), 除法 (/ or div) 与餘除 (% or mod) 。下面是算术运算的 一些例子: 运算符 结果 #{1} 1 #{1 + 2} 3 #{1.2 + 2.3} 3.5 #{1.2E4 + 1.4} 12001.4 #{-4 - 2} -6 #{21 * 2} 42 #{3/4} 0.75 #{3 div 4} 0.75,除法 #{3/0} Infinity #{10%4} 2 #{10 mod 4} 2,也是餘除 #{(1==2) ? 3 : 4} 4 如同在 Java 语法一样 ( expression ? result1 : result2)是个三元运算,expression 为 true 显示 result1,false 显示 result2。 逻辑运算有:and(或&&)、or(或!!)、not(或!)。一些例子为: 运算符 结果 #{true and false} false #{true or false} true #{not true} false 关係运算有:小于 Less-than (< or lt)、大于 Greater-than (> or gt)、小于或等于 Less-than-or-equal (<= or le)、大于或等于 Greater-than-or-equal (>= or ge)、等于 Equal (== or eq)、不等于 Not Equal (!= or ne),由英 文名称可以得到 lt、gt 等运算子之缩写词,以下是 Tomcat 的一些例子: 运算符 结果 #{1 < 2} true #{1 lt 2} true #{1 > (4/2)} false #{1 > (4/2)} false #{4.0 >= 3} true #{4.0 ge 3} true #{4 <= 3} false #{4 le 3} false #{100.0 == 100} true #{100.0 eq 100} true #{(10*10) != 100} false #{(10*10) ne 100} false 左边是运算子的使用方式,右边的是运算结果,关係运算也可以用来比较字元或字串,按字典顺序来 决定比较结果,例如: 运算符 结果 #{'a' < 'b'} true #{'hip' > 'hit'} false #{'4' > 3} true EL 运算子的执行优先顺序与 Java 运算子对应,如果有疑虑的话,也可以使用括号()来自行决定先后 顺序。 6 国际化 JSF 的国际化(Internnationalization)信息处理是基于 Java 对国际化的支持,您可以在一个信息资源档 中统一管理信息资源,资源档的名称是.properties,而内容是名称与值的配对,例如: 资源档 messages.properties 的内容: titleText=JSF Demo hintText=Please input your name and password nameText=name passText=password commandText=Submit 资源档名称由 basename 加上语言与地区来组成,例如: * basename.properties * basename_en.properties * basename_zh_TW.properties 没有指定语言与地区的 basename 是预设的资源档名称,JSF 会根据浏览器送来的 Accept-Language header 中的内容来决定该使用哪一个资源档名称,例如: Accept-Language: zh_TW, en-US, en 如果浏览器送来这些 header,则预设会使用繁体中文,接著是美式英文,再来是英文语系,如果找不 到对应的信息资源档,则会使用预设的信息资源档。 由于信息资源档必须是 ISO-8859-1 编码,所以对于非西方语系的处理,必须先将之转换为 Java Unicode Escape 格式,例如您可以先在信息资源档中写下以下的内容: messages_zh_TW.txt titleText=JSF 示范 hintText=请输入名称与密码 nameText=名称 passText=密码 commandText=送出 然后使用 JDK 的工具程序 native2ascii 来转换,例如: native2ascii -encoding Big5 messages_zh_TW.txt messages_zh_TW.properties 转换后的内容会如下: messages_zh_TW.properties titleText=JSF\u793a\u7bc4 hintText=\u8acb\u8f38\u5165\u540d\u7a31\u8207\u5bc6\u78bc nameText=\u540d\u7a31 passText=\u5bc6\u78bc commandText=\u9001\u51fa 接下来您可以使用标签来指定载入信息资源,一个例子如下: index.jsp index.jsp <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=UTF8"%> <h:outputText value="#{msgs.titleText}"/>

:

:

如此一来,如果您的浏览器预设接受 zh_TW 语系的话,则页面上就可以显示中文,否则预设将以英 文显示,也就是 messages.properties 的内容,为了能显示多国语系,我们设定网页编码为 UTF8。 可以设定 locale 属性,直接指定所要使用的语系,例如: 直接指定以上的话,则会使用繁体中文来显示,JSF会根据的basename属性加上 的 locale 属性来决定要使用哪一个信息资源档,就上例而言,就是使用 messages_zh_TW.properties,如果 设定为以下的话,就会使用 messages_en.properties: 您也可以在 faces-config.xml 中设定语系,例如: en zh_TW ..... 一定有一个,而 可以有好几个,这告诉 JSF 您的应用 程序支持哪些语系。 当然,如果您可以提供一个选项让使用者选择自己的语系会是更好的方式,例如根据 user 这个 Bean 的 locale 属性来决定页面语系: 在页面中设定一个表单,可以让使用者选择语系,例如设定单选钮: 第二部分 Managed Beans 1 Backing Beans JSF 使用 JavaBean 来达到程序逻辑与视图分离的目的,在 JSF 中的 Bean 其角色是属于 Backing Bean, 又称之为 Glue Bean,其作用是在真正的业务逻辑 Bean 及 UI 元件之间搭起桥樑,在 Backing Bean 中会呼 叫业务逻辑 Bean 处理使用者的请求,或者是将业务处理结果放置其中,等待 UI 元件取出当中的值并显示 结果给使用者。 JSF 将 Bean 的管理集中在 faces-config.xml 中,一个例子如下: .... user onlyfun.caterpillar.UserBean session .... 这个例子我们在 第一个 JSF 程序 看过,设定所要使用的 Bean 类别, 设定之名称,可供我们在 JSF 页面上使用 Expression Language 来取得或设定 Bean 的 属性,例如: 设定 Bean 的存活范围,您可以设定为 request、session 与 application,设定为 request 时,Bean 的存活时间为请求阶段,设定为 session 则在使用者应用程序交互开始,直到关闭浏览器 或显式的结束会话为止(例如注销程序),设定为 application 的话,则 Bean 会一直存活,直到应用程序关 闭为止。 您还可以将存活范围设定为 none,当设定为 none 时会在需要的时候生成一个新的 Bean,例如您在一 个 method 中想要生成一个临时的 Bean,就可以将之设定为 none。 在 JSF 页面上要取得 Bean 的属性,是使用 JSF 表示语言 (Expression Language),要注意到的是,JSF 表示语言是写成 #{expression},而 JSP 表示语言 是写成 ${expression},因为表示层可能是使用 JSP,所 以必须特别区分,另外要注意的是,JSF 的标签上的属性设定时,只接受 JSF 表示语言。 2 Beans 的组态与设定 JSF 预设会读取 faces-config.xml 中关于 Bean 的定义,如果想要自行设置定义档的名称,我们是在 web.xml 中提供 javax.faces.CONFIG_FILES 参数,例如: javax.faces.CONFIG_FILES /WEB-INF/beans.xml ... 定义档可以有多个,中间以 "," 区隔,例如: /WEB-INF/navigation.xml , /WEB-INF/beans.xml 一个Bean 最基本要定义 Bean 的名称、类别与存活范围,例如: .... user onlyfun.caterpillar.UserBean session .... 如果要在其它类别中取得 Bean 物件,则可以先取得 javax.faces.context.FacesContext,它代表了 JSF 目 前的执行环境物件,接著尝试取得 javax.faces.el.ValueBinding 物件,从中取得指定的 Bean 物件,例如: FacesContext context = FacesContext.getCurrentInstance(); ValueBinding binding = context.getApplication().createValueBinding("#{user}"); UserBean user = (UserBean) binding.getValue(context); 如果只是要尝试取得 Bean 的某个属性,则可以如下: FacesContext context = FacesContext.getCurrentInstance(); ValueBinding binding = context.getApplication().createValueBinding("#{user.name}"); String name = (String) binding.getValue(context); 如果有必要在启始 Bean 时,自动设置属性的初始值,则可以如下设定: .... user onlyfun.caterpillar.UserBean session name caterpillar password 123456 .... 如果要设定属性为 null 值,则可以使用标签,例如: .... name password .... 当然,您的属性不一定是字串值,也许会是 int、float、boolean 等等型态,您可以设定 值时指 定这些值的字串名称,JSF 会尝试进行转换,例如设定为 true 时,会尝试使用 Boolean.valueOf()方法转换 为 boolean 的 true,以下是一些可能进行的转换: 型态 转换 short、int、long、float、double、 byte,或相应的 Wrapper 类别 尝试使用 Wrapper 的 valueOf()进行转换,如果没有设置, 则设为 0 boolean 或 Boolean 尝试使用 Boolean.valueOf()进行转换,如果没有设置,则设 为 false char 或 Character 取设置的第一个字元,如果没有设置,则设为 0 String 或 Object 即设定的字串值,如果没有设定,则为空字串 new String("") 您也可以将其它产生的 Bean 设定给另一个 Bean 的属性,例如: .... user onlyfun.caterpillar.UserBean session other onlyfun.caterpillar.OtherBean session user #{user} .... 在上面的设定中,在 OtherBean 中的 user 属性,接受一个 UserBean 型态的物件,我们设定为前一个 名称为 user 的 UserBean 物件。 3 Beans 上的 List, Map 如果您的 Bean 上有接受 List 或 Map 型态的属性,则您也可以在组态档案中直接设定这些属性的值, 一个例子如下: .... someBean onlyfun.caterpillar.SomeBean session someProperty java.lang.Integer 1 2 3 .... 这是一个设定接受 List 型态的属性,我们使用标签指定将设定一个 List 物件,其中 指定将存入 List 的类型,而指定其值,如果是基本类型,则会尝试使用指定的 来作 Wrapper 类别。 设定Map 的话,则是使用标签,例如: .... someBean onlyfun.caterpillar.SomeBean session someProperty java.lang.Integer someKey1 100 someKey2 200 .... 由于Map 物件是以 key-value 对的方式来存入,所以我们在每一个中使用 标签来分别指定。 您也可以直接像设定 Bean 一样,设定一个 List 或 Map 物件,例如在 JSF 附的范例中,有这样的设定: .... Special expense item types specialTypes java.util.TreeMap application java.lang.Integer Presentation Material 100 Software 101 Balloons 102 .... 而范例中另一个设定 List 的例子如下: .... statusStrings java.util.ArrayList request Open Submitted Accepted Rejected .... 第三部分 资料转换与验证 1 标准转换器 Web 应用程序与浏览器之间是使用 HTTP 进行沟通,所有传送的资料基本上都是字串文字,而 Java 应用程序本身基本上则是物件,所以物件资料必须经由转换传送给浏览器,而浏览器送来的资料也必须转 换为物件才能使用。 JSF 定义了一系列标准的转换器(Converter),对于基本资料类型(primitive type)或是其 Wrapper 类 别,JSF 会使用 javax.faces.Boolean 、 javax.faces.Byte 、 javax.faces.Character 、 javax.faces.Double 、 javax.faces.Float、 javax.faces.Integer、javax.faces.Long、javax.faces.Short 等自动进行转换,对于 BigDecimal、 BigInteger,则会使用 javax.faces.BigDecimal、javax.faces.BigInteger 自动进行转换。 至于DateTime、Number,我们可以使用标签进行转换,它们 各自提供有一些简单的属性,可以让我们在转换时指定一些转换的格式细节。 来看个简单的例子,首先我们定义一个简单的 Bean: UserBean.java UserBean.java package onlyfun.caterpillar; import java.util.Date; public class UserBean { private Date date = new Date(); public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } } 这个Bean 的属性接受 Date 型态的参数,按理来说,接收到 HTTP 传来的资料中若有相关的日期资讯, 我们必须剖析这个资讯,再转换为 Date 物件,然而我们可以使用 JSF 的标准转换器来协助这项工作,例如: index.jsp <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=Big5"%> 转换器示范 设定的日期是:
中,我们使用 pattern 指定日期的样式为 dd/MM/yyyy,即「日/月/西元」格式, 如果转换错误,则可以显示错误信息,for 属性参考至 的 id 属性,表示将有关 dateField 的错误信息显示出来。 假设faces-config.xml 是这样定义的: faces-config.xml /* show /pages/index.jsp user onlyfun.caterpillar.UserBean session 首次连上页面时显示的画面如下: 如您所看到的,转换器自动依 pattern 设定的样式将 Date 物件格式化了,当您依格式输入资料并送出 后,转换器也会自动将您输入的资料转换为 Date 物件,如果转换时发生错误,则会出现以下的信息: 标签还有几个可用的属性,您可以参考 Tag Library Documentation 的说明,而依 照类似的方式,您也可以使用来转换数值。 您还可以参考 Using the Standard Converters 这篇文章中有关于标准转换器的说明。 2 自订转换器 除了使用标准的转换器之外,您还可以自行定制您的转换器,您可以完成 javax.faces.convert.Converter 接口,这个接口有两个要完成的方法: public Object getAsObject(FacesContext context, UIComponent component, String str); public String getAsString(FacesContext context, UIComponent component, Object obj); 简单的说,第一个方法会接收从客户端经由 HTTP 传来的字串资料,您在第一个方法中将之转换为您 的自订物件,这个自订物件将会自动设定给您指定的 Bean 物件;第二个方法就是将从您的 Bean 物件得到 的物件转换为字串,如此才能藉由 HTTP 传回给客户端。 直接以一个简单的例子来作说明,假设您有一个 User 类别: User.java package onlyfun.caterpillar; public class User { private String firstName; private String lastName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } } 这个User 类别是我们转换器的目标物件,而您有一个 GuestBean 类别: GuestBean.java package onlyfun.caterpillar; public class GuestBean { private User user; public void setUser(User user) { this.user = user; } public User getUser() { return user; } } 这个Bean 上的属性直接传回或接受 User 型态的参数,我们来完成一个简单的转换器,为 HTTP 字串 与 User 物件进行转换: UserConverter.java package onlyfun.caterpillar; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.convert.Converter; import javax.faces.convert.ConverterException; public class UserConverter implements Converter { public Object getAsObject(FacesContext context, UIComponent component, String str) throws ConverterException { String[] strs = str.split(","); User user = new User(); try { user.setFirstName(strs[0]); user.setLastName(strs[1]); } catch(Exception e) { // 转换错误,简单的丢出例外 throw new ConverterException(); } return user; } public String getAsString(FacesContext context, UIComponent component, Object obj) throws ConverterException { String firstName = ((User) obj).getFirstName(); String lastName = ((User) obj).getLastName(); return firstName + "," + lastName; } } 完成这个转换器,我们要告诉 JSF 这件事,这是在 faces-config.xml 中完成注册: faces-config.xml /* show /pages/index.jsp onlyfun.caterpillar.User onlyfun.caterpillar.UserConverter guest onlyfun.caterpillar.GuestBean session 注册转换器时,需提供转换器识别(Converter ID)与转换器类别,接下来要在 JSF 页面中使用转换器 的话,就是指定所要使用的转换器识别,例如: index.jsp <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=Big5"%> 自订转换器 Guest 名称是:
您也可以标签并使用 converterId 属性来指定转换器,例如: 除了向 JSF 注册转换器之外,还有一个方式可以不用注册,就是直接在 Bean 上提供一个取得转换器 的方法,例如: GuestBean.java package onlyfun.caterpillar; import javax.faces.convert.Converter; public class GuestBean { private User user; private Converter converter = new UserConverter(); public void setUser(User user) { this.user = user; } public User getUser() { return user; } public Converter getConverter() { return converter; } } 之后可以直接结合 JSF Expression Language 来指定转换器: 3 标准验证器 当应用程序要求使用者输入资料时,必然考虑到使用者输入资料之正确性,对于使用者的输入必须进 行检验,检验必要的两种验证是语法检验(Synatic Validation)与语意检验(Semantic Validation)。 语法检验是要检查使用者输入的资料是否合乎我们所要求的格式,最基本的就是检查使用者是否填入 了栏位值,或是栏位值的长度、大小值等等是否符合要求。语意检验是在语法检验之后,在格式符合需求 之后,我们进一步验证使用者输入的资料语意上是否正确,例如检查使用者的名称与密码是否匹配。 在 简单的导航 (Navigation) 中,我们对使用者名称与密码检查是否匹配,这是语意检验,我们可以 使用 JSF 所提供的标准验证器,为其加入语法检验,例如: index.jsp <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=Big5"%> 验证器示范

请输入您的名称

名称:

密码:

、中,我们设定了 required 属性为 true,这表示这个栏位一定要输入值, 我们也在设定了,并设定其 minimum 属性为 6,这表示这个栏位最少需 要 6 个字元。 这一次在错误信息的显示上,我们使用标签,当有验证错误发生时,相关的错误信息会 收集起来,使用标签可以一次将所有的错误信息显示出来。 下面是一个验证错误的信息显示: JSF 提供了三种标准验证器:, 您可以分别查询它们的 Tag Library Documentation,了解他们有哪些属性可以使用,或者是参考 Using the Standard Validators 这篇文章中有关于标准验证器的说明。 4 自订验证器 您可以自订自己的验证器,所需要的是完成 javax.faces.validator.Validator 接口,例如我们完成一个简 单的密码验证器,检查字元长度,以及密码中是否包括字元与数字: PasswordValidator.java package onlyfun.caterpillar; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.validator.Validator; import javax.faces.validator.ValidatorException; public class PasswordValidator implements Validator { public void validate(FacesContext context, UIComponent component, Object obj) throws ValidatorException { String password = (String) obj; if(password.length() < 6) { FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_ERROR, "字元长度小于 6", "字元长度不得小于 6"); throw new ValidatorException(message); } if(!password.matches(".+[0-9]+")) { FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_ERROR, "密码必须包括字元与数字", "密码必须是字元加数字所组成"); throw new ValidatorException(message); } } } 您要完成 javax.faces.validator.Validator 界面中的 validate()方法,如果验证错误,则丢出一个 ValidatorException,它接受一个 FacesMessage 物件,这个物件接受三个参数,分别表示信息的严重程度 (INFO、 WARN、ERROR、FATAL)、信息概述与详细信息内容,这些信息将可以使用标签显示在页面上。 接下来要在 faces-config.xml 中注册验证器的识别(Validater ID),要加入以下的内容: faces-config.xml faces-config.xml .... onlyfun.caterpillar.Password onlyfun.caterpillar.PasswordValidator .... 要使用自订的验证器,我们可以使用标签并设定 validatorId 属性,例如: ....

.... 您也可以让 Bean 自行负责验证的工作,可以在 Bean 上提供一个验证方法,这个方法没有传回值,并 可以接收 FacesContext、UIComponent、Object 三个参数,例如: UserBean.java package onlyfun.caterpillar; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.validator.ValidatorException; public class UserBean { .... public void validate(FacesContext context, UIComponent component, Object obj) throws ValidatorException { String password = (String) obj; if(password.length() < 6) { FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_ERROR, "字元长度小于 6", "字元长度不得小于 6"); throw new ValidatorException(message); } if(!password.matches(".+[0-9]+")) { FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_ERROR, "密码必须包括字元与数字", "密码必须是字元加数字所组成"); throw new ValidatorException(message); } } } 接著可以在页面下如下使用验证器: ..... .... 5 错误信息处理 在使用标准转换器或验证器时,当发生错误时,会有一些预设的错误信息显示,这些信息可以使用 标签来显示出来,而这些预设的错误信息也是可以修改的,您所要作的是提供 一个信息资源档案,例如: messages.properties javax.faces.component.UIInput.CONVERSION=Format Error. javax.faces.component.UIInput.REQUIRED=Please input your data. .... javax.faces.component.UIInput.CONVERSION 是用来设定当转换器发现错误时显示的信息,而 javax.faces.component.UIInput.REQUIRED 是在标签设定了 required 为 true,而使用者没有在栏位输入时显 示的错误信息。 您要在 faces-config.xml 中告诉 JSF 您使用的信息档案名称,例如: faces-config.xml en zh_TW messages ..... 在这边我们设定了信息档案的名称为 messages_xx_YY.properties,其中 xx_YY 是根据您的 Locale 来决 定,转换器或验证器的错误信息如果有设定的话,就使用设定值,如果没有设定的话,就使用预设值。 验证器错误信息,除了上面的 javax.faces.component.UIInput.REQUIRED 之外,还有以下的几个: 信息识别 预设信息 用于 javax.faces.validator.NOT_IN_ RANGE Validation Error: Specified attribute is not between the expected values of {0} and {1}. DoubleRangeValidator 于 LongRangeValidator,{0}于{1}分 别代表 minimum 于 maximum 所设 定的属性 javax.faces.validator.DoubleRa ngeValidator.MAXIMUM、 javax.faces.validator.LongRang eValidator.MAXIMUM Validation Error: Value is greater than allowable maximum of '{0}'. DoubleRangeValidator 或 LongRangeValidator,{0}表示 maximum 属性 javax.faces.validator.DoubleRa ngeValidator.MINIMUM、 javax.faces.validator.LongRang eValidator.MINIMUM Validation Error: Value is less than allowable minimum of '{0}'. DoubleRangeValidator 或 LongRangeValidator,{0}代表 minimum 属性 javax.faces.validator.DoubleRa ngeValidator.TYPE、 javax.faces.validator.LongRang eValidator.TYPE Validation Error: Value is not of the correct type. DoubleRangeValidator 或 LongRangeValidator javax.faces.validator.LengthVal idator.MAXIMUM Validation Error: Value is greater than allowable maximum of ''{0}''. LengthValidator,{0}代表 maximum javax.faces.validator.LengthVal idator.MINIMUM Validation Error: Value is less than allowable minimum of ''{0}''. LengthValidator,{0}代表minimum 属性 在您提供自订信息的时候,也可以提供{0}或{1}来设定显示相对的属性值,以提供详细正确的错误提 示信息。 信息的显示有概述信息与详述信息,如果是详述信息,则在识别上加上 "_detail",例如: javax.faces.component.UIInput.CONVERSION=Error. javax.faces.component.UIInput.CONVERSION_detail= Detail Error. .... 除了在信息资源档中提供信息,您也可以在程序中使用 FacesMessage 来提供信息,例如在 自订验证 器 中我们就这麼用过: .... if(password.length() < 6) { FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_ERROR, "字元长度小于 6", "字元长度不得小于 6"); throw new ValidatorException(message); } .... 最好的方法是在信息资源档中提供信息,这麼一来如果我们要修改信息,就只要修改信息资源档的内 容,而不用修改程序,来看一个简单的例子,假设我们的信息资源档中有以下的内容: onlyfun.caterpillar.message1=This is message1. onlyfun.caterpillar.message2=This is message2 with \{0} and \{1}. 则我们可以在程序中取得信息资源档的内容,例如: package onlyfun.caterpillar; import java.util.Locale; import java.util.ResourceBundle; import javax.faces.context.FacesContext; improt javax.faces.component.UIComponent; import javax.faces.application.Application; import javax.faces.application.FacesMessage; .... public void xxxMethod(FacesContext context, UIComponent component, Object obj) { // 取得应用程序代表物件 Application application = context.getApplication(); // 取得信息档案主名称 String messageFileName = application.getMessageBundle(); // 取得当前 Locale 物件 Locale locale = context.getViewRoot().getLocale(); // 取得信息绑定 ResourceBundle 物件 ResourceBundle rsBundle = ResourceBundle.getBundle(messageFileName, locale); String message = rsBundle.getString( "onlyfun.caterpillar.message1"); FacesMessage facesMessage = new FacesMessage( FacesMessage.SEVERITY_FATAL, message, message); .... } .... .... 接下来您可以将 FacesMessage 物件填入 ValidatorException 或 ConverterException 后再丢出, FacesMessage 建构时所使用的三个参数是严重程度、概述信息与详述信息,严重程度有 SEVERITY_FATAL、 SEVERITY_ERROR、SEVERITY_WARN 与 SEVERITY_INFO 四种。 如果需要在信息资源档中设定{0}、{1}等参数,则可以如下: .... String message = rsBundle.getString( "onlyfun.caterpillar.message2"); Object[] params = {"param1", "param2"}; message = java.text.MessageFormat.format(message, params); FacesMessage facesMessage = new FacesMessage( FacesMessage.SEVERITY_FATAL, message, message); .... 如此一来,在显示信息时,onlyfun.caterpillar.message2 的{0}与{1}的位置就会被"param1"与"param2" 所取代。 6 自订转换, 验证标签 在 自订验证器 中,我们的验证器只能验证一种 pattern(.+[0-9]+),我们希望可以在 JSF 页面上自订 匹配的 pattern,然而由于我们使用这个通用的验证器标签,为了要能提供 pattern 属性,我们 可以使用标签来设置,例如: ....

.... 使用标签来设定属性,接著我们可以如下取得所设定的属性: .... public void validate(FacesContext context, UIComponent component, Object obj) throws ValidatorException { .... String pattern = (String) component.getAttributes().get("pattern"); .... } .... 您也可以开发自己的一组验证标签,并提供相关属性设定,这需要了解 JSP Tag Library 的撰写,所以 请您先参考 JSP/Servlet 中有关于 JSP Tag Library 的介绍。 要开发验证器转用标签,您可以直接继承 javax.faces.webapp.ValidatorTag,这个类别可以帮您处理大 部份的细节,您所需要的,就是重新定义它的 createValidator()方法,我们以改写 自订验证器 中的 PasswordValidator 为例: PasswordValidator.java PasswordValidator.java package onlyfun.caterpillar; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.validator.Validator; import javax.faces.validator.ValidatorException; public class PasswordValidator implements Validator { private String pattern; public void setPattern(String pattern) { this.pattern = pattern; } public void validate(FacesContext context, UIComponent component, Object obj) throws ValidatorException { String password = (String) obj; if(password.length() < 6) { FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_ERROR, "字元长度小于 6", "字元长度不得小于 6"); throw new ValidatorException(message); } if(pattern != null && !password.matches(pattern)) { FacesMessage message = new FacesMessage( FacesMessage.SEVERITY_ERROR, "密码必须包括字元与数字", "密码必须是字元加数字所组成"); throw new ValidatorException(message); } } } 主要的差别是我们提供了 pattern 属性,在 validate()方法中进行验证时,是根据我们所设定的 pattern 属性,接著我们继承 javax.faces.webapp.ValidatorTag 来撰写自己的验证标签: PasswordValidatorTag.java package onlyfun.caterpillar; import javax.faces.application.Application; import javax.faces.context.FacesContext; import javax.faces.validator.Validator; import javax.faces.webapp.ValidatorTag; public class PasswordValidatorTag extends ValidatorTag { private String pattern; public void setPattern(String pattern) { this.pattern = pattern; } protected Validator createValidator() { Application application = FacesContext.getCurrentInstance(). getApplication(); PasswordValidator validator = (PasswordValidator) application.createValidator( "onlyfun.caterpillar.Password"); validator.setPattern(pattern); return validator; } } application.createValidator()方法建立验证器物件时,是根据在 faces-config.xml 中注册验证器的识别 (Validater ID): faces-config.xml faces-config.xml .... onlyfun.caterpillar.Password onlyfun.caterpillar.PasswordValidator .... 剩下来的工作,就是佈署 tld 描述档了,我们简单的定义一下: taglib.tld taglib.tld PasswordValidator Tag 1.0 2.0 co http://caterpillar.onlyfun.net PasswordValidator passwordValidator onlyfun.caterpillar.PasswordValidatorTag empty pattern true false 而我们的 index.jsp 改写如下: index.jsp index.jsp <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="/WEB-INF/taglib.tld" prefix="co" %> <%@page contentType="text/html;charset=Big5"%> 验证器示范

请输入您的名称

名称:

密码:

主要的差别是,我们使用了自己的验证器标签: 如果要自订转换器标签,方法也是类似,您要作的是继承 javax.faces.webapp.ConverterTag,并重新定 义其 createConverter()方法。 第四部分 事件处理 1 动作事件 JSF 支持事件处理模型,虽然由于 HTTP 本身无状态(stateless)的特性,使得这个模型多少有些地方 仍不太相同,但 JSF 所提供的事件处理模型已足以让一些传统 GUI 程序的设计人员,可以用类似的模型来 开发程序。 在 简单的导航 中,我们根据动作方法(action method)的结果来决定要导向的网页,一个按钮连接 至一个方法,这样的作法实际上就是 JSF 所提供的简化的事件处理程序,在按钮上使用 action 连接至一个 动作方法(action method),实际上 JSF 会为其自动产生一个「预设的 ActionListener」来处理事件,并根 据其传回值来决定导向的页面。 如果您需要使用同一个方法来应付多种事件来源,并想要取得事件来源的相关信息,您可以让处理事 件的方法接收一个 javax.faces.event.ActionEvent 事件参数,例如: UserBean.java package onlyfun.caterpillar; import javax.faces.event.ActionEvent; public class UserBean { private String name; private String password; private String errMessage; private String outcome; public void setName(String name) { this.name = name; } public String getName() { return name; } public void setPassword(String password) { this.password = password; } public String getPassword() { return password; } public void setErrMessage(String errMessage) { this.errMessage = errMessage; } public String getErrMessage() { return errMessage; } public void verify(ActionEvent e) { if(!name.equals("justin") || !password.equals("123456")) { errMessage = "名称或密码错误" + e.getSource(); outcome = "failure"; } else { outcome = "success"; } } public String outcome() { return outcome; } } 在上例中,我们让 verify 方法接收一个 ActionEvent 物件,当使用者按下按钮,会自动产生 ActionEvent 物件代表事件来源,我们故意在错误信息之后如上事件来源的字串描述,这样就可以在显示错误信息时一 併显示事件来源描述。 为了提供 ActionEvent 的存取能力,您的 index.jsp 可以改写如下: index.jsp <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=Big5"%> 第一个 JSF 程序

请输入您的名称

名称:

密码:

主要改变的是按钮上使用了 actionListener 属性,这种方法可以使用一个 ActionListener,JSF 会先检查 是否有指定的 actionListener,然后再检查是否指定了动作方法并产生预设的 ActionListener,并根据其传回 值导航页面。 如果您要注册多个 ActionListener,例如当使用者按下按钮时,顺便在记录档中增加一些记录信息,您 可以完成 javax.faces.event.ActionListener,例如: LogHandler.java package onlyfun.caterpillar; import javax.faces.event.ActionListener; .... public class LogHandler implements ActionListener { public void processAction(ActionEvent e) { // 处理 Log } } VerifyHandler.java package onlyfun.caterpillar; import javax.faces.event.ActionListener; .... public class VerifyHandler implements ActionListener { public void processAction(ActionEvent e) { // 处理验证 } } 这麼一来,您就可以使用标签向元件注册事件,例如: 会自动产生 type 所指定的物件,并呼叫元件的 addActionListener()方法注册 Listener。 2 即时事件 所谓的即时事件(Immediate Events),是指 JSF 视图元件在取得请求中该取得的值之后,即立即处理 指定的事件,而不再进行后续的转换器处理、验证器处理、更新模型值等流程。 在JSF 的事件模型中会有所谓即时事件,是因为 Web 应用程序的先天特性不同于 GUI 程序,所以 JSF 的事件模式与 GUI 程序的事件模式仍有相当程度的不同,一个最基本的问题正因为 HTTP 无状态的特性, 使得 Web 应用程序天生就无法直接唤起服务器端的特定组件。 所有的物件唤起都是在服务器端执行的,至于该唤起什么物件,则是依一个基本的流程: 回复画面(Restore View) 依客户端传来的 session 资料或服务器端上的 session 资料,回复 JSF 画面元件。 套用请求值(Apply Request Values) JSF 画面元件各自获得请求中的值属于自己的值,包括旧的值与新的值。 执行验证(Process Validations) 转换为物件并进行验证。 更新模型值(Update Model Values) 更新Bean 或相关的模型值。 唤起应用程序(Invoke Application) 执行应用程序相关逻辑。 绘制回应画面(Render Response) 对先前的请求处理完之后,产生画面以回应客户端执行结果。 对于动作事件(Action Event)来说,元件的动作事件是在套用请求值阶段就生成 ActionEvent 物件了, 但相关的事件处理并不是马上进行,ActionEvent 会先被排入序列,然后必须再通过验证、更新模式值阶段, 之后才处理序列中的事件。 这样的流程对于按下按钮然后执行后端的应用程序来说不成问题,但有些事件并不需要这样的流程, 例如只影响画面的事件。 举个例子来说,在表单中可能有使用者名称、密码等栏位,并提供有一个地区选项按钮,使用者可以 在不填下按钮的情况下,就按下地区选项按钮,如果依照正常的流程,则会进行验证、更新模型值、唤起 应用程序等流程,但显然的,使用者名称与密码是空白的,这会引起不必要的错误。 您可以设定元件的事件在套用请求值之后立即被处理,并跳过后续的阶段,直接进行画面绘制以回应 请求,对于 JSF 的 input 与 command 元件,都有一个 immediate 属性可以设定,只要将其设定为 true,则 指定的事件就成为立即事件。 一个例子如下: index.jsp <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=UTF8"%> <h:outputText value="#{msgs.titleText}"/>

:

:

这是一个可以让使用者决定使用语系的示范,最后一个 commandButton 元件被设定了 immediate 属性, 当按下这个按钮后,JSF 套用请求值之后会立即处理指定的 actionListener,而不再进行验证、更新模型值, 简单的说,就这个程序来说,您在输入栏位与密码栏位中填入的值,不会影响您的 user.name 与 user.password。 基于范例的完整起见,我们列出这个程序 Bean 物件及 faces-config.xml: UserBean.java package onlyfun.caterpillar; import javax.faces.event.ActionEvent; public class UserBean { private String locale = "en"; private String name; private String password; private String errMessage; public void changeLocale(ActionEvent e) { if(locale.equals("en")) locale = "zh_TW"; else locale = "en"; } public String getLocale() { if (locale == null) { locale = "en"; } return locale; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setPassword(String password) { this.password = password; } public String getPassword() { return password; } public void setErrMessage(String errMessage) { this.errMessage = errMessage; } public String getErrMessage() { return errMessage; } public String verify() { if(!name.equals("justin") || !password.equals("123456")) { errMessage = "名称或密码错误"; return "failure"; } else { return "success"; } } }faces-config.xml faces-config.xml /pages/index.jsp success /pages/welcome.jsp failure /pages/index.jsp user onlyfun.caterpillar.UserBean session 信息资源档的内容则是如下: messages_en.properties messages_en.properties titleText=JSF Demo hintText=Please input your name and password nameText=name passText=password commandText=Submit Text=\u4e2d\u6587 Text 中设定的是「中文」转换为 Java Unicode Escape 格式的结果,另一个信息资源档的内容则是英文 信息的翻译而已,其转换为 Java Unicode Escape 格式结果如下: messages_zh_TW.properties messages_zh_TW.properties titleText=JSF\u793a\u7bc4 hintText=\u8acb\u8f38\u5165\u540d\u7a31\u8207\u5bc6\u78bc nameText=\u540d\u7a31 passText=\u5bc6\u78bc commandText=\u9001\u51fa Text=English welcome.jsp 就请自行设计了,程序的画面如下: 3 值变事件 如果使用者改变了 JSF 输入元件的值后送出表单,就会发生值变事件(Value Change Event),这会丢 出一个 javax.faces.event.ValueChangeEvent 物件,如果您想要处理这个事件,有两种方式,一是直接设定 JSF 输入元件的 valueChangeListener 属性,例如: 为了模拟 GUI 中选择了选单项目之后就立即发生反应,我们在 onchange 属性中使用了 JavaScript,其 作用是在选项项目发生改变之后,立即送出表单,而不用按下提交按钮;而 valueChangeListener 属性所绑 定的 user.changeLocale 方法必须接受 ValueChangeEvent 物件,例如: UserBean.java package onlyfun.caterpillar; import javax.faces.event.ValueChangeEvent; public class UserBean { private String locale = "en"; private String name; private String password; private String errMessage; public void changeLocale(ValueChangeEvent event) { if(locale.equals("en")) locale = "zh_TW"; else locale = "en"; } public void setLocale(String locale) { this.locale = locale; } public String getLocale() { if (locale == null) { locale = "en"; } return locale; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setPassword(String password) { this.password = password; } public String getPassword() { return password; } public void setErrMessage(String errMessage) { this.errMessage = errMessage; } public String getErrMessage() { return errMessage; } public String verify() { if(!name.equals("justin") || !password.equals("123456")) { errMessage = "名称或密码错误"; return "failure"; } else { return "success"; } } } 另一个方法是完成 javax.faces.event.ValueChangeListener 界面,并定义其 processValueChange()方法, 例如: SomeListener.java package onlyfun.caterpillar; .... public class SomeListener implements ValueChangeListener { public void processValueChange(ValueChangeEvent event) { .... } .... } 然后在 JSF 页面上使用标签,并设定其 type 属性,例如: {code:borderStyle=solid} 下面这个页面是对 立即事件 中的范例程序作一个修改,将语言选项改以下拉式选单的选择方式呈 现,这必须配合上面提供的 UserBean 类别来使用: index.jsp <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@page contentType="text/html;charset=UTF8"%> <h:outputText value="#{msgs.titleText}"/>

:

:

4 Phase 事件 在 即时事件 中我们提到,JSF 的请求执行到回应,完整的过程会经过六个阶段: 回复画面(Restore View) 依客户端传来的 session 资料或服务器端上的 session 资料,回复 JSF 画面元件。 套用请求值(Apply Request Values) JSF 画面元件各自获得请求中的值属于自己的值,包括旧的值与新的值。 执行验证(Process Validations) 转换为物件并进行验证。 更新模型值(Update Model Values) 更新Bean 或相关的模型值。 唤起应用程序(Invoke Application) 执行应用程序相关逻辑。 绘制回应画面(Render Response) 对先前的请求处理完之后,产生画面以回应客户端执行结果。 在每个阶段的前后会引发 javax.faces.event.PhaseEvent,如果您想尝试在每个阶段的前后捕捉这个事 件,以进行一些处理,则可以完成 javax.faces.event.PhaseListener,并向 javax.faces.lifecycle.Lifecycle 登记 这个 Listener,以有适当的时候通知事件的发生。 PhaseListener 有三个必须完成的方法 getPhaseId()、beforePhase()与 afterPhase(),其中 getPhaseId()传回 一个 PhaseId 物件,代表 Listener 想要被通知的时机,可以设定的时机有: PhaseId.RESTORE_VIEW PhaseId.APPLY_REQUEST_VALUES PhaseId.PROCESS_VALIDATIONS PhaseId.UPDATE_MODEL_VALUES PhaseId.INVOKE_APPLICATION PhaseId.RENDER_RESPONSE PhaseId.ANY_PHASE 其中PhaseId.ANY_PHASE 指的是任何的阶段转换时,就进行通知;您可以在beforePhase()与afterPhase() 中撰写阶段前后撰写分别想要处理的动作,例如下面这个简单的类别会列出每个阶段的名称: ShowPhaseListener.java package onlyfun.caterpillar; import javax.faces.event.PhaseEvent; import javax.faces.event.PhaseId; import javax.faces.event.PhaseListener; public class ShowPhaseListener implements PhaseListener { public void beforePhase(PhaseEvent event) { String phaseName = event.getPhaseId().toString(); System.out.println("Before " + phaseName); } public void afterPhase(PhaseEvent event) { String phaseName = event.getPhaseId().toString(); System.out.println("After " + phaseName); } public PhaseId getPhaseId() { return PhaseId.ANY_PHASE; } } 撰写好 PhaseListener 后,我们可以在 faces-config.xml 中向 Lifecycle 进行注册: faces-config.xml onlyfun.caterpillar.ShowPhaseListener ...... 您可以使用这个简单的类别,看看在请求任一个 JSF 画面时所显示的内容,藉此了解 JSF 每个阶段的 流程变化。 第五部分 JSF 标签 1 标签入门 1.1 简介 JSF 标准标签 JSF 提供了标准的 HTML Renderer Kit,可以让您搭配 JSF 元件输出 HTML 文件,标准的 HTML Renderer Kit 主要包括了几个类别: 输出(Outputs) 其名称以 output 作为开头,作用为输出指定的信息或绑定值。 输入(Inputs) 其名称以 input 作为开头,其作用为提供使用者输入栏位。 命令(Commands) 其名称以 command 作为开头,其作用为提供命令或连结按钮。 选择(Selections) 其名称以 select 作为开头,其作用为提供使用者选项的选取。 其它 包括了 form、message、messages、graphicImage 等等未分类的标签。 JSF 标准 HTML 标签包括了几个共通的属性,整理如下: 属性名称 适用 说明 id 所有元件 可指定 id 名称,以让其它标签或元件参考 binding 所有元件 绑定至 UIComponent rendered 所有元件 是否显示元件 styleClass 所有元件 设定 Cascading stylesheet (CSS) value 输入、输出、命令元件 设定值或绑定至指定的值 valueChangeListener 输入元件 设定值变事件处理者 converter 输入、输出元件 设定转换器 validator 输入元件 设定验证器 required 输入元件 是否验证必填栏位 immediate 输入、命令元件 是否为立即事件 除了共通的属性之外,您还可以在某些元件上设定标签 HTML 4.01 的属性,像是 size、alt、width 等 属性,或者是设定 DHTML 事件属性,例如 onchange、onclick 等等。 除了JSF 的标准 HTML 标签之外,您还需要一些标准核心标签,这些标签是独立于 Renderer Kit 的, JSF 并不限制在 HTML 输出表示层,核心标签可以搭配其它的 Renderer Kit 来使用。 详细的 HTML 标签或核心标签的使用与属性说明可以查询 Tag Library Documentation 文件 1.2 输出类标签 输出类的标签包括了 outputLabel、outputLink、outputFormat 与 outputText,分别举例说明如下: 1.2.1 outputLabel 产生