Owen的酒楼

--酒楼上渡过的人生--
生命太短,人生太长,但愿别喝醉了。

导航

JSF最佳入门之一 ZT

Posted on 2007-11-26 14:53  Hicome  阅读(488)  评论(0编辑  收藏  举报
一、簡介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
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">

<description>
JSF Demo
</description>
<display-name>JSF Demo</display-name>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>
javax.faces.webapp.FacesServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>

  在上面的定義中,我們將所有.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
<?xml version="1.0"?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

<faces-config>
<navigation-rule>
<from-view-id>/pages/index.jsp</from-view-id>
<navigation-case>
<from-outcome>login</from-outcome>
<to-view-id>/pages/welcome.jsp</to-view-id>
</navigation-case>
</navigation-rule>

<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>
onlyfun.caterpillar.UserBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
</faces-config>

  在<navigation-rule>中,我們定義了頁面流程,當請求來自<from-view- id>中指定的頁面,並且指定了<navigation-case>中的<from-outcome>為login時,則 會將請求導向至<to-view-id>所指定的頁面。

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

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

網頁設計人員

  首先網頁設計人員撰寫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"%>
<html>
<head>
<title>第一個JSF程式</title>
</head>
<body>
<f:view>
<h:form>
<h3>請輸入您的名稱</h3>
名稱: <h:inputText value="#{user.name}"/><p>
<h:commandButton value="送出" action="login"/>
</h:form>
</f:view>
</body>
</html>

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

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

  html標籤庫中幾乎都是與HTML標籤相關的進階標籤,<h:form>會產生一個表單,我們使用<h: inputText>來顯示user這個Bean物件的name屬性,而<h:commandButton>會產生一個提交按鈕,我們 在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"%>
<html>
<head>
<title>第一個JSF程式</title>
</head>
<body>
<f:view>
<h:outputText value="#{user.name}"/> 您好!
<h3>歡迎使用 JavaServer Faces!</h3>
</f:view>
</body>
</html>

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

  接下來啟動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
<?xml version="1.0"?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

<faces-config>
<navigation-rule>
<from-view-id>/pages/index.jsp</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/pages/welcome.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>failure</from-outcome>
<to-view-id>/pages/index.jsp</to-view-id>
</navigation-case>
</navigation-rule>

<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>
onlyfun.caterpillar.UserBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
</faces-config>

  根據上面的定義,當傳回的字串是"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"%>
<html>
<head>
<title>第一個JSF程式</title>
</head>
<body>
<f:view>
<h:form>
<h3>請輸入您的名稱</h3>
<h:outputText value="#{user.errMessage}"/><p>
名稱: <h:inputText value="#{user.name}"/><p>
密碼: <h:inputSecret value="#{user.password}"/><p>
<h:commandButton value="送出"
action="#{user.verify}"/>
</h:form>
</f:view>
</body>
</html>

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

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

四、 導航規則設置

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

....
<navigation-rule>
<from-view-id>/pages/index.jsp</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/pages/welcome.jsp</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>failure</from-outcome>
<to-view-id>/pages/index.jsp</to-view-id>
</navigation-case>
</navigation-rule>
....

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

  在<navigation-rule>中的<from-view-id>是個選擇性的定義,它規定了來源頁面的條件, <navigation-case>中定義各種導覽條件,<from-outcome>定義當表單結果符合的條件時,各自改導向 哪一個目的頁面,目的頁面是在<to-view-id>中定義。

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

....
<navigation-rule>
<from-view-id>/pages/index.jsp</from-view-id>
<navigation-case>
<from-action>#{user.verify}</from-action>
<from-outcome>success</from-outcome>
<to-view-id>/pages/welcome.jsp</to-view-id>
</navigation-case>
....
</navigation-rule>
....

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

....
<navigation-rule>
<from-view-id>/pages/index.jsp</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/pages/welcome.jsp</to-view-id>
<redirect/>
</navigation-case>
....
</navigation-rule>
....

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

....
<navigation-rule>
<from-view-id>/admin/*</from-view-id>
<navigation-case>
<from-action>#{user.verify}</from-action>
<from-outcome>success</from-outcome>
<to-view-id>/pages/welcome.jsp</to-view-id>
</navigation-case>
....
</navigation-rule>
....

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

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

....
<navigation-rule>
<from-view-id>/*</from-view-id>
<navigation-case>
....
</navigation-rule>
....

或者是這樣:

....
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
....
</navigation-rule>
....

五、 JSF Expression Language

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

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

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

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

...
<f:view>
<h:outputText value="#{userBean.name}"/>
</f:view>
...

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

...
<f:view>
名稱, 年齡:<h:outputText
value="#{userBean.name}, #{userBean.age}"/>
</f:view>
...

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

名稱, 年齡: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"%>

<html>
<head>
<title></title>
</head>
<body>
<f:view>
<b> 您好, <h:outputText value="#{param.name}"/> </b>
</f:view>

</body>
</html>

  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,也可以使用 [ 與 ] 來指定,例如:

...
<f:view>
<b> 您好, <h:outputText value="#{param['name']}"/> </b>
</f:view>
...

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

...
<f:view>
<h:outputText value="#{someBean.someMap[user.name]}"/>
</f:view>
...

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

....
<f:view>
<h:outputText value="#{someBean.someList[0]}"/>
<h:outputText value="#{someBean.someArray[1]}"/>
<h:outputText
value="#{someBean.someListOrArray[user.age]}"/>
</f:view>
....

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

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

<h:outputText value="#{'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

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

  • 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"%>

<f:view>
<f:loadBundle basename="messages" var="msgs"/>

<html>
<head>
<title><h:outputText value="#{msgs.titleText}"/></title>
</head>
<body>

<h:form>
<h3><h:outputText value="#{msgs.hintText}"/></h3>
<h:outputText value="#{msgs.nameText}"/>:
<h:inputText value="#{user.name}"/><p>
<h:outputText value="#{msgs.passText}"/>:
<h:inputSecret value="#{user.password}"/><p>
<h:commandButton value="#{msgs.commandText}"
actionListener="#{user.verify}"
action="#{user.outcome}"/>
</h:form>

</body>
</html>

</f:view>

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

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

<f:view locale="zh_TW">
<f:loadBundle basename="messages" var="msgs"/>

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

<f:view locale="en">
<f:loadBundle basename="messages" var="msgs"/>

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

<faces-config>
<application>
<local-config>
<default-locale>en</default-locale>
<supported-locale>zh_TW</supported-locale>
</local-config>
</application>

.....
</faces-config>

  在<local-config>一定有一個<default-locale>,而<supported-locale>可以有好幾個,這告訴JSF您的應用程式支援哪些語系。

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

<f:view locale="#{user.locale}">
<f:loadBundle basename="messages" var="msgs"/>

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

<h:selectOneRadio value="#{user.locale}">
<f:selectItem itemValue="zh_TW"
itemLabel="#{msgs.zh_TWText}"/>
<f:selectItem itemValue="en"
itemLabel="#{msgs.enText}"/>
</h:selectOneRadio>

七、 Backing Beans

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

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

....
<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>
onlyfun.caterpillar.UserBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
....

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

<h:inputText value="#{user.name}"/>

  <managed-bean-scope>設定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參數,例如:

<web-app>
<context-param>
<param-name>javax.faces.CONFIG_FILES</param-name>
<param-value>/WEB-INF/beans.xml</param-value>
</context-param>

...
</web-app>

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

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

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

....
<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>
onlyfun.caterpillar.UserBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
....

  如果要在其它類別中取得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時,自動設置屬性的初始值,則可以如下設定:

....
<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>
onlyfun.caterpillar.UserBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>name</property-name>
<value>caterpillar</value>
</managed-property>
<managed-property>
<property-name>password</property-name>
<value>123456</value>
</managed-property>
</managed-bean>
....

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

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

  當然,您的屬性不一定是字串值,也許會是int、float、boolean等等型態,您可以設定<value> 值時指定這些值的字串名稱,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的屬性,例如:

....
<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>
onlyfun.caterpillar.UserBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>

<managed-bean>
<managed-bean-name>other</managed-bean-name>
<managed-bean-class>
onlyfun.caterpillar.OtherBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>user</property-name>
<value>#{user}</value>
</managed-property>
</managed-bean>
....

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

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

<managed-property>
<property-name>someProperty</property-name>
<list-entries>
<value-class>java.lang.Integer</value-class>
<value>1</value>
<value>2</value>
<value>3</value>
</list-entries>
</managed-property>
</managed-bean>
....

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

  設定Map的話,則是使用<map-entries>標籤,例如:

....
<managed-bean>
<managed-bean-name>someBean</managed-bean-name>
<managed-bean-class>
onlyfun.caterpillar.SomeBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>

<managed-property>
<property-name>someProperty</property-name>
<map-entries>
<value-class>java.lang.Integer</value-class>
<map-entry>
<key>someKey1</key>
<value>100</value>
</map-entry>
<map-entry>
<key>someKey2</key>
<value>200</value>
</map-entry>
</map-entries>
</managed-property>
</managed-bean>
....

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

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

....
<managed-bean>
<description>
Special expense item types
</description>
<managed-bean-name>specialTypes</managed-bean-name>
<managed-bean-class>
java.util.TreeMap
</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
<map-entries>
<value-class>java.lang.Integer</value-class>
<map-entry>
<key>Presentation Material</key>
<value>100</value>
</map-entry>
<map-entry>
<key>Software</key>
<value>101</value>
</map-entry>
<map-entry>
<key>Balloons</key>
<value>102</value>
</map-entry>
</map-entries>
</managed-bean>
....

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

....
<managed-bean>
<managed-bean-name>statusStrings</managed-bean-name>
<managed-bean-class>
java.util.ArrayList
</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<list-entries>
<null-value/>
<value>Open</value>
<value>Submitted</value>
<value>Accepted</value>
<value>Rejected</value>
</list-entries>
</managed-bean>
....

十、 標準轉換器

  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,我們可以使用<f:convertDateTime>、<f:convertNumber>標籤進行轉換,它們各自提供有一些簡單的屬性,可以讓我們在轉換時指定一些轉換的格式細節。

  來看個簡單的例子,首先我們定義一個簡單的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"%>

<f:view>

<html>
<head>
<title>轉換器示範</title>
</head>
<body>

設定的日期是:
<b>
<h:outputText value="#{user.date}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:outputText>
</b>

<h:form>
<h:inputText id="dateField" value="#{user.date}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:inputText>
<h:message for="dateField" style="color:red"/>
<br>
<h:commandButton value="送出" action="show"/>
</h:form>
</body>
</html>

</f:view>

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

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

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

<faces-config>
<navigation-rule>
<from-view-id>/*</from-view-id>
<navigation-case>
<from-outcome>show</from-outcome>
<to-view-id>/pages/index.jsp</to-view-id>
</navigation-case>
</navigation-rule>

<managed-bean>
<managed-bean-name>user</managed-bean-name>
<managed-bean-class>
onlyfun.caterpillar.UserBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
</faces-config>

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

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

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

  您還可以參考 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
<?xml version="1.0"?>
<!DOCTYPE faces-config PUBLIC
"-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

<faces-config>
<navigation-rule>
<from-view-id>/*</from-view-id>
<navigation-case>
<from-outcome>show</from-outcome>
<to-view-id>/pages/index.jsp</to-view-id>
</navigation-case>
</navigation-rule>

<converter>
<converter-id>onlyfun.caterpillar.User</converter-id>
<converter-class>
onlyfun.caterpillar.UserConverter
</converter-class>
</converter>

<managed-bean>
<managed-bean-name>guest</managed-bean-name>
<managed-bean-class>
onlyfun.caterpillar.GuestBean
</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
</faces-config>

  註冊轉換器時,需提供轉換器識別(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"%>

<f:view>

<html>
<head>
<title>自訂轉換器</title>
</head>
<body>

Guest名稱是:<b>
<h:outputText value="#{guest.user}"
converter="onlyfun.caterpillar.User"/>
</b>

<h:form>
<h:inputText id="userField"
value="#{guest.user}"
converter="onlyfun.caterpillar.User"/>
<h:message for="userField" style="color:red"/>
<br>
<h:commandButton value="送出" action="show"/>
</h:form>
</body>
</html>

</f:view>

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

<h:inputText id="userField" value="#{guest.user}">
<f:converter converterId="onlyfun.caterpillar.User"/>
</h:inputText>