使用 JSF 构建数据库驱动的应用程序

开发基于 JSF 的 Web 应用程序,这些应用程序使用 Oracle TopLink 和 JSTL 的 SQL 标记更新和查询关系数据库

本文相关下载:
示例代码
Oracle TopLink 10g
Oracle 数据库 10g
OC4J 10g
Oracle ADF 组件
JavaServer Faces

JavaServer Faces (JSF) 是一个人们期待已久的标准 Java 技术,用于创建基于 Web 的用户界面。本文将介绍如何构建 JSF 表单、如何使用 JSF 验证表单数据、如何实现访问数据库的 JSF 操作以及如何使用 JSF 呈现 SQL 结果集。可以使用低级 API (JDBC)、对象关系 (O-R) 映射框架(如 Oracle TopLink)或 JSP 标记库(例如,JSTL)更新和查询关系数据库。本文通篇介绍了这些选件以及主要的 JSF 特性。

 

应用程序概述

JSF 并非仅是另一个用于 Web 开发的标记库。它是一个基于 MVC 的框架,用于控制 Web 表单和管理 JavaBean。JSF 提供了一组有限的标准 UI 组件,但您可以使用组件库(如 Oracle 应用程序开发框架 (ADF) Faces),还可以构建您自己的定制组件。注意,由于所有这些 UI 组件均基于标准的 JSF API,因此它们可以在同一网页中一起使用。

JSF 页面是常规的 JSP,它们使用标准 JSF 标记库和/或其他基于 JSF API 的库。执行 JSF 页面时,JSF 框架尝试获取或恢复所谓的“组件树”(或“视图”)。它包含 Web 页面组件的信息、必须执行的数据转换和验证、UI 状态必须存储到的 bean 属性、注册的事件监听器等。JSF 创建组件树(如果它不存在),然后将它保存到客户端(使用隐藏的表单域)或服务器(作为会话属性)以备后续请求使用。

本文提供了一个供用户订阅几个时事通讯的示例 Web 应用程序。订户通过提供他们的电子邮件地址、姓名和首选项进行注册。他们还必须选择一个口令,以便以后可以更改他们的配置文件。

图 1 显示了订阅表单。该示例应用程序不分发任何时事通讯,而实际的应用程序将向管理者、开发人员和管理员发送不同的时事通讯。(同一订户可能收到多个时事通讯。)该示例应用程序将用户配置文件保存到关系数据库中,并允许订户更改他们的配置文件。

图 1
图 1:“订阅”窗体

应用程序配置

为使 JSF 控制您的 Web 表单,必须在 web.xml 应用程序描述文件中配置 Faces Servlet。您的 Web 页面将具有 .jsp 扩展名,但 URL 将使用 .faces 后缀或 /faces/ 前缀。必须将 JSF 控制器 servlet 映射到 web.xml 文件中的 *.faces/faces/*。本文提供的示例应用程序使用后缀映射,并将 JSF 视图保存到客户端上:

<web-app>
...
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<servlet>
<servlet-name>FacesServlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>FacesServlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
...
</web-app>

 

模型视图分离

 

资源

 

Oracle JDeveloper 10g (10.1.3) 开发人员预览版为您提供了单一 IDE,通过它您除了可以编写 Java 代码以外,还可以定义 toplink 映射以及直观构建 JSF 和 JSP 页面。Oracle JDeveloper 还提供了由丰富的 JSF 组件组成的 ADF Faces 集,您可以在应用程序中重用这些组件。

1) Oracle JDeveloper 10g (10.1.3) 开发人员预览版下载

2) 使用 JDeveloper 构建 JSF/TopLink

3) 获得 Oracle ADF Faces 组件的预先试用版:一组丰富的 JSF 组件。

其他资源:

了解如何结合使用 ADF Faces 和 JDeveloper 10g

制作 Faces
JavaServer Faces (JSF) 为创建 Web GUI 提供了一个强大的新框架。

从 ADF UIX 到 JSF
Oracle ADF Faces 组件为 JavaServer Faces 提供了一个可重用的资料库。

Oracle JDeveloper 和 JSF

Java Web 应用程序(包括那些基于 JSF 的应用程序)使用 JSP 和 JavaBean 将表现形式与应用程序逻辑分离。使用类似 JSP 2.0 EL 的表达式语言 (EL) 可以将 JSF 标记的属性绑定到 bean 属性(有时为 bean 方法)。JSF EL 通常使用 #{...}语法(而非 ${...} 构造),以便用于 JSP 1.2 和 JSP 2.0。它还允许 JSF 在任何必要的情况下求解表达式(和重新求解),而不是让 JSP 容器控制表达式的求解。在有意义的情况下,JSF EL 绑定是双向的。例如,UI 组件可以取得 bean 属性的值并将其作为默认值提供给用户。当用户提交表单数据时,此 UI 组件会更新 bean 属性,以便应用程序逻辑可以处理该新值。

JavaBean(模型)、JSF 页面(视图)和 Faces Servlet(控制器)具有定义良好的角色,从而分离了对象模型、表现方式和请求处理。您可以更深入一步,将 UI 特有的 Java 代码与其实例维护和处理数据的类分离。一个可行的解决方案是构建对用户界面一无所知的模型 bean,然后使用 JSF 特有的方法(如操作和验证器,本文稍后将对其进行介绍)扩展这些模型类。

该示例应用程序有两个模型 bean(LoginInfoSubscriber),它们由两个视图 bean(LoginInfoBeanSubscriberBean)扩展,JSF 对这两个视图 bean 的实例进行管理。每个视图 bean 实例都有一个标识符和一个作用域(您可以在表 1 中找到它们)。请注意,JSF 规范在提到视图 bean 时使用了术语“托管 bean”(managed bean) 或“辅助 bean”(backing bean)。JSF 对“模型 bean”和“视图 bean”不作区分。实际上,您可能将应用程序逻辑和 UI 代码置于同一个类中,但这可能会降低类的可重用性,使维护变得更加困难,并且指定的开发人员无法将精力集中于他们的主要任务(如应用程序逻辑或 Web 设计)。

模型 bean 类 视图 bean 类 Bean 标识符 JSF 作用域
LoginInfo LoginInfoBean loginInfo request
Subscriber SubscriberBean subscriber session

表 1:示例应用程序的模型 bean 和视图 bean

 

常规 JSP 页面使用 <jsp:useBean> 标准操作实例化 JavaBean。使用 JSF 框架时,您不必在网页中指定类名。而是在 XML 文件(通常名为 faces-config.xml)中配置 bean 实例。.(如果您开发大型应用程序,则可能使用多个配置文件。这种情况下,必须在 web.xml 文件中添加一个 javax.faces.CONFIG_FILES 参数。)对于每个 bean 实例,您必须指定标识符(bean 名)、类名和一个有效的 JSF 作用域(application、session、requestnone)。当在 JSF 表达式 (#{...}) 中引用 bean 时,JSF 框架将验证该 bean 实例在给定作用域中是否存在,如果未找到,则创建它并使用默认值(这些默认值也可以在 JSF 配置文件中指定)对其进行初始化:

<faces-config>
...
<managed-bean>
<managed-bean-name>loginInfo</managed-bean-name>
<managed-bean-class>jsfdb.view.LoginInfoBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<managed-bean>
<managed-bean-name>subscriber</managed-bean-name>
<managed-bean-class>jsfdb.view.SubscriberBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>email</property-name>
<null-value/>
</managed-property>
...
<managed-property>
<property-name>subscriptionType</property-name>
<value>1</value>
</managed-property>
</managed-bean>
...
</faces-config>

 

由于可以将未声明的属性绑定到 JSF 页面的 UI 组件,因此不必在 JSF 配置文件中将每个 bean 属性声明为托管属性。仅当您要使用默认值初始化 bean 属性时,才需要在 faces-config.xml 文件中指定它们。

Bean 与页面的关系

数据访问方法(在下一部分中介绍)从视图 bean 的操作方法中调用。每个操作方法与 JSP 页面中的一个提交按钮绑定(如表 2 所示)。当用户单击该按钮时,Web 浏览器将表单数据发送给服务器。JSF 框架验证表单数据,并在出错情况下将该表单返回给用户。否则,则将有效的表单数据存储到托管 bean 的属性中,且 JSF 将调用绑定到被单击按钮的操作方法。因此,您的操作方法应该实现应用程序逻辑,以处理该 bean 的属性。

数据访问方法 托管 bean 和 JSF 操作 JSF 页面
SubscriberDAO.select() LoginInfoBean.loginAction() login.jsp
SubscriberDAO.insert() SubscriberBean.subscribeAction() subscribe.jsp
SubscriberDAO.update() SubscriberBean.profileAction() profile.jsp
SubscriberDAO.delete() SubscriberBean.unsubscribeAction() unsubscribe.jsp

表 2:绑定到 JSF 页面中的提交按钮的 JSF 操作使用的 DAO 方法

 

页面到页面的导航

每个操作方法都返回一个名为“结果”的字符串。JSF 使用导航处理程序确定对每个结果所执行的操作。如果操作方法返回 null,则必须重新显示同一页面。否则,会根据返回的结果显示其他页面。表 3 包含该示例 Web 应用程序的 JSF 表单、可能的结果以及针对每个结果显示的页面。

JSF 表单 可能的结果 显示的页面
subscribe.jsp subscribed subscribed.jsp
login.jsp profile profile.jsp
login.jsp list list.jsp
profile.jsp login login.jsp
unsubscribe.jsp login login.jsp
unsubscribe.jsp unsubscribed unsubscribed.jsp
unsubscribe.jsp cancel profile.jsp

表 3:当用户单击表单的提交按钮时,JSF 将验证表单数据,
并在未出错的情况下,调用绑定到被单击按钮的操作方法。然后,JSF 将使用该操作方法返回的结果
确定接下来应显示的页面。

 

默认 JSF 导航处理程序使用在 JSF 配置文件中指定的一组导航规则。例如,当用户填写登录表单 (login.jsp) 并单击 Login 按钮时,loginAction() 方法会根据登录用户的角色(订户或管理员)返回 profilelist。请注意,如果用户是未知的或者口令错误,则 loginAction() 将返回 null。这种情况下,JSF 将显示同一登录表单。如果身份验证成功,则 JSF 将根据 loginAction() 返回的结果将该请求转给 profile.jsplist.jsp。以下是 faces-config.xml 中规定的导航规则:

<faces-config>
...
<navigation-rule>
<from-view-id>/login.jsp</from-view-id>
<navigation-case>
<from-outcome>profile</from-outcome>
<to-view-id>/profile.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>list</from-outcome>
<to-view-id>/list.jsp</to-view-id>
</navigation-case>
</navigation-rule>
...
</faces-config>

 

JavaBean 和数据访问对象 (DAO)

本部分介绍了如何创建模型 bean 和视图 bean,以及如何实现 bean 的持久性。模型 bean 定义必须保存到数据库中的属性。视图 bean 使用 UI 特有的代码(操作、验证器等)扩展模型 bean。JSF 创建 faces-config.xml 文件中指定的视图 bean 的实例,而该示例应用程序的持久层使用模型 bean。因此,该应用程序需要一个类似 ModelUtils.copy() 的实用方法,该方法将 JSF 实例化的视图 bean 的属性复制到持久层创建的模型对象,反之亦然。通过 ModelUtils 类,您还可以从名为 ModelResources 的资源包中获取模型资源(如配置参数、SQL 语句和错误消息)。最后,ModelUtils 包含一个 getSubscriberDAO() 方法,该方法返回 SubscriberDAO 接口的一个实例,该接口定义了在关系数据库中选择、删除、插入、更新 Subscriber 对象的方法。

该示例应用程序提供了 SubscriberDAO 接口的两个实现,一个基于 JDBC API,另一个使用 Oracle TopLink。这两个实现不在同一应用程序实例中使用。ModelResources 包有一个 DAO 参数,用于指定要使用的 DAO 实现。基于 JDBC 的 DAO 的唯一好处是它只使用标准的 Java API 和 SQL。TopLink 也在内部使用 JDBC,但它提供了很多好处:

  • 由于使用可视化工具(Mapping Workbench 和 Session Editor)定义对象关系映射和配置应用程序,因此提高了工作效率。这显著减少了手动编写的代码数量;
  • 不必再使用类似 JDBC 这样的低级 API;
  • 因为 TopLink 缓存从数据库读取的对象,所以应用程序速度更快。此外,TopLink 在内部生成了非常高效的 SQL 语句;
  • 除 SQL 以外,TopLink 还支持几种其他查询机制,如“按示例查询”、基于 Java 表达式的查询和 EJB QL;
  • 对多服务器实例(集群)的支持使您能够构建可伸缩的应用程序。

 

创建模型 Bean 该示例 Web 应用程序包含两个未使用任何 JSF API 的模型 bean(LoginInfoSubscriber)。LoginInfo 类定义了两个属性(emailpassword),并同任何 JavaBean 一样,借助于 java.io.Serializable 接口声明为可串行化:

package jsfdb.model;
public class LoginInfo implements java.io.Serializable {
private String email;
private String password;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

 

Subscriber 类扩展了 LoginInfo、定义了五个其他属性(name、manager、developer、administratorsubscriptionType),并有一个名为 countNewsletters() 的方法:

package jsfdb.model;
public class Subscriber extends LoginInfo {
public static final int TYPE_DAILY = 1;
public static final int TYPE_WEEKLY = 2;
public static final int TYPE_MONTHLY = 3;
private String name;
private boolean manager;
private boolean developer;
private boolean administrator;
private int subscriptionType;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
...
public int countNewsletters() {
int count = 0;
if (isManager())
count++;
if (isDeveloper())
count++;
if (isAdministrator())
count++;
return count;
}
}

 

Subscriber 的实例被保存到一个表中,可以使用以下 SQL 语句创建该表:

CREATE TABLE subscribers (
subscriberEmail VARCHAR2(80) PRIMARY KEY,
subscriberPassword VARCHAR2(20),
subscriberName VARCHAR2(80),
managerFlag NUMBER(1),
developerFlag NUMBER(1),
administratorFlag NUMBER(1),
subscriptionType NUMBER(1) )

 

通常,该 bean 的属性和相应的表列应具有相同的名称。本文使用了不同的名称,以便您可以轻松地辨认哪些地方使用了属性、哪些地方使用了列。

使用数据访问对象SubscriberDAO 接口定义视图 bean 为保存和检索从模型 bean 继承的属性而调用的方法:

package jsfdb.model.dao;
import jsfdb.model.LoginInfo;
import jsfdb.model.Subscriber;
import jsfdb.model.err.IncorrectPasswordException;
import jsfdb.model.err.LoginException;
import jsfdb.model.err.ProfileException;
import jsfdb.model.err.SubscribeException;
import jsfdb.model.err.UnknownSubscriberException;
import jsfdb.model.err.UnsubscribeException;
public interface SubscriberDAO {
public Subscriber select(LoginInfo loginInfo)
throws LoginException,
UnknownSubscriberException,
IncorrectPasswordException;
public void insert(Subscriber subscriber)
throws SubscribeException;
public void update(Subscriber subscriber)
throws ProfileException;
public void delete(Subscriber subscriber)
throws UnsubscribeException;
}

 

getSubscriberDAO() 方法加载一个 DAO 实现(TopLinkSubscriberDAOJDBCSubscriberDAO)并返回所加载类的实例:

package jsfdb.model;
import jsfdb.model.dao.SubscriberDAO;
...
public class ModelUtils {
...
private static SubscriberDAO subscriberDAO;
...
public synchronized static SubscriberDAO getSubscriberDAO() {
if (subscriberDAO == null)
try {
Class daoClass = Class.forName(getResource("DAO"));
subscriberDAO
= (SubscriberDAO) daoClass.newInstance();
} catch (Exception x) {
log(x);
throw new InternalError(x.getMessage());
} catch (Exception x) {
log(x);
throw new InternalError(x.getMessage());
} catch (Exception x) {
log(x);
throw new InternalError(x.getMessage());
}
return subscriberDAO;
}
...
}

 

getSubscriberDAO() 方法使用 getResource()(使用 getResources() 获取模型资源)获取 DAO 实现的名称。使用 log() 方法记录任何意外错误:

package jsfdb.model;
...
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ModelUtils {
public static final String RESOURCES
= ModelUtils.class.getPackage().getName()
+ ".res.ModelResources";
private static ResourceBundle resources;
public static void log(Throwable x) {
Logger.global.log(Level.SEVERE, x.getMessage(), x);
}
public static synchronized ResourceBundle getResources() {
if (resources == null)
try {
resources = ResourceBundle.getBundle(RESOURCES);
} catch (MissingResourceException x) {
log(x);
throw new InternalError(x.getMessage());
}
return resources;
}
public static String getResource(String key) {
return getResources().getString(key);
}
...
}

 

ModelResources 包包含 DAO 参数、基于 JDBC 的 DAO 使用的 SQL 语句以及由 DAO 方法抛出的异常消息:

DAO=jsfdb.model.dao.TopLinkSubscriberDAO
TopLinkSession=JSFDBSession
# DAO=jsfdb.model.dao.JDBCSubscriberDAO
JavaCompEnv=java:comp/env
DataSource=jdbc/OracleDS
SelectStatement=...
InsertStatement=...
UpdateStatement=...
DeleteStatement=...
SubscribeException=Subscription failed. \
Please try another email address.
ProfileException=Couln't update your profile. \
Please contact the Webmaster.
UnsubscribeException=Unsubscription failed. \
Please contact the Webmaster.
LoginException=Login failed. \
Please contact the Webmaster.
UnknownSubscriberException=Unknown subscriber. \
Please subscribe.
IncorrectPasswordException=Incorrect password. \
Please try to login again.

 

所有异常类均扩展了 ModelException:

package jsfdb.model.err;
import jsfdb.model.ModelUtils;
public class ModelException extends Exception {
public ModelException(String messageKey) {
super(ModelUtils.getResource(messageKey));
}
}

 

每个异常类只包含一个构造函数,该函数将类名传递给 ModelException 超类的构造函数:

package jsfdb.model.err;
public class LoginException extends ModelException {
public LoginException() {
super("LoginException");
}
}

 

JDBCSubscriberDAO 类包含一个具有 JNDI 的 DataSource,并实现由 SubscriberDAO 定义的方法。请参阅本文附带的可下载存档中提供的 JDBCSubscriberDAO.java 源代码。

TopLinkSubscriberDAO 类也实现 SubscriberDAO 接口。TopLinkSubscriberDAO() 构造函数获取一个服务器会话,并添加一个将在 JVM 停止时关闭会话的关闭钩子 (hook)。基于 TopLink 的 DAO 包含用于获取由实现 SubscriberDAO 接口的方法使用的客户端会话和工作单元的实用方法:

package jsfdb.model.dao;
...
import oracle.toplink.sessions.UnitOfWork;
import oracle.toplink.threetier.ClientSession;
import oracle.toplink.threetier.Server;
import oracle.toplink.tools.sessionmanagement.SessionManager;
public class TopLinkSubscriberDAO implements SubscriberDAO {
private Server serverSession;
public TopLinkSubscriberDAO() {
SessionManager manager = SessionManager.getManager();
String id = ModelUtils.getResource("TopLinkSession");
ClassLoader loader = this.getClass().getClassLoader();
serverSession = (Server) manager.getSession(id, loader);
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
serverSession.logout();
SessionManager.getManager().getSessions().remove(
ModelUtils.getResource("TopLinkSession"));
}
});
}
private ClientSession acquireClientSession() {
return serverSession.acquireClientSession();
}
private UnitOfWork acquireUnitOfWork() {
return acquireClientSession().acquireUnitOfWork();
}
...
}

 

创建视图 Bean 如前所述,这两个视图 bean(LoginInfoBean 和 SubscriberBean)使用 UI 特有的代码(如用于验证数据的方法(验证器)和用于处理数据的方法(操作))扩展了模型 bean(LoginInfoSubscriber)。LoginInfoBean 类包含一个名为 loginAction() 的操作方法。另一个视图 bean (SubscriberBean) 定义另一个属性 (loggedIn),实现 emailValidator() 方法(用于验证电子邮件地址中是否存在字符 @)、提供几个操作方法,并将从 Subscriber 继承的常量作为只读属性公开,以便可以在 JSF 页面中使用 EL 访问它们:

package jsfdb.view;
import jsfdb.model.Subscriber;
...
public class SubscriberBean extends Subscriber {
...
private transient boolean loggedIn = false;
public boolean isLoggedIn() {
return loggedIn;
}
public void setLoggedIn(boolean loggedIn) {
this.loggedIn = loggedIn;
}
public void emailValidator(FacesContext context,
UIComponent comp, Object value) {
...
}
public String subscribeAction() {
...
}
public String profileAction() {
...
}
public String unsubscribeAction() {
...
}
public String cancelAction() {
if (!loggedIn)
return "login";
else
return "cancel";
}
public int getDailyConst() {
return TYPE_DAILY;
}
public int getWeeklyConst() {
return TYPE_WEEKLY;
}
public int getMonthlyConst() {
return TYPE_MONTHLY;
}
}

 

本部分的下列段落将详细介绍这两个视图 bean 的操作方法。具体说来,您将了解

  • loginAction() 如何选择行,
  • subscribeAction() 如何插入新行,
  • profileAction() 如何更新现有行,
  • unsubscribeAction() 如何删除行。

 

下一部分(JSF 视图和数据验证

  • 提供该示例应用程序的 JSF 页面,
  • 介绍了如何借助 JSF 对数据进行验证,
  • 演示了如何创建 UI 组件和 bean 属性之间的绑定,
  • 演示了如何将操作方法绑定到 JSF 表单的提交按钮。

 

选择行(登录操作)JDBCSubscriberDAOselect() 方法执行一个 SQL 查询,选择具有给定电子邮件地址的订户,然后验证口令。以下是基于 JDBC 的 DAO 使用的 SELECT 语句:

SELECT subscriberPassword,
subscriberName,
managerFlag,
developerFlag,
administratorFlag,
subscriptionType
FROM subscribers
WHERE subscriberEmail=?

 

使用 TopLink 时,尽管您可以使用 SQL,但不必构建 SQL 语句。如果在私有 read() 方法中使用查询 API(它从 TopLinkSubscriberDAO 的多个公共方法中调用,执行一个返回具有给定电子邮件地址的订户的查询),则 TopLink 框架将生成 SQL 查询:

package jsfdb.model.dao;
...
import oracle.toplink.expressions.ExpressionBuilder;
import oracle.toplink.queryframework.ReadObjectQuery;
import oracle.toplink.sessions.Session;
public class TopLinkSubscriberDAO implements SubscriberDAO {
...
private Subscriber read(Session session, String email) {
ReadObjectQuery query
= new ReadObjectQuery(Subscriber.class);
ExpressionBuilder builder = new ExpressionBuilder();
query.setSelectionCriteria(
builder.get("email").equal(email));
return (Subscriber) session.executeQuery(query);
}
...
}

 

TopLinkSubscriberDAOselect() 方法取得客户端会话并调用 read()

package jsfdb.model.dao;
...
public class TopLinkSubscriberDAO implements SubscriberDAO {
...
public Subscriber select(LoginInfo loginInfo)
throws LoginException,
UnknownSubscriberException,
IncorrectPasswordException {
Subscriber s = null;
try {
ClientSession session = acquireClientSession();
s = read(session, loginInfo.getEmail());
} catch (Exception x) {
ModelUtils.log(x);
throw new LoginException();
}
if (s == null)
throw new UnknownSubscriberException();
if (!s.getPassword().equals(loginInfo.getPassword()))
throw new IncorrectPasswordException();
return s;
}
...
}

 

LoginInfoBean 类包含 loginAction() 方法,该方法使用 ViewUtils.eval() 获取 SubscriberBean 类(由 JSF 管理)的实例,并获取 adminEmail 初始化参数的值。随后,loginAction() 调用 select(),使用 ModelUtils.copy() 将选定订户的属性复制到由 JSF 管理的 JavaBean,将 loggedIn 标记设置为 true,然后根据订户的电子邮件返回 listprofile 结果:

package jsfdb.view;
import jsfdb.model.LoginInfo;
import jsfdb.model.Subscriber;
import jsfdb.model.ModelUtils;
import jsfdb.model.err.LoginException;
import jsfdb.model.err.IncorrectPasswordException;
import jsfdb.model.err.UnknownSubscriberException;
public class LoginInfoBean extends LoginInfo {
public String loginAction() {
SubscriberBean subscriber
= (SubscriberBean) ViewUtils.eval("#{subscriber}");
String adminEmail
= (String) ViewUtils.eval("#{initParam.adminEmail}");
try {
Subscriber selectedSubscriber
= ModelUtils.getSubscriberDAO().select(this);
ModelUtils.copy(selectedSubscriber, subscriber);
subscriber.setLoggedIn(true);
if (subscriber.getEmail().equals(adminEmail))
return "list";
else
return "profile";
} catch (LoginException x) {
ViewUtils.addExceptionMessage(x);
return null;
} catch (UnknownSubscriberException x) {
ViewUtils.addExceptionMessage(x);
return null;
} catch (IncorrectPasswordException x) {
ViewUtils.addExceptionMessage(x);
return null;
}
}
}

 

ViewUtils 类提供用于求解 JSF 表达式和将错误消息添加到 JSF 上下文的实用方法:

package jsfdb.view;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import java.util.ResourceBundle;
public class ViewUtils {
public static Object eval(String expr) {
FacesContext context
= FacesContext.getCurrentInstance();
ValueBinding binding
= context.getApplication().createValueBinding(expr);
return binding.getValue(context);
}
public static void addErrorMessage(FacesContext context,
String compId, String messageId) {
ResourceBundle bundle = ResourceBundle.getBundle(
context.getApplication().getMessageBundle());
FacesMessage message = new FacesMessage(
bundle.getString(messageId));
message.setSeverity(FacesMessage.SEVERITY_ERROR);
context.addMessage(compId, message);
}
public static void addExceptionMessage(Exception x) {
FacesContext context
= FacesContext.getCurrentInstance();
FacesMessage message
= new FacesMessage(x.getMessage());
message.setSeverity(FacesMessage.SEVERITY_FATAL);
context.addMessage(null, message);
}
}

 

ModelUtilscopy() 方法获取源 bean 的属性,并使用 JavaBean Introspection API 和 Java Reflection API 设置另一个 bean 的属性:

package jsfdb.model;
...
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.IntrospectionException;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
...
public class ModelUtils {
...
public static void copy(Object source, Object dest) {
try {
Class sourceClass = source.getClass();
Class destClass = dest.getClass();
BeanInfo info = Introspector.getBeanInfo(sourceClass);
PropertyDescriptor props[]
= info.getPropertyDescriptors();
Object noParams[] = new Object[0];
Object oneParam[] = new Object[1];
for (int i = 0; i < props.length; i++) {
Method getter = props[i].getReadMethod();
if (getter == null)
continue;
Object value = getter.invoke(source, noParams);
Method setter = props[i].getWriteMethod();
if (setter != null && sourceClass != destClass)
try {
setter = destClass.getMethod(
setter.getName(),
setter.getParameterTypes());
} catch (NoSuchMethodException x) {
setter = null;
}
if (setter != null) {
oneParam[0] = value;
setter.invoke(dest, oneParam);
}
}
} catch (IntrospectionException x) {
log(x);
throw new InternalError(x.getMessage());
} catch (IllegalAccessException x) {
log(x);
throw new InternalError(x.getMessage());
} catch (IllegalArgumentException x) {
log(x);
throw new InternalError(x.getMessage());
} catch (SecurityException x) {
log(x);
throw new InternalError(x.getMessage());
} catch (InvocationTargetException x) {
log(x.getTargetException());
throw new InternalError(
x.getTargetException().getMessage());
}
}
}

 

如果所有属性都是基元对象或不变对象(如 String),则 copy() 方法将比较适用。如果某个 bean 包含可变对象(如子 bean 或集合),则应克隆这些属性的值并将它们的克隆存储到 dest bean 中。

插入新行(订阅操作)JDBCSubscriberDAOinsert() 方法执行一个 SQL 语句,向数据库中插入一个新订户:

INSERT INTO subscribers (
subscriberEmail,
subscriberPassword,
subscriberName,
managerFlag,
developerFlag,
administratorFlag,
subscriptionType )
VALUES (?, ?, ?, ?, ?, ?, ?)

 

使用 TopLink 时,在必须使用 TopLink Mapping Workbench 工具定义的对象关系映射中将自动生成 SQL 语句。TopLinkSubscriberDAOinsert() 方法获取一个工作单元、创建一个新的 Subscriber 实例、设置它的属性、注册新对象并调用 commit()

package jsfdb.model.dao;
...
public class TopLinkSubscriberDAO implements SubscriberDAO {
...
public void insert(Subscriber subscriber)
throws SubscribeException {
try {
UnitOfWork uow = acquireUnitOfWork();
Subscriber s = new Subscriber();
ModelUtils.copy(subscriber, s);
uow.registerObject(s);
uow.commit();
} catch (Exception x) {
ModelUtils.log(x);
throw new SubscribeException();
}
}
...
}

 

SubscriberBeansubscribeAction() 方法调用 insert()、将 loggedIn 标记设置为 true,并在未出错的情况下返回 subscribed 结果:

package jsfdb.view;
...
public class SubscriberBean extends Subscriber {
...
public final static String SELECT_NEWSLETTER_ID
= "jsfdb.view.SubscriberBean.SELECT_NEWSLETTER";
...
public String subscribeAction() {
if (countNewsletters() == 0) {
ViewUtils.addErrorMessage(
FacesContext.getCurrentInstance(),
null, SELECT_NEWSLETTER_ID);
return null;
}
try {
ModelUtils.getSubscriberDAO().insert(this);
setLoggedIn(true);
return "subscribed";
} catch (SubscribeException x) {
ViewUtils.addExceptionMessage(x);
return null;
}
}
...
}

 

更新现有行(配置文件操作)JDBCSubscriberDAOupdate() 方法执行一个 SQL 语句,更新现有订户的配置文件:

UPDATE subscribers SET
subscriberPassword=?,
subscriberName=?,
managerFlag=?,
developerFlag=?,
administratorFlag=?,
subscriptionType=?
WHERE subscriberEmail=?

 

TopLinkSubscriberDAOupdate() 方法获取一个工作单元、调用私有 read() 方法获取具有给定电子邮件的 bean、更新 bean 属性并调用 commit()

package jsfdb.model.dao;
...
public class TopLinkSubscriberDAO implements SubscriberDAO {
...
public void update(Subscriber subscriber)
throws ProfileException {
try {
UnitOfWork uow = acquireUnitOfWork();
Subscriber s = read(uow, subscriber.getEmail());
ModelUtils.copy(subscriber, s);
uow.commit();
} catch (Exception x) {
ModelUtils.log(x);
throw new ProfileException();
}
}
...
}

 

SubscriberBeanprofileAction() 方法调用 update() 并返回 null 结果:

package jsfdb.view;
...
public class SubscriberBean extends Subscriber {
...
public final static String SELECT_NEWSLETTER_ID
= "jsfdb.view.SubscriberBean.SELECT_NEWSLETTER";
...
public String profileAction() {
if (!loggedIn)
return "login";
if (countNewsletters() == 0) {
ViewUtils.addErrorMessage(
FacesContext.getCurrentInstance(),
null, SELECT_NEWSLETTER_ID);
return null;
}
try {
ModelUtils.getSubscriberDAO().update(this);
return null;
} catch (ProfileException x) {
ViewUtils.addExceptionMessage(x);
return null;
}
}
...
}

 

删除行(取消订阅操作)JDBCSubscriberDAOdelete() 方法执行一个 SQL 语句,从数据库中删除一个订户:

DELETE FROM subscribers WHERE subscriberEmail=?

 

TopLinkSubscriberDAOdelete() 方法获取一个工作单元、调用私有 read() 方法获取具有给定电子邮件的 bean、删除 bean 属性并调用 commit()

package jsfdb.model.dao;
...
public class TopLinkSubscriberDAO implements SubscriberDAO {
...
public void delete(Subscriber subscriber)
throws UnsubscribeException {
try {
UnitOfWork uow = acquireUnitOfWork();
Subscriber s = read(uow, subscriber.getEmail());
uow.deleteObject(s);
uow.commit();
} catch (Exception x) {
ModelUtils.log(x);
throw new UnsubscribeException();
}
}
}

 

SubscriberBeanunsubscribeAction() 方法调用 delete() 并在未出错的情况下返回 unsubscribed 结果:

package jsfdb.view;
...
public class SubscriberBean extends Subscriber {
...
public String unsubscribeAction() {
if (!loggedIn)
return "login";
try {
ModelUtils.getSubscriberDAO().delete(this);
return "unsubscribed";
} catch (UnsubscribeException x) {
ViewUtils.addExceptionMessage(x);
return null;
}
}
...
}

 

创建 TopLink 项目以下步骤说明了如何使用 Mapping Workbench 创建一个 TopLink 项目。

第 1 步:启动 OracleAS TopLink Mapping Workbench。然后,从主菜单中选择 FileNew Project...。在 Create New Project 对话框中,提供现有数据库的名称(例如,orcl),然后单击 OK

第 2 步:Mapping Workbench 将显示一个窗口让您保存该项目。选择一个目录,提供一个文件名(例如,jsfdb),然后单击 Save

第 3 步:在左侧 Navigator 窗格中选择该数据库,单击 Add...,输入一个登录标签(例如,orclLogin),然后单击 OK

第 4 步:在主窗口中,选择新建的登录,并提供 JDBC 驱动程序类的名称、数据库 URL、用户名和口令。别忘了选中 Save Password 复选框。

图 2

第 5 步:Navigator 窗格中右键单击该数据库名称,然后单击 Log In to Database

第 6 步:Navigator 窗格中再次右键单击该数据库名称,然后单击 Add or Update Existing Tables from Database

第 7 步:Import Tables from Database 窗口中的 Table Name Pattern 域中,输入 SUBSCRIBERS,单击 Get Table Names,从左侧 Available Tables 窗格中选择 SUBSCRIBERS 表,将它添加到右侧的 Selected Tables 窗格,然后单击 OK

图 3

第 8 步:Navigator 窗格中选择该项目 (jsfdb),确保 General 是右侧窗格的当前选项卡,单击 Add Entries...,在显示的目录树中寻找,选择包含该 Web 应用程序已编译的类的目录,然后单击 OK。选中的目录将被添加到主窗口的 Class Path 列表中。

第 9 步:Navigator 窗格中再次右键单击该项目,然后单击 Add or Refresh Classes...

第 10 步:Select Classes 窗口左侧的 Available Packages/Classes 窗格中选择 LoginInfoSubscriber 类。

第 11 步:LoginInfoSubscriber 类移到右侧的 Selected Classes 窗格中,然后单击 OK。只有 Subscriber 类被映射到了表上,但由于 LoginInfoSubscriber 的超类,因此我们还需要将其映射到表上。

图 4

第 12 步:在主窗口的 Navigator 窗格中选择 Subscriber 类。在右侧的 Editor 窗格中,确保 Descriptor 是当前选项卡,并在 Associated Table 列表中选择 SUBSCRIBERS

图 5

第 13 步:Navigator 窗格中,右键单击 Subscriber 类,选择 Map Inherited Attributes 并单击 To Superclass

第 14 步:右键单击 Subscriber 类的每个 bean 属性(例如,administrator),选择 Map As...,然后单击 Direct to Field

第 15 步:Navigator 窗格中选择 Subscriber 类的每个 bean 属性(例如,email),然后在右侧的 Editor 窗格中设置相应的 Database Field(例如,SUBSCRIBEREMAIL)。

图 6

第 16 步:由于 LoginInfo 类未映射到任何表,因此必须从项目中删除它。在 Navigator 窗格中右键单击 LoginInfo,然后单击 Remove Class。当显示 Remove Descriptors 对话框时,单击 Remove

第 17 步:在主菜单中,选择 File、Export、Project Deployment XML... 或使用 CTRL+D。输入 JSFDBProject.xml 并单击 OK。然后,选择要将该 XML 文件保存到的目录。

图 8

 

 

第 18 步:使用 FileSave 或使用 CTRL+S 保存该项目。除了将项目设置存储到 jsfdb.mwp 文件中以外,保存操作还将在 class、descriptortable 子目录中创建几个 XML 文件。现在,您可以选择 OracleAS TopLink Mapping Workbench 了。

配置 TopLink 会话下列步骤说明了如何使用 Sessions Editor 配置一个 TopLink 会话。

第 1 步:启动 OracleAS TopLink Sessions Editor。从主菜单中选择 FileNew...(或使用 CTRL+N)。在 New 对话框中,保持 sessions.xml 文件名不变,单击 Browse 按钮为该文件选择 Location,在 Session Name 中输入 JSFDBSession,然后单击 OK

第 2 步:Navigator 窗格中选择 JSFDBSession,确保 GeneralEditor 窗格的当前选项卡,然后在 Project Type 组的 XML 域中输入 JSFDBProject.xml

图 7

第 3 步:选择 Editor 窗格的 Logging 选项卡,然后在Enable Logging 列表中选择 True

第 4 步:选择 Editor 窗格的 Login 选项卡,选中 Database Platform 复选框,在相应的列表中选择 Oracle,然后在 Data Source 域中输入 java:comp/env/jdbc/OracleDS

图 9

第 5 步:从主菜单中选择 FileSave(或使用 CTRL+S)保存 sessions.xml 文件。现在,您可以关闭 OracleAS TopLink Sessions Editor 了。

JSF 视图和数据验证

该示例 Web 应用程序包含一个订阅页面 (subscribe.jsp)、一个登录页面 (login.jsp)、一个配置文件编辑页面 (profile.jsp)、一个取消订阅页面 (unsubscribe.jsp、两个确认页面(subscribed.jspunsubscribed.jsp)、一个列出所有订户的页面 (list.jsp) 和一个注销页面 (logout.jsp)。由 JSF 页面创建的某些表单借助于先前部分中的 DAO 方法触发执行基本数据库操作(INSERT、SELECT、UPDATEDELETE)的操作。

在网页中使用 JSFJSF 定义了两个标准标记库(Core 和 HTML),您必须在 JSP 页面中使用 <%@taglib%> 指令声明它们。JSF Core 库包含不依赖任何标记语言的标记,而 JSF HTML 库是为在 Web 浏览器中查看的页面设计的。这两个标记库的标准前缀分别是 f(用于 JSF Core)和 h(用于 JSF HTML)。所有 JSF 标记必须嵌套在 <f:view> 元素的内部。通过 <f:view> 标记,JSF 框架可以将 UI 组件的状态作为 HTTP 请求响应的一部分保存。

该示例 Web 应用程序的页面使用 JSF 和 JSTL Core 库创建 HTML 头:

<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<f:view>
<f:loadBundle var="labels" basename="jsfdb.view.res.Labels"/>
<c:set var="stylesheet"
value="${pageContext.request.contextPath}/stylesheet.css"/>
<html>
<head>
<title><h:outputText value="#{labels.subscribe}"/></title>
<link rel="stylesheet" type="text/css"
href="<c:out value='${stylesheet}'/>">
</head>
<body>
...
</body>
</html>
</f:view>

 

JSF 的 <h:outputText> 标记与 JSTL 的 <c:out> 标记非常相似,但也有一些差别。例如,<h:outputText> 使用 JSF EL,而 <c:out> 使用最初为 JSTL 1.0 创建并在随后被 JSP 2.0 采用的表达式语言。在上一个代码段中,<f:loadBundle> 标记加载了一个包含所有 JSF 页面的标签和标题的资源包:

subscribe=Subscribe
subscribed=Subscribed
login=Login
logout=Logout
profile=Profile
update=Update
unsubscribe=Unsubscribe
unsubscribed=Unsubscribed
cancel=Cancel
email=Email
password=Password
passwordDetail=Useful to change your profile
name=Name
newsletters=Newsletters
manager=Manager
developer=Developer
administrator=Administrator
subscriptionType=Subscription Type
daily=Daily
weekly=Weekly
monthly=Monthly
list=List

 

使用 JSF 标记构建表单许多应用程序的网页使用 JSF 创建表单。例如,login.jsp 使用 <h:inputText><h:inputSecret>,它们呈现两个类型分别为 textpassword 的 HTML <input> 元素。每个表单都有一个使用 <h:outputLabel> 呈现的标签。所有错误消息都使用 <h:message><h:messages> 呈现。<f:validateLength> 标记验证用户在表单域中输入的字符串的长度。<h:commandButton> 呈现一个 HTML 提交按钮。所有这些标记都位于 <h:form> 元素(呈现 HTML <form></form> 标记)中。除了 HTML 表单以外,login.jsp 页面还提供了订阅表单的链接,并使用 <h:outputLink><h:outputText> 呈现 HTML <a> 元素及其内容:

<!-- login.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<c:remove var="subscriber" scope="session"/>
<f:view>
...
<h1><h:outputText value="#{labels.login}"/></h1>
<h:outputLink value="subscribe.faces">
<h:outputText value="#{labels.subscribe}"/>
</h:outputLink>
<h:form id="login">
<h:messages globalOnly="true" styleClass="message"/>
<p><h:outputLabel for="email"
value="#{labels.email}"/>
<h:message for="email" styleClass="message"/><br>
<h:inputText id="email" required="true"
value="#{loginInfo.email}"
size="40" maxlength="80">
<f:validateLength minimum="1" maximum="80"/>
</h:inputText>
<p><h:outputLabel for="password"
value="#{labels.password}"/>
<h:message for="password" styleClass="message"/><br>
<h:inputSecret id="password" required="true"
value="#{loginInfo.password}"
size="10" maxlength="20">
<f:validateLength minimum="6" maximum="20"/>
</h:inputSecret>
<p><h:commandButton id="command"
value="#{labels.login}"
action="#{loginInfo.loginAction}"/>
</h:form>
...
</f:view>

 

当执行 login.jsp 页面时,JSF 创建 LoginInfoBean 实例,该实例的属性绑定到 UI 组件。当单击 Login 按钮时,Web 浏览器将用户的电子邮件地址和口令提交给服务器。JSF 框架验证用户是否为这两个必填域提供了值。如果字符串值的长度通过了 <f:validateLength> 执行的验证,则 JSF 设置 bean 属性并调用 loginAction() 方法对用户进行身份验证。

请注意,login.jsp 使用 JSTL 的 <c:remove> 标记从 session 作用域中删除任何 subscriber bean。如果用户首次访问该 Web 应用程序,或者如果他的前一个会话已经过期,则不存在这样的 bean 实例,因此 <c:remove> 将不起作用。否则,该标记清理该会话作用域。由于在 loginAction() 中求解的一个 EL 表达式中引用了 subscriber bean,因此 JSF 将创建 SubscriberBean(在 faces-config.xml 文件中指定)的实例。loginAction() 方法从数据库中读取订户的配置文件并设置该 bean 的属性。subscriber bean 将由其他 JSF 页面使用,并将保存在 session 作用域中,直到 logout.jsp 删除它:

<!-- logout.jsp -->
...
<f:view>
...
</f:view>
<c:remove var="subscriber" scope="session"/>

 

subscribe.jspprofile.jsp 页面使用 JSF 呈现其他表单元素,如复选框和列表。JSF 的 <h:panelGrid><h:panelGroup> 标记用于呈现其单元格包含订阅表单的三个复选框的 HTML 表格。<h:selectBooleanCheckbox> 标记呈现 checkbox 类型的 <input> 元素

<!-- subscribe.jsp -->
...
<f:view>
...
<h:form id="subscribe">
...
<p><h:outputText value="#{labels.newsletters}"/>
<h:message for="newsletters" styleClass="message"/><br>
<h:panelGrid id="newsletters"
columns="3" border="0" cellspacing="5">
<h:panelGroup>
<h:selectBooleanCheckbox id="manager"
value="#{subscriber.manager}"/>
<h:outputLabel for="manager"
value="#{labels.manager}"/>
</h:panelGroup>
<h:panelGroup>
<h:selectBooleanCheckbox id="developer"
value="#{subscriber.developer}"/>
<h:outputLabel for="developer"
value="#{labels.developer}"/>
</h:panelGroup>
<h:panelGroup>
<h:selectBooleanCheckbox id="administrator"
value="#{subscriber.administrator}"/>
<h:outputLabel for="administrator"
value="#{labels.administrator}"/>
</h:panelGroup>
</h:panelGrid>
...
</h:form>
...
</f:view>

 

<h:selectOneMenu><f:selectItem> 标记呈现一个下拉式列表,用户可以通过该列表选择他们的订阅类型:每日、每周或每月:

<!-- subscribe.jsp -->
...
<f:view>
...
<h:form id="subscribe">
...
<p><h:outputLabel for="subscriptionType"
value="#{labels.subscriptionType}"/>
<h:message for="subscriptionType"
styleClass="message"/><br>
<h:selectOneMenu id="subscriptionType"
value="#{subscriber.subscriptionType}"
required="true">
<f:validateLongRange minimum="1" maximum="3"/>
<f:selectItem itemLabel="#{labels.daily}"
itemValue="#{subscriber.dailyConst}"/>
<f:selectItem itemLabel="#{labels.weekly}"
itemValue="#{subscriber.weeklyConst}"/>
<f:selectItem itemLabel="#{labels.monthly}"
itemValue="#{subscriber.monthlyConst}"/>
</h:selectOneMenu>
...
</h:form>
...
</f:view>

 

subscribeAction() 方法(在 subscribe.jsp 中使用)将新订户的配置文件保存到数据库中。profile.jsp 页面显示由 subscriber bean 保持的首选设置,并允许用户更改他的配置文件(profileAction() 方法更新存储在数据库中的信息)。

unsubscribe.jsp 呈现的表单包含两个绑定到不同操作方法的提交按钮:

<!-- unsubscribe.jsp -->
...
<f:view>
...
<h:form id="unsubscribe">
...
<p><h:commandButton id="command"
value="#{labels.unsubscribe}"
action="#{subscriber.unsubscribeAction}"/>
<h:commandButton id="cancel"
value="#{labels.cancel}"
action="#{subscriber.cancelAction}"/>
</h:form>
...
</f:view>

 

unsubscribeAction() 方法从数据库中删除订户的配置文件。先前部分(JavaBean 和数据访问对象)介绍了所有操作方法的代码。

使用标准的和定制的验证器 JSF 提供了几个验证器标记,如在示例应用程序的 JSF 页面中使用的 <f:validateLength><f:validateLongRange>。此外,许多 HTML 标记支持 required 属性。通过该属性可以指定用户是否必须总要为该 UI 组件提供值。但在大多数情况下,您需要应用程序特有的验证。您可以构建定制验证标记,但 validator 属性(许多标准 JSF 标记都支持它)更容易使用。您可以使用该属性指定一个将用于验证用户输入值的方法:

<!-- subscribe.jsp -->
...
<f:view>
...
<h:form id="subscribe">
...
<p><h:outputLabel for="email"
value="#{labels.email}"/>
<h:message for="email" styleClass="message"/><br>
<h:inputText id="email" required="true"
validator="#{subscriber.emailValidator}"
value="#{subscriber.email}"
size="40" maxlength="80">
<f:validateLength minimum="1" maximum="80"/>
</h:inputText>
...
</h:form>
...
</f:view>

 

SubscriberBeanemailValidator() 方法验证电子邮件地址是否包含 @ 字符:

package jsfdb.view;
...
import javax.faces.component.UIComponent;
import javax.faces.component.EditableValueHolder;
import javax.faces.context.FacesContext;
public class SubscriberBean extends Subscriber {
public final static String INVALID_EMAIL_ID
= "jsfdb.view.SubscriberBean.INVALID_EMAIL";
...
public void emailValidator(FacesContext context,
UIComponent comp, Object value) {
String email = (String) value;
if (email.indexOf("@") == -1) {
String compId = comp.getClientId(context);
ViewUtils.addErrorMessage(
context, compId, INVALID_EMAIL_ID);
((EditableValueHolder) comp).setValid(false);
}
}
...
}

 

错误消息通过 <h:message/> 在 JSF 页面中呈现,并从应用程序消息包中被获取:

jsfdb.view.SubscriberBean.INVALID_EMAIL=invalid
jsfdb.view.SubscriberBean.SELECT_NEWSLETTER=\
You must subscribe to at least one newsletter.
javax.faces.component.UIInput.REQUIRED=required
javax.faces.validator.LengthValidator.MAXIMUM=\
may contain maximum {0} characters
javax.faces.validator.LengthValidator.MINIMUM=\
must contain minimum {0} characters

 

除了应用程序特有的消息外,该资源包还包含几个默认 JSF 消息的替代形式。例如,javax.faces.component.UIInput.REQUIRED 关键字之后是一个错误消息,它将在用户没有为必填表单元素提供值时呈现。必须在 faces-config.xml 中配置应用程序消息包:

<faces-config>
<application>
<locale-config>
<default-locale>en</default-locale>
</locale-config>
<message-bundle>jsfdb.view.res.Messages</message-bundle>
</application>
...
</faces-config>

 

验证器标记和方法验证单个 UI 组件的值。但有时您不得不一起验证一组组件。这可以在一个操作方法中完成。例如,用户可能没有选中订阅表单的任何复选框,但至少应订阅一种时事通讯。如果 countNewsletters() 返回 0,则 subscribeAction() 方法将发出错误消息:

package jsfdb.view;
...
public class SubscriberBean extends Subscriber {
...
public final static String SELECT_NEWSLETTER_ID
= "jsfdb.view.SubscriberBean.SELECT_NEWSLETTER";
...
public String subscribeAction() {
if (countNewsletters() == 0) {
ViewUtils.addErrorMessage(
FacesContext.getCurrentInstance(),
null, SELECT_NEWSLETTER_ID);
return null;
}
...
}
...
}

 

未关联到任何特定 UI 组件的错误消息可以在 JSF 页面中使用 <h:messages globalOnly="true"/> 呈现。

结合使用 JSF 与 JSTL 的 SQL 标记 JSF API 为表定义了一个抽象数据模型 (javax.faces.model.DataModel),并提供了用于包装数组、列表、JDBC 结果集、JSTL 的 <sql:query> 标记的结果以及标量的实现。JSF 数据模型包含用于访问行数据的方法,但行的表示方式由具体的模型实现决定。当使用数组或列表时,每个元素都是一行。当使用结果集时,每一行都表示为一个其关键字为表列的 java.util.Map。由于不限制行模型,因此 Web 开发人员应负责使用 <h:column> 标记(必须嵌套在 <h:dataTable> 元素的内部)定义列在 JSF 页面中的呈现方式。

该示例应用程序的 web.xml 文件使用 <resource-ref> 声明一个数据源,并使用一个名为 javax.servlet.jsp.jstl.sql.dataSource 的上下文参数将它配置为默认的 JSTL 数据源。另一个参数 (adminEmail) 指定一个电子邮件地址,用于与登录订户的 email 属性进行比较(在 list.jsp 中):

<web-app>
<context-param>
<param-name>adminEmail</param-name>
<param-value>admin@localhost</param-value>
</context-param>
<context-param>
<param-name>javax.servlet.jsp.jstl.sql.dataSource</param-name>
<param-value>jdbc/OracleDS</param-value>
</context-param>
...
<resource-ref>
<res-ref-name>jdbc/OracleDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
...
</web-app>

 

只有在 web.xml 中指定了电子邮件地址的用户才可以执行 list.jsp 页面。因此,您必须在部署应用程序后以 admin@localhost 的身份进行订阅。当使用 adminEmail 地址登录时,login.jsp 页面将该请求转给 list.jsp。该示例应用程序中使用的此身份验证机制演示了操作方法(如 loginAction())如何根据可编程条件返回不同的结果(listprofile)。实际的应用程序将使用基于 HTTP 或基于表单的身份验证验证管理员的身份。

list.jsp 页面使用 JSTL 标记执行 SQL 查询,并创建一个用于保存结果集的 subscriberList 变量。该变量被传递给 <h:dataTable>,后者迭代结果集的各行,并调用每个行的标记主体。每个 <h:column> 标记都包含一个 <h:outputText value="#{row...}"/> 标记(用于呈现当前单元格的值)和一个作为表格的表头的一部分呈现的 facet:

<!-- list.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<%@ taglib prefix="sql" uri="http://java.sun.com/jstl/sql" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<c:if test="${subscriber == null || !subscriber.loggedIn}">
<c:redirect url="/login.faces"/>
</c:if>
<c:if test="${subscriber.email != initParam.adminEmail}">
<c:redirect url="/profile.faces"/>
</c:if>
<sql:query var="subscriberList" scope="request">
SELECT * FROM subscribers ORDER BY subscriberEmail
</sql:query>
<f:view>
...
<h:form id="list">
<h:dataTable id="table" var="row"
value="#{subscriberList}"
border="1" cellpadding="5">
<h:column>
<f:facet name="header">
<h:outputText value="#{labels.email}"/>
</f:facet>
<h:outputText value="#{row.subscriberEmail}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{labels.password}"/>
</f:facet>
<h:outputText value="#{row.subscriberPassword}"/>
</h:column>
...
</h:dataTable>
</h:form>
</body>
</html>
</f:view>

 

由于 JSF 不支持 page 作用域,因此 <sql:query> 标记使用 list.jsp 页面中的 request 作用域。如果您忘了指定 JSF 支持的作用域,则 JSTL 标记将使用默认的页面作用域,且这两个标记库的标记将无法通信。因此,请确保结合使用 JSF 和 JSTL 的标记时使用一个通用的作用域(request、sessionapplication)。

总结

本文演示了一个基于 JSF 的 Web 应用程序,它使用 DAO 模式、JDBC、SQL、TopLink 和 JSTL 访问关系数据库。您可以在开发实际应用程序时应用本文介绍的技术,并且您可以使用 Oracle 数据库、Oracle Application Server Containers for J2EE、TopLink 对象关系映射框架和 JSF 引用实现测试本文的示例。


Andrei Cioroianu (
devtools@devsphere.com) 是 Devsphere 的创始人,这是一家 Java 框架、XML 咨询和 Web 开发服务的供应商。Andrei 编写了许多 Java 文章,分别由 Oracle 技术网、ONJavaJavaWorldJava Developer's Journal 发表。他还与别人合著了 Java XML Programmer's Reference Professional Java XML 两书(均由 Wrox Press 出版)。

 

 

posted on 2008-04-27 16:03 Jarod.cn.LuLuLife 阅读(354) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航:
 
<2024年12月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

导航

统计

公告

我的知识Blog!

常用链接

留言簿(3)

随笔档案

文章档案

Image

搜索

最新评论

阅读排行榜

评论排行榜