來自:http://www-128.ibm.com/developerworks/library/j-jsf1/
翻譯:zhouy
--------------------------------------------------------------------------------
恐懼、不確定和懷疑 JSF 的觀念環繞著 JSF 技術已經有一段時間了,我覺得是阻止這種觀念繼續曼延的時候了——或者至少在兩者這間取得平衡點。關于 JSF ,首當其充的誤解,就是離不開拖拽式所見即所得工具進行 JSF 開發。其二, JSF 不支持類似 Struts 的 MVC Model 2 框架。而最后一點,也是困擾 JSF 開發最大的一點,就是關于其開發困難度的說法。
在這個四小節的討論中,我將盡我所能,通過教你如何利用JSF進行開發這種最實際的方法,來消除上述所有三種誤解。事實上,如果你覺得JSF開發是件困難的事,那么很有可能是因為你沒有正確的使用它——幸運的是要修正它并不難。我會讓你對JSF有一個大體的觀念,并且利用一個可工作的示例展示MVC和JSF的基本原理。在我們開始所有介紹之前,先花一分鐘時間將事實不符的FUD區分清楚。
不要相信FUD
如前面提到的,關于JSF有三個大的誤解,其一便是JSF要求所見即所得工具進行開發工作。這是錯誤的,就像許多Swing開發人員不使用所見即所得工具構建Swing應用程序一樣,你不必需要所見即所得編輯器來構建JSF應用。事實上,不使用所見即所得工具來開發 JSF 會比傳統的Model 2框架如Struts和WebWork開發來得簡單。在稍后的章節中我會有詳細的解釋,但在這里請記住:JSF開發比Struts來得簡單,即使不使用所見即所得工具!
關于JSF的另一個誤解是,它不支持Model 2模型。現在,這種說法只是部分正確。事實是Model 2是一種建立在Servlets基礎上的MVC瀑布模型版本。與Model 2面向無狀態協議(HTTP)所不同的是,JSF支持富MVC模型——一種與傳統的GUI應用更加相似的模型。雖然MVC的基礎概念使得JSF框架的實現比其它框架困難,但在上層結構上許多實現JSF的工作已經為你做好,得到的結果是,付出減少,卻能夠獲得更多的回報。
流傳最廣的關于JSF的誤解是在于JSF的開發難度。我經常從人們口中聽到這樣的說法,他們閱讀了大量的技術文檔,但是并沒有實際動手去嘗試這項技術,所以我認為要消除這個慮慮很簡單。如果你是通過 JSF 浩瀚無邊的規格說明來建立你對JSF的理解,那么這些東西肯定會使你受驚嚇。要明確的是,所有的這些規格本質上是給實現工具使用的,而不是應用程序開發者。如前所述,JSF是為方便應用程序開發者而設計的。
雖然從某種意義上說,JSF的組件基礎和事件驅動的GUI式的開發模型對于Java世界來說是一種新鮮的事物,但是它其實早已在某些領域中存在了。Apple的WebObjects和ASP.net與JSF就十分的相似。Tapestry是一項開源的Web組件框架,它與JSF所采用的方法不同,但也是一種基于Web GUI組件框型的技術。
無論如何,關于FUD的討論或許可以終止。解決關于JSF的編見最快捷的方法便是正確地研究這門技術,這正是我們稍后要做的事情。考慮到這或許是你第一次了解JSF,我將為你介紹它的總體概念。
給JSF初學者
類似于Swing和AWT,JSF提供了一套標準和可重用的GUI組件。JSF被用來構建Web應用表層,它提供如下開發優勢:
- 動作邏輯與表現邏輯的明顯區分
- 有狀態事務的組件級別控制
- 事件與服務端代碼的輕松綁定
- UI組件及Web層觀念
- 提供多樣的、標準的開發商實現規則
- 供管理應用狀態和動作的JavaBeans組件
- 事件驅動開發(通過與傳統GUI開發類似的監聽器來實現)
- 展示MVC樣式的頁面,通過JSF組件樹引用視圖
組件基礎的框架模式
JSF為標準HTML中每個可用的輸入域提供了組件標簽,你也可以為你應用中的特殊目的定義自己的組件,或者也可以將多項HTML組件組合起來成為一個新的組合——例如一個數據采集組件可以包含三個下拉菜單。JSF組件是有狀態的,組件的狀態由JSF框架來提供,JSF用組件來處理HTML響應。
JSF的組件集包含事件發布模型、一個輕量級的IoC容量、擁有其它普遍的GUI特征的組件,包括可插入的渲染、服務器端校驗、數據轉換、頁面導航控制等等。作為一種基于組件的框架,JSF極具可配置性和可護展性。大部分的JSF功能,比如導航和受管bean查詢,可以被可插入組件所替換。這樣的插入程度給開發人員構建Web應用GUI時提供了極大的彈性,允許開發人員將其它基于組件的技術應用于JSF的開發過程中來。比如說,你可以將JSF的內嵌IoC框架替換為更加全能的Spring的IoC/AOP以供受管bean查詢。
JSF和JSP技術
一個JSF應用的用戶表層被包含在JSP(JavaServer Pages)頁面中。每個JSP頁面包含有JSF組件,這些組件描繪了GUI功能。你可以在JSP頁面里使用 JSF 定制標簽庫來渲染UI組件,注冊事件句柄,實現組件與校驗器的交互及組件與數據轉換器的交互等等。
那就是說,JSF不是固定與JSP技術相關聯。事實上,JSF標簽僅僅引用組件以實現顯示的目的。你會發現當你第一次修改一個JSP頁面中某一JSF組件的屬性,在重新載入頁面的時候并沒有發生應有的變化。這是因為標簽在它自己的當前狀態中進行查詢,如果組件已經存在,標簽就不會改變它的狀態。組件模型允許控制代碼改變一個組件的狀態(例如將一個輸入域置為不可用),當視圖被展現的時候,組件樹的當前狀態也隨著一覽無余。
一個典型的JSF應用在UI層無需任何的Java代碼,只需要很少的一部份JSTL EL( JSP 標準標簽庫,表達式語言)。前面已經提及,有非常多IDE工具可以幫助我們構建JSF應用,并且有越來越多的第三方JSF GUI組件市場。不使用所見即所得工具來編寫JSF也是可行的。
JSF和MVC
JSF技術是在多年Java平臺上的Web開發技術的總結的產物。這種趨勢起源于JSP。JSP是一種很好的表現層,但同時它容易將Java代碼與HTML頁面混淆起來。另一個不好的原因與Model 1架構有關,它使得開發人員通過使用 <jsp:useBean> 標簽將許多原本應該在后端處理的代碼引入到Web頁面中來。這種方法在簡單的Web應用中可以運行得很好,但許多Java開發者不喜歡這種類似C++的靜態引入組合方式,所以Model 2架構隨之被引進。
本質上,Model 2架構是MVC Web應用的一種瀑布模型版本。在Model 2架構中控制器通過Servlets來表現,顯示層則通過JSP頁面來展現。Struts是一種最簡單的Model 2實現,它使用Actions取代了Servlets。在Struts中應用的控制邏輯與數據層(通過ActionForms來展現)相分離。反對Struts的主要的不滿在于,Struts更多偏向程序化,而非面向對象。WebWork和Spring MVC是Model 2的另外兩種框架,它們比起Struts更大的改進在于盡量減少程序化,但是兩者都沒有Struts普及。另外兩者也不像JSF般提供組件模型。
大多數Model 2框架真正的因素在于它們的事件模型顯得過于單薄,留下了太多的工作需要開發人員自己來處理。一個富事件模型使多數用戶所希望的交互變得簡單。許多Model 2技術就像JPS一樣,很容易將HTML布局及格式與GUI標簽相混合,在表現上不像真正的組件。而一些Model 2的架構(如Struts)錯誤地將表現與狀態分離,便得許多Java開發人員感覺他們像是在編寫COBOL程序。
富MVC環境
JSF提供一種組件模型及比其它Model 2實現更為豐富的MVC環境。本質上說,JSF比Model 2架構更接近真正的MVC開發環境,雖然它仍然屬于無狀態協議。JSF能夠構建比起其它Model 2框架更精彩的事件驅動 GUI 。JSF提供一系列事件選項如menu選項的selected事件,button的clicked事件等等,這與在其它Model 2框架中更多地依賴簡單的“request received”不同。
JSF的易于翻轉的事件模型允許應用程序更少地依賴HTTP實現細節,簡化你的開發量。JSF改善了傳統Model 2架構,它更容易將表現層和業務層移出控制器,并將業務邏輯從JSP頁面中移出。事實上,簡單的控制器類根本無需與JSF相關聯,方便了對控制器類的測試。與真正的MVC架構不同,JSF不會在多于一個視點上發出多條事件,我們仍然是在處理一個無狀態的協議,這使得這一點變得無關緊要。系統事件對一個視圖的變動或更新通常來自于用戶的請求。
JSF的MVC實現細節
在JSF的MVC實現中,作為映射的backing bean在視圖和模型間扮演著協調的作用。正因如此,在backing bean里限制業務邏輯和持久層邏輯就顯得猶為重要。一種普遍的做法是將業務邏輯置入應用模型中。這樣的話backing bean仍會映射模型對象以供視圖顯示。另一種選擇是將業務邏輯放入業務代理——一種與模型相作用的表層。
與JSP技術不同,JSF的視圖實現是一種有狀態組件模型。JSF的視圖由兩部分組成:根視圖和JSP頁面。根視圖是UI組件的集合,它負責維護UI的狀態。就如像Swing和AWT,JSF組件使用組合式設計模式來管理組件樹,用按鈕來管理事件句柄及動作方法。
Figure 1 通過MVC角度來透析的示例
一個
JSF
例子
在文章剩余的章節里,我將專注于構建一個 JSF 應用的過程。這個例子是 JSF 技術的一個非常簡單的展現,主要表現在以下幾個方面:
- 如何設置 JSF 應用程序的格局
- 如何為 JSF 配置 web.xml 文件
- 如何為程序配置 faces-config.xml 文件
- 編寫模型(即 backing bean )
- 利用 JSP 技術構建視圖
- 利用傳統標簽庫在根視圖框建組件樹
- Form 的默認校驗規則
例子是一個簡單的計算器程序,使目標用戶能夠輸入兩個數并計算。因此頁面上有兩個文本域,兩個標簽,兩處錯誤提示和一個提交按鈕。文本域用來輸入數字,標簽用來標示輸入的文本域,錯誤提示用來顯示文本域中的輸入在校驗或數據轉換時出現的錯誤。總共有三個 JSP 頁面: index.jsp 用來重定向到 calculator.jsp 頁面; calculator.jsp 用來顯示上面提及的 GUI ; result.jsp 用來顯示最后結果。一個受管 bean : CalculatorController 作為 calculator.jsp 和 result.jsp 的 backing bean 。
?
為了構建計算器程序,你需要如下步驟:
- ? 收集 web.xml 和 faces-config.xml 文件,可以在下面的示例源代碼中找到。
- 在 web.xml 中聲明 Faces Servlet 和 Faces Servlet 映射。
- 在 web.xml 中指定 faces-config.xml 。
- 在 faces-config.xml 中聲明受管于 JSF 的 bean 。
- 在 faces-config.xml 中聲明導航規則。
- 構建模型對象 Calculator 。
- 用 CalculatorController 與 Calculator 交互。
- 創建 index.jsp 頁面。 ?
- 創建 calculator.jsp 頁面。
- 創建 results.jsp 頁面。
?
為了使用
Faces
,你需要在
web.xml
中裝載
Faces Servlet
如下:
<servlet>
? <servlet-name>Faces Servlet</servlet-name>
? <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
? <load-on-startup>1</load-on-startup>
</servlet>
這和大多數web.xml描述文件類似,所不同的是將request的掌握權轉向JSF Servlet,而非自定義的Servlet。所有請求引用了f:view的JSP文件的request都將通過此Servlet。因此需要增加相應的映射,并且將允許使用JSF的JSP技術通過映射裝載進來。
<servlet-mapping>
??? <servlet-name>Faces Servlet</servlet-name>
??? <url-pattern>/calc/*</url-pattern>
</servlet-mapping>
以上配置告知Faces Servlet容器將所有符合/calc/的請求都轉給Faces Servlet處理。這使得JSF能夠初始化JSF內容和根視圖。
?? ?...
? <managed-bean>
??? <description>
????? The "backing file" bean that backs up the calculator webapp
??? </description>
??? <managed-bean-name>CalcBean</managed-bean-name>
<managed-bean-class>
?? ?com.arcmind.jsfquickstart.controller.CalculatorController
</managed-bean-class>
??? <managed-bean-scope>session</managed-bean-scope>
? </managed-bean>
</faces-config>
上面的配置告知JSF要往JSF環境中增加一個叫做CalcBean的bean,你也可以把受管bean取任何名字。Bean已經聲明了,然后你要做的就是為應用定義導航規則。
? <from-view-id>/calculator.jsp</from-view-id>
? <navigation-case>
??? <from-outcome>success</from-outcome>
??? <to-view-id>/results.jsp</to-view-id>
? </navigation-case>
</navigation-rule>
上面的代碼表明,如果一個動作從/calculator.jsp頁面返回邏輯輸出為“success”,那么它將用戶轉向/result.jsp頁面。
/**
?* Calculator
?*
?* @author Rick Hightower
?* @version 0.1
?*/
public class Calculator {
??? //~Methods------------------------------------------------
??? /**
???? * add numbers.
???? *
???? * @param a first number
???? * @param b second number
???? *
???? * @return result
???? */
??? public int add(int a, int b) {
??????? return a + b;
??? }
??? /**
???? * multiply numbers.
???? *
???? * @param a first number
???? * @param b second number
???? *
???? * @return result
???? */
??? public int multiply(int a, int b) {
??????? return a * b;
??? }
}
這樣,業務邏輯就全部建立了。下一步便是將其顯示在Web表現層上。
import com.arcmind.jsfquickstart.model.Calculator;
/**
?* Calculator Controller
?*
?* @author $author$
?* @version $Revision$
?*/
public class CalculatorController {
??? //~ Instance fields --------------------------------------------------------
??? /**
???? * Represent the model object.
???? */
??? private Calculator calculator = new Calculator();
??? /** First number used in operation. */
??? private int firstNumber = 0;
??? /** Result of operation on first number and second number. */
??? private int result = 0;
??? /** Second number used in operation. */
??? private int secondNumber = 0;
??? //~ Constructors -----------------------------------------------------------
??? /**
???? * Creates a new CalculatorController object.
???? */
??? public CalculatorController() {
??????? super();
??? }
??? //~ Methods ----------------------------------------------------------------
??? /**
???? * Calculator, this class represent the model.
???? *
???? * @param aCalculator The calculator to set.
???? */
??? public void setCalculator(Calculator aCalculator) {
??????? this.calculator = aCalculator;
??? }
??? /**
???? * First Number property
???? *
???? * @param aFirstNumber first number
???? */
??? public void setFirstNumber(int aFirstNumber) {
??????? this.firstNumber = aFirstNumber;
??? }
??? /**
???? * First number property
???? *
???? * @return First number.
???? */
??? public int getFirstNumber() {
??????? return firstNumber;
??? }
??? /**
???? * Result of the operation on the first two numbers.
???? *
???? * @return Second Number.
???? */
??? public int getResult() {
??????? return result;
??? }
??? /**
???? * Second number property
???? *
???? * @param aSecondNumber Second number.
???? */
??? public void setSecondNumber(int aSecondNumber) {
??????? this.secondNumber = aSecondNumber;
??? }
??? /**
???? * Get second number.
???? *
???? * @return Second number.
???? */
??? public int getSecondNumber() {
??????? return secondNumber;
??? }
??? /**
???? * Adds the first number and second number together.
???? *
???? * @return next logical outcome.
???? */
??? public String add() {
???????
??????? result = calculator.add(firstNumber, secondNumber);
??????? return "success";
??? }
??? /**
???? * Multiplies the first number and second number together.
???? *
???? * @return next logical outcome.
???? */
??? public String multiply() {
??????? result = calculator.multiply(firstNumber, secondNumber);
??? ???
??????? return "success";
??? }
}
請注意,在Listing 2中乘法和加法都返回“success”。“success”表明一個邏輯輸出,它不是保留字,它是與faces-config.xml的導航規則相結合的,通過增加或定義操作執行,頁面將轉向result.jsp。
?
這一頁較為復雜,我將一步一步進行講解。首先你需要聲明供JSF使用的標簽庫:
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
上述代碼告訴JSP引擎你將使用兩個JSF標簽庫:html和core。Html標簽庫包含所有與forms和其它HTML規范有關的標簽。Core標簽庫包含所有邏輯、校驗、控制等JSF標簽。
? <h:form id="calcForm">
???? ...?? ?
? </h:form>
</f:view>
上面的第一行是<f:view>的聲明,告知容器它受管于JSF。下一行是<h:form>標簽,它告知JSF你需要在此建一個HTML FORM。在位于FORM組件內的組件容器的語法渲染期間,所有的組件將會被問詢自動渲染,這樣它們將會生成標準的HTML代碼。
接下來,你告知JSF在FORM里所需的其它組件。在<h:form>中定義了一個panelGrid。panelGrid是一個組合組件——也就是一個組件里包含有其它的組件。panelGrid定義其它組件的布局,Listing 3顯示如何定義panelGrid的代碼:
? <h:outputLabel value="First Number" for="firstNumber" />
? <h:inputText id="firstNumber" value="#{CalcBean.firstNumber}" required="true" />
? <h:message for="firstNumber" />???
? <h:outputLabel value="Second Number" for="secondNumber" />
? <h:inputText id="secondNumber" value="#{CalcBean.secondNumber}" required="true" />
? <h:message for="secondNumber" />
</h:panelGrid>
屬性columns被定義為3表明所有組件將被布置在擁有3列空間的格局中。我們在panelGrid中加入了6個組件,共占2行。每一行包含一個outputLabel,一個inputText和一條message。Label和message都和inputText組件關聯,因此當一個校驗錯誤或或誤信息關聯到textField時,信息將會顯示在message組件上。兩個文本輸入域都要求有,如果在提交的時候檢測到無值,錯誤信息將會被創建,控制也將會轉到這個視圖來。
注意到兩個inputFields都使用一條JSF表達式來做數值綁定。乍一看這很像一條JSTL表達式。但是,JSF表達式確實與綁定著后臺代碼相應字段的輸入域相關聯。這種關聯是反向的,如果firstNumber是100,那么在form顯示的時候100也會被顯示。同樣,如果用戶提交一個有效的值,例如200,那么200也將作為firstNumber的新值。
一個更加實用的目的是,后臺代碼通過綁定模型對象的屬性的值到輸入域中,從而將模型對象展現出來。你將在此節稍后看到關于此目的的例子。
除了輸入域,calForm通過panelGroup中的兩個commandButton與兩個動作關聯:
??? <h:commandButton id="submitAdd" action="#{CalcBean.add}"? value="Add" />
??? <h:commandButton id="submitMultiply" action="#{CalcBean.multiply}" value="Multiply" />
</h:panelGroup>
panelGroup的概念與panelGrid很相似,除了它們顯示方式的不同。命令按鈕利用action=”#{CalcBean.add}”將按鈕與后臺動作綁定在一起。因此當這個form通過這個按鈕提交的時候,相關聯的方法便開始執行。
創建results.jsp頁面
Results.jsp頁面是用來顯示最后計算結果。如Listing 4所示:
Listing 4. The
results.jsp page
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
...
<f:view>
? First Number: <h:outputText id="firstNumber" value="#{CalcBean.firstNumber}"/>
? <br />
? Second Number: <h:outputText id="secondNumber" value="#{CalcBean.secondNumber}"/>
? <br />
? Result: <h:outputText id="result" value="#{CalcBean.result}"/>
? <br />
</f:view>
Results.jsp是一個相對簡單的頁面,將加法運算的結果顯示給用戶。通過<h:outputText>標簽來完成這一功能。<h:outputText>標簽帶有id和value屬性,value屬性輸出值,它在渲染的時候將被當作string看待。Value屬性通過JSF將要輸出的值與后臺代碼的屬性綁定在一起。
Figure 4. Validation and
data conversion errors
總結