JSF入门

一、簡介JSF

  Web應用程式的開發與傳統的單機程式開發在本質上存在著太多的差異,Web應用程式開發人員至今不可避免的必須處理HTTP的細節,而 HTTP無狀態的(stateless)本質,與傳統應用程式必須維持程式運行過程中的資訊有明顯的違背,再則Web應用程式面對網站上不同的使用者同時 的存取,其執行緒安全問題以及資料驗證、轉換處理等問題,又是複雜且難以解決的。

  另一方面,本質上是靜態的HTML與本質上是動態的應用程式又是一項違背,這造成不可避免的,處理網頁設計的美術人員與程式設計人員,必須被彼 此加入至視圖元件中的邏輯互相干擾,即便一些視圖呈現邏輯以標籤的方式呈現,試圖展現對網頁設計美術人員的親切,但它終究必須牽涉到相關的流程邏輯。

  有很多方案試著解決種種的困境,而各自的著眼點各不相同,有的從程式設計人員的角度來解決,有的從網頁設計人員的角度來解決,各種的框架被提 出,所造成的是各種不統一的標籤與框架,為了促進產能的整合開發環境(IDE)難以整合這些標籤與框架,另一方面,開發人員的學習負擔也不斷的加重,他們 必須一人瞭解多個角色的工作。

  JavaServer Faces 的提出在試圖解決這個問題,它試圖在不同的角度上提供網頁設計人員、應用程式設計人員、元件開發人員解決方案,讓不同技術的人員可以彼此合作又不互相干 擾,它綜合了各家廠商現有的技術特點,由Java Community Process(JCP)團隊研擬出來的一套標準,並在2004年三月發表了JavaServer Faces 1.0實作成果。

  從網頁設計人員的角度來看,JavaServer Faces提供了一套像是新版本的HTML標籤,但它不是靜態的,而是動態的,可以與後端的動態程式結合,但網頁設計人員不需要理會後端的動態部份,網頁 設計人員甚至不太需要接觸JSTL這類的標籤,也可以動態的展現資料(像是動態的查詢表格內容),JavaServer Faces提供標準的標籤,這可以與網頁編輯程式結合在一起,另一方面,JavaServer Faces也允許您自訂標籤。

  從應用程式設計人員的角度來看,JavaServer Faces提供一個與傳統應用程式開發相類似的模型(當然因某些本質上的差異,模型還是稍有不同),他們可以基於事件驅動來開發程式,不必關切HTTP的 處理細節,如果必須處理一些視覺元件的屬性的話,他們也可以直接在整合開發環境上拖拉這些元件,點選設定元件的屬性,JavaServer Faces甚至還為應用程式設計人員處理了物件與字串(HTTP傳送本質上就是字串)間不匹配的轉換問題。

  從UI元件開發人員的角度來看,他們可以設計通用的UI元件,讓應用程式的開發產能提高,就如同在設計Swing元件等,UI開發人員可以獨立開發,只要定義好相關的屬性選項來調整細節,而不用受到網頁設計人員或應用程式設計人員的干擾。

  三個角色的知識領域原則上可以互不干擾,根據您的角色,您只要瞭解其中一個知識領域,就可以運用JavaServer Faces,其它角色的知識領域您可以不用瞭解太多細節。

  當然,就其中一個角色單獨來看,JavaServer Faces隱藏了許多細節,若要全盤瞭解,其實JavaServer Faces是複雜的,每一個處理的環境都值得深入探討,所以學習JavaServer Faces時,您要選擇的是通盤瞭解,還是從使用的角度來瞭解,這就決定了您學習時所要花費的心力。

  要使用JSF,首先您要先取得JavaServer Faces參考實作(JavaServer Faces Reference Implementation),在將來,JSF會與Container整合在一起,屆時您只要下載支援的Container,就可以使用JSF的功能。

  請至 JSF 官方網站的 下載區 下載參考實作,在下載壓縮檔並解壓縮之後,將其 lib 目錄下的 jar 檔案複製至您的Web應用程式的/WEB-INF/lib目錄下,另外您還需要 jstl.jar 與 standard.jar 檔案,這些檔案您可以在 sample 目錄下,解壓縮當中的一個範例,在它的/WEB-INF/lib目錄下找到,將之一併複製至您的Web應用程式的/WEB-INF/lib目錄下,您總共 需要以下的檔案:

* jsf-impl.jar
    * jsf-api.jar
    * commons-digester.jar
    * commons-collections.jar
    * commons-beanutils.jar
    * jstl.jar
    * standard.jar

  接下來配置Web應用程式的web.xml,使用JSF時,所有的請求都透過FacesServlet來處理,您可以如下定義:


  • web.xml
web.xml
"1.0" encoding="ISO-8859-1"?>
 "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-app_2_4.xsd"
    version="2.4">
 
    
        JSF Demo
    
    JSF Demo
    
        Faces Servlet
        
            javax.faces.webapp.FacesServlet
        
        1
    
   
    
        Faces Servlet
        *.faces
    
   
    
        index.html
    
 

  在上面的定義中,我們將所有.faces的請求交由FaceServlet來處理,FaceServlet會喚起相對的.jsp網頁,例如請求是/index.faces的話,則實際上會喚起/index.jsp網頁,完成以上的配置,您就可以開始使用JSF了。


 二、 第一個JSF程式

  現在可以開發一個簡單的程式了,我們將設計一個簡單的登入程式,使用者送出名稱,之後由程式顯示使用者名稱及歡迎訊息。

程式開發人員

先看看應用程式開發人員要作些什麼事,我們撰寫一個簡單的JavaBean:

UserBean.java
package onlyfun.caterpillar;

 public class UserBean {
    private String name;
   
    public void setName(String name) {
        this.name = name;
    }
   
    public String getName() {
        return name;
    }
 }

  這個Bean將儲存使用者的名稱,編譯好之後放置在/WEB-INF/classes下。

  接下來設計頁面流程,我們將先顯示一個登入網頁/pages/index.jsp,使用者填入名稱並送出表單,之後在/pages/welcome.jsp中顯示Bean中的使用者名稱與歡迎訊息。

  為了讓JSF知道我們所設計的Bean以及頁面流程,我們定義一個/WEB-INF/faces-config.xml:

faces-config.xml
"1.0"?>
 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 
    
        /pages/index.jsp
        case>
            login
            /pages/welcome.jsp
        case>
    
       
    
        user
         
             onlyfun.caterpillar.UserBean
         
        session
    
 

  在中,我們定義了頁面流程,當請求來自中指定的頁面,並且指定了中的為login時,則 會將請求導向至所指定的頁面。

  在中我們可以統一管理我們的Bean,我們設定Bean物件的存活範圍是session,也就是使用者開啟瀏覽器與程式互動過程中都存活。

  接下來要告訴網頁設計人員的資訊是,他們可以使用的Bean名稱,即中設定的名稱,以及上面所定義的頁面流程。

網頁設計人員

  首先網頁設計人員撰寫index.jsp網頁:

index.jsp
<%@taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@page contentType="text/html;charset=Big5"%> 
 
 
 第一個JSF程式
 
 
    
        
            

請輸入您的名稱

名稱: "#{user.name}"/>

"送出" action="login"/>

  我們使用了JSF的core與html標籤庫,core是有關於UI元件的處理,而html則是有關於HTML的進階標籤。

  與有類似的作用,當您要開始使用JSF元件時,這些元件一定要在之間,就如同使用HTML時,所有的標籤一定要在與< /html>之間。

  html標籤庫中幾乎都是與HTML標籤相關的進階標籤,會產生一個表單,我們使用來顯示user這個Bean物件的name屬性,而會產生一個提交按鈕,我們 在action屬性中指定將根據之前定義的login頁面流程中前往welcome.jsp頁面。

  網頁設計人員不必理會表單傳送之後要作些什麼,他只要設計好歡迎頁面就好了:

welcome.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@page contentType="text/html;charset=Big5"%> 
 
 
 第一個JSF程式
 
 
    
        "#{user.name}"/> 您好!
        

歡迎使用 JavaServer Faces!

  這個頁面沒什麼需要解釋的了,如您所看到的,在網頁上沒有程式邏輯,網頁設計人員所作的就是遵照頁面流程,使用相關名稱取出資料,而不用擔心實際上程式是如何運作的。

  接下來啟動Container,連接上您的應用程式網址,例如:http://localhost:8080/jsfDemo/pages/index.faces,填入名稱並送出表單,您的歡迎頁面就會顯示了。

三、 簡單的導航 Navigation

  在 第一個JSF程式 中,我們簡單的定義了頁面的流程由 index.jsp 到 welcome.jsp,接下來我們擴充程式,讓它可以根據使用者輸入的名稱與密碼是否正確,決定要顯示歡迎訊息或是將使用者送回原頁面進行重新登入。

  首先我們修改一下UserBean:

UserBean.java
package onlyfun.caterpillar;

 public class UserBean {
    private String name;
    private String password;
    private String errMessage;
   
    public void setName(String name) {
        this.name = name;
    }
   
    public String getName() {
        return name;
    }
   
    public void setPassword(String password) {
        this.password = password;
    }
   
    public String getPassword() {
        return password;
    }
   
    public void setErrMessage(String errMessage) {
        this.errMessage = errMessage;
    }
   
    public String getErrMessage() {
        return errMessage;
    }
   
    public String verify() {
        if(!name.equals("justin") ||
           !password.equals("123456")) {
            errMessage = "名稱或密碼錯誤";
            return "failure";
        }
        else {
            return "success";
        }
    }
 }

  在UserBean中,我們增加了密碼與錯誤訊息屬性,在verify()方法中,我們檢查使用者名稱與密碼,它傳回一個字串,"failure"表示登入錯誤,並會設定錯誤訊息,而"success"表示登入正確,這個傳回的字串將決定頁面的流程。

  接下來我們修改一下 faces-config.xml 中的頁面流程定義:

faces-config.xml
"1.0"?>
 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 
    
        /pages/index.jsp
        case>
            success
            /pages/welcome.jsp
        case>
        case>
            failure
            /pages/index.jsp
        case>
    
       
    
        user
        
            onlyfun.caterpillar.UserBean
        
        session
    
 

  根據上面的定義,當傳回的字串是"success"時,將前往 welcome.jsp,如果是"failure"的話,將送回 index.jsp。

  接下來告訴網頁設計人員Bean名稱與相關屬性,以及決定頁面流程的verify名稱,我們修改 index.jsp 如下:

index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@page contentType="text/html;charset=Big5"%> 
 
 
 第一個JSF程式
 
 
    
        
            

請輸入您的名稱

"#{user.errMessage}"/>

名稱: "#{user.name}"/>

密碼: "#{user.password}"/>

"送出" action="#{user.verify}"/>

  當要根據verify運行結果來決定頁面流程時,action屬性中使用 JSF Expression Language "#{user.verify}",如此JSF就知道必須根據verify傳回的結果來導航頁面。

  可以取出指定的Bean之屬性值,當使用者因驗證錯誤而被送回原頁面時,這個錯誤訊息就可以顯示在頁面上。

四、 導航規則設置

  在JSF中是根據faces-config.xml中設定,以決定在符合的條件成立時,該連結至哪一個頁面,一個基本的設定如下:

....
    
        /pages/index.jsp
        case>
            success
            /pages/welcome.jsp
        case>
        case>
            failure
            /pages/index.jsp
        case>
    
 ....

  對於JSF,每一個視圖(View)都有一個獨特的識別(identifier),稱之為View ID,在JSF中的View ID是從Web應用程式的環境相對路徑開始計算,設定時都是以/作為開頭,如果您請求時的路徑是/pages/index.faces,則JSF會將副檔 名改為/pages/index.jsp,以此作為view-id。

  在中的是個選擇性的定義,它規定了來源頁面的條件, 中定義各種導覽條件,定義當表單結果符合的條件時,各自改導向 哪一個目的頁面,目的頁面是在中定義。

  您還可以在中加入,進一步規範表單結果必須根據哪一個動作方法(action method),當中是使用 JSF Expression Language 來設定,例如:

....
    
        /pages/index.jsp
        case>
            #{user.verify}
            success
            /pages/welcome.jsp
        case>
        ....
    
 ....

  在導航時,預設都是使用forward的方式,您可以在中加入一個,讓JSF發出讓瀏覽器重新導向(redirect)的header,讓瀏覽器主動要求新網頁,例如:

....
    
        /pages/index.jsp
        case>
            success
            /pages/welcome.jsp
            
        case>
        ....
    
 ....

您的來源網頁可能是某個特定模組,例如在/admin/下的頁面,您可以在中使用wildcards,也就是使用 * 字元,例如:

....
    
        /admin/*
        case>
            #{user.verify}
            success
            /pages/welcome.jsp
        case>
        ....
    
 ....

在上面的設定中,只要來源網頁是從/admin來的,都可以開始測試接下來的

  如果沒有設定,表示來源網頁不作限制,您也可以使用 * 顯式的在定義檔中表明,例如:

....
    
        /*
        case>
        ....
    
 ....

或者是這樣:

....
    
        *
        case>
        ....
    
 ....

五、 JSF Expression Language

  JSF Expression Language 搭配 JSF 標籤來使用,是用來存取資料物件的一個簡易語言。

  JSF EL是以#開始,將變數或運算式放置在

Unknown macro: { 與 }
之間,例如:
#{someBeanName}

  變數名稱可以是faces-config.xml中定義的名稱,如果是Bean的話,可以透過使用 '.' 運算子來存取它的屬性,例如:

...
 
    "#{userBean.name}"/>
 
 ...

  在JSF標籤的屬性上," 與 " (或'與')之間如果含有EL,則會加以運算,您也可以這麼使用它:

...
 
    名稱, 年齡:"#{userBean.name}, #{userBean.age}"/>
 
 ...

  一個執行的結果可能是這樣顯示的:

名稱, 年齡:Justin, 29

  EL的變數名也可以程式執行過程中所宣告的名稱,或是JSF EL預設的隱含物件,例如下面的程式使用param隱含物件來取得使用者輸入的參數:

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@page contentType="text/html; charset=Big5"%>
      
 
 
 
 
 
 
     您好, "#{param.name}"/> 
 

 
 

  param是JSF EL預設的隱含物件變數,它代表request所有參數的集合,實際是一個java.util.Map型態物件,JSF所提供的隱含物件,大致上對應於JSP隱含物件, 不過JSF隱含物件移除了pageScope與pageContext,而增加了facesContext與view,它們分別對應於 javax.faces.context.FacesContext與javax.faces.component.UIViewRoot。

  對於Map型態物件,我們可以使用 '.' 運算子指定key值來取出對應的value,也可以使用 [ 與 ] 來指定,例如:

...
 
     您好, "#{param['name']}"/> 
 
 ...

  在 [ 與 ] 之間,也可以放置其它的變數值,例如:

...
 
    "#{someBean.someMap[user.name]}"/> 
 
 ...

  如果變數是List型態或陣列的話,則可以在 [] 中指定索引,例如:

....
 
     "#{someBean.someList[0]}"/> 
     "#{someBean.someArray[1]}"/>
     "#{someBean.someListOrArray[user.age]}"/>  
 
 ....

  您也可以指定字面常數,對於true、false、字串、數字,JSF EL會嘗試進行轉換,例如:

....
 "#{true}"/>
 ....

 "#{'This is a test'}"/>
 ....

  如果要輸出字串,必須以單引號 ' 或雙引數 " 括住,如此才不會被認為是變數名稱。

  在宣告變數名稱時,要留意不可與JSF的保留字或關鍵字同名,例如不可取以下這些名稱:

true false null div mod and or not eq ne lt gt le ge instanceof empty

  使用EL,您可以直接實行一些算術運算、邏輯運算與關係運算,其使用就如同在一般常見的程式語言中之運算。

  算術運算子有:加法 (+), 減法 (-), 乘法 (*), 除法 (/ or div) 與餘除 (% or mod) 。下面是算術運算的一些例子:

運算式 結果
#{1} 1
#{1 + 2} 3
#{1.2 + 2.3} 3.5
#{1.2E4 + 1.4} 12001.4
#{-4 - 2} -6
#{21 * 2} 42
#{3/4} 0.75
#{3 div 4} 0.75,除法
#{3/0} Infinity
#{10%4} 2
#{10 mod 4} 2,也是餘除
#{(1==2) ? 3 : 4} 4

  如同在Java語法一樣 ( expression ? result1 : result2)是個三元運算,expression為true顯示result1,false顯示result2。

  邏輯運算有:and(或&&)、or(或!!)、not(或!)。一些例子為:

運算式 結果
#{true and false} false
#{true or false} true
#{not true} false

  關係運算有:小於Less-than (< or lt)、大於Greater-than (> or gt)、小於或等於Less-than-or-equal (<= or le)、大於或等於Greater-than-or-equal (>= or ge)、等於Equal (== or eq)、不等於Not Equal (!= or ne),由英文名稱可以得到lt、gt等運算子之縮寫詞,以下是Tomcat的一些例子:

運算式 結果
#{1 < 2} true
#{1 lt 2} true
#{1 > (4/2)} false
#{1 > (4/2)} false
#{4.0 >= 3} true
#{4.0 ge 3} true
#{4 <= 3} false
#{4 le 3} false
#{100.0 == 100} true
#{100.0 eq 100} true
#{(10*10) != 100} false
#{(10*10) ne 100} false

  左邊是運算子的使用方式,右邊的是運算結果,關係運算也可以用來比較字元或字串,按字典順序來決定比較結果,例如:

運算式 結果
#{'a' < 'b'} true
#{'hip' > 'hit'} false
#{'4' > 3} true

  EL運算子的執行優先順序與Java運算子對應,如果有疑慮的話,也可以使用括號()來自行決定先後順序。

六、 國際化訊息

  JSF的國際化(Internnationalization)訊息處理是基於Java對國際化的支援,您可以在一個訊息資源檔中統一管理訊息資源,資源檔的名稱是.properties,而內容是名稱與值的配對,例如:

  • messages.properties
titleText=JSF Demo
 hintText=Please input your name and password
 nameText=name
 passText=password
 commandText=Submit

  資源檔名稱由basename加上語言與地區來組成,例如:

* basename.properties
    * basename_en.properties
    * basename_zh_TW.properties

  沒有指定語言與地區的basename是預設的資源檔名稱,JSF會根據瀏覽器送來的Accept-Language header中的內容來決定該使用哪一個資源檔名稱,例如:


Accept-Language: zh_TW, en-US, en


  如果瀏覽器送來這些header,則預設會使用繁體中文,接著是美式英文,再來是英文語系,如果找不到對應的訊息資源檔,則會使用預設的訊息資源檔。

  由於訊息資源檔必須是ISO-8859-1編碼,所以對於非西方語系的處理,必須先將之轉換為Java Unicode Escape格式,例如您可以先在訊息資源檔中寫下以下的內容:

  • messages_zh_TW.txt
titleText=JSF示範
 hintText=請輸入名稱與密碼
 nameText=名稱
 passText=密碼
 commandText=送出

  然後使用JDK的工具程式native2ascii來轉換,例如:


native2ascii -encoding Big5 messages_zh_TW.txt messages_zh_TW.properties


  轉換後的內容會如下:

  • messages_zh_TW.properties
titleText=JSF/u793a/u7bc4
 hintText=/u8acb/u8f38/u5165/u540d/u7a31/u8207/u5bc6/u78bc
 nameText=/u540d/u7a31
 passText=/u5bc6/u78bc
 commandText=/u9001/u51fa

  接下來您可以使用標籤來指定載入訊息資源,一個例子如下:

  • index.jsp
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@page contentType="text/html;charset=UTF8"%>

 
 "messages" var="msgs"/>

 
 
 <h:outputText value=<span class="code-quote">"#{msgs.titleText}"</span>/>
 
 

    
        

"#{msgs.hintText}"/>

"#{msgs.nameText}"/>: "#{user.name}"/>

"#{msgs.passText}"/>: "#{user.password}"/>

"#{msgs.commandText}" actionListener="#{user.verify}" action="#{user.outcome}"/>

  如此一來,如果您的瀏覽器預設接受zh_TW語系的話,則頁面上就可以顯示中文,否則預設將以英文顯示,也就是messages.properties的內容,為了能顯示多國語系,我們設定網頁編碼為UTF8。

  可以設定locale屬性,直接指定所要使用的語系,例如:

"zh_TW">
 "messages" var="msgs"/>

  直接指定以上的話,則會使用繁體中文來顯示,JSF會根據的basename屬性加上的locale屬性來決定要使用哪一個訊息資源檔,就上例而言,就是使用 messages_zh_TW.properties,如果設定為以下的話,就會使用messages_en.properties:

"en">
 "messages" var="msgs"/>

  您也可以在faces-config.xml中設定語系,例如:


    
        
            <default-locale>endefault-locale>
            zh_TW
        
    
 
 .....
 

  在一定有一個,而可以有好幾個,這告訴JSF您的應用程式支援哪些語系。

  當然,如果您可以提供一個選項讓使用者選擇自己的語系會是更好的方式,例如根據user這個Bean的locale屬性來決定頁面語系:

"#{user.locale}">
 "messages" var="msgs"/>

  在頁面中設定一個表單,可以讓使用者選擇語系,例如設定單選鈕:

"#{user.locale}">
     "zh_TW" 
                   itemLabel="#{msgs.zh_TWText}"/>
     "en" 
                   itemLabel="#{msgs.enText}"/>
 

七、 Backing Beans

  JSF使用 JavaBean 來達到程式邏輯與視圖分離的目的,在JSF中的Bean其角色是屬於Backing Bean,又稱之為Glue Bean,其作用是在真正的業務邏輯Bean及UI元件之間搭起橋樑,在Backing Bean中會呼叫業務邏輯Bean處理使用者的請求,或者是將業務處理結果放置其中,等待UI元件取出當中的值並顯示結果給使用者。

  JSF將Bean的管理集中在faces-config.xml中,一個例子如下:

....
 
    user
        
            onlyfun.caterpillar.UserBean
        
    session
 
 ....

  這個例子我們在 第一個JSF程式 看過,設定所要使用的Bean類別,設定之名稱,可供我們在JSF頁面上使用Expression Language來取得或設定Bean的屬性,例如:

"#{user.name}"/>

  設定Bean的存活範圍,您可以設定為request、session 與application,設定為request時,Bean的存活時間為請求階最,設定為session則在使用者應用程式交互開始,直到關閉瀏覽器或 顯式的結束會話為止(例如登出程式),設定為application的話,則Bean會一直存活,直到應用程式關閉為止。

  您還可以將存活範圍設定為none,當設定為none時會在需要的時候生成一個新的Bean,例如您在一個method中想要生成一個臨時的Bean,就可以將之設定為none。

  在JSF頁面上要取得Bean的屬性,是使用 JSF表示語言 (Expression Language),要注意到的是,JSF表示語言是寫成 #{expression},而 JSP表示語言 是寫成 ${expression},因為表示層可能是使用JSP,所以必須特別區分,另外要注意的是,JSF的標籤上之屬性設定時,只接受JSF表示語言。

八、 Beans 的組態與設定

  JSF預設會讀取faces-config.xml中關於Bean的定義,如果想要自行設置定義檔的名稱,我們是在web.xml中提供javax.faces.CONFIG_FILES參數,例如:


   
      javax.faces.CONFIG_FILES
      /WEB-INF/beans.xml
   

   ...
 

  定義檔可以有多個,中間以 "," 區隔,例如:

/WEB-INF/navigation.xml,/WEB-INF/beans.xml

  一個Bean最基本要定義Bean的名稱、類別與存活範圍,例如:

....
 
    user
        
            onlyfun.caterpillar.UserBean
        
    session
 
 ....

  如果要在其它類別中取得Bean物件,則可以先取得javax.faces.context.FacesContext,它代表了JSF目前的 執行環境物件,接著嘗試取得javax.faces.el.ValueBinding物件,從中取得指定的Bean物件,例如:

FacesContext context = FacesContext.getCurrentInstance();
 ValueBinding binding = 
    context.getApplication().createValueBinding("#{user}");
 UserBean user = (UserBean) binding.getValue(context);

  如果只是要嘗試取得Bean的某個屬性,則可以如下:

FacesContext context = FacesContext.getCurrentInstance();
 ValueBinding binding = 
    context.getApplication().createValueBinding(
                                     "#{user.name}");
 String name = (String) binding.getValue(context);

  如果有必要在啟始Bean時,自動設置屬性的初始值,則可以如下設定:

....
 
    user
        
            onlyfun.caterpillar.UserBean
        
    session
    
        name
        caterpillar
    
    
        password
        123456
    
 
 ....

  如果要設定屬性為 null 值,則可以使用標籤,例如:

....
    
        name
        <null-value/>
    
    
        password
        <null-value/>
    
 ....

  當然,您的屬性不一定是字串值,也許會是int、float、boolean等等型態,您可以設定 值時指定這些值的字串名稱,JSF會嘗試進行轉換,例如設定為true時,會嘗試使用Boolean.valueOf()方法轉換為boolean的 true,以下是一些可能進行的轉換:

型態 轉換
short、int、long、float、double、byte,或相應的Wrapper類別 嘗試使用Wrapper的valueOf()進行轉換,如果沒有設置,則設為0
boolean 或 Boolean 嘗試使用Boolean.valueOf()進行轉換,如果沒有設置,則設為false
char 或 Character 取設置的第一個字元,如果沒有設置,則設為0
String 或 Object 即設定的字串值,如果沒有設定,則為空字串new String("")

  您也可以將其它產生的Bean設定給另一個Bean的屬性,例如:

....
 
    user
        
            onlyfun.caterpillar.UserBean
        
    session
 

 
    other
        
            onlyfun.caterpillar.OtherBean
        
    session
    
      user
      #{user}
    
 
 ....

  在上面的設定中,在OtherBean中的user屬性,接受一個UserBean型態的物件,我們設定為前一個名稱為user的UserBean物件。

九、 Beans 上的 List, Map
 如果您的Bean上有接受List或Map型態的屬性,則您也可以在組態檔案中直接設定這些屬性的值,一個例子如下:
....
 
    someBean
    
       onlyfun.caterpillar.SomeBean
    
    session

    
        someProperty
        
            java.lang.Integer
            1
            2
            3
        
    
 
 ....

  這是一個設定接受List型態的屬性,我們使用標籤指定將設定一個List物件,其中< value-class>指定將存入List的型態,而指定其值,如果是基本型態,則會嘗試使用指定的 來作Wrapper類別。

  設定Map的話,則是使用標籤,例如:

....
 
    someBean
    
       onlyfun.caterpillar.SomeBean
    
    session

    
        someProperty
        
            java.lang.Integer
            
                someKey1
                100
            
            
                someKey2
                200
            
        
    
 
 ....

  由於Map物件是以key-value對的方式來存入,所以我們在每一個中使用標籤來分別指定。

  您也可以直接像設定Bean一樣,設定一個List或Map物件,例如在JSF附的範例中,有這樣的設定:

....
   
    
      Special expense item types
    
    specialTypes
    
        java.util.TreeMap
    
    application
    
      java.lang.Integer
      
        Presentation Material
        100
      
      
        Software
        101
      
      
        Balloons
        102
      
    
  
 ....

  而範例中另一個設定List的例子如下:

....
  
    statusStrings
    
        java.util.ArrayList
    
    request
    
      <null-value/>
      Open
      Submitted
      Accepted
      Rejected
    
  
 ....

十、 標準轉換器

  Web應用程式與瀏覽器之間是使用HTTP進行溝通,所有傳送的資料基本上都是字串文字,而Java應用程式本身基本上則是物件,所以物件資料必須經由轉換傳送給瀏覽器,而瀏覽器送來的資料也必須轉換為物件才能使用。

  JSF定義了一系列標準的轉換器(Converter),對於基本資料型態(primitive type)或是其Wrapper類別,JSF會使用javax.faces.Boolean、javax.faces.Byte、 javax.faces.Character、javax.faces.Double、javax.faces.Float、 javax.faces.Integer、javax.faces.Long、javax.faces.Short等自動進行轉換,對於 BigDecimal、BigInteger,則會使用javax.faces.BigDecimal、javax.faces.BigInteger自 動進行轉換。

  至於DateTime、Number,我們可以使用標籤進行轉換,它們各自提供有一些簡單的屬性,可以讓我們在轉換時指定一些轉換的格式細節。

  來看個簡單的例子,首先我們定義一個簡單的Bean:

  • UserBean.java
UserBean.java
package onlyfun.caterpillar;

 import java.util.Date;

 public class UserBean {
    private Date date = new Date();
    
    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }
 }

  這個Bean的屬性接受Date型態的參數,按理來說,接收到HTTP傳來的資料中若有相關的日期資訊,我們必須剖析這個資訊,再轉換為Date物件,然而我們可以使用JSF的標準轉換器來協助這項工作,例如:

  • index.jsp
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@page contentType="text/html;charset=Big5"%>

 

 
 
 轉換器示範
 
 

 設定的日期是:
           
           "#{user.date}">
               "dd/MM/yyyy"/>
           
           

    
        "dateField" value="#{user.date}">
            "dd/MM/yyyy"/>
        
        for="dateField" style="color:red"/>
        
"送出" action="show"/>

  在中,我們使用pattern指定日期的樣式為dd/MM/yyyy,即「日/月/西元」 格式,如果轉換錯誤,則可以顯示錯誤訊息,for屬性參考至 的id屬性,表示將有關dateField的錯誤訊息顯示出來。

  假設faces-config.xml是這樣定義的:

faces-config.xml
"1.0"?>
 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 
    
        /*
        case>
            show
            /pages/index.jsp
        case>
    
       
    
        user
        
            onlyfun.caterpillar.UserBean
        
        session
    
 

  首次連上頁面時顯示的畫面如下:

  如您所看到的,轉換器自動依pattern設定的樣式將Date物件格式化了,當您依格式輸入資料並送出後,轉換器也會自動將您輸入的資料轉換為Date物件,如果轉換時發生錯誤,則會出現以下的訊息:

  標籤還有幾個可用的屬性,您可以參考 Tag Library Documentation 的說明,而依照類似的方式,您也可以使用來轉換數值。

  您還可以參考 Using the Standard Converters 這篇文章中有關於標準轉換器的說明。

十一、 自訂轉換器
 除了使用標準的轉換器之外,您還可以自行定製您的轉換器,您可以實作javax.faces.convert.Converter介面,這個介面有兩個要實作的方法:
public Object getAsObject(FacesContext context, 
                           UIComponent component, 
                           String str);
 public String getAsString(FacesContext context, 
                           UIComponent component, 
                           Object obj);

  簡單的說,第一個方法會接收從客戶端經由HTTP傳來的字串資料,您在第一個方法中將之轉換為您的自訂物件,這個自訂物件將會自動設定給您指定的Bean物件;第二個方法就是將從您的Bean物件得到的物件轉換為字串,如此才能藉由HTTP傳回給客戶端。

  直接以一個簡單的例子來作說明,假設您有一個User類別:

  • User.java
User.java
package onlyfun.caterpillar;

 public class User {
    private String firstName;
    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 }

  這個User類別是我們轉換器的目標物件,而您有一個GuestBean類別:

  • GuestBean.java
GuestBean.java
package onlyfun.caterpillar;

 public class GuestBean {
    private User user;
    
    public void setUser(User user) {
        this.user = user;
    }
    
    public User getUser() {
        return user;
    }
 }

  這個Bean上的屬性直接傳回或接受User型態的參數,我們來實作一個簡單的轉換器,為HTTP字串與User物件進行轉換:

  • UserConverter.java
UserConverter.java
package onlyfun.caterpillar;

 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.convert.Converter;
 import javax.faces.convert.ConverterException;

 public class UserConverter implements Converter {
    public Object getAsObject(FacesContext context, 
                              UIComponent component, 
                              String str) 
                                 throws ConverterException {
        String[] strs = str.split(",");
        
        User user = new User();
        
        try {
            user.setFirstName(strs[0]);
            user.setLastName(strs[1]);
        }
        catch(Exception e) {
            // 轉換錯誤,簡單的丟出例外
            throw new ConverterException();
        }
        
        return user;
    }

    public String getAsString(FacesContext context, 
                              UIComponent component, 
                              Object obj)
                                 throws ConverterException {
        String firstName = ((User) obj).getFirstName();
        String lastName = ((User) obj).getLastName();
        
        return firstName + "," + lastName;
    }
 }

  實作完成這個轉換器,我們要告訴JSF這件事,這是在faces-config.xml中完成註冊:

  • faces-config.xml
faces-config.xml
"1.0"?>
 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 
    
        /*
        case>
            show
            /pages/index.jsp
        case>
    
       
    
        onlyfun.caterpillar.User
        
            onlyfun.caterpillar.UserConverter
        
    

    
        guest
        
            onlyfun.caterpillar.GuestBean
        
        session
    
 

  註冊轉換器時,需提供轉換器識別(Converter ID)與轉換器類別,接下來要在JSF頁面中使用轉換器的話,就是指定所要使用的轉換器識別,例如:

  • index.jsp
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@page contentType="text/html;charset=Big5"%>

 

 
 
 自訂轉換器
 
 

 Guest名稱是:
           "#{guest.user}" 
                   converter="onlyfun.caterpillar.User"/>
           

    
        "userField" 
                     value="#{guest.user}"
                     converter="onlyfun.caterpillar.User"/>
        for="userField" style="color:red"/>
        
"送出" action="show"/>

  您也可以標籤並使用converterId屬性來指定轉換器,例如:

"userField" value="#{guest.user}">
     "onlyfun.caterpillar.User"/>
 

  除了向JSF註冊轉換器之外,還有一個方式可以不用註冊,就是直接在Bean上提供一個取得轉換器的方法,例如:

  • GuestBean.java
GuestBean.java
package onlyfun.caterpillar;

 import javax.faces.convert.Converter;

 public class GuestBean {
    private User user;
    private Converter converter = new UserConverter();
    
    public void setUser(User user) {
        this.user = user;
    }
    
    public User getUser() {
        return user;
    }
    
    public Converter getConverter() {
        return converter;
    }
 }

  之後可以直接結合 JSF Expression Language 來指定轉換器:

"userField" 
     value="#{guest.user}"
     converter="#{guest.converter}"/>

十二、 標準驗證器

  當應用程式要求使用者輸入資料時,必然考慮到使用者輸入資料之正確性,對於使用者的輸入必須進行檢驗,檢驗必要的兩種驗證是語法檢驗(Synatic Validation)與語意檢驗(Semantic Validation)。

  語法檢驗是要檢查使用者輸入的資料是否合乎我們所要求的格式,最基本的就是檢查使用者是否填入了欄位值,或是欄位值的長度、大小值等等是否符合 要求。語意檢驗是在語法檢驗之後,在格式符合需求之後,我們進一步驗證使用者輸入的資料語意上是否正確,例如檢查使用者的名稱與密碼是否匹配。

  在 簡單的導航 (Navigation) 中,我們對使用者名稱與密碼檢查是否匹配,這是語意檢驗,我們可以使用JSF所提供的標準驗證器,為其加入語法檢驗,例如:

  • index.jsp
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@page contentType="text/html;charset=Big5"%> 
 
 
 驗證器示範
 
 
    
        "table" style="color:red"/>
        
            

請輸入您的名稱

"#{user.errMessage}"/>

名稱: "#{user.name}" required="true"/>

密碼: "#{user.password}" required="true"> "6"/>

"送出" action="#{user.verify}"/>

  在、中,我們設定了required屬性為true,這 表示這個欄位一定要輸入值,我們也在設定了,並設定其minimum屬性為6,這表示這個欄位最少需要6個字元。

  這一次在錯誤訊息的顯示上,我們使用標籤,當有驗證錯誤發生時,相關的錯誤訊息會收集起來,使用標籤可以一次將所有的錯誤訊息顯示出來。

  下面是一個驗證錯誤的訊息顯示:

  JSF提供了三種標準驗證器:,您可以分別查詢它們的Tag Library Documentation,瞭解他們有哪些屬性可以使用,或者是參考Using the Standard Validators 這篇文章中有關於標準驗證器的說明。

十三、 自訂驗證器

  您可以自訂自己的驗證器,所需要的是實作javax.faces.validator.Validator介面,例如我們實作一個簡單的密碼驗證器,檢查字元長度,以及密碼中是否包括字元與數字:

  • PasswordValidator.java
PasswordValidator.java
package onlyfun.caterpillar;

 import javax.faces.application.FacesMessage;
 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.validator.Validator;
 import javax.faces.validator.ValidatorException;

 public class PasswordValidator implements Validator {
    public void validate(FacesContext context, 
                         UIComponent component, 
                         Object obj)
            throws ValidatorException {
        String password = (String) obj;
        
        if(password.length() < 6) {
            FacesMessage message = new FacesMessage(
                 FacesMessage.SEVERITY_ERROR, 
                 "字元長度小於6", 
                 "字元長度不得小於6");
            throw new ValidatorException(message);
        }
        
        if(!password.matches(".+[0-9]+")) {
            FacesMessage message = new FacesMessage(
                   FacesMessage.SEVERITY_ERROR, 
                   "密碼必須包括字元與數字", 
                   "密碼必須是字元加數字所組成");
            throw new ValidatorException(message);
        }
    }
 }

  您要實作javax.faces.validator.Validator介面中的validate()方法,如果驗證錯誤,則丟出一個 ValidatorException,它接受一個FacesMessage物件,這個物件接受三個參數,分別表示訊息的嚴重程度(INFO、 WARN、ERROR、FATAL)、訊息概述與詳細訊息內容,這些訊息將可以使用標籤顯示在頁面上。

  接下來要在faces-config.xml中註冊驗證器的識別(Validater ID),要加入以下的內容:

  • faces-config.xml
faces-config.xml
"1.0"?>
 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 
 ....
    
        
            onlyfun.caterpillar.Password
        
        
            onlyfun.caterpillar.PasswordValidator
        
    
 ....
 

  要使用自訂的驗證器,我們可以使用標籤並設定validatorId屬性,例如:

....
 "#{user.password}" required="true"> 
    "onlyfun.caterpillar.Password"/>
 

....

  您也可以讓Bean自行負責驗證的工作,可以在Bean上提供一個驗證方法,這個方法沒有傳回值,並可以接收FacesContext、UIComponent、Object三個參數,例如:

  • UserBean.java
UserBean.java
package onlyfun.caterpillar;

 import javax.faces.application.FacesMessage;
 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.validator.ValidatorException;

 public class UserBean {
    ....
    
    public void validate(FacesContext context, 
                         UIComponent component, 
                         Object obj)
            throws ValidatorException {
        String password = (String) obj;
        
        if(password.length() < 6) {
            FacesMessage message = new FacesMessage(
                 FacesMessage.SEVERITY_ERROR, 
                 "字元長度小於6", 
                 "字元長度不得小於6");
            throw new ValidatorException(message);
        }
        
        if(!password.matches(".+[0-9]+")) {
            FacesMessage message = new FacesMessage(
                   FacesMessage.SEVERITY_ERROR, 
                   "密碼必須包括字元與數字", 
                   "密碼必須是字元加數字所組成");
            throw new ValidatorException(message);
        }
    }
 }

  接著可以在頁面下如下使用驗證器:

.....
 "#{user.password}" 
                required="true"  
                validator="#{user.validate}"/> 
 ....


十四、 錯誤訊息處理

  在使用標準轉換器或驗證器時,當發生錯誤時,會有一些預設的錯誤訊息顯示,這些訊息可以使用標籤來顯示出來,而這些預設的錯誤訊息也是可以修改的,您所要作的是提供一個訊息資源檔案,例如:

messages.properties
javax.faces.component.UIInput.CONVERSION=Format Error.
 javax.faces.component.UIInput.REQUIRED=Please input your data.
 ....

  javax.faces.component.UIInput.CONVERSION是用來設定當轉換器發現錯誤時顯示的訊息,而 javax.faces.component.UIInput.REQUIRED是在標籤設定了required為true,而使用者沒有在欄位輸入時顯 示的錯誤訊息。

  您要在faces-config.xml中告訴JSF您使用的訊息檔案名稱,例如:

faces-config.xml
"1.0"?>
 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 
    
        
            <default-locale>endefault-locale>
            zh_TW
        
        messages
    
    .....

 

  在這邊我們設定了訊息檔案的名稱為messages_xx_YY.properties,其中xx_YY是根據您的Locale來決定,轉換器或驗證器的錯誤訊息如果有設定的話,就使用設定值,如果沒有設定的話,就使用預設值。

  驗證器錯誤訊息,除了上面的javax.faces.component.UIInput.REQUIRED之外,還有以下的幾個:

訊息識別 預設訊息 用於
javax.faces.validator.NOT_IN_RANGE Validation Error: Specified attribute is not between the expected values of {0} and {1}. DoubleRangeValidator與LongRangeValidator,{0}與{1}分別代表minimum與maximum所設定的屬性
javax.faces.validator.DoubleRangeValidator.MAXIMUM、javax.faces.validator.LongRangeValidator.MAXIMUM Validation Error: Value is greater than allowable maximum of '{0}'. DoubleRangeValidator或LongRangeValidator,{0}表示maximum屬性
javax.faces.validator.DoubleRangeValidator.MINIMUM、javax.faces.validator.LongRangeValidator.MINIMUM Validation Error: Value is less than allowable minimum of '{0}'. DoubleRangeValidator或LongRangeValidator,{0}代表minimum屬性
javax.faces.validator.DoubleRangeValidator.TYPE、javax.faces.validator.LongRangeValidator.TYPE Validation Error: Value is not of the correct type. DoubleRangeValidator或LongRangeValidator
javax.faces.validator.LengthValidator.MAXIMUM Validation Error: Value is greater than allowable maximum of ''{0}''. LengthValidator,{0}代表maximum
javax.faces.validator.LengthValidator.MINIMUM Validation Error: Value is less than allowable minimum of ''{0}''. LengthValidator,{0}代表minimum屬性

  在您提供自訂訊息的時候,也可以提供{0}或{1}來設定顯示相對的屬性值,以提供詳細正確的錯誤提示訊息。

  訊息的顯示有概述訊息與詳述訊息,如果是詳述訊息,則在識別上加上 "_detail",例如:

javax.faces.component.UIInput.CONVERSION=Error.
 javax.faces.component.UIInput.CONVERSION_detail= Detail Error.
 ....

  除了在訊息資源檔中提供訊息,您也可以在程式中使用FacesMessage來提供訊息,例如在 自訂驗證器 中我們就這麼用過:

....
         if(password.length() < 6) {
            FacesMessage message = new FacesMessage(
                 FacesMessage.SEVERITY_ERROR, 
                 "字元長度小於6", 
                 "字元長度不得小於6");
            throw new ValidatorException(message);
        }
 ....

  最好的方法是在訊息資源檔中提供訊息,這麼一來如果我們要修改訊息,就只要修改訊息資源檔的內容,而不用修改程式,來看一個簡單的例子,假設我們的訊息資源檔中有以下的內容:

onlyfun.caterpillar.message1=This is message1.
 onlyfun.caterpillar.message2=This is message2 with /{0} and /{1}.

  則我們可以在程式中取得訊息資源檔的內容,例如:

package onlyfun.caterpillar;

 import java.util.Locale;
 import java.util.ResourceBundle;
 import javax.faces.context.FacesContext;
 improt javax.faces.component.UIComponent;
 import javax.faces.application.Application;
 import javax.faces.application.FacesMessage;
 
     ....
     public void xxxMethod(FacesContext context, 
                          UIComponent component, 
                          Object obj) {
         // 取得應用程式代表物件
         Application application = context.getApplication();
         // 取得訊息檔案主名稱
         String messageFileName = 
                           application.getMessageBundle();
         // 取得當前 Locale 物件
         Locale locale = context.getViewRoot().getLocale();
         // 取得訊息綁定 ResourceBundle 物件
         ResourceBundle rsBundle = 
           ResourceBundle.getBundle(messageFileName, locale);

         String message = rsBundle.getString(
                          "onlyfun.caterpillar.message1");
         FacesMessage facesMessage = new FacesMessage(
              FacesMessage.SEVERITY_FATAL, message, message);
         ....
     }
     ....
 ....

  接下來您可以將FacesMessage物件填入ValidatorException或ConverterException後再丟出, FacesMessage建構時所使用的三個參數是嚴重程度、概述訊息與詳述訊息,嚴重程度有SEVERITY_FATAL、 SEVERITY_ERROR、SEVERITY_WARN與SEVERITY_INFO四種。

  如果需要在訊息資源檔中設定{0}、{1}等參數,則可以如下:

....
 String message = rsBundle.getString(
                      "onlyfun.caterpillar.message2");
 Object[] params = {"param1", "param2"};
 message = java.text.MessageFormat.format(message, params);

 FacesMessage facesMessage = new FacesMessage(
              FacesMessage.SEVERITY_FATAL, message, message);
         ....

  如此一來,在顯示訊息時,onlyfun.caterpillar.message2的{0}與{1}的位置就會被"param1"與"param2"所取代。

十五、 自訂轉換, 驗證標籤

  在 自訂驗證器 中,我們的驗證器只能驗證一種pattern(.+[0-9]+),我們希望可以在JSF頁面上自訂匹配的pattern,然而由於我們使用這個通用的驗證器標籤,為了要能提供pattern屬性,我們可以使用標籤來設置,例 如:

....
  "#{user.password}" required="true"> 
    "onlyfun.caterpillar.Password"/>
    "pattern" value=".+[0-9]+"/>
 

....

  使用標籤來設定屬性,接著我們可以如下取得所設定的屬性:

....
 public void validate(FacesContext context, 
                         UIComponent component, 
                         Object obj)
            throws ValidatorException {
     ....
     String pattern = (String)
                  component.getAttributes().get("pattern");
     ....
 }
 ....

  您也可以開發自己的一組驗證標籤,並提供相關屬性設定,這需要瞭解JSP Tag Library的撰寫,所以請您先參考 JSP/Servlet 中有關於JSP Tag Library的介紹。

  要開發驗證器轉用標籤,您可以直接繼承javax.faces.webapp.ValidatorTag,這個類別可以幫您處理大部份的細節,您所需要的,就是重新定義它的createValidator()方法,我們以改寫自訂驗證器 中的PasswordValidator為例:

  • PasswordValidator.java
PasswordValidator.java
package onlyfun.caterpillar;

 import javax.faces.application.FacesMessage;
 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.validator.Validator;
 import javax.faces.validator.ValidatorException;

 public class PasswordValidator implements Validator {
    private String pattern;

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }
    
    public void validate(FacesContext context, 
                         UIComponent component, 
                         Object obj)
            throws ValidatorException {
        String password = (String) obj;
        
        if(password.length() < 6) {
            FacesMessage message = new FacesMessage(
              FacesMessage.SEVERITY_ERROR, 
              "字元長度小於6", "字元長度不得小於6");
            throw new ValidatorException(message);
        }
        
        if(pattern != null && !password.matches(pattern)) {
            FacesMessage message = new FacesMessage(
                  FacesMessage.SEVERITY_ERROR, 
                  "密碼必須包括字元與數字", 
                  "密碼必須是字元加數字所組成");
            throw new ValidatorException(message);
        }
    }
 }

  主要的差別是我們提供了pattern屬性,在validate()方法中進行驗證時,是根據我們所設定的pattern屬性,接著我們繼承javax.faces.webapp.ValidatorTag來撰寫自己的驗證標籤:

PasswordValidatorTag.java
package onlyfun.caterpillar;

 import javax.faces.application.Application;
 import javax.faces.context.FacesContext;
 import javax.faces.validator.Validator;
 import javax.faces.webapp.ValidatorTag;

 public class PasswordValidatorTag extends ValidatorTag {
    private String pattern;
    
    public void setPattern(String pattern) {
        this.pattern = pattern;
    }
    
    protected Validator createValidator() {
        Application application = 
            FacesContext.getCurrentInstance().
                         getApplication();
        PasswordValidator validator = 
            (PasswordValidator) application.createValidator(
                    "onlyfun.caterpillar.Password");
        validator.setPattern(pattern);
        return validator;
    }
 }

  application.createValidator()方法建立驗證器物件時,是根據在faces-config.xml中註冊驗證器的識別(Validater ID):

  • faces-config.xml
faces-config.xml
"1.0"?>
 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 
 ....
    
        
            onlyfun.caterpillar.Password
        
        
            onlyfun.caterpillar.PasswordValidator
        
    
 ....
 

  剩下來的工作,就是佈署tld描述檔了,我們簡單的定義一下:

  • taglib.tld
taglib.tld
"1.0" encoding="UTF-8" ?> 
 
 "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 
                        web-jsptaglibrary_2_0.xsd" 
    version="2.0"> 
    
    PasswordValidator Tag 
    1.0 
    2.0 
    <short-name>coshort-name> 
    http://caterpillar.onlyfun.net 

     
        PasswordValidator 
        passwordValidator 
        
            onlyfun.caterpillar.PasswordValidatorTag
         
        empty 
         
            pattern 
            true 
            false 
         
     

 

  而我們的index.jsp改寫如下:

  • index.jsp
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@ taglib uri="/WEB-INF/taglib.tld" prefix="co" %>
 <%@page contentType="text/html;charset=Big5"%> 
 
 
 驗證器示範
 
 
    
        "table" style="color:red"/>
        
            

請輸入您的名稱

"#{user.errMessage}"/>

名稱: "#{user.name}" required="true"/>

密碼: "#{user.password}" required="true"> ".+[0-9]+"/>

"送出" action="#{user.verify}"/>

  主要的差別是,我們使用了自己的驗證器標籤:

".+[0-9]+"/>

  如果要自訂轉換器標籤,方法也是類似,您要作的是繼承javax.faces.webapp.ConverterTag,並重新定義其createConverter()方法。

十六、 動作事件

  JSF支援事件處理模型,雖然由於HTTP本身無狀態(stateless)的特性,使得這個模型多少有些地方仍不太相同,但JSF所提供的事件處理模型已足以讓一些傳統GUI程式的設計人員,可以用類似的模型來開發程式。

  在 簡單的導航 中,我們根據動作方法(action method)的結果來決定要導向的網頁,一個按鈕繫結至一個方法,這樣的作法實際上即使JSF所提供的簡化的事件處理程序,在按鈕上使用action繫 結至一個動作方法(action method),實際上JSF會為其自動產生一個「預設的ActionListener」來處理事件,並根據其傳回值來決定導向的頁面。

  如果您需要使用同一個方法來應付多種事件來源,並想要取得事件來源的相關訊息,您可以讓處理事件的方法接收一個javax.faces.event.ActionEvent事件參數,例如:

  • UserBean.java
UserBean.java
package onlyfun.caterpillar;

 import javax.faces.event.ActionEvent;

 public class UserBean {
    private String name;
    private String password;
    private String errMessage;
    private String outcome;
   
    public void setName(String name) {
        this.name = name;
    }
   
    public String getName() {
        return name;
    }
   
    public void setPassword(String password) {
        this.password = password;
    }
   
    public String getPassword() {
        return password;
    }
   
    public void setErrMessage(String errMessage) {
        this.errMessage = errMessage;
    }
   
    public String getErrMessage() {
        return errMessage;
    }
   
    public void verify(ActionEvent e) {
        if(!name.equals("justin") ||
           !password.equals("123456")) {
            errMessage = "名稱或密碼錯誤" + e.getSource();
            outcome = "failure";
        }
        else {
            outcome = "success";
        }
    }

    public String outcome() {
        return outcome;
    }
 }

  在上例中,我們讓verify方法接收一個ActionEvent物件,當使用者按下按鈕,會自動產生ActionEvent物件代表事件來源,我們故意在錯誤訊息之後如上事件來源的字串描述,這樣就可以在顯示錯誤訊息時一併顯示事件來源描述。

  為了提供ActionEvent的存取能力,您的index.jsp可以改寫如下:

  • index.jsp
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@page contentType="text/html;charset=Big5"%> 
 
 
 第一個JSF程式
 
 
    
        
            

請輸入您的名稱

"#{user.errMessage}"/>

名稱: "#{user.name}"/>

密碼: "#{user.password}"/>

"送出" actionListener="#{user.verify}" action="#{user.outcome}"/>

  主要改變的是按鈕上使用了actionListener屬性,這種方法可以使用一個ActionListener,JSF會先檢查是否有指定的 actionListener,然後再檢查是否指定了動作方法並產生預設的ActionListener,並根據其傳回值導航頁面。

  如果您要註冊多個ActionListener,例如當使用者按下按鈕時,順便在記錄檔中增加一些記錄訊息,您可以實作javax.faces.event.ActionListener,例如:

LogHandler.java
package onlyfun.caterpillar;

 import javax.faces.event.ActionListener;
 ....

 public class LogHandler implements ActionListener {
     public void processAction(ActionEvent e) {
         // 處理Log
     }
 }
VerifyHandler.java
package onlyfun.caterpillar;

 import javax.faces.event.ActionListener;
 ....

 public class VerifyHandler implements ActionListener {
     public void processAction(ActionEvent e) {
         // 處理驗證
     }
 }

  這麼一來,您就可以使用標籤向元件註冊事件,例如:

"送出" action="#{user.outcome}">
    "onlyfun.caterpillar.LogHandler"/>
    "onlyfun.caterpillar.VerifyHandler"/>
 

  會自動產生type所指定的物件,並呼叫元件的addActionListener()方法註冊Listener。

十七、 即時事件

  所謂的即時事件(Immediate Events),是指JSF視圖元件在取得請求中該取得的值之後,即立即處理指定的事件,而不再進行後續的轉換器處理、驗證器處理、更新模型值等流程。

  在JSF的事件模型中會有所謂即時事件,導因於Web應用程式的先天特性不同於GUI程式,所以JSF的事件模式與GUI程式的事件模式仍有相當程度的不同,一個最基本的問題正因為HTTP無狀態的特性,使得Web應用程式天生就無法直接喚起伺服端的特定物件。

  所有的物件喚起都是在伺服端執行的,至於該喚起什麼物件,則是依一個基本的流程:

  • 回復畫面(Restore View)

  依客戶端傳來的session資料或伺服端上的session資料,回復JSF畫面元件。

  • 套用請求值(Apply Request Values)

  JSF畫面元件各自獲得請求中的值屬於自己的值,包括舊的值與新的值。

  • 執行驗證(Process Validations)

  轉換為物件並進行驗證。

  • 更新模型值(Update Model Values)

  更新Bean或相關的模型值。

  • 喚起應用程式(Invoke Application)

  執行應用程式相關邏輯。

  • 繪製回應畫面(Render Response)

  對先前的請求處理完之後,產生畫面以回應客戶端執行結果。

  對於動作事件(Action Event)來說,元件的動作事件是在套用請求值階段就生成ActionEvent物件了,但相關的事件處理並不是馬上進行,ActionEvent會先被排入佇列,然後必須再通過驗證、更新模式值階段,之後才處理佇列中的事件。

  這樣的流程對於按下按鈕然後執行後端的應用程式來說不成問題,但有些事件並不需要這樣的流程,例如只影響畫面的事件。

  舉個例子來說,在表單中可能有使用者名稱、密碼等欄位,並提供有一個地區選項按鈕,使用者可以在不填下按鈕的情況下,就按下地區選項按鈕,如果 依照正常的流程,則會進行驗證、更新模型值、喚起應用程式等流程,但顯然的,使用者名稱與密碼是空白的,這會引起不必要的錯誤。

  您可以設定元件的事件在套用請求值之後立即被處理,並跳過後續的階段,直接進行畫面繪製以回應請求,對於JSF的input與command元件,都有一個immediate屬性可以設定,只要將其設定為true,則指定的事件就成為立即事件。

  一個例子如下:

  • index.jsp
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@page contentType="text/html;charset=UTF8"%>

 "#{user.locale}">
 "messages" var="msgs"/>

 
 
 <h:outputText value=<span class="code-quote">"#{msgs.titleText}"</span>/>
 
 

    
        

"#{msgs.hintText}"/>

"#{msgs.nameText}"/>: "#{user.name}"/>

"#{msgs.passText}"/>: "#{user.password}"/>

"#{msgs.commandText}" action="#{user.verify}"/> "#{msgs.Text}" immediate="true" actionListener="#{user.changeLocale}"/>

  這是一個可以讓使用者決定使用語系的示範,最後一個commandButton元件被設定了immediate屬性,當按下這個按鈕後,JSF 套用請求值之後會立即處理指定的actionListener,而不再進行驗證、更新模型值,簡單的說,就這個程式來說,您在輸入欄位與密碼欄位中填入的 值,不會影響您的user.name與user.password。

  基於範例的完整起見,我們列出這個程式Bean物件及faces-config.xml:

  • UserBean.java
UserBean.java
package onlyfun.caterpillar;

 import javax.faces.event.ActionEvent;

 public class UserBean {
    private String locale = "en";
    private String name;
    private String password;
    private String errMessage;
   
    public void changeLocale(ActionEvent e) {
        if(locale.equals("en"))
            locale = "zh_TW";
        else
            locale = "en";
    }
    
    public String getLocale() {
        if (locale == null) {
            locale = "en";
        }
        return locale;
    }
    
    public void setName(String name) {
        this.name = name;
    }
   
    public String getName() {
        return name;
    }
   
    public void setPassword(String password) {
        this.password = password;
    }
   
    public String getPassword() {
        return password;
    }
   
    public void setErrMessage(String errMessage) {
        this.errMessage = errMessage;
    }
   
    public String getErrMessage() {
        return errMessage;
    }
   
    public String verify() {
        if(!name.equals("justin") ||
           !password.equals("123456")) {
            errMessage = "名稱或密碼錯誤";
            return "failure";
        }
        else {
            return "success";
        }
    }
 }
  • faces-config.xml
faces-config.xml
"1.0"?>
 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 
    
        /pages/index.jsp
        case>
            success
            /pages/welcome.jsp
        case>
        case>
            failure
            /pages/index.jsp
        case>
    
       
    
        user
        
            onlyfun.caterpillar.UserBean
        
        session
    
 

  訊息資源檔的內容則是如下:

  • messages_en.properties
messages_en.properties
titleText=JSF Demo
 hintText=Please input your name and password
 nameText=name
 passText=password
 commandText=Submit
 Text=/u4e2d/u6587

  Text中設定的是「中文」轉換為Java Unicode Escape格式的結果,另一個訊息資源檔的內容則是英文訊息的翻譯而已,其轉換為Java Unicode Escape格式結果如下:

  • messages_zh_TW.properties
messages_zh_TW.properties
titleText=JSF/u793a/u7bc4
 hintText=/u8acb/u8f38/u5165/u540d/u7a31/u8207/u5bc6/u78bc
 nameText=/u540d/u7a31
 passText=/u5bc6/u78bc
 commandText=/u9001/u51fa
 Text=English

  welcome.jsp就請自行設計了,程式的畫面如下:


十八、 值變事件

  如果使用者改變了JSF輸入元件的值後送出表單,就會發生值變事件(Value Change Event),這會丟出一個javax.faces.event.ValueChangeEvent物件,如果您想要處理這個事件,有兩種方式,一是直接 設定JSF輸入元件的valueChangeListener屬性,例如:

"#{user.locale}" 
                  οnchange="this.form.submit();"
                  valueChangeListener="#{user.changeLocale}">

     "zh_TW" itemLabel="Chinese"/>
     "en" itemLabel="English"/>
 

  為了模擬GUI中選擇了選單項目之後就立即發生反應,我們在onchange屬性中使用了JavaScript,其作用是在選項項目發生改變之 後,立即送出表單,而不用按下提交按鈕;而valueChangeListener屬性所綁定的user.changeLocale方法必須接受 ValueChangeEvent物件,例如:

UserBean.java
package onlyfun.caterpillar;

 import javax.faces.event.ValueChangeEvent;

 public class UserBean {
    private String locale = "en";
    private String name;
    private String password;
    private String errMessage;
   
    public void changeLocale(ValueChangeEvent event) {
        if(locale.equals("en"))
            locale = "zh_TW";
        else
            locale = "en";
    }
    
    public void setLocale(String locale) {
        this.locale = locale;
    }
    
    public String getLocale() {
        if (locale == null) {
            locale = "en";
        }
        return locale;
    }
    
    public void setName(String name) {
        this.name = name;
    }
   
    public String getName() {
        return name;
    }
   
    public void setPassword(String password) {
        this.password = password;
    }
   
    public String getPassword() {
        return password;
    }
   
    public void setErrMessage(String errMessage) {
        this.errMessage = errMessage;
    }
   
    public String getErrMessage() {
        return errMessage;
    }
   
    public String verify() {
        if(!name.equals("justin") ||
           !password.equals("123456")) {
            errMessage = "名稱或密碼錯誤";
            return "failure";
        }
        else {
            return "success";
        }
    }
 }

  另一個方法是實作javax.faces.event.ValueChangeListener介面,並定義其processValueChange()方法,例如:

SomeListener.java
package onlyfun.caterpillar;
 ....
 public class SomeListener implements ValueChangeListener {
    public void processValueChange(ValueChangeEvent event) {
        ....
    }
    ....
 }
 

  然後在JSF頁面上使用標籤,並設定其type屬性,例如:

{code:borderStyle=solid} 
 "#{user.locale}" 
                  οnchange="this.form.submit();">
     "onlyfun.caterpillar.SomeListener"/>
     "zh_TW" itemLabel="Chinese"/>
     "en" itemLabel="English"/>
 

  下面這個頁面是對 立即事件 中的範例程式作一個修改,將語言選項改以下拉式選單的選擇方式呈現,這必須配合上面提供的UserBean類別來使用:

index.jsp
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
 <%@page contentType="text/html;charset=UTF8"%>

 "#{user.locale}">
 "messages" var="msgs"/>

 
 
 <h:outputText value=<span class="code-quote">"#{msgs.titleText}"</span>/>
 
 

    
        "#{user.locale}" 
                  immediate="true"
                  οnchange="this.form.submit();"       
                  valueChangeListener="#{user.changeLocale}">

            "zh_TW" 
                          itemLabel="Chinese"/>
            "en" 
                          itemLabel="English"/>
        
    
        

"#{msgs.hintText}"/>

"#{msgs.nameText}"/>: "#{user.name}"/>

"#{msgs.passText}"/>: "#{user.password}"/>

"#{msgs.commandText}" action="#{user.verify}"/>

十九、 Phase 事件

即時事件 中我們提到,JSF的請求執行到回應,完整的過程會經過六個階段:

  • 回復畫面(Restore View)

  依客戶端傳來的session資料或伺服端上的session資料,回復JSF畫面元件。

  • 套用請求值(Apply Request Values)

  JSF畫面元件各自獲得請求中的值屬於自己的值,包括舊的值與新的值。

  • 執行驗證(Process Validations)

  轉換為物件並進行驗證。

  • 更新模型值(Update Model Values)

  更新Bean或相關的模型值。

  • 喚起應用程式(Invoke Application)

  執行應用程式相關邏輯。

  • 繪製回應畫面(Render Response)

  對先前的請求處理完之後,產生畫面以回應客戶端執行結果。

  在每個階段的前後會引發javax.faces.event.PhaseEvent,如果您想嘗試在每個階段的前後捕捉這個事件,以進行一些處 理,則可以實作javax.faces.event.PhaseListener,並向javax.faces.lifecycle.Lifecycle 登記這個Listener,以有適當的時候通知事件的發生。

  PhaseListener有三個必須實作的方法getPhaseId()、beforePhase()與afterPhase(),其中getPhaseId()傳回一個PhaseId物件,代表Listener想要被通知的時機,可以設定的時機有:

  • PhaseId.RESTORE_VIEW
  • PhaseId.APPLY_REQUEST_VALUES
  • PhaseId.PROCESS_VALIDATIONS
  • PhaseId.UPDATE_MODEL_VALUES
  • PhaseId.INVOKE_APPLICATION
  • PhaseId.RENDER_RESPONSE
  • PhaseId.ANY_PHASE

  其中PhaseId.ANY_PHASE指的是任何的階段轉換時,就進行通知;您可以在beforePhase()與afterPhase()中撰寫階段前後撰寫分別想要處理的動作,例如下面這個簡單的類別會列出每個階段的名稱:

ShowPhaseListener.java
package onlyfun.caterpillar;

 import javax.faces.event.PhaseEvent;
 import javax.faces.event.PhaseId;
 import javax.faces.event.PhaseListener;

 public class ShowPhaseListener implements PhaseListener {

    public void beforePhase(PhaseEvent event) {
        String phaseName = event.getPhaseId().toString();
        System.out.println("Before " + phaseName);
    }

    public void afterPhase(PhaseEvent event) {
        String phaseName = event.getPhaseId().toString();
        System.out.println("After " + phaseName);
    }

    public PhaseId getPhaseId() {
        return PhaseId.ANY_PHASE;
    }
 }

  撰寫好PhaseListener後,我們可以在faces-config.xml中向Lifecycle進行註冊:

faces-config.xml
"1.0"?>
 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 
    
        
            onlyfun.caterpillar.ShowPhaseListener
        
    
    ......
 

  您可以使用這個簡單的類別,看看在請求任一個JSF畫面時所顯示的內容,藉此瞭解JSF每個階段的流程變化。

二十、 簡介 JSF 標準標籤
JSF提供了標準的HTML Renderer Kit,可以讓您搭配JSF元件輸出HTML文件,標準的HTML Renderer Kit主要包括了幾個類別:
  • 輸出(Outputs)

  其名稱以output作為開頭,作用為輸出指定的訊息或綁定值。

  • 輸入(Inputs)

  其名稱以input作為開頭,其作用為提供使用者輸入欄位。

  • 命令(Commands)

  其名稱以command作為開頭,其作用為提供命令或連結按鈕。

  • 選擇(Selections)

  其名稱以select作為開頭,其作用為提供使用者選項的選取。

  • 其它

  包括了form、message、messages、graphicImage等等未分類的標籤。

  JSF標準HTML標籤包括了幾個共通的屬性,整理如下:

屬性名稱 適用 說明
id 所有元件 可指定id名稱,以讓其它標籤或元件參考
binding 所有元件 綁定至UIComponent
rendered 所有元件 是否顯示元件
styleClass 所有元件 設定Cascading stylesheet (CSS)
value 輸入、輸出、命令元件 設定值或綁定至指定的值
valueChangeListener 輸入元件 設定值變事件處理者
converter 輸入、輸出元件 設定轉換器
validator 輸入元件 設定驗證器
required 輸入元件 是否驗證必填欄位
immediate 輸入、命令元件 是否為立即事件

  除了共通的屬性之外,您還可以在某些元件上設定標籤HTML 4.01的屬性,像是size、alt、width等屬性,或者是設定DHTML事件屬性,例如onchange、onclick等等。

  除了JSF的標準HTML標籤之外,您還需要一些標準核心標籤,這些標籤是獨立於Renderer Kit的,JSF並不限制在HTML輸出表示層,核心標籤可以搭配其它的Renderer Kit來使用。

詳細的HTML標籤或核心標籤的使用與屬性說明可以查詢 Tag Library Documentation 文件

二十一、 輸出類標籤

  輸出類的標籤包括了outputLabel、outputLink、outputFormat與 outputText,分別舉例說明如下:

outputLabel

  產生

"user" value="#{user.name}"/>
for="user" value="#{user.name}"/>

  這會產生像是以下的標籤:

"user" type="text" name="user" value="guest" />

outputLink

  產生 HTML標籤,例如:

"../index.jsp">
    "Link to Index"/>
    "name" value="MyName"/>

  你可搭配幫鏈結加上參數,所有的參數都會變成 name=value 的型態附加在連結後。

  value所指定的內容也可以是JSF EL綁定。

outputFormat

  產生指定的文字訊息,可以搭配來設定訊息的參數以格式化文字訊息,例如:

"messages" var="msgs"/>
 "#{msgs.welcomeText}">
     "Hello"/>
     "Guest"/>
 

  如果您的messages.properties包括以下的內容:

welcomeText={0}, Your name is {1}.

  則{0}與{1}會被取代為設定的文字,最後顯示的文字會是:

Hello, Your name is Guest.

  另一個使用的方法則是:

"{0}, Your name is {1}.">
     "Hello"/>
     "Guest"/>
 

outputText

  簡單的顯示指定的值或綁定的訊息,例如:

"#{user.name}"/>


二十二、
輸入類標籤
 輸入類標籤包括了inputText、inputTextarea、inputSecret、 inputHidden,分別舉例說明如下:

inputText

  顯示單行輸入欄位,即輸出 HTML標籤,其type屬性設定為text,例如:

"#{user.name}"/>

inputTextarea

  顯示多行輸入文字區域,即輸出