2008-02-20

JSF 资料 转载

学习新技术的一个最好方法,就是通过简单实际的示例去实践。基于这个目的,本章用JavaServer Faces(JSF)技术开发一个典型的Web注册应用程序。Web注册应用程序提供的功能足以展示如何运用JSF的核心技术,例如用户界面组件、托管bean、导航模型以及基本的数据验证和转换。在后续章节中,会把这个注册应用程序整合进一个更大更全面贯穿本书的“虚拟教练”示例应用程序。现在,通过这个注册应用程序,可以理解JSF应用程序的关键元素和架构。另外,它还提供了JSF开发过程的概述。

除了介绍如何构建简单的JSF注册应用程序,本章还介绍了如何设置JSF开发环境,有了开发环境,就可以编译、打包和运行应用程序。为了确保对JSF应用程序的核心技术要求有坚实的理解,本章的应用程序是手动构建的。在后续章节中,将看到如何用一些先进的针对JavaServer Faces的集成可视开发环境快速构建JSF应用程序。

2.1 应用程序概述
这个示例应用程序叫做JSFReg,是个简单的Web注册应用程序,它和现在在Web上注册各种服务时都会遇到的注册应用程序类似。应用程序由几个JSP页面构成,每个页面各自包含JSF用户界面组件(后面详细介绍)、临时保存用户信息的Java类、一组配置文件和必需的运行时库。

应用程序的起始页面包含一个超链接,指向包含注册表单的页面。注册表单允许用户输入名称、性别和其他基本个人信息。在表单上包含一个Register按钮,在单击按钮时,调用对输入数据的验证。如果出现验证错误,就在不正确的输入字段旁显示错误消息。这样用户可以修改错误字段,通过验证。在输入数据通过验证之后,用户仍然可以修正其他输入数据,或者继续进行注册确认。在单击最终确认按钮之后,出现最终页面,显示用户的实际“注册”数据。同时调用负责注册的Java方法。这个Java方法可以实际链接到数据库或某个服务上,执行实际的数据记录插入。在这个简单示例中,Java注册方法只是向应用程序服务器的标准输出(或控制台)输出消息“Adding new user”。

图2-1说明了JSFReg应用程序中的所有页面。



图2-1 JSFReg应用程序图表

注册过程似乎过于详细(带有返回并修改或者前进到确认页面这些选项)看起来可能有点多余,但是是故意这么做的,以显示怎样根据不同情况处理不同页面导航选项。注册页面显示了如何用JSF规范的标准HTML组件库提供的各种用户界面组件的集合构建表单。这些组件有:输入文本字段、单选按钮、选择菜单以及表单提交按钮。应用程序相当强壮的验证需求突出了JSF的内置验证和数据转换技术以及如何实现(非常简单的)定制验证逻辑。第7章会更详细地介绍JSF验证和转换。

由于JSF在提供可用用户界面组件集合的同时,还提供了相当数量的内置验证和数据转换功能,所以可以看见,大部分JSF应用程序开发不过是用已经准备好的用户界面组件装配起用户界面而已。而且,就像示例中强调的,可以为这些组件配置上某种验证,然后绑定到现有的Java bean属性和方法。同一组件还可以绑定到叫做JSF导航模型的页面流程导航规则集中(导航模型提供了整个应用程序的导航规则)。实际上JSF应用程序开发大致就是这样。现在走近一些,看看如何构建示例应用程序。

2.1.1 JSFReg应用程序文件
所有JSF应用程序都由特定的一套必需的配置文件集和Web内容文件组成。必需的关键配置文件是faces-config.xml和标准的J2EE Web应用程序配置/部署描述符web.xml。Web内容文件可以包含JSP文件和一般HTML内容,例如HTML页面、图片和层级样式表(CSS)。

表2-1列出了JSFReg应用程序使用的每个文件和它的用途。

表2-1 JSFReg应用程序使用的文件及其用途 文件
说明

faces-config.xml
所有JSF应用程序都需要的主配置文件。包含对Faces应用程序中所有工作部分的引用



续表 文件
说明

web.xml
J2EE Web应用程序必需的J2EE Web部署描述符和主配置文件。JSF应用程序的web.xml文件中还有特定于JSF的设置,例如servlet映射“/faces”它允许JSF控制器servlet把这个请求放在其他请求之外单独处理

index.jsp
起始页面,上面有到支持JSF的初始JSP页面的转发:main.jsp:<jsp:forward page=“/faces/
main.jsp”/>
请注意:到main.jsp的路径包含“/faces”映射,这样Faces控制器就可以在路由到main.jsp页面之前准备JSF上下文

main.jsp
应用程序的主Faces入口点

register.jsp
应用程序中包含应用程序注册表单的页面。注册表单由不同的JSF用户界面组件构成,组件渲染成输入字段、菜单和按钮

confirm.jsp
这个JSP页面显示验证过的用户输入数据,带有两个提供Edit或Confirm选项的按钮。单击Edit按钮会把用户送回注册表单,单击Confirm按钮会确认输入,并重定向到最后的完成页面:done.jsp

done.jsp
这个JSP页面表明用户信息成功提交,并显示最终确认的用户信息

Userbean.java
这个Java类由JSF用作“托管bean”,临时保存用户提供的注册信息。类中包含代表用户名称、性别、电子邮件、出生日期等等的字段,还包含简单的电子邮件地址验证方法



除了这些核心应用程序文件,还需要额外的Java库才能编译和创建可部署的打包应用程序。详细信息将在本章末尾介绍。

2.1.2 装配JSFReg应用程序
熟悉基本J2EE Web应用程序的读者会注意到,所有JSF应用程序基本都是标准J2EE应用程序,只有少量区别。区别包括:Web应用程序web.xml文件中的JSF设置、JSF配置文件(faces-config.xml)和必要的运行时库。现在来看如何从头开始构建JSFReg应用程序。

要开始构建JSFReg,需要在文件系统上创建一个开发目录,作为自己J2EE Web应用程序的根目录。这个目录可以位于文件系统的任何位置,但是为了跟上这个示例,请使用C:\JSReg。这个目录将包含构建JSFReg Web应用程序所必需的全部元素。其中包含Java源代码目录src和J2EE Web模块根目录web(里面包含最终的可部署内容)。src目录包含这个应用程序需要的Java源文件的完整路径。J2EE Web开发人员把web子目录当成标准J2EE Web模块目录,里面包含必需的WEB-INF子目录树;WEB-INF中又包含Web模块的部署描述符web.xml和应用程序的Web内容,包括JSP、HTML、图片、CSS等。WEB-INF目录树还包含lib和classes子目录,里面包含应用程序的运行时Java库和编译后的类。稍后在编译应用程序,把应用程序打包成Web档案(WAR)文件之前,要确保把编译后的Java类放到了WEB-INF/lib/classes目录中。空的目录结构看起来如图2-2所示。

完整目录结构最终应当包含以下文件:

● C:\JSFReg\web\main.jsp;

● C:\JSFReg\web\index.jsp;

● C:\JSFReg\web\register.jsp;

● C:\JSFReg\web\confirm.jsp;

● C:\JSFReg\web\done.jsp;

● C:\JSFReg\web\WEB-INF\web.xml;

● C:\JSFReg\web\WEB-INF\faces-config.xml;

● C:\JSFReg\web\WEB-INF\classes\包含编译的Java类;

● C:\JSFReg\web\WEB-INF\lib\包含需要的JSF运行时JAR文件;

● C:\JSFReg\src\com\jsfcompref\jsfreg\Userbean.java。

2.1.3 配置文件
要开始构建应用程序,首先要创建新的Web模块部署描述符文件(web.xml)和Faces配置文件(faces-config.xml),这两个文件都必须放在web/WEB-INF目录中。web.xml文件指明这个目录结构是J2EE Web应用程序。完成之后,这个目录结构会原封不动地部署到任何标准J2EE Web容器/应用程序服务器上。

下面是初始的web.xml文件:

<?xml version = '1.0' encoding = 'windows-1252'?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee

http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"

xmlns="http://java.sun.com/xml/ns/j2ee">

<description>Empty web.xml file for a Web Application </description>

<servlet>

<servlet-name>Faces Servlet</servlet-name>

<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>Faces Servlet</servlet-name>

<url-pattern>/faces/*</url-pattern>

</servlet-mapping>

</web-app>

需要注意的关键是web.xml文件中有Faces servlet的入口javax.faces.webapp.FacesServlet,它充当Faces的控制器servlet。Faces控制器servlet之所以能拦截所有Faces请求,是因为它们都有与servlet的映射url–pattern匹配的/faces/*模式。实际上,url–pattern可以随意设置,例如*.faces或*.jsf。只要模式能让应用程序区分Faces请求和非Faces请求即可。所有支持JSF的页面都必须通过Faces请求访问(使用适当的映射模式),以便首先调用Faces servlet控制器,控制器的任务是在路由到请求的页面之前准备JSF上下文。关于JSF生命周期和上下文的更多介绍将在后续章节中提供。

下面,添加faces-config.xml文件,它也位于WEB-INF子目录中。 注意 faces-config.xml文件实际上可以用任意名称,也可以放在任意定制位置内(只要Web应用程序能够访问到即可)。甚至还可以有多个faces配置文件。默认目录位置是WEB-INF目录。非默认位置需要在web.xml中加一个入口,说明faces配置文件(或多个文件)的名称和位置。初始的空白faces-config.xml文件看起来像这样:



<?xml version="1.0"?>

<!DOCTYPE faces-config PUBLIC

"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"

"http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config xmlns="http://java.sun.com/JSF/Configuration">

</faces-config> JSF1.2提示 如果构建Faces 1.2版应用程序,那么必须用XML方案指定faces-config文件,就像这样:



<?xml version="1.0"?>

<faces-config xmlns=http://java.sun.com/xml/ns/j2ee

xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee

http://java.sun.com/xml/ns/j2ee/web-facesconfig_1_2.xsd"

version="1.2">

</faces-config>

构建应用程序时会向faces-config.xml文件添加入口,包括导航规则和托管bean。

2.1.4 JSP页面
首先从index.jsp页面开始。index.jsp页面通常被用作JSP应用程序的起始页面。为了遵守这个约定,我们在里面只提供一个JSP转发,自动把用户转发到第一个支持Faces的页面main.jsp,用/faces/url-pattern初始化Faces请求:

<jsp:forward page="/faces/main.jsp"/>

请求发出后,Faces控制器接收到请求,并初始化JSF生命周期,其中一件事就是准备JSF上下文并把用户路由到JSF页面main.jsp。在后续章节中会看到,JSF上下文为从JSF页面访问应用程序数据提供了简单而连贯的途径。

main.jsp页面如图2-3所示,它是应用程序中第一个支持JSF的页面。它包含一个JSF用户界面组件HtmlCommandLink,这个组件渲染成简单的HTML超链接,链接到注册页面。



图2-3 JSFReg主页

这个页面的主要用途就是显示一个非常简单的示例,展示如何使用带有导航规则的HTMLCommandLink组件。下面来研究支持JSF的第一个JSP页面的源代码:

<%@ page contentType="text/html"%>

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

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

<f:view>

<html>

<head>

<title>A Simple JavaServer Faces Registration Application</title>

</head>

<body>

<h:form >

<h2>

JSF Registration App

</h2>

<h:commandLink action="register">

<h:outputText value="Click here to register.."/>

</h:commandLink>

</h:form>

</body>

</html>

</f:view>

要注意的第一件事是页面顶部的两个taglib指令:

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

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

这两条JSP指令允许JSP页面使用JSF规范的参考实现里提供的JSFCore和HTML标签库,这两个标签库允许JSP在页面上使用底层JSF用户界面组件。请记住:JSF用户界面组件独立于客户端,只要有对应的渲染器,就可以用于不同客户端。后续章节将介绍用户界面组件的渲染,现在需要了解的主要事情,就是JSF的设计目的是不仅能用于传统的HTML浏览器客户端,还能用于其他类型的客户端,例如PDA和其他设备。

在taglib指令后,会注意到的下一件事是<f:view>和<h:form>标签。这两个标签是整个页面内容的主标签和父标签。在后续章节中会详细介绍这点,现在要记住的就是为客户端(例如浏览器)渲染JSF页面,会在服务器内存中用View组件做组件树的根,实例化一个同样的组件树。

现在转移到第一个真正可用的用户界面组件,可以看见:

<h:commandLink action="register">

<h:outputText value="Click here to register.."/>

</h:commandLink>

<h:commandLink>这个JSP标签调用服务器上的JSF HtmlCommandLink用户界面组件,围绕标签主体中的内容渲染一个HTML超链接。关于这个组件,有件有趣的事值得一提:它渲染成链接,但动作却像按钮,会引起表单提交。在这个示例中,标签的主体是子标签<h:outputText>,它调用HtmlOutputText用户界面组件,后者只是把value属性中的文本渲染给客户端。最后,也是最重要的,就是commandLink标签的action属性。这是JSF事件模型发挥作用的地方,因为标签的action属性实际描述了如何处理单击链接时触发的事件。由于action属性设置成“register”,所以传递给JSF导航模型一个ActionEvent,然后导航模型查看from-outcome是“register”的导航块,并按着找到的导航块的to-view-id导航路径走。为了更好地理解这点,请看faces-config.xml中处理这个“register”动作的导航规则。处理这个结果的导航规则是:

<navigation-rule>

<from-view-id>/main.jsp</from-view-id>

<navigation-case>

<from-outcome>register</from-outcome>

<to-view-id>/register.jsp</to-view-id>

</navigation-case>

</navigation-rule>

可以看到,这个导航规则指定main.jsp页面的from-view-id或起源,如果from-outcome事件等于导航块中指定的“register”,就会导航到register.jsp页面。当单击页面上渲染的超链接时,action属性“register”迫使这个from-outcome情景变成“register”,因此能够导航到register.jsp。

现在来看这个示例应用程序的核心——register.jsp页面,如图2-4所示。当页面在浏览器中渲染时,页面和典型的注册页面很相似。

在研究页面源代码时,首先会注意到针对Core和HTML JSF标签库的同样的taglib指令,以及周围的<f:view>和<h:form>标签,后面跟着一系列输入标签,会在页面上渲染不同的表单字段。还会注 意到使用了一个HTML表格为表单提供布局结构。后面会介绍到,JSF也有提供布局结构的组件,例如<h:panelGrid>提供了与HTML表格类似,但是不需要行和单元格标签的布局。但是,还没有必要使用JSF的布局组件。



图2-4 JSFReg注册表单页面

<%@ page contentType="text/html"%>

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

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

<f:view>

<html>

<head><title>A Simple JavaServer Faces Registration Application</title></head>

<body>

<h:form>

<h2>JSF Registration App</h2>

<h4>Registration Form</h4>

<table>

<tr>

<td>First Name:</td>

<td>

<h:inputText id="fname" value="#{UserBean.firstName}"

required="true"/>

<h:message for="fname"/>

</td>

</tr>

<tr>

<td>Last Name:</td>

<td>

<h:inputText id="lname" value="#{UserBean.lastName}"

required="true" />

<h:message for="lname"/>

</td>

</tr>

<tr>

<td>Gender:</td>

<td>

<h:selectOneRadio id="gender" value="#{UserBean.gender}"

required="true"/>

<f:selectItem itemLabel="Male" itemValue="male"/>

<f:selectItem itemLabel="Female" itemValue="female"/>

</h:selectOneRadio>

<h:message for="gender"/>

</td>

</tr>

<tr>

<td>Date of Birth:</td>

<td>

<h:inputText value="#{UserBean.dob}" id="dob" required="true" >

<f:convertDateTime pattern="MM-dd-yy"/>

</h:inputText> (mm-dd-yy)

<h:message for="dob"/>

</td>

</tr>

<tr>

<td>Email Address:</td>

<td>

<h:inputText id="email" value="#{UserBean.email}" required="true"

validator="#{UserBean.validateEmail}"/>

<h:message for="email"/>

</td>

</tr>

<tr>

<td>Service Level:</td>

<td>

<h:selectOneMenu value="#{UserBean.serviceLevel}">

<f:selectItem itemLabel="Basic" itemValue="basic"/>

<f:selectItem itemLabel="Medium" itemValue="medium"/>

<f:selectItem itemLabel="Premium" itemValue="premium"/>

</h:selectOneMenu>

</td>

</tr>

</table>

<p><h:commandButton value="Register" action="register" /></p>

</h:form>

</body>

</html>

</f:view>

现在研究这个文件的关键部分,先从前两个接收名称和姓氏的输入字段标签开始。

<h:inputText id="fname" value="#{UserBean.firstName}" required="true"/>

<h:message for="fname"/>

可以注意到,为了要求用户必须输入值,required属性设成了“true”。如果用户试图在这个字段为空时就提交表单,那么就会在<h:message>标签的位置上显示内置的验证错误消息。请注意消息标签实际上可以位于页面上的任一位置,因为它是通过ID“fname”与inputText字段链接的。这是JSF提供的内置验证机制的一个示例。接下来需要注意,也是最重要的是,inputText标签的value属性:#{UserBean.first-
Name}。这叫作JSF值绑定表达式,提供了与托管beanUserBean的firstName属性的直接连接。 JSF1.2提示 JSF1.1值绑定表达式的语法与JSP 2.0的表达式语言非常像,而且作用也基本相同。对于JSF1.2版,JSF和JSP 2.1版都使用统一表达式语言。后续章节将提供JSF 1.2和统一表达式语言的更多信息。



那么什么是托管bean呢?您可能听说过“控制反转”或“依赖关系注入”这些术语。这些不过是唬人的术语,表达的都是在不引入过多相互依赖(“紧耦合”)的情况下,把应用程序的不同部分挂接在一起的方法。托管bean做的就是这件事。基本上,托管bean就是JSF应用程序正式注册的Java类。它是遵守JavaBean命名约定的POJO(普通Java对象)。为了让JSF应用程序能引用Java类及它们的方法和属性,它在Java类路径中必须可用,并在faces-config.xml中注册。下面是这个示例应用程序的UserBean.java类在faces-config.xml中的入口。

<managed-bean>

<managed-bean-name>UserBean</managed-bean-name>

<managed-bean-class>

com.jsfcompref.register.UserBean

</managed-bean-class>

<managed-bean-scope>session</managed-bean-scope>

</managed-bean>

托管bean注册之后,就可以用JSF值表达式在任何用户界面组件属性中引用它。最后,请注意managed-bean-scope;它与标准JSP的useBean指令的范围设置类似,但并不完全相同(JSP的useBean的范围设置允许开发人员分配request、session、application或none这4个范围设置,控制Java类的寿命)。 注意 第4章提供了对托管bean和范围设置的进一步介绍。



现在已经介绍了如何把Java类注册成托管bean,下面来看在这个示例应用程序中实际使用的托管bean——UserBean.java:

package com.jsfcompref.register;

import java.util.Date;

import javax.faces.application.FacesMessage;

import javax.faces.component.UIComponent;

import javax.faces.component.UIInput;

import javax.faces.context.FacesContext;

public class UserBean {

String firstName;

String lastName;

Date dob;

String gender;

String email;

String serviceLevel;

public UserBean() { }

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;

}

public Date getDob() {

return dob;

}

public void setDob(Date dob) {

this.dob = dob;

}

public String getGender() {

return gender;

}

public void setGender(String gender) {

this.gender = gender;

}

public String getEmail() {

return email;

}

public void setEmail(String email) {

this.email = email;

}

public String getServiceLevel() {

return serviceLevel;

}

public void setServiceLevel(String serviceLevel) {

this.serviceLevel = serviceLevel;

}

public void validateEmail(FacesContext context, UIComponent toValidate,

Object value) throws ValidatorException {

String eMail = (String) value;

if(eMail.indexOf("@")<0) {

FacesMessage message = new FacesMessage("Invalid email address");

throw new ValidatorException(message);

}

}

public String addConfirmedUser() {

// This method would call a database or other service and add the

// confirmed user information.

System.out.println("Adding new user");

return "success";

}

}

可以看到,UserBean Java类是个简单的Java bean,带有几个字段——firstName、lastName、gender、dob(出生日期)和serviceLevel——除了dob是Date类型之外,其余几个都是String类型。还请注意每个字段的getter和setter。每个字段在register.jsp注册窗体中都有对应的JSF用户界面组件,组件被值绑定到bean的属性。还会注意到额外的方法:validateEmail( )和addConfirmedUser( )。这两个方法是定制方法,做的事情就像它们的名字表示的那样,对电子邮件和用户进行验证。这两个方法也绑定到页面的用户界面组件。马上就会看到如何绑定。

既然了解了托管bean是什么,也知道了如何配置托管bean、如何访问它们的属性,那么回头来看register.jsp页面。继续浏览页面的其余部分,会看到单选按钮用户界面组件实际是由几个标签组合而成的:

<h:selectOneRadio id="gender"value="#{UserBean.gender}"required="true"/>

<f:selectItem itemLabel="Male" itemValue="male"/>

<f:selectItem itemLabel="Female" itemValue="female"/>

</h:selectOneRadio>

<h:message for="gender"/>

<h:selectOneRadio>是主标签,也是父标签;它通过JavaBean的getter和setter,被值绑定到UserBean的gender属性。这意味着只要在单选按钮控件中选择了值,表单提交时托管bean的gender属性就会被更新。对于每个选择项,子标签<f:selectItem>既提供显示的值itemLabel,又提供实际的值itemValue,itemValue是出现选择时实际使用的值。同以前一样,选择是必需的,如果为空,就会通过<h:message>标签显示错误消息。

现在转到下一个输入字段,可以看到以下代码:

<h:inputText value="#{UserBean.dob}" id="dob" required="true" >

<f:convertDateTime pattern="MM-dd-yy"/>

</h:inputText> (mm-dd-yy)

<h:message for="dob"/>

同以前一样,这个输入字段是必需的,但是这次它绑定的Bean属性类型不是String,这个inputText组件被绑定到UserBean.dob属性,这个属性的类型是java.util.Date。为了把进入的字符串值转换成服务器端的Date类型,这里通过<f:convertDateTime>标签使用了一个JSF转换器。请注意转换器标签的pattern属性还定义了期望的日期格式模式是“MM-dd-yy”。 注意 对于JSF,大写的“MM”实际上意味着月份,小写的“mm”意味着分钟。但是,多数最终用户不必知道这点,所以上面的日期模式提示是故意采用小写的(mm-dd-yy)。



当用户按指定格式输入日期字符串时,转换器会把它转换成Date对象,并把它分配给UserBean.dob属性。但是,如果使用了错误的日期格式,就会显示转换错误。

要查看输入错误信息并提交注册表单的效果,请看图2-5,其中包含的注册页面截屏中,显示了各种验证和转换器错误消息。

请注意在输入“foo”的时候,显示Email Address错误消息。这是因为UserBean托管bean中关联的验证方法validateEmail( )不接受“foo”字符串作为正确电子邮件地址:

<h:inputText id="email" value="#{UserBean.email}" required="true"

validator="#{UserBean.validateEmail}"/>

<h:message for="email"/>

这实际是JavaServer Faces中定制验证的最简单形式。另一种创建定制验证的方法是创建独立的Validator类,然后在faces-config.xml中注册。而且,更强壮的定制验证过程应当是创建一个带有内置验证的定制组件。

后续章节会介绍更多验证示例。要解理这个简单的电子邮件验证的工作方式,还请来看validateEmail( )代码:



图2-5 JSFReg的验证错误

public void validateEmail(FacesContext context, UIComponent toValidate,

Object value) throws ValidatorException {

String eMail = (String) value;

if(eMail.indexOf("@")<0) {

FacesMessage message = new FacesMessage("Invalid email address");

throw new ValidatorException(message);

}

}

定制验证方法中需要注意的关键是:字段的value检验是否包含符号@,如果没有找到@符号,就会创建“Invalid e-mail...”FacesMessage消息。然后抛出ValidatorException异常,中止后续处理,在电子邮件输入字段旁边显示错误消息。

现在来看register.jsp页面中的下一个输入字段,可以看到另一种输入选择菜单。这次是用<h:selectOneMenu>标签创建的。

<h:selectOneMenu value="#{UserBean.serviceLevel}">

<f:selectItem itemLabel="Basic" itemValue="basic"/>

<f:selectItem itemLabel="Medium" itemValue="medium"/>

<f:selectItem itemLabel="Premium" itemValue="premium"/>

</h:selectOneMenu>

这个标签的用法与前面的单选按钮标签基本相同,区别是它渲染成下拉菜单而不是单选按钮。它有同样的子标签<f:selectItem>作为下拉菜单选项,但是渲染成不同的选择项。

最后,页面底部是Register按钮。单击按钮引起表单提交,触发JSF事件模型执行验证,并用新的输入字段值更新模型或托管bean的属性。为了在输入数据验证成功时导航到确认页面,按钮的action属性设置成文字值“register”。

<h:commandButton value="Register" action="register"/>

与前面index.jsp页面中的commandLink类似,通过faces-config.xml中的以下导航规则,这个commandButton利用JSF action“register”导航到confirm.jsp页面:

<navigation-rule>

<from-view-id>/register.jsp</from-view-id>

<navigation-case>

<from-outcome>register</from-outcome>

<to-view-id>/confirm.jsp</to-view-id>

</navigation-case>

</navigation-rule>

成功导航到confirm.jsp页面之后(如图2-6所示),用户看到输入的数据,在页面底部带有提供选择项的按钮,可以返回注册表单(register.jsp)修改任何数据条目,或者前进到最后的done.jsp页面完成注册过程。



图2-6 JSFReg确认页面

在确认页面上用标准<h:outputText>标签显示注册数据的值,例如:

<h:outputText value="#{UserBean.firstName}"/>



<h:outputText value="#{UserBean.lastName}"/>

页面底部两个按钮的代码是:

<h:commandButton value="Edit" action="revise"/>

<h:commandButton value="Confirm" action="#{UserBean.addConfirmedUser}" />

Edit按钮使用硬编码的action值“revise”,它对应的导航规则把用户返回到register.jsp页面。Confirm按钮指定动作方法addConfirmedUser( ),在它的方法逻辑中用编程方式确定结果。对于这个简单示例,动作方法只是向标准输出输出消息“Adding new user”,而且总是返回“success”结果。

public String addConfirmedUser(){

// This method would call a database or other service to add the

// confirmed user information. This version is not implemented.

System.out.println("Adding new user");

return "success";

} 注意 在真实世界的应用程序中,方法addConfirmedUser( )通常会调用与数据库或其他数据服务类型接口的外部数据管理方法。



为了处理返回上页编辑数据或成功“adding”用户后前进到“done”页面这两个导航块,在faces-config.xml最终的导航规则中指定了“revise”和“success”两个结果:

<navigation-rule>

<from-view-id>/confirm.jsp</from-view-id>

<navigation-case>

<from-outcome>revise</from-outcome>

<to-view-id>/register.jsp</to-view-id>

</navigation-case>

<navigation-case>

<from-outcome>success</from-outcome>

<to-view-id>/done.jsp</to-view-id>

</navigation-case>

</navigation-rule>

单击Confirm按钮之后,用户被导航到应用程序的最终页面done.jsp。如图2-7所示。



图2-7 JSFReg确认完成页面

最终页面的源代码与前面的确认页面类似,在这里outputText用户界面组件渲染UserBean字段的当前值。但是,最终页面不再需要页面底部的Edit和Confirm这两个按钮。
14:00 | 添加评论 | 发送消息 | 固定链接 | 查看引用通告 (0) | 写入日志jsf
JavaServer Faces规范最漂亮的一个设计就是:它完全依赖现有的J2EE Web技术作为自己的基础。这意味着JSF应用程序就是一个带有少量特定配置的标准J2EE Web应用程序。

特定配置包括:

● 在Web应用程序的web.xml文件中的入口,在指定某个URL模式时(例如/faces/*),启用Faces控制器servlet。

● JSF配置文件faces-config.xml,支持对JSF应用程序所有元素的配置。这个文件被作为web.xml文件的搭档,所以通常放在Web应用程序的WEB-INF/目录下。后续章节中将详细介绍这个文件的准确结构及其中包含的元素。

● WEB-INF目录,里面有以下Java库:

q 实际的JSF库:jsf-api.jar和jsf-impl.jar。

q 附加的Apache“公共”库:commons-beanutils.jar、commonscollections.jar、commons-
digester.jar和commons-logging.jar。虽然不属于JSF核心技术,但JSF依赖这些库,所
以也需要放在应用程序的WEB-INF/lib目录下。

q JSTL jar文件:jstl.jar和standard.jar。 注意 有些J2EE应用程序服务器可能在类路径中把这些jar文件作为默认项提供,所以把它们放在WEB-INF中可能是可选的。可以参考J2EE应用程序服务器的文档,确定这些必需的jar文件是否已经预先加载。



正确地为JSF配置了J2EE Web应用程序之后,就可以用Java服务器页面(但不限于此)构建视图。用Java服务器页面构建JSF应用程序是用支持JSF的JSP标签库完成的。要让JSP页面支持JSF,首先必须包含JSF实现提供的JSF JSP taglib指令。以下taglib指令针对的是来自Sun参考实现的核心和HTML库:

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

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

然后在JSP的主体中,必须添加<f:view>标签。它会成为基用户界面组件,当请求页面查看时,它在服务器内存中会变成一个组件树。如果页面要处理表单输入,而不仅仅是显示输出,那么还需要添加<h:form>标签,作为<f:view>标签的子标签。<h:form>标签的后续子标签会成为表单元素,例如<h:inputText>会渲染成输入字段,而<h:commandButton>会渲染成表单提交按钮。

为了理解JavaServer Faces如何在内存中创建和管理与页面中包含的组件直接对应的服务器端组件树,请看以下支持JSF的JSP页面:

<%@ page contentType="text/html"%>

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

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

<f:view>

<html>

<body>

<h:form >

<h2>

A Simple JSF Page

</h2>

<h:inputText value="#{modelBean.username}"/>

<h:commandButton value="Click Here"/>

</h:form>

</body>

</html>

</f:view>

如图1-1所示,在服务器上实例化的JSF用户界面组件树,恰好与页面上的用户界面组件匹配。一旦用户界面组件树实例化并驻留在内存中,就可以与服务器端用户界面组件交互,并在服务器上操纵和修改这些组件的属性。



图1-1 JSF用户界面组件树 注意 JSF版本1.2实际上把周边标记或“模板文本”也实例化成JSF组件。这增强了渲染,并提供了对整个页面的可编程访问。



在后续章节中会看到,了解如何与服务器端用户界面组件树交互,对于更高级的JSF开发来说通常是必需的。对于基本JSF应用程序,只要把一些用户界面组件放在页面上,设置一些属性,然后靠JSF内置的“衔接”负责输入的处理工作即可。现在走近一些来看JSF的“衔接”(也称为JSF请求处理生命周期)。

1.5.1 JSF请求处理生命周期
当请求支持JSF的JSP页面时,或者用户在启用了JSF的JSP页面上调用某个用户界面组件上的动作时,理解为了实现查看请求或向JSF页面提交而在服务器上发生的事件的正确顺序很重要。在向JSF页面请求期间触发的事件的顺序称作JSF请求处理生命周期,有时就叫作JSF生命周期。这一过程如图1-2所示。



图1-2 JSF请求处理生命周期

我们已经知道,在第一次请求JSF页面时,JSF运行时会在内存中创建组件树。在请求当中,如果应用程序中什么也没发生,那么组件树通常被缓存。在接收到后续请求时,树被迅速重建,而且如果在请求中发送了表单输入的值,就会处理这些值,并执行验证。在验证成功之后,更新这些输入字段对应的服务器端模型的值。接下来继续进行事件处理和错误报告。当所有事件处理和模型更新(如果需要的话)都完成之后,最终把响应渲染回客户端。

第3章提供了对JSF请求处理生命周期的详细介绍。现在知道JSF生命周期就是后端“衔接”事件、自动管理输入数据,由此Web开发就不必手动编写处理请求的代码,这就足够了。这与多数Web技术(包括CGI、PHP和Struts)的差距已经达到了一定程度,因为在那些技术中,开发人员都要专门编写代码处理进入请求和处理结果。这实际上是JSF带给Web应用程序开发的一大进步。它完全消除了“必须处理进入的Web请求”这个概念。相反,Web开发人员可以依靠JSF生命周期自动处理后端衔接,可以让JSF事件模型参与进来,只在需要的时候进行定制处理。

作为没有定制事件要处理的简单示例,只要把用户界面组件(例如输入字段)绑定到托管bean的属性,生命周期就会自动用用户界面组件的值更新托管bean的属性值。回顾JSF JSP示例,在里面用JSF表达式语言(EL)把inputText组件(值)绑定到托管bean “modelBean”的“username”属性上。

<h:inputText value="#{modelBean.username}"/>

为了让用户能提交表单并初始化JSF生命周期,还用以下标签在页面上添加了commandButton用户界面组件:

<h:commandButton value="Click Here"/>

由于JSF生命周期利用JavaBean的事件模型,所以用户只是在运行的时候单击呈现的命令按钮,那么JSF生命周期就会自动地用输入字段提供的值更新JavaBean的“username”属性!

关于JSF请求处理生命周期以及JSF的表达式语言的深入介绍,将在后续章节详细说明。

1.5.2 JSF导航模型
像Struts一样,JSF遵守模型—视图—控制器设计范式。可以想起MVC应用程序分成三个独立应用程序部件:

● 模型,包含业务逻辑或非用户界面代码。

● 视图,是向用户呈现用户界面所需要的全部代码。

● 控制器,是前端代理,直接处理用户请求,并分派合适的视图。

这三个元素,如图1-3所示,组合在一起,形成一个能够生成界限清晰、可以独立维护代码的架构。



图1-3 模型—视图—控制器设计范式

JavaServer Faces从创建开始,就严格遵守MVC设计的方法论。它提供了把表示(视图)代码与后端业务逻辑(模型)代码分离的漂亮途径。它还提供了前端(控制器)servlet,处理所有Faces请求并与必要的应用程序一起,把请求分派给合适的视图组件(页面)。正如前面看到的,JSF应用程序的视图部分是在支持JSF的JSP页面中用用户界面组件创建的。模型绑定到faces-config.xml中指定的“托管bean”的方法和属性上。现在来看看Faces控制器在JSF应用程序中的工作方式。

如上所述,Faces控制器被实现成servlet,响应符合web.xml中定义的某个URL模式(例如/faces/*)的所有请求。使用适当Faces URL模式的请求可以被当成“Faces请求”,当Faces控制器收到请求时,控制器准备叫做JSF上下文的对象,对请求进行处理,JSF上下文对象中包含所有可以访问的应用程序数据,还有使客户到适当视图组件(页面)的路径。控制器路由这些请求的规则在faces-config.xml文件中集中管理,这些路由规则就叫做JSF导航模型。

JSF导航模型是跟踪整个JSF应用程序中全部导航的一个漂亮的解决方案。它极大地提高了应用程序的可管理性,因为维护集中的导航模型,要比更新多个页面中的多个页面链接容易得多。导航模型集中放在一个XML文件中,对工具也是友好的,这样供应商的工具就可以提供可视化方式,方便地定义JSF导航模型。

导航模型基于“导航规则”集,规则集定义了一个“from”页面(from-view-id)和一个或多个“to”导航块。每个导航块都有一个关联的“outcome”和“to”页面(to-view-id)。例如,要在遇到“success”这个结果时,从page1导航到page2,则在faces-config.xml中指定以下规则:

<navigation-rule>

<from-view-id>/page1.jsp</from-view-id>

<navigation-case>

<from-outcome>success</from-outcome>

<to-view-id>/page2.jsp</to-view-id>

</navigation-case>

</navigation-rule>

可以猜到,可以为“failure”结果定义第二个导航块,把查看者路由到page3.jsp。

<navigation-rule>

<from-view-id>/page1.jsp</from-view-id>

<navigation-case>

<from-outcome>success</from-outcome>

<to-view-id>/page2.jsp</to-view-id>

</navigation-case>

<navigation-case>

<from-outcome>failure</from-outcome>

<to-view-id>/page3.jsp</to-view-id>

</navigation-case>

</navigation-rule>

下面要解决的问题就是:如何定义“outcome”——结果?既可以硬编码,也可以动态地派生自单击按钮时触发的方法的返回值。回想一下,用户界面组件既可以绑定到属性,也可以绑定到方法,所以可以把按钮单击与托管bean中的特定方法关联,然后返回一个String值的“outcome”。然后JSF事件模型处理这个“outcome”String值,然后按着与方法的结果对应的导航模型中定义的导航块走。

到现在为止,已经了解了JSF的历史和它背后的理论,也看到了一个非常简单的可以运行的JSF页面示例,是时候来研究更详细的JSF示例应用程序了。第2章开发了一个简短但实用的注册表单示例,其中利用了JavaServer Faces的许多关键特性。这个注册模块也是本书第2部分介绍的更精细的“虚拟教练”示例应用程序几个模块中的一个。
评论
发表评论

您还没有登录,请登录后发表评论

isky
搜索本博客
我的相册
B4a53845-f18b-3b2c-8f27-319d394b08e1-thumb
SL372159
共 29 张
存档
最新评论