在 JSF中JSP扮演的角色
學習如何與JSF一起使用JSP
摘要
在這個來自JavaServer Faces in Action的摘錄裡 (Manning, November 2004), 作者Kito Mann 解釋JSP如何與JSF協同工作 (1,800 words; December 13, 2004)
JSF 應用程式需要某種顯示技術, 例如JSP。JSP的酷特性之一是它能用定製標記延伸,一個定製標記就是在背後有JAVA代碼支援的特殊 XML 元素。在JSP中除使用標準JSP元素或HTML元素之外還能使用定製標記。 定製標記幾乎能做任何事情:顯示變數的值,處理XML,有條件地顯示頁面的一部分,訪問一個資料庫,等等 (是否任何人都應該用JSP標記做所有的這些事情,那是將來的問題...). 他們主要的目的是將java代碼從頁面中分離並允許前台開發人員用簡單、熟悉的標記替代。一組有關係的定製標記構成一個標記庫。 JSF與JSP一起使用定製標記。 到目前為止,在這本書裡我們已經樣本過的所有JSF標記 —<h:inputText>, <h:outputText>, <h:form>, <f:view>, 等等—都是定製標記。 JSF 實現提供訪問標準組件,繪製器,驗證器和轉換器的JSP定製標記,在下表中列出了這些標記庫(JSF的JAR檔案中包含了這些庫)
URI 名稱 首碼 描述
http://java.sun.com/jsf/core Core f Contains tags that are independent of a particular render kit (like <f:view>, <validator>, and so on)
http://java.sun.com/jsf/html HTML h Contains tags for all of the standard components and the HTML render kit
所有這些標記庫中的標記必須命名和以一種特殊的方式實現。 這種方式,能確保基於JSP的應用程式訪問不同的JSF實現。大多數 IDEs 支援JSP,在極大程度上, JSF和JSP一起使用就象是使用JSF定製標記庫。 然而,有一些你應該明白的細微差別, 例如象使用JSP includes。
Using JSP includes
JSP的關鍵特性之一是它能將多個JSP頁面的內容包含到一個單一的JSP頁面。 常使用這個特性執行一些有趣的任務如包含一個標題或頁尾。 JSP 支援兩種類型的 includes: 動態和靜態。動態包含 (用<jsp:include> 標記或 JSTL <c:import> 標記執行) 在運行時訪問資源。在這種情形下, 控制轉到被包含的JSP頁,來自被包含頁面的響應與包含頁面的響應結合在一起。 當改變動態包含頁的內容時,能自動顯示新的變化,靜態包含在編譯成java代碼時已與包含頁面的內容結合。本質上,這些被包含頁面的內容被拷貝到了包含頁。改變被包含頁的內容一般不會自動顯示新的變化,因為他們已經有了他們先前內容的拷貝。 只有重新編譯新的內容才有正確的顯示。 (JSP 2.0的 implicit includes, 能通過web.xml配置, 它的處理方式象靜態包含) JSF使用兩種類型的JSP includes. 對於動態includes, 有兩個必要條件:
被包含的頁面必須用JSF核心標記 <f:subview> 封閉。 <f:subview>標記可以用在被包含頁面的內部。也可以用於封閉 include 語句。如下所示(這裡是站長加的代碼)
<f:view>
...
<f:subview id="header">
<c:import url="header.jsp" />
</f:subview>
...
</f:view>
在被包含的JSP頁面內的所有模板文本和非JSF 標記應該用 JSF核心標記 <f:verbatim>封閉;
所以,如果在一個JSP頁面內我們有下列代碼:
<f:view>
...
<jsp:include page="foo.jsp"/>
...
</f:view>
foo.jsp 應該象下面這樣:
<f:subview>
<h:outputText value="heyah!"/>
...
<f:verbatim>
<b>Template text.</b>
<customtag:dothis/>
</f:verbatim>
</f:subview>
象你看到那樣,整個被包含的頁面封閉在一個 <f:subview> 標記內,所有的非JSF標記和模板文本被標記<f:verbatim>封閉。 作為選擇,我們可以把<f:subview> 標記放入初始頁封閉<jsp:include> 。 使用靜態include非常簡單,沒有什麼限制—你甚至不需要使用<f:subview> 標記。在最後的例子中,我們示範一個自訂標籤<customtag:dothis>, 它執行一些隨機的任務,這強調了一個重點:你能把其它的JSP自訂標籤與JSF一起使用。
和 JSF 一起使用 JSTL 和其它的 JSP 定製標記
所有談到的JSF定製標記庫都是不錯的,但是,如果我有自己的定製標記,或有第三方的標記庫應該怎麼辦?或者我要使用JSP標準標記庫 (JSTL)?它是一組能做我們剛提到的所有事情的標記庫。 在極大程度上,這些標記能與JSF標記混合使用。 Faces標記能在其它標記的內部嵌套使用,反之亦然。 一些產品,象IBM的 WebSphere Application Developer, 鼓勵這種方法。其它的如 Sun的 Java Creator Studio則選擇純的JSF標記, 另一方面,Oracle的 JDeveloper 讓你混合和配合使用,但也鼓勵使用純JSF標記。
注意: 無論何時,你將JSF 標記嵌套在非JSF定製標記內時,你必須指派一個組件標識符到JSF 標記。 因為JSTL 是標準的並且許多人熟悉它,我們將用它示範如何將它與 JSF定製標記一起使用。 (如果你想全面瞭解JSTL, 請看 Shawn Bayern寫的一本極好的書, JSTL in Action.) 讓我們從簡單的例子開始 (顯示在清單1) JSTL 標記和 JSF 標記混合和配合使用。代碼引入了兩個 JSF 標記庫和核心 JSTL 標記庫。
清單 1. JSTL 標記與 JSF 標記混合使用
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<html>
<head>
<title>JSF in Action: JSTL Example 1 - Mixing JSF with other custom tags
</title>
</head>
<body bgcolor="#FFFFFF">
<f:view>
<h1>
<h:outputText value="Example of using JSF tags with other custom tags"/>
</h1>
<p>
<b>
<c:out value="Here's the value of your web.xml (don't do this at home):"/>
</b>
<blockquote>
<f:verbatim>
<c:import url="WEB-INF/web.xml"/>
</f:verbatim>
</blockquote>
</p>
</f:view>
</body>
</html></code>
在這個例子裡, JSTL 和JSF 標記嵌套在 JSF標記 <f:view>內, <f:view>定義了JSF組件樹的開始。這個例子使用了 JSF 的h標記 (<h:outputText>) 和JSTL <c:out> 標記顯示文本。 在這個頁面內,JSTL <c:import> 標記包含系統的 web.xml 檔案 (這不是你想與其它人共用檔案的正確方法,所以,不要在一台真實的伺服器上這樣做). 因為 web.xml 是一個XML 檔案, <c:import>標記要嵌套在<f:verbatim> 內, <f:verbatim>是一個 JSF UIOutput組件並且繪製時轉義XML元素,所以能在HTML頁內正確顯示。這個例子沒有太多的內容,但它示範了在同一頁面內不同的標記能一起使用。注意我們把JSTL 標記嵌套在 JSF <f:verbatim> 標記內,一般而言,它比將 JSF 標記嵌套在其它標記內容易。事實上,任何有子組件的組件如 HtmlDataTable和 HtmlPanelGrid需要將模板文本嵌套在一個<f:verbatim> 標記內。 JSTL 標記與 JSF 標記一起使用使JSF變得更強大,這兩者都使用類似的運算式語言。 (對 JSP 2.0's 運算式也是如此l). 這允許你以一種直觀的方式在JSTL 和 JSF 標記間共用資料。 這裡舉例說明這一點,讓我們看另一個例子, 它允許使用者在 HtmlInputText 控制項中輸入一個值,然後利用這個值用 JSTL <c:forEach>標記重複輸出一個字串。 代碼在清單2列出。
清單2. JSF 、JSTL 標記和同一個 backing bean
...
<f:view>
<jsp:useBean class="org.jia.examples.TestForm" id="exampleBean" scope="session"/>
<h1>
<h:outputText value="Example of using JSF and JSTL expression languages"/>
</h1>
<h:form>
<h:outputLabel for="inputInt">
<h:outputText value="How many times do you want to repeat the Oracle's prophecy?"/>
</h:outputLabel>
<h:inputText id="inputInt" value="#{sessionScope.exampleBean.number}"/>
<h:commandButton value="Go!"/>
<p>
<c:if test="$ {sessionScope.exampleBean.number > 0}">
<c:forEach begin="0" end="$ {sessionScope.exampleBean.number - 1}" var="count">
Queen Tracey will achieve world domination.<br>
</c:forEach>
</c:if>
</p>
</h:form>
...
</f:view>
...
警告: 如果你用 JSP 或 JSTL 運算式訪問 managed beans, 你必須確保 beans 已經被建立,這是因為這些舊的運算式語言不知道JSF中的Managed Bean如何建立。這個例子中定義了一個叫exampleBean的JavaBean, 它有一個int類型的 number 屬性。使用HtmlInputText 組件基於使用者的輸入更新bean的屬性值。當使用者點擊Go! 按鈕時 (一個 HtmlCommandButton 組件), 更新number屬性的值並重新顯示頁面。 當這一切發生時, JSTL <c:forEach> 標記通過 JSTL <c:out> 標記重複顯示文本 exampleBean.number 次。當exampleBean.number的值大於0時,<c:forEach> 標記才執行,這通過JSTL <c:if>進行測試。
你不能在疊代主體的迴圈標記內部使用 JSF 組件標記,象JSTL 的 <c:forEach>標記。 推薦的方法是使用 HtmlDataTable 組件或其它的組件疊代一個資料集或集合,在這個例子中,沒有 JSF 組件 嵌套在 JSTL <c:if>標記內。 但是如果組件顯示一次然後當頁面再次顯示時被條件標記(如 <c:if>)隱藏會發生什麼呢? 第一次顯示組件時,組件被添加到視圖,第二次如果<c:if> 標記不顯示組件, JSF 將從視圖中刪除它。意思就是任何輸入控制項會丟失他們的本地值,你不能再引用這些組件,舉一個例子,看清單3, 它來自清單2的同一頁.
清單 3. 用JSTL標記條件顯示JSF 組件
...
<h:form>
<h:outputText value="If you entered a number greater than 10,
two input controls will display below."/>
<p>
<c:if test="$ {sessionScope.exampleBean.number > 10}">
<h:outputLabel id="inputStringLabel"for="inputString">
<h:outputText id="outputStringLabel" value="Enter in your string.
JSF will remember the value unless this control is hidden."/>
</h:outputLabel>
<h:inputText id="inputString"/>
<h:commandButton value="Go!"/>
</c:if>
</p>
</h:form>
...
如果exampleBean.number的值比10大, JSTL <c:if> 標記將會執行它的主體。如果主體被執行,那麼所有嵌套其中的組件將增加到視圖並顯示。 如果沒有執行,組件將會刪除 (如果先前已增加). 這顯示在圖1. 如果你用JSTL 條件標記(或其它的定製標記)控制組件的可見度。如果它們沒有顯示,組件將被從視圖中移走。這意思是說組件會忘記他們的本地值。
Figure 1. The JSTL <c:if> tag will execute its body if the value of exampleBean.number is greater than 10. Click on thumbnail to view full-sized image.
圖 2 顯示了清單2和清單3的輸出。頂部輸入欄位(一個 HtmlInputText 組件)的值關聯到了 exampleBean.number的屬性, JSTL <c:forEach>使用它顯示一個字串exampleBean.number次,在頁面底部,如果exampleBean.number的值大於10, JSTL <c:if>標記顯示一個帶有JSF組件的表格。 否由,組件將不會顯示, 並從視圖中移出 (輸入控制項將丟失它的值).
Figure 2. The output of the JSP page shown in Listings 2 and 3. Click on thumbnail to view full-sized image.
你能達到清單3中代碼同樣的效果,通過把組件放入一個 HtmlPanelGroup 並設定它的 rendered 屬性等於同樣的運算式。 HtmlPanelGroup 可以作為多個組件的容器,下面是例子:
<h:panelGroup rendered="#{sessionScope.exampleBean.number > 10}">
<h:outputLabel id="inputStringLabel2" for="inputString">
<h:outputText id="outputStringLabel2" value="Enter in your string. JSF
will remember the value."/>
</h:outputLabel>
<h:inputText id="inputString2"/>
<h:commandButton value="Go!"/>
</h:panelGroup>
如果exampleBean.number 大於10,這個面板變得可見。在這種情況下,如果組件沒有顯示也不會刪除。這是一個使用純JSF而不用JSTL的好例子。
Tip: 即使你使用JSTL提供一個有很多功能的定製標記,如果你從零開始開發 (或重做), 你應當首先看看用標準的JSF組件能否實現你想要的行為。使用好的組件和設計良好的backing beans, 在頁面中能消除很多JSTL標記。使用標準的JSF,你能顯示或隱藏面板和做各種各樣的有強大功能的事情。 下面是幾個可以讓JSF標記和JSTL國際化、格式化標記協同工作的條件:
不推薦使用<fmt:parseDate>和<fmt:parseNumber> . 你應該使用一個帶有的日期或數字轉換器的HtmlInputText 組件。
不應該使用<fmt:requestEncoding>標記指定頁面的字元編碼,通常, JSF自動處理。如果你要強迫用特殊的字元編碼, 你應該用JSP頁面指示:
<%page contentType="[contenttype];[charset]"%>.
也不應該使用<fmt:setLocale> 標記,因為它不知道, 它可能引起你的JSTL標記使用一個地區而你的JSF組件使用另一個, 代替這種災難發生的處方是,你應該使用JSF的國際化特性。為了控制特殊頁面的場所,使用UIViewRoot 組件的locale 屬性。 JSF的國際化特效能在JSF和JSTL兩者中工作。
結合JSF和JSTL能變得十分強大。 在這裡我們示範了你的定製標記或從第三方擷取的標記與JSF、JSTL一起工作。 通常,當可能時,你應該儘可能地使用JSF 標記。