<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    使用 JSF 構(gòu)建數(shù)據(jù)庫驅(qū)動的應(yīng)用程序

    開發(fā)基于 JSF 的 Web 應(yīng)用程序,這些應(yīng)用程序使用 Oracle TopLink 和 JSTL 的 SQL 標(biāo)記更新和查詢關(guān)系數(shù)據(jù)庫

    本文相關(guān)下載:
    示例代碼
    Oracle TopLink 10g
    Oracle 數(shù)據(jù)庫 10g
    OC4J 10g
    Oracle ADF 組件
    JavaServer Faces

    JavaServer Faces (JSF) 是一個人們期待已久的標(biāo)準(zhǔn) Java 技術(shù),用于創(chuàng)建基于 Web 的用戶界面。本文將介紹如何構(gòu)建 JSF 表單、如何使用 JSF 驗(yàn)證表單數(shù)據(jù)、如何實(shí)現(xiàn)訪問數(shù)據(jù)庫的 JSF 操作以及如何使用 JSF 呈現(xiàn) SQL 結(jié)果集。可以使用低級 API (JDBC)、對象關(guān)系 (O-R) 映射框架(如 Oracle TopLink)或 JSP 標(biāo)記庫(例如,JSTL)更新和查詢關(guān)系數(shù)據(jù)庫。本文通篇介紹了這些選件以及主要的 JSF 特性。

     

    應(yīng)用程序概述

    JSF 并非僅是另一個用于 Web 開發(fā)的標(biāo)記庫。它是一個基于 MVC 的框架,用于控制 Web 表單和管理 JavaBean。JSF 提供了一組有限的標(biāo)準(zhǔn) UI 組件,但您可以使用組件庫(如 Oracle 應(yīng)用程序開發(fā)框架 (ADF) Faces),還可以構(gòu)建您自己的定制組件。注意,由于所有這些 UI 組件均基于標(biāo)準(zhǔn)的 JSF API,因此它們可以在同一網(wǎng)頁中一起使用。

    JSF 頁面是常規(guī)的 JSP,它們使用標(biāo)準(zhǔn) JSF 標(biāo)記庫和/或其他基于 JSF API 的庫。執(zhí)行 JSF 頁面時,JSF 框架嘗試獲取或恢復(fù)所謂的“組件樹”(或“視圖”)。它包含 Web 頁面組件的信息、必須執(zhí)行的數(shù)據(jù)轉(zhuǎn)換和驗(yàn)證、UI 狀態(tài)必須存儲到的 bean 屬性、注冊的事件監(jiān)聽器等。JSF 創(chuàng)建組件樹(如果它不存在),然后將它保存到客戶端(使用隱藏的表單域)或服務(wù)器(作為會話屬性)以備后續(xù)請求使用。

    本文提供了一個供用戶訂閱幾個時事通訊的示例 Web 應(yīng)用程序。訂戶通過提供他們的電子郵件地址、姓名和首選項(xiàng)進(jìn)行注冊。他們還必須選擇一個口令,以便以后可以更改他們的配置文件。

    圖 1 顯示了訂閱表單。該示例應(yīng)用程序不分發(fā)任何時事通訊,而實(shí)際的應(yīng)用程序?qū)⑾蚬芾碚摺㈤_發(fā)人員和管理員發(fā)送不同的時事通訊。(同一訂戶可能收到多個時事通訊。)該示例應(yīng)用程序?qū)⒂脩襞渲梦募4娴疥P(guān)系數(shù)據(jù)庫中,并允許訂戶更改他們的配置文件。

    圖 1
    圖 1:“訂閱”窗體

    應(yīng)用程序配置

    為使 JSF 控制您的 Web 表單,必須在 web.xml 應(yīng)用程序描述文件中配置 Faces Servlet。您的 Web 頁面將具有 .jsp 擴(kuò)展名,但 URL 將使用 .faces 后綴或 /faces/ 前綴。必須將 JSF 控制器 servlet 映射到 web.xml 文件中的 *.faces/faces/*。本文提供的示例應(yīng)用程序使用后綴映射,并將 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) 開發(fā)人員預(yù)覽版為您提供了單一 IDE,通過它您除了可以編寫 Java 代碼以外,還可以定義 toplink 映射以及直觀構(gòu)建 JSF 和 JSP 頁面。Oracle JDeveloper 還提供了由豐富的 JSF 組件組成的 ADF Faces 集,您可以在應(yīng)用程序中重用這些組件。

    1) Oracle JDeveloper 10g (10.1.3) 開發(fā)人員預(yù)覽版下載

    2) 使用 JDeveloper 構(gòu)建 JSF/TopLink

    3) 獲得 Oracle ADF Faces 組件的預(yù)先試用版:一組豐富的 JSF 組件。

    其他資源:

    了解如何結(jié)合使用 ADF Faces 和 JDeveloper 10g

    制作 Faces
    JavaServer Faces (JSF) 為創(chuàng)建 Web GUI 提供了一個強(qiáng)大的新框架。

    從 ADF UIX 到 JSF
    Oracle ADF Faces 組件為 JavaServer Faces 提供了一個可重用的資料庫。

    Oracle JDeveloper 和 JSF

    Java Web 應(yīng)用程序(包括那些基于 JSF 的應(yīng)用程序)使用 JSP 和 JavaBean 將表現(xiàn)形式與應(yīng)用程序邏輯分離。使用類似 JSP 2.0 EL 的表達(dá)式語言 (EL) 可以將 JSF 標(biāo)記的屬性綁定到 bean 屬性(有時為 bean 方法)。JSF EL 通常使用 #{...}語法(而非 ${...} 構(gòu)造),以便用于 JSP 1.2 和 JSP 2.0。它還允許 JSF 在任何必要的情況下求解表達(dá)式(和重新求解),而不是讓 JSP 容器控制表達(dá)式的求解。在有意義的情況下,JSF EL 綁定是雙向的。例如,UI 組件可以取得 bean 屬性的值并將其作為默認(rèn)值提供給用戶。當(dāng)用戶提交表單數(shù)據(jù)時,此 UI 組件會更新 bean 屬性,以便應(yīng)用程序邏輯可以處理該新值。

    JavaBean(模型)、JSF 頁面(視圖)和 Faces Servlet(控制器)具有定義良好的角色,從而分離了對象模型、表現(xiàn)方式和請求處理。您可以更深入一步,將 UI 特有的 Java 代碼與其實(shí)例維護(hù)和處理數(shù)據(jù)的類分離。一個可行的解決方案是構(gòu)建對用戶界面一無所知的模型 bean,然后使用 JSF 特有的方法(如操作和驗(yàn)證器,本文稍后將對其進(jìn)行介紹)擴(kuò)展這些模型類。

    該示例應(yīng)用程序有兩個模型 bean(LoginInfoSubscriber),它們由兩個視圖 bean(LoginInfoBeanSubscriberBean)擴(kuò)展,JSF 對這兩個視圖 bean 的實(shí)例進(jìn)行管理。每個視圖 bean 實(shí)例都有一個標(biāo)識符和一個作用域(您可以在表 1 中找到它們)。請注意,JSF 規(guī)范在提到視圖 bean 時使用了術(shù)語“托管 bean”(managed bean) 或“輔助 bean”(backing bean)。JSF 對“模型 bean”和“視圖 bean”不作區(qū)分。實(shí)際上,您可能將應(yīng)用程序邏輯和 UI 代碼置于同一個類中,但這可能會降低類的可重用性,使維護(hù)變得更加困難,并且指定的開發(fā)人員無法將精力集中于他們的主要任務(wù)(如應(yīng)用程序邏輯或 Web 設(shè)計)。

    模型 bean 類 視圖 bean 類 Bean 標(biāo)識符 JSF 作用域
    LoginInfo LoginInfoBean loginInfo request
    Subscriber SubscriberBean subscriber session

    表 1:示例應(yīng)用程序的模型 bean 和視圖 bean

     

    常規(guī) JSP 頁面使用 <jsp:useBean> 標(biāo)準(zhǔn)操作實(shí)例化 JavaBean。使用 JSF 框架時,您不必在網(wǎng)頁中指定類名。而是在 XML 文件(通常名為 faces-config.xml)中配置 bean 實(shí)例。.(如果您開發(fā)大型應(yīng)用程序,則可能使用多個配置文件。這種情況下,必須在 web.xml 文件中添加一個 javax.faces.CONFIG_FILES 參數(shù)。)對于每個 bean 實(shí)例,您必須指定標(biāo)識符(bean 名)、類名和一個有效的 JSF 作用域(application、session、requestnone)。當(dāng)在 JSF 表達(dá)式 (#{...}) 中引用 bean 時,JSF 框架將驗(yàn)證該 bean 實(shí)例在給定作用域中是否存在,如果未找到,則創(chuàng)建它并使用默認(rèn)值(這些默認(rèn)值也可以在 JSF 配置文件中指定)對其進(jìn)行初始化:

    <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 屬性聲明為托管屬性。僅當(dāng)您要使用默認(rèn)值初始化 bean 屬性時,才需要在 faces-config.xml 文件中指定它們。

    Bean 與頁面的關(guān)系

    數(shù)據(jù)訪問方法(在下一部分中介紹)從視圖 bean 的操作方法中調(diào)用。每個操作方法與 JSP 頁面中的一個提交按鈕綁定(如表 2 所示)。當(dāng)用戶單擊該按鈕時,Web 瀏覽器將表單數(shù)據(jù)發(fā)送給服務(wù)器。JSF 框架驗(yàn)證表單數(shù)據(jù),并在出錯情況下將該表單返回給用戶。否則,則將有效的表單數(shù)據(jù)存儲到托管 bean 的屬性中,且 JSF 將調(diào)用綁定到被單擊按鈕的操作方法。因此,您的操作方法應(yīng)該實(shí)現(xiàn)應(yīng)用程序邏輯,以處理該 bean 的屬性。

    數(shù)據(jù)訪問方法 托管 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 方法

     

    頁面到頁面的導(dǎo)航

    每個操作方法都返回一個名為“結(jié)果”的字符串。JSF 使用導(dǎo)航處理程序確定對每個結(jié)果所執(zhí)行的操作。如果操作方法返回 null,則必須重新顯示同一頁面。否則,會根據(jù)返回的結(jié)果顯示其他頁面。表 3 包含該示例 Web 應(yīng)用程序的 JSF 表單、可能的結(jié)果以及針對每個結(jié)果顯示的頁面。

    JSF 表單 可能的結(jié)果 顯示的頁面
    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:當(dāng)用戶單擊表單的提交按鈕時,JSF 將驗(yàn)證表單數(shù)據(jù),
    并在未出錯的情況下,調(diào)用綁定到被單擊按鈕的操作方法。然后,JSF 將使用該操作方法返回的結(jié)果
    確定接下來應(yīng)顯示的頁面。

     

    默認(rèn) JSF 導(dǎo)航處理程序使用在 JSF 配置文件中指定的一組導(dǎo)航規(guī)則。例如,當(dāng)用戶填寫登錄表單 (login.jsp) 并單擊 Login 按鈕時,loginAction() 方法會根據(jù)登錄用戶的角色(訂戶或管理員)返回 profilelist。請注意,如果用戶是未知的或者口令錯誤,則 loginAction() 將返回 null。這種情況下,JSF 將顯示同一登錄表單。如果身份驗(yàn)證成功,則 JSF 將根據(jù) loginAction() 返回的結(jié)果將該請求轉(zhuǎn)給 profile.jsplist.jsp。以下是 faces-config.xml 中規(guī)定的導(dǎo)航規(guī)則:

    <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 和數(shù)據(jù)訪問對象 (DAO)

    本部分介紹了如何創(chuàng)建模型 bean 和視圖 bean,以及如何實(shí)現(xiàn) bean 的持久性。模型 bean 定義必須保存到數(shù)據(jù)庫中的屬性。視圖 bean 使用 UI 特有的代碼(操作、驗(yàn)證器等)擴(kuò)展模型 bean。JSF 創(chuàng)建 faces-config.xml 文件中指定的視圖 bean 的實(shí)例,而該示例應(yīng)用程序的持久層使用模型 bean。因此,該應(yīng)用程序需要一個類似 ModelUtils.copy() 的實(shí)用方法,該方法將 JSF 實(shí)例化的視圖 bean 的屬性復(fù)制到持久層創(chuàng)建的模型對象,反之亦然。通過 ModelUtils 類,您還可以從名為 ModelResources 的資源包中獲取模型資源(如配置參數(shù)、SQL 語句和錯誤消息)。最后,ModelUtils 包含一個 getSubscriberDAO() 方法,該方法返回 SubscriberDAO 接口的一個實(shí)例,該接口定義了在關(guān)系數(shù)據(jù)庫中選擇、刪除、插入、更新 Subscriber 對象的方法。

    該示例應(yīng)用程序提供了 SubscriberDAO 接口的兩個實(shí)現(xiàn),一個基于 JDBC API,另一個使用 Oracle TopLink。這兩個實(shí)現(xiàn)不在同一應(yīng)用程序?qū)嵗惺褂谩?/span>ModelResources 包有一個 DAO 參數(shù),用于指定要使用的 DAO 實(shí)現(xiàn)。基于 JDBC 的 DAO 的唯一好處是它只使用標(biāo)準(zhǔn)的 Java API 和 SQL。TopLink 也在內(nèi)部使用 JDBC,但它提供了很多好處:

    • 由于使用可視化工具(Mapping Workbench 和 Session Editor)定義對象關(guān)系映射和配置應(yīng)用程序,因此提高了工作效率。這顯著減少了手動編寫的代碼數(shù)量;
    • 不必再使用類似 JDBC 這樣的低級 API;
    • 因?yàn)?TopLink 緩存從數(shù)據(jù)庫讀取的對象,所以應(yīng)用程序速度更快。此外,TopLink 在內(nèi)部生成了非常高效的 SQL 語句;
    • 除 SQL 以外,TopLink 還支持幾種其他查詢機(jī)制,如“按示例查詢”、基于 Java 表達(dá)式的查詢和 EJB QL;
    • 對多服務(wù)器實(shí)例(集群)的支持使您能夠構(gòu)建可伸縮的應(yīng)用程序。

     

    創(chuàng)建模型 Bean 該示例 Web 應(yīng)用程序包含兩個未使用任何 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 類擴(kuò)展了 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 的實(shí)例被保存到一個表中,可以使用以下 SQL 語句創(chuàng)建該表:

    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 的屬性和相應(yīng)的表列應(yīng)具有相同的名稱。本文使用了不同的名稱,以便您可以輕松地辨認(rèn)哪些地方使用了屬性、哪些地方使用了列。

    使用數(shù)據(jù)訪問對象SubscriberDAO 接口定義視圖 bean 為保存和檢索從模型 bean 繼承的屬性而調(diào)用的方法:

    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 實(shí)現(xiàn)(TopLinkSubscriberDAOJDBCSubscriberDAO)并返回所加載類的實(shí)例:

    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 實(shí)現(xiàn)的名稱。使用 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 參數(shù)、基于 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.
    

     

    所有異常類均擴(kuò)展了 ModelException:

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

     

    每個異常類只包含一個構(gòu)造函數(shù),該函數(shù)將類名傳遞給 ModelException 超類的構(gòu)造函數(shù):

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

     

    JDBCSubscriberDAO 類包含一個具有 JNDI 的 DataSource,并實(shí)現(xiàn)由 SubscriberDAO 定義的方法。請參閱本文附帶的可下載存檔中提供的 JDBCSubscriberDAO.java 源代碼。

    TopLinkSubscriberDAO 類也實(shí)現(xiàn) SubscriberDAO 接口。TopLinkSubscriberDAO() 構(gòu)造函數(shù)獲取一個服務(wù)器會話,并添加一個將在 JVM 停止時關(guān)閉會話的關(guān)閉鉤子 (hook)。基于 TopLink 的 DAO 包含用于獲取由實(shí)現(xiàn) SubscriberDAO 接口的方法使用的客戶端會話和工作單元的實(shí)用方法:

    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();
    }
    ...
    }
    

     

    創(chuàng)建視圖 Bean 如前所述,這兩個視圖 bean(LoginInfoBean 和 SubscriberBean)使用 UI 特有的代碼(如用于驗(yàn)證數(shù)據(jù)的方法(驗(yàn)證器)和用于處理數(shù)據(jù)的方法(操作))擴(kuò)展了模型 bean(LoginInfoSubscriber)。LoginInfoBean 類包含一個名為 loginAction() 的操作方法。另一個視圖 bean (SubscriberBean) 定義另一個屬性 (loggedIn),實(shí)現(xiàn) emailValidator() 方法(用于驗(yàn)證電子郵件地址中是否存在字符 @)、提供幾個操作方法,并將從 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;
    }
    }
    

     

    本部分的下列段落將詳細(xì)介紹這兩個視圖 bean 的操作方法。具體說來,您將了解

    • loginAction() 如何選擇行,
    • subscribeAction() 如何插入新行,
    • profileAction() 如何更新現(xiàn)有行,
    • unsubscribeAction() 如何刪除行。

     

    下一部分(JSF 視圖和數(shù)據(jù)驗(yàn)證

    • 提供該示例應(yīng)用程序的 JSF 頁面,
    • 介紹了如何借助 JSF 對數(shù)據(jù)進(jìn)行驗(yàn)證,
    • 演示了如何創(chuàng)建 UI 組件和 bean 屬性之間的綁定,
    • 演示了如何將操作方法綁定到 JSF 表單的提交按鈕。

     

    選擇行(登錄操作)JDBCSubscriberDAOselect() 方法執(zhí)行一個 SQL 查詢,選擇具有給定電子郵件地址的訂戶,然后驗(yàn)證口令。以下是基于 JDBC 的 DAO 使用的 SELECT 語句:

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

     

    使用 TopLink 時,盡管您可以使用 SQL,但不必構(gòu)建 SQL 語句。如果在私有 read() 方法中使用查詢 API(它從 TopLinkSubscriberDAO 的多個公共方法中調(diào)用,執(zhí)行一個返回具有給定電子郵件地址的訂戶的查詢),則 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() 方法取得客戶端會話并調(diào)用 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 管理)的實(shí)例,并獲取 adminEmail 初始化參數(shù)的值。隨后,loginAction() 調(diào)用 select(),使用 ModelUtils.copy() 將選定訂戶的屬性復(fù)制到由 JSF 管理的 JavaBean,將 loggedIn 標(biāo)記設(shè)置為 true,然后根據(jù)訂戶的電子郵件返回 listprofile 結(jié)果:

    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 表達(dá)式和將錯誤消息添加到 JSF 上下文的實(shí)用方法:

    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 設(shè)置另一個 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 或集合),則應(yīng)克隆這些屬性的值并將它們的克隆存儲到 dest bean 中。

    插入新行(訂閱操作)JDBCSubscriberDAOinsert() 方法執(zhí)行一個 SQL 語句,向數(shù)據(jù)庫中插入一個新訂戶:

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

     

    使用 TopLink 時,在必須使用 TopLink Mapping Workbench 工具定義的對象關(guān)系映射中將自動生成 SQL 語句。TopLinkSubscriberDAOinsert() 方法獲取一個工作單元、創(chuàng)建一個新的 Subscriber 實(shí)例、設(shè)置它的屬性、注冊新對象并調(diào)用 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() 方法調(diào)用 insert()、將 loggedIn 標(biāo)記設(shè)置為 true,并在未出錯的情況下返回 subscribed 結(jié)果:

    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;
    }
    }
    ...
    }
    

     

    更新現(xiàn)有行(配置文件操作)JDBCSubscriberDAOupdate() 方法執(zhí)行一個 SQL 語句,更新現(xiàn)有訂戶的配置文件:

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

     

    TopLinkSubscriberDAOupdate() 方法獲取一個工作單元、調(diào)用私有 read() 方法獲取具有給定電子郵件的 bean、更新 bean 屬性并調(diào)用 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() 方法調(diào)用 update() 并返回 null 結(jié)果:

    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() 方法執(zhí)行一個 SQL 語句,從數(shù)據(jù)庫中刪除一個訂戶:

    DELETE FROM subscribers WHERE subscriberEmail=?
    

     

    TopLinkSubscriberDAOdelete() 方法獲取一個工作單元、調(diào)用私有 read() 方法獲取具有給定電子郵件的 bean、刪除 bean 屬性并調(diào)用 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() 方法調(diào)用 delete() 并在未出錯的情況下返回 unsubscribed 結(jié)果:

    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;
    }
    }
    ...
    }
    

     

    創(chuàng)建 TopLink 項(xiàng)目以下步驟說明了如何使用 Mapping Workbench 創(chuàng)建一個 TopLink 項(xiàng)目。

    第 1 步:啟動 OracleAS TopLink Mapping Workbench。然后,從主菜單中選擇 FileNew Project...。在 Create New Project 對話框中,提供現(xiàn)有數(shù)據(jù)庫的名稱(例如,orcl),然后單擊 OK

    第 2 步:Mapping Workbench 將顯示一個窗口讓您保存該項(xiàng)目。選擇一個目錄,提供一個文件名(例如,jsfdb),然后單擊 Save

    第 3 步:在左側(cè) Navigator 窗格中選擇該數(shù)據(jù)庫,單擊 Add...,輸入一個登錄標(biāo)簽(例如,orclLogin),然后單擊 OK

    第 4 步:在主窗口中,選擇新建的登錄,并提供 JDBC 驅(qū)動程序類的名稱、數(shù)據(jù)庫 URL、用戶名和口令。別忘了選中 Save Password 復(fù)選框。

    圖 2

    第 5 步:Navigator 窗格中右鍵單擊該數(shù)據(jù)庫名稱,然后單擊 Log In to Database

    第 6 步:Navigator 窗格中再次右鍵單擊該數(shù)據(jù)庫名稱,然后單擊 Add or Update Existing Tables from Database

    第 7 步:Import Tables from Database 窗口中的 Table Name Pattern 域中,輸入 SUBSCRIBERS,單擊 Get Table Names,從左側(cè) Available Tables 窗格中選擇 SUBSCRIBERS 表,將它添加到右側(cè)的 Selected Tables 窗格,然后單擊 OK

    圖 3

    第 8 步:Navigator 窗格中選擇該項(xiàng)目 (jsfdb),確保 General 是右側(cè)窗格的當(dāng)前選項(xiàng)卡,單擊 Add Entries...,在顯示的目錄樹中尋找,選擇包含該 Web 應(yīng)用程序已編譯的類的目錄,然后單擊 OK。選中的目錄將被添加到主窗口的 Class Path 列表中。

    第 9 步:Navigator 窗格中再次右鍵單擊該項(xiàng)目,然后單擊 Add or Refresh Classes...

    第 10 步:Select Classes 窗口左側(cè)的 Available Packages/Classes 窗格中選擇 LoginInfoSubscriber 類。

    第 11 步:LoginInfoSubscriber 類移到右側(cè)的 Selected Classes 窗格中,然后單擊 OK。只有 Subscriber 類被映射到了表上,但由于 LoginInfoSubscriber 的超類,因此我們還需要將其映射到表上。

    圖 4

    第 12 步:在主窗口的 Navigator 窗格中選擇 Subscriber 類。在右側(cè)的 Editor 窗格中,確保 Descriptor 是當(dāng)前選項(xiàng)卡,并在 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),然后在右側(cè)的 Editor 窗格中設(shè)置相應(yīng)的 Database Field(例如,SUBSCRIBEREMAIL)。

    圖 6

    第 16 步:由于 LoginInfo 類未映射到任何表,因此必須從項(xiàng)目中刪除它。在 Navigator 窗格中右鍵單擊 LoginInfo,然后單擊 Remove Class。當(dāng)顯示 Remove Descriptors 對話框時,單擊 Remove

    第 17 步:在主菜單中,選擇 File、Export、Project Deployment XML... 或使用 CTRL+D。輸入 JSFDBProject.xml 并單擊 OK。然后,選擇要將該 XML 文件保存到的目錄。

    圖 8

     

     

    第 18 步:使用 FileSave 或使用 CTRL+S 保存該項(xiàng)目。除了將項(xiàng)目設(shè)置存儲到 jsfdb.mwp 文件中以外,保存操作還將在 class、descriptortable 子目錄中創(chuàng)建幾個 XML 文件。現(xiàn)在,您可以選擇 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 窗格的當(dāng)前選項(xiàng)卡,然后在 Project Type 組的 XML 域中輸入 JSFDBProject.xml

    圖 7

    第 3 步:選擇 Editor 窗格的 Logging 選項(xiàng)卡,然后在Enable Logging 列表中選擇 True

    第 4 步:選擇 Editor 窗格的 Login 選項(xiàng)卡,選中 Database Platform 復(fù)選框,在相應(yīng)的列表中選擇 Oracle,然后在 Data Source 域中輸入 java:comp/env/jdbc/OracleDS

    圖 9

    第 5 步:從主菜單中選擇 FileSave(或使用 CTRL+S)保存 sessions.xml 文件。現(xiàn)在,您可以關(guān)閉 OracleAS TopLink Sessions Editor 了。

    JSF 視圖和數(shù)據(jù)驗(yàn)證

    該示例 Web 應(yīng)用程序包含一個訂閱頁面 (subscribe.jsp)、一個登錄頁面 (login.jsp)、一個配置文件編輯頁面 (profile.jsp)、一個取消訂閱頁面 (unsubscribe.jsp、兩個確認(rèn)頁面(subscribed.jspunsubscribed.jsp)、一個列出所有訂戶的頁面 (list.jsp) 和一個注銷頁面 (logout.jsp)。由 JSF 頁面創(chuàng)建的某些表單借助于先前部分中的 DAO 方法觸發(fā)執(zhí)行基本數(shù)據(jù)庫操作(INSERT、SELECT、UPDATEDELETE)的操作。

    在網(wǎng)頁中使用 JSFJSF 定義了兩個標(biāo)準(zhǔn)標(biāo)記庫(Core 和 HTML),您必須在 JSP 頁面中使用 <%@taglib%> 指令聲明它們。JSF Core 庫包含不依賴任何標(biāo)記語言的標(biāo)記,而 JSF HTML 庫是為在 Web 瀏覽器中查看的頁面設(shè)計的。這兩個標(biāo)記庫的標(biāo)準(zhǔn)前綴分別是 f(用于 JSF Core)和 h(用于 JSF HTML)。所有 JSF 標(biāo)記必須嵌套在 <f:view> 元素的內(nèi)部。通過 <f:view> 標(biāo)記,JSF 框架可以將 UI 組件的狀態(tài)作為 HTTP 請求響應(yīng)的一部分保存。

    該示例 Web 應(yīng)用程序的頁面使用 JSF 和 JSTL Core 庫創(chuàng)建 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> 標(biāo)記與 JSTL 的 <c:out> 標(biāo)記非常相似,但也有一些差別。例如,<h:outputText> 使用 JSF EL,而 <c:out> 使用最初為 JSTL 1.0 創(chuàng)建并在隨后被 JSP 2.0 采用的表達(dá)式語言。在上一個代碼段中,<f:loadBundle> 標(biāo)記加載了一個包含所有 JSF 頁面的標(biāo)簽和標(biāo)題的資源包:

    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 標(biāo)記構(gòu)建表單許多應(yīng)用程序的網(wǎng)頁使用 JSF 創(chuàng)建表單。例如,login.jsp 使用 <h:inputText><h:inputSecret>,它們呈現(xiàn)兩個類型分別為 textpassword 的 HTML <input> 元素。每個表單都有一個使用 <h:outputLabel> 呈現(xiàn)的標(biāo)簽。所有錯誤消息都使用 <h:message><h:messages> 呈現(xiàn)。<f:validateLength> 標(biāo)記驗(yàn)證用戶在表單域中輸入的字符串的長度。<h:commandButton> 呈現(xiàn)一個 HTML 提交按鈕。所有這些標(biāo)記都位于 <h:form> 元素(呈現(xiàn) HTML <form></form> 標(biāo)記)中。除了 HTML 表單以外,login.jsp 頁面還提供了訂閱表單的鏈接,并使用 <h:outputLink><h:outputText> 呈現(xiàn) HTML <a> 元素及其內(nèi)容:

    <!-- 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>
    

     

    當(dāng)執(zhí)行 login.jsp 頁面時,JSF 創(chuàng)建 LoginInfoBean 實(shí)例,該實(shí)例的屬性綁定到 UI 組件。當(dāng)單擊 Login 按鈕時,Web 瀏覽器將用戶的電子郵件地址和口令提交給服務(wù)器。JSF 框架驗(yàn)證用戶是否為這兩個必填域提供了值。如果字符串值的長度通過了 <f:validateLength> 執(zhí)行的驗(yàn)證,則 JSF 設(shè)置 bean 屬性并調(diào)用 loginAction() 方法對用戶進(jìn)行身份驗(yàn)證。

    請注意,login.jsp 使用 JSTL 的 <c:remove> 標(biāo)記從 session 作用域中刪除任何 subscriber bean。如果用戶首次訪問該 Web 應(yīng)用程序,或者如果他的前一個會話已經(jīng)過期,則不存在這樣的 bean 實(shí)例,因此 <c:remove> 將不起作用。否則,該標(biāo)記清理該會話作用域。由于在 loginAction() 中求解的一個 EL 表達(dá)式中引用了 subscriber bean,因此 JSF 將創(chuàng)建 SubscriberBean(在 faces-config.xml 文件中指定)的實(shí)例。loginAction() 方法從數(shù)據(jù)庫中讀取訂戶的配置文件并設(shè)置該 bean 的屬性。subscriber bean 將由其他 JSF 頁面使用,并將保存在 session 作用域中,直到 logout.jsp 刪除它:

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

     

    subscribe.jspprofile.jsp 頁面使用 JSF 呈現(xiàn)其他表單元素,如復(fù)選框和列表。JSF 的 <h:panelGrid><h:panelGroup> 標(biāo)記用于呈現(xiàn)其單元格包含訂閱表單的三個復(fù)選框的 HTML 表格。<h:selectBooleanCheckbox> 標(biāo)記呈現(xiàn) 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> 標(biāo)記呈現(xiàn)一個下拉式列表,用戶可以通過該列表選擇他們的訂閱類型:每日、每周或每月:

    <!-- 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 中使用)將新訂戶的配置文件保存到數(shù)據(jù)庫中。profile.jsp 頁面顯示由 subscriber bean 保持的首選設(shè)置,并允許用戶更改他的配置文件(profileAction() 方法更新存儲在數(shù)據(jù)庫中的信息)。

    unsubscribe.jsp 呈現(xiàn)的表單包含兩個綁定到不同操作方法的提交按鈕:

    <!-- 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() 方法從數(shù)據(jù)庫中刪除訂戶的配置文件。先前部分(JavaBean 和數(shù)據(jù)訪問對象)介紹了所有操作方法的代碼。

    使用標(biāo)準(zhǔn)的和定制的驗(yàn)證器 JSF 提供了幾個驗(yàn)證器標(biāo)記,如在示例應(yīng)用程序的 JSF 頁面中使用的 <f:validateLength><f:validateLongRange>。此外,許多 HTML 標(biāo)記支持 required 屬性。通過該屬性可以指定用戶是否必須總要為該 UI 組件提供值。但在大多數(shù)情況下,您需要應(yīng)用程序特有的驗(yàn)證。您可以構(gòu)建定制驗(yàn)證標(biāo)記,但 validator 屬性(許多標(biāo)準(zhǔn) JSF 標(biāo)記都支持它)更容易使用。您可以使用該屬性指定一個將用于驗(yàn)證用戶輸入值的方法:

    <!-- 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() 方法驗(yàn)證電子郵件地址是否包含 @ 字符:

    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 頁面中呈現(xiàn),并從應(yīng)用程序消息包中被獲取:

    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
    

     

    除了應(yīng)用程序特有的消息外,該資源包還包含幾個默認(rèn) JSF 消息的替代形式。例如,javax.faces.component.UIInput.REQUIRED 關(guān)鍵字之后是一個錯誤消息,它將在用戶沒有為必填表單元素提供值時呈現(xiàn)。必須在 faces-config.xml 中配置應(yīng)用程序消息包:

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

     

    驗(yàn)證器標(biāo)記和方法驗(yàn)證單個 UI 組件的值。但有時您不得不一起驗(yàn)證一組組件。這可以在一個操作方法中完成。例如,用戶可能沒有選中訂閱表單的任何復(fù)選框,但至少應(yīng)訂閱一種時事通訊。如果 countNewsletters() 返回 0,則 subscribeAction() 方法將發(fā)出錯誤消息:

    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;
    }
    ...
    }
    ...
    }
    

     

    未關(guān)聯(lián)到任何特定 UI 組件的錯誤消息可以在 JSF 頁面中使用 <h:messages globalOnly="true"/> 呈現(xiàn)。

    結(jié)合使用 JSF 與 JSTL 的 SQL 標(biāo)記 JSF API 為表定義了一個抽象數(shù)據(jù)模型 (javax.faces.model.DataModel),并提供了用于包裝數(shù)組、列表、JDBC 結(jié)果集、JSTL 的 <sql:query> 標(biāo)記的結(jié)果以及標(biāo)量的實(shí)現(xiàn)。JSF 數(shù)據(jù)模型包含用于訪問行數(shù)據(jù)的方法,但行的表示方式由具體的模型實(shí)現(xiàn)決定。當(dāng)使用數(shù)組或列表時,每個元素都是一行。當(dāng)使用結(jié)果集時,每一行都表示為一個其關(guān)鍵字為表列的 java.util.Map。由于不限制行模型,因此 Web 開發(fā)人員應(yīng)負(fù)責(zé)使用 <h:column> 標(biāo)記(必須嵌套在 <h:dataTable> 元素的內(nèi)部)定義列在 JSF 頁面中的呈現(xiàn)方式。

    該示例應(yīng)用程序的 web.xml 文件使用 <resource-ref> 聲明一個數(shù)據(jù)源,并使用一個名為 javax.servlet.jsp.jstl.sql.dataSource 的上下文參數(shù)將它配置為默認(rèn)的 JSTL 數(shù)據(jù)源。另一個參數(shù) (adminEmail) 指定一個電子郵件地址,用于與登錄訂戶的 email 屬性進(jìn)行比較(在 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 中指定了電子郵件地址的用戶才可以執(zhí)行 list.jsp 頁面。因此,您必須在部署應(yīng)用程序后以 admin@localhost 的身份進(jìn)行訂閱。當(dāng)使用 adminEmail 地址登錄時,login.jsp 頁面將該請求轉(zhuǎn)給 list.jsp。該示例應(yīng)用程序中使用的此身份驗(yàn)證機(jī)制演示了操作方法(如 loginAction())如何根據(jù)可編程條件返回不同的結(jié)果(listprofile)。實(shí)際的應(yīng)用程序?qū)⑹褂没?HTTP 或基于表單的身份驗(yàn)證驗(yàn)證管理員的身份。

    list.jsp 頁面使用 JSTL 標(biāo)記執(zhí)行 SQL 查詢,并創(chuàng)建一個用于保存結(jié)果集的 subscriberList 變量。該變量被傳遞給 <h:dataTable>,后者迭代結(jié)果集的各行,并調(diào)用每個行的標(biāo)記主體。每個 <h:column> 標(biāo)記都包含一個 <h:outputText value="#{row...}"/> 標(biāo)記(用于呈現(xiàn)當(dāng)前單元格的值)和一個作為表格的表頭的一部分呈現(xiàn)的 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> 標(biāo)記使用 list.jsp 頁面中的 request 作用域。如果您忘了指定 JSF 支持的作用域,則 JSTL 標(biāo)記將使用默認(rèn)的頁面作用域,且這兩個標(biāo)記庫的標(biāo)記將無法通信。因此,請確保結(jié)合使用 JSF 和 JSTL 的標(biāo)記時使用一個通用的作用域(request、sessionapplication)。

    總結(jié)

    本文演示了一個基于 JSF 的 Web 應(yīng)用程序,它使用 DAO 模式、JDBC、SQL、TopLink 和 JSTL 訪問關(guān)系數(shù)據(jù)庫。您可以在開發(fā)實(shí)際應(yīng)用程序時應(yīng)用本文介紹的技術(shù),并且您可以使用 Oracle 數(shù)據(jù)庫、Oracle Application Server Containers for J2EE、TopLink 對象關(guān)系映射框架和 JSF 引用實(shí)現(xiàn)測試本文的示例。


    Andrei Cioroianu (
    devtools@devsphere.com) 是 Devsphere 的創(chuàng)始人,這是一家 Java 框架、XML 咨詢和 Web 開發(fā)服務(wù)的供應(yīng)商。Andrei 編寫了許多 Java 文章,分別由 Oracle 技術(shù)網(wǎng)、ONJavaJavaWorldJava Developer's Journal 發(fā)表。他還與別人合著了 Java XML Programmer's Reference Professional Java XML 兩書(均由 Wrox Press 出版)。

     

     

    posted on 2008-04-27 16:03 Jarod.cn.LuLuLife 閱讀(358) 評論(0)  編輯  收藏


    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導(dǎo)航:
     
    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    導(dǎo)航

    統(tǒng)計

    公告

    我的知識Blog!

    常用鏈接

    留言簿(3)

    隨筆檔案

    文章檔案

    Image

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 久久成人永久免费播放| 亚洲w码欧洲s码免费| 亚洲日韩精品A∨片无码| 亚洲毛片不卡av在线播放一区| 免费观看国产精品| 国产又黄又爽又猛的免费视频播放 | 精品剧情v国产在免费线观看 | 中国一级毛片免费看视频| 国产高清视频免费在线观看 | 国产精品亚洲色婷婷99久久精品| 亚洲日本va一区二区三区| 亚洲aⅴ天堂av天堂无码麻豆| 久久水蜜桃亚洲AV无码精品| 污网站在线免费观看| 国产成人精品免费大全| a级毛片免费网站| 久久久久久久岛国免费播放 | 全免费毛片在线播放| 天天干在线免费视频| 国产一区视频在线免费观看| 国产亚洲午夜高清国产拍精品| 中文字幕人成人乱码亚洲电影| 亚洲国产精品成人精品无码区| 亚洲av日韩av天堂影片精品| 亚洲成人网在线播放| 亚洲精品无码久久久久久| 国产91成人精品亚洲精品| 国产精品免费久久久久久久久| 免费国产叼嘿视频大全网站| 亚洲一级免费视频| 国产精品久久久久影院免费| 亚洲精品无码久久久| 7777久久亚洲中文字幕蜜桃| 亚洲自偷自偷在线成人网站传媒 | 亚洲精品免费网站| 九九综合VA免费看| 免费精品无码AV片在线观看 | 视频免费在线观看| 免费AA片少妇人AA片直播| 全黄a免费一级毛片人人爱| 国产精品亚洲а∨无码播放|