級(jí)別: 初級(jí)
Mark A. Kolb (mak@taglib.com), 軟件工程師
2003 年 6 月 21 日
顧名思義,JSP 標(biāo)準(zhǔn)標(biāo)記庫(JSP Standard Tag Library,JSTL) core 庫為一些基本功能(如,管理限定了作用域的變量和與 URL 交互等)和基本操作(如,迭代和條件化)提供了定制標(biāo)記。這些標(biāo)記不僅可以由頁面設(shè)計(jì)人員直接利用,而且還為與其它 JSTL 庫相結(jié)合從而提供更復(fù)雜的表示邏輯奠定了基礎(chǔ)。Mark Kolb 在本文中繼續(xù)對(duì) JSTL 和 core 庫進(jìn)行探討,研究用標(biāo)記來協(xié)助流控制和 URL 管理。
通過閱讀本系列的 第一篇文章,您對(duì) JSTL 有了初步的了解。我們描述了使用其 表達(dá)式語言(EL)來訪問數(shù)據(jù)和操作數(shù)據(jù)。正如您所了解的那樣,EL 用來為 JSTL 定制標(biāo)記的屬性賦予動(dòng)態(tài)值,因此,它所起的作用與 JSP 表達(dá)式一樣,為內(nèi)置操作及其它定制標(biāo)記庫指定請(qǐng)求時(shí)的屬性值。
為了演示 EL 的用法,我們介紹了 core 庫中的三個(gè)標(biāo)記: <c:set> 、 <c:remove> 和 <c:out> 。 <c:set> 和 <c:remove> 用于管理限定了作用域的變量;而 <c:out> 用于顯示數(shù)據(jù),尤其是顯示用 EL 計(jì)算出的值。在此基礎(chǔ)上,接下來本文把注意力集中在 core 庫的其余標(biāo)記上,這些標(biāo)記可以大致歸為兩大類別:流控制和 URL 管理。
示例應(yīng)用程序
為了演示 JSTL 標(biāo)記,我們將使用來自一個(gè)工作應(yīng)用程序的示例,本系列中余下的文章都將使用此應(yīng)用程序。由于基于 Java 的 Weblog 日漸流行及為人們所熟悉,因此我們將出于此目的使用一個(gè)簡(jiǎn)單的基于 Java 的 Weblog;參閱 參考資料以下載該應(yīng)用程序的 JSP 頁面和源代碼。Weblog(也稱為 blog)是一種基于 Web 的簡(jiǎn)短注釋的日志,這些注釋是有關(guān) Weblog 的作者所感興趣的主題,通常帶有與 Web 上其它地方的相關(guān)文章及討論的鏈接。圖 1 中顯示了該應(yīng)用程序正在運(yùn)行時(shí)的抓屏。
圖 1. Weblog 應(yīng)用程序
雖然完整的實(shí)現(xiàn)需要二十四個(gè) Java 類,但在表示層中卻只涉及 Weblog 應(yīng)用程序中的兩個(gè)類, Entry 和 UserBean 。這樣,對(duì)于理解 JSTL 示例而言,只有這兩個(gè)類比較重要。圖 2 顯示了 Entry 和 UserBean 的類圖。
圖 2. Weblog 應(yīng)用程序的類圖
Entry 類表示 Weblog 中一個(gè)標(biāo)有日期的項(xiàng)。其 id 屬性用于在數(shù)據(jù)庫中存儲(chǔ)及檢索該項(xiàng),而 title 和 text 屬性則表示該項(xiàng)的實(shí)際內(nèi)容。 created 和 lastModified 屬性引用了 Java 語言中 Date 類的兩個(gè)實(shí)例,分別用來表示最初創(chuàng)建該項(xiàng)的時(shí)間和最后編輯該項(xiàng)的時(shí)間。 author 屬性引用了標(biāo)識(shí)該項(xiàng)的創(chuàng)建者的 UserBean 實(shí)例。
UserBean 類存儲(chǔ)了有關(guān)應(yīng)用程序的已認(rèn)證用戶的信息,如用戶名、全名和電子郵件地址。該類還包含一個(gè)用于與相關(guān)數(shù)據(jù)庫進(jìn)行交互的 id 屬性。其最后一個(gè)屬性 roles 引用一列 String 值,這列值標(biāo)識(shí)與相應(yīng)用戶相關(guān)的、特定于應(yīng)用程序的角色。對(duì)于 Weblog 應(yīng)用程序,相關(guān)的角色是“User”(所有應(yīng)用程序用戶常用的缺省角色)和“Author”(該角色指定可以創(chuàng)建和編輯 Weblog 項(xiàng)的用戶)。
流控制
由于可以用 EL 替代 JSP 表達(dá)式來指定動(dòng)態(tài)屬性值,因此頁面創(chuàng)作人員無需使用腳本編制元素。因?yàn)槟_本編制元素可能是引起 JSP 頁面中維護(hù)問題的主要原因,所以 JSTL 的主要優(yōu)點(diǎn)就在于提供了這樣簡(jiǎn)單(且標(biāo)準(zhǔn))的替代方法。
EL 從 JSP 容器檢索數(shù)據(jù),遍歷對(duì)象層次結(jié)構(gòu),然后對(duì)結(jié)果執(zhí)行簡(jiǎn)單的操作。不過,除了訪問和操作數(shù)據(jù)之外,JSP 腳本編制元素的另一個(gè)常見用法是流控制。尤其是,頁面創(chuàng)作人員常借助 scriptlet 來實(shí)現(xiàn)迭代或條件內(nèi)容。然而,因?yàn)檫@樣的操作超出了 EL 的能力,所以 core 庫提供了幾個(gè)定制操作來管理流控制,其形式有 迭代、 條件化和 異常處理。
迭代
在 Web 應(yīng)用程序環(huán)境中,迭代主要用于訪存和顯示數(shù)據(jù)集,通常是以列表或表中的一系列行的形式顯示。實(shí)現(xiàn)迭代內(nèi)容的主要 JSTL 操作是 <c:forEach> 定制標(biāo)記。該標(biāo)記支持兩種不同樣式的迭代:整數(shù)范圍上的迭代(類似 Java 語言的 for 語句)和集合上的迭代(類似 Java 語言的 Iterator 和 Enumeration 類)。
進(jìn)行整數(shù)范圍迭代用到了清單 1 中所示的 <c:forEach> 標(biāo)記的語法。 begin 和 end 屬性要么是靜態(tài)整數(shù)值,要么是可以得出整數(shù)值的表達(dá)式。它們分別指定迭代索引的初始值以及迭代索引的終止值。當(dāng)使用 <c:forEach> 在整數(shù)范圍內(nèi)進(jìn)行迭代時(shí),這兩個(gè)屬性是必需的,而其它所有屬性都是可選的。
清單 1. 通過 <c:forEach> 操作進(jìn)行數(shù)字迭代的語法
<c:forEach var="
name" varStatus="
name"
begin="
expression" end="
expression" step="
expression">
body content
</c:forEach>
|
當(dāng)出現(xiàn) step 時(shí),它也必須是整數(shù)值。它指定每次迭代后索引的增量。這樣,迭代索引從 begin 屬性的值開始,以 step 屬性的值為增量進(jìn)行遞增,在迭代索引超過 end 屬性的值時(shí)停止迭代。注:如果省略了 step 屬性,那么步長(zhǎng)缺省為 1。
如果指定了 var 屬性,那么將會(huì)創(chuàng)建一個(gè)帶有指定名稱的并限定了作用域的變量,并將每次迭代的當(dāng)前索引值賦給該變量。這一限定了作用域的變量具有嵌套式可視性 ― 只可以在 <c:forEach> 標(biāo)記體內(nèi)對(duì)其進(jìn)行訪問。(我們很快將討論可選屬性 varStatus 的用法。)清單 2 顯示了對(duì)一組固定整數(shù)值進(jìn)行迭代的 <c:forEach> 操作示例。
清單 2. 使用 <c:forEach> 標(biāo)記來生成表列數(shù)據(jù),這些數(shù)據(jù)對(duì)應(yīng)于某一范圍內(nèi)的數(shù)值
<table>
<tr><th>Value</th>
<th>Square</th></tr>
<c:forEach var="x" begin="0" end="10" step="2">
<tr><td><c:out value="${x}"/></td>
<td><c:out value="${x * x}"/></td></tr>
</c:forEach>
</table>
|
如圖 3 中所示,上面的示例代碼生成了一張表,顯示前五個(gè)偶數(shù)及其平方。這是通過將 begin 和 step 屬性值指定為 2,而將 end 屬性值指定為 10 實(shí)現(xiàn)的。此外,用 var 屬性創(chuàng)建用于存儲(chǔ)索引值的限定了作用域的變量, <c:forEach> 標(biāo)記體內(nèi)引用了該變量。尤其是,使用了一對(duì) <c:out> 操作來顯示索引及其平方,其中索引的平方是使用一個(gè)簡(jiǎn)單的表達(dá)式計(jì)算得來的。
圖 3. 清單 2 的輸出
在對(duì)集合的成員進(jìn)行迭代時(shí),用到了 <c:forEach> 標(biāo)記的另一個(gè)屬性: items 屬性,清單 3 中顯示了該屬性。當(dāng)使用這種形式的 <c:forEach> 標(biāo)記時(shí), items 屬性是唯一必需的屬性。 items 屬性的值應(yīng)該是一個(gè)集合,對(duì)該集合的成員進(jìn)行迭代,通常使用 EL 表達(dá)式指定值。如果變量名稱是通過 <c:forEach> 標(biāo)記的 item 屬性指定的,那么對(duì)于每次迭代該已命名變量都將被綁定到集合后續(xù)元素上。
清單 3. 通過 <c:forEach> 操作對(duì)集合進(jìn)行迭代的語法
<c:forEach var="
name" items="
expression" varStatus="
name"
begin="
expression" end="
expression" step="
expression">
body content
</c:forEach>
|
<c:forEach> 標(biāo)記支持 Java 平臺(tái)所提供的所有標(biāo)準(zhǔn)集合類型。此外,您可以使用該操作來迭代數(shù)組(包括基本類型數(shù)組)中的元素。表 1 列出了 items 屬性所支持的所有值。正如表的最后一行所指出的那樣,JSTL 定義了它自己的接口 javax.servlet.jsp.jstl.sql.Result ,用來迭代 SQL 查詢的結(jié)果。(我們將在本系列后面的文章中詳細(xì)討論這一功能。)
表 1. <c:forEach> 標(biāo)記的 items 屬性所支持的集合
items 的值 |
所產(chǎn)生的 item 值 |
java.util.Collection |
調(diào)用 iterator() 所獲得的元素 |
java.util.Map |
java.util.Map.Entry 的實(shí)例 |
java.util.Iterator |
迭代器元素 |
java.util.Enumeration |
枚舉元素 |
Object 實(shí)例數(shù)組 |
數(shù)組元素 |
基本類型值數(shù)組 |
經(jīng)過包裝的數(shù)組元素 |
用逗號(hào)定界的 String |
子字符串 |
javax.servlet.jsp.jstl.sql.Result |
SQL 查詢所獲得的行 |
可以使用 begin 、 end 和 step 屬性來限定在迭代中包含集合中哪些元素。和通過 <c:forEach> 進(jìn)行數(shù)字迭代的情形一樣,在迭代集合中的元素時(shí)同樣要維護(hù)一個(gè)迭代索引。 <c:forEach> 標(biāo)記實(shí)際上只處理那些與索引值相對(duì)應(yīng)的元素,這些索引值與指定的 begin 、 end 和 step 值相匹配。
清單 4 顯示了用來迭代集合的 <c:forEach> 標(biāo)記。對(duì)于該 JSP 代碼段, entryList 這一限定了作用域的變量被設(shè)置成了 Entry 對(duì)象列表(確切的說, ArrayList )。 <c:forEach> 標(biāo)記依次處理列表中的每個(gè)元素,將其賦給一個(gè)限定了作用域的變量 blogEntry ,然后生成兩個(gè)表行 ― 一個(gè)用于 Weblog 項(xiàng)的 title ,另一個(gè)則用于該項(xiàng) text 。這些特性是通過一對(duì)帶有相應(yīng) EL 表達(dá)式的 <c:out> 操作從 blogEntry 變量檢索得到的。注:由于 Weblog 項(xiàng)的標(biāo)題和文本都可能包含 HTML 標(biāo)記,因此這兩個(gè) <c:out> 標(biāo)記的 escapeXml 屬性都被設(shè)置成了 false。圖 4 顯示了結(jié)果。
清單 4. 使用 <c:forEach> 標(biāo)記顯示表示給定日期的 Weblog 項(xiàng)
<table>
<c:forEach items="${entryList}" var="blogEntry">
<tr><td align="left" class="blogTitle">
<c:out value="${blogEntry.title}" escapeXml="false"/>
</td></tr>
<tr><td align="left" class="blogText">
<c:out value="${blogEntry.text}" escapeXml="false"/>
</td></tr>
</c:forEach>
</table>
|
圖 4. 清單 4 的輸出
不論是對(duì)整數(shù)還是對(duì)集合進(jìn)行迭代, <c:forEach> 剩余的屬性 varStatus 所起的作用相同。和 var 屬性一樣, varStatus 用于創(chuàng)建限定了作用域的變量。不過,由 varStatus 屬性命名的變量并不存儲(chǔ)當(dāng)前索引值或當(dāng)前元素,而是賦予 javax.servlet.jsp.jstl.core.LoopTagStatus 類的實(shí)例。該類定義了一組特性,它們描述了迭代的當(dāng)前狀態(tài),表 2 中列出了這些特性。
表 2. LoopTagStatus 對(duì)象的特性
特性 |
Getter |
描述 |
current |
getCurrent() |
當(dāng)前這次迭代的(集合中的)項(xiàng) |
index |
getIndex() |
當(dāng)前這次迭代從 0 開始的迭代索引 |
count |
getCount() |
當(dāng)前這次迭代從 1 開始的迭代計(jì)數(shù) |
first |
isFirst() |
用來表明當(dāng)前這輪迭代是否為第一次迭代的標(biāo)志 |
last |
isLast() |
用來表明當(dāng)前這輪迭代是否為最后一次迭代的標(biāo)志 |
begin |
getBegin() |
begin 屬性值 |
end |
getEnd() |
end 屬性值 |
step |
getStep() |
step 屬性值 |
清單 5 顯示了關(guān)于如何使用 varStatus 屬性的一個(gè)示例。這個(gè)示例修改了清單 4 中的代碼,將 Weblog 項(xiàng)的編號(hào)添加到顯示 Weblog 標(biāo)題(title)的表行。它是通過為 varStatus 屬性指定一個(gè)值,然后訪問所生成的限定了作用域的變量的 count 特性來實(shí)現(xiàn)這一點(diǎn)的。結(jié)果顯示在圖 5 中。
清單 5. 使用 varStatus 屬性來顯示 Weblog 項(xiàng)的數(shù)目
<table>
<c:forEach items=
"${entryList}" var="blogEntry" varStatus="status">
<tr><td align="left" class="blogTitle">
<c:out value="${status.count}"/>.
<c:out value="${blogEntry.title}" escapeXml="false"/>
</td></tr>
<tr><td align="left" class="blogText">
<c:out value="${blogEntry.text}" escapeXml="false"/>
</td></tr>
</c:forEach>
</table>
|
圖 5. 清單 5 的輸出
除 <c:forEach> 以外, core 庫還提供了另一個(gè)迭代標(biāo)記: <c:forTokens> 。JSTL 的這個(gè)定制操作與 Java 語言的 StringTokenizer 類的作用相似。清單 6 中顯示的 <c:forTokens> 標(biāo)記除了比 <c:forEach> 的面向集合版本多一個(gè)屬性之外,其它屬性都相同。對(duì)于 <c:forTokens> 而言,通過 items 屬性指定要標(biāo)記化的字符串,而通過 delims 屬性提供用于生成標(biāo)記的一組定界符。和 <c:forEach> 的情形一樣,可以使用 begin 、 end 和 step 屬性將要處理的標(biāo)記限定為那些與相應(yīng)索引值相匹配的標(biāo)記。
清單 6. 使用 <c:forTokens> 操作來迭代字符串標(biāo)記的語法
<c:forTokens var="
name" items="
expression"
delims="
expression" varStatus="
name"
begin="
expression" end="
expression" step="
expression">
body content
</c:forTokens>
|
條件化
對(duì)于包含動(dòng)態(tài)內(nèi)容的 Web 頁面,您可能希望不同類別的用戶看到不同形式的內(nèi)容。例如,在我們的 Weblog 中,訪問者應(yīng)該能夠閱讀各項(xiàng),也許還應(yīng)該能夠提交反饋,但只有經(jīng)過授權(quán)的用戶才能公布新項(xiàng),或編輯已有內(nèi)容。
在同一個(gè) JSP 頁面內(nèi)實(shí)現(xiàn)這樣的功能,然后使用條件邏輯來根據(jù)每條請(qǐng)求控制所顯示的內(nèi)容,這樣做常常能夠改善實(shí)用性和軟件維護(hù)。 core 庫提供了兩個(gè)不同的條件化標(biāo)記 ― <c:if> 和 <c:choose> ― 來實(shí)現(xiàn)這些功能。
<c:if> 是這兩個(gè)操作中較簡(jiǎn)單的一個(gè),它簡(jiǎn)單地對(duì)單個(gè)測(cè)試表達(dá)式進(jìn)行求值,接下來,僅當(dāng)對(duì)表達(dá)式求出的值為 true 時(shí),它才處理標(biāo)記的主體內(nèi)容。如果求出的值不為 true ,就忽略該標(biāo)記的主體內(nèi)容。如清單 7 所示, <c:if> 可以通過其 var 和 scope 屬性(它們所起的作用和在 <c:set> 中所起的作用一樣)選擇將測(cè)試結(jié)果賦給限定了作用域的變量。當(dāng)測(cè)試代價(jià)非常高昂時(shí),這種能力尤為有用:可以將結(jié)果高速緩存在限定了作用域的變量中,然后在隨后對(duì) <c:if> 或其它 JSTL 標(biāo)記的調(diào)用中檢索該結(jié)果。
清單 7. <c:if> 條件操作的語法
<c:if test="
expression" var="
name" scope="
scope">
body content
</c:if>
|
清單 8 顯示了與 <c:forEach> 標(biāo)記的 LoopTagStatus 對(duì)象的 first 特性一起使用的 <c:if> 。如圖 6 中所示,在這種情況下,只在 Weblog 項(xiàng)的第一項(xiàng)上顯示這組項(xiàng)的創(chuàng)建日期,而不在任何其它項(xiàng)前面重復(fù)該日期。
清單 8. 使用 <c:if> 來為 Weblog 項(xiàng)顯示日期
<table>
<c:forEach items=
"${entryList}" var="blogEntry" varStatus="status">
<c:if test="${status.first}">
<tr><td align="left" class="blogDate">
<c:out value="${blogEntry.created}"/>
</td></tr>
</c:if>
<tr><td align="left" class="blogTitle">
<c:out value="${blogEntry.title}" escapeXml="false"/>
</td></tr>
<tr><td align="left" class="blogText">
<c:out value="${blogEntry.text}" escapeXml="false"/>
</td></tr>
</c:forEach>
</table>
|
圖 6. 清單 8 的輸出
如清單 8 所示, <c:if> 標(biāo)記為條件化內(nèi)容的一些簡(jiǎn)單情形提供了一種非常簡(jiǎn)潔的表示法。對(duì)于需要進(jìn)行互斥測(cè)試來確定應(yīng)該顯示什么內(nèi)容的情況下,JSTL core 庫還提供了 <c:choose> 操作。清單 9 中顯示了 <c:choose> 的語法。
清單 9. <c:choose> 操作的語法
<c:choose>
<c:when test="
expression">
body content
</c:when>
...
<c:otherwise>
body content
</c:otherwise>
</c:choose>
|
每個(gè)要測(cè)試的條件都由相應(yīng)的 <c:when> 標(biāo)記來表示,至少要有一個(gè) <c:when> 標(biāo)記。只會(huì)處理第一個(gè)其 test 值為 true 的 <c:when> 標(biāo)記體內(nèi)的內(nèi)容。如果沒有一個(gè) <c:when> 測(cè)試返回 true ,那么會(huì)處理 <c:otherwise> 標(biāo)記的主體內(nèi)容。注:盡管如此, <c:otherwise> 標(biāo)記卻是可選的; <c:choose> 標(biāo)記至多可有一個(gè)嵌套的 <c:otherwise> 標(biāo)記。如果所有 <c:when> 測(cè)試都為 false ,而且又沒有給出 <c:otherwise> 操作,那么不會(huì)處理任何 <c:choose> 標(biāo)記的主體內(nèi)容。
清單 10 顯示了運(yùn)用 <c:choose> 標(biāo)記的示例。在這里,檢索請(qǐng)求對(duì)象而獲得協(xié)議信息(通過 EL 的 pageContext 隱式對(duì)象),并用簡(jiǎn)單的字符串比較對(duì)協(xié)議信息進(jìn)行測(cè)試。根據(jù)這些測(cè)試的結(jié)果,會(huì)顯示相應(yīng)的文本消息。
清單 10. 使用 <c:choose> 進(jìn)行內(nèi)容條件化
<c:choose>
<c:when test="${pageContext.request.scheme eq 'http'}">
This is an insecure Web session.
</c:when>
<c:when test="${pageContext.request.scheme eq 'https'}">
This is a secure Web session.
</c:when>
<c:otherwise>
You are using an unrecognized Web protocol. How did this happen?!
</c:otherwise>
</c:choose>
|
異常處理
最后一個(gè)流控制標(biāo)記是 <c:catch> ,它允許在 JSP 頁面內(nèi)進(jìn)行初級(jí)的異常處理。更確切地說,在該標(biāo)記的主體內(nèi)容中產(chǎn)生的任何異常都會(huì)被捕獲并被忽略(即,不會(huì)調(diào)用標(biāo)準(zhǔn)的 JSP 錯(cuò)誤處理機(jī)制)。然而,如果產(chǎn)生了一個(gè)異常并且已經(jīng)指定了 <c:catch> 標(biāo)記的可選屬性 var ,那么會(huì)將異常賦給(具有頁面作用域的)指定的變量,這使得能夠在頁面自身內(nèi)部進(jìn)行定制錯(cuò)誤處理。清單 11 顯示了 <c:catch> 的語法(稍后在 清單 18中給出一個(gè)示例)。
清單 11. <c:catch> 操作的語法
<c:catch var="
name">
body content
</c:catch>
|
URL 操作
JSTL core 庫中的其余標(biāo)記主要是關(guān)于 URL。這些標(biāo)記中的第一個(gè)被適當(dāng)?shù)孛麨?<c:url> 標(biāo)記,用于生成 URL。尤其是, <c:url> 提供了三個(gè)功能元素,它們?cè)跒?J2EE Web 應(yīng)用程序構(gòu)造 URL 時(shí)特別有用:
- 在前面附加當(dāng)前 servlet 上下文的名稱
- 為會(huì)話管理重寫 URL
- 請(qǐng)求參數(shù)名稱和值的 URL 編碼
清單 12 顯示了 <c:url> 標(biāo)記的語法。 value 屬性用來指定基本 URL,然后在必要時(shí)標(biāo)記對(duì)其進(jìn)行轉(zhuǎn)換。如果這個(gè)基本 URL 以一個(gè)斜杠開始,那么會(huì)在它前面加上 servlet 的上下文名稱。可以使用 context 屬性提供顯式的上下文名稱。如果省略該屬性,那么就使用當(dāng)前 servlet 上下文的名稱。這一點(diǎn)特別有用,因?yàn)?servlet 上下文名稱是在部署期間而不是開發(fā)期間決定的。(如果這個(gè)基本 URL 不是以斜杠開始的,那么就認(rèn)為它是一個(gè)相對(duì) URL,這時(shí)就不必添加上下文名稱。)
清單 12. <c:url> 操作的語法
<c:url value="
expression" context="
expression"
var="
name" scope="
scope">
<c:param name="
expression" value="
expression"/>
...
</c:url>
|
URL 重寫是由 <c:url> 操作自動(dòng)執(zhí)行的。如果 JSP 容器檢測(cè)到一個(gè)存儲(chǔ)用戶當(dāng)前會(huì)話標(biāo)識(shí)的 cookie,那么就不必進(jìn)行重寫。但是,如果不存在這樣的 cookie,那么 <c:url> 生成的所有 URL 都會(huì)被重寫以編碼會(huì)話標(biāo)識(shí)。注:如果在隨后的請(qǐng)求中存在適當(dāng)?shù)?cookie,那么 <c:url> 將停止重寫 URL 以包含該標(biāo)識(shí)。
如果為 var 屬性提供了一個(gè)值(還可以同時(shí)為 scope 屬性提供一個(gè)相應(yīng)的值,這是可選的),那么將生成的 URL 賦值給這個(gè)限定了作用域的指定變量。否則,將使用當(dāng)前的 JspWriter 輸出生成的 URL。這種直接輸出其結(jié)果的能力允許 <c:url> 標(biāo)記作為值出現(xiàn),例如,作為 HTML <a> 標(biāo)記的 href 屬性的值,如清單 13 中所示。
清單 13. 生成 URL 作為 HTML 標(biāo)記的屬性值
<a href="<c:url value='/content/sitemap.jsp'/>">View sitemap</a>
|
最后,如果通過嵌套 <c:param> 標(biāo)記指定了任何請(qǐng)求參數(shù),那么將會(huì)使用 HTTP GET 請(qǐng)求的標(biāo)準(zhǔn)表示法將它們的名稱和值添加到生成的 URL 后面。此外,還進(jìn)行 URL 編碼:為了生成有效的 URL,將對(duì)這些參數(shù)的名稱或值中出現(xiàn)的任何字符適當(dāng)?shù)剡M(jìn)行轉(zhuǎn)換。清單 14 演示了 <c:url> 的這種行為。
清單 14. 生成帶請(qǐng)求參數(shù)的 URL
<c:url value="/content/search.jsp">
<c:param name="keyword" value="${searchTerm}"/>
<c:param name="month" value="02/2003"/>
</c:url>
|
清單 14 中的 JSP 代碼被部署到一個(gè)名為 blog 的 servlet 上下文,限定了作用域的變量 searchTerm 的值被設(shè)置為 "core library" 。如果檢測(cè)到了會(huì)話 cookie,那么清單 14 生成的 URL 將類似于清單 15 中的 URL。注:在前面添加上下文名稱,而在后面附加請(qǐng)求參數(shù)。此外, keyword 參數(shù)值中的空格和 month 參數(shù)值中的斜杠都被按照 HTTP GET 參數(shù)的需要進(jìn)行了編碼(確切地說,空格被轉(zhuǎn)換成了 + ,而斜杠被轉(zhuǎn)換成了 %2F 序列)。
清單 15. 有會(huì)話 cookie 時(shí)生成的 URL
/blog/content/search.jsp?keyword=foo+bar&month=02%2F2003
|
當(dāng)沒有會(huì)話 cookie 時(shí),生成的結(jié)果如清單 16 中所示。同樣,servlet 上下文被添加到了前面,而 URL 編碼的請(qǐng)求參數(shù)被附加到了后面。不過,除此以外還重寫了基本 URL 以包含指定的會(huì)話標(biāo)識(shí)。當(dāng)瀏覽器發(fā)送用這種方式重寫的 URL 請(qǐng)求時(shí),JSP 容器自動(dòng)抽取會(huì)話標(biāo)識(shí),并將請(qǐng)求與相應(yīng)的會(huì)話進(jìn)行關(guān)聯(lián)。這樣,需要會(huì)話管理的 J2EE 應(yīng)用程序就無需依賴由應(yīng)用程序用戶啟用的 cookie 了。
清單 16. 沒有會(huì)話 cookie 時(shí)生成的 URL
/blog/content/search.jsp;jsessionid=233379C7CD2D0ED2E9F3963906DB4290
?keyword=foo+bar&month=02%2F2003
|
導(dǎo)入內(nèi)容
JSP 有兩種內(nèi)置機(jī)制可以將來自不同 URL 的內(nèi)容合并到一個(gè) JSP 頁面: include 偽指令和 <jsp:include> 操作。不過,不管是哪種機(jī)制,要包含的內(nèi)容都必須屬于與頁面本身相同的 Web 應(yīng)用程序(或 servlet 上下文)。兩個(gè)標(biāo)記之間的主要區(qū)別在于: include 偽指令在頁面編譯期間合并被包含的內(nèi)容,而 <jsp:include> 操作卻在請(qǐng)求處理 JSP 頁面時(shí)進(jìn)行。
從本質(zhì)上講, core 庫的 <c:import> 操作是更通用、功能更強(qiáng)大的 <jsp:include> 版本(好像是 <jsp:include> “服用了興奮劑”的)。和 <jsp:include> 一樣, <c:import> 也是一種請(qǐng)求時(shí)操作,它的基本任務(wù)就是將其它一些 Web 資源的內(nèi)容插入 JSP 頁面中。如清單 17 中所示,它的語法非常類似于 <c:url> 的語法。
清單 17. <c:import> 操作的語法
<c:import url="
expression" context="
expression"
charEncoding="
expression" var="
name" scope="
scope">
<c:param name="
expression" value="
expression"/>
...
</c:import>
|
通過 url 屬性指定將要導(dǎo)入內(nèi)容的 URL,這個(gè)屬性是 <c:import> 的唯一一個(gè)必選屬性。這里允許使用相對(duì) URL,并且根據(jù)當(dāng)前頁面的 URL 來解析這個(gè)相對(duì) URL。但是,如果 url 屬性的值以斜杠開始,那么它就被解釋成本地 JSP 容器內(nèi)的絕對(duì) URL。如果沒有為 context 屬性指定值,那么就認(rèn)為這樣的絕對(duì) URL 引用當(dāng)前 servlet 上下文內(nèi)的資源。如果通過 context 屬性顯式地指定了上下文,那么就根據(jù)指定的 servlet 上下文解析絕對(duì)(本地)URL。
但 <c:import> 操作并不僅僅限于訪問本地內(nèi)容。也可以將包含協(xié)議和主機(jī)名的完整 URI 指定為 url 屬性的值。實(shí)際上,協(xié)議甚至不僅局限于 HTTP。 <c:import> 的 url 屬性值可以使用 java.net.URL 類所支持的任何協(xié)議。清單 18 中顯示了這種能力。
其中, <c:import> 操作用來包含通過 FTP 協(xié)議訪問的文檔內(nèi)容。此外,還使用了 <c:catch> 操作,以便在本地處理 FTP 文件傳送期間可能發(fā)生的任何錯(cuò)誤。錯(cuò)誤處理是這樣實(shí)現(xiàn)的:使用 <c:catch> 的 var 屬性為異常指定一個(gè)限定了作用域的變量,然后使用 <c:if> 檢查其值。如果產(chǎn)生了異常,那么就會(huì)對(duì)那個(gè)限定了作用域的變量進(jìn)行賦值:如清單 18 中的 EL 表達(dá)式所顯示的那樣,該變量的值將 不會(huì)為空。由于 FTP 文檔的檢索將會(huì)失敗,因此會(huì)顯示有關(guān)這種情況的錯(cuò)誤消息。
清單 18. 將 <c:import> 與 <c:catch> 相結(jié)合的示例
<c:catch var="exception">
<c:import url="ftp://ftp.example.com/package/README"/>
</c:catch>
<c:if test="${not empty exception}">
Sorry, the remote content is not currently available.
</c:if>
|
<c:import> 操作的最后兩個(gè)(可選的)屬性是 var 和 scope 。 var 屬性會(huì)導(dǎo)致從指定 URL 獲取的內(nèi)容(作為 String 值)被存儲(chǔ)在一個(gè)限定了作用域的變量中,而不是包含在當(dāng)前 JSP 頁面中。 scope 屬性控制該變量的作用域,缺省情況下是頁面作用域。如同我們?cè)诮窈蟮奈恼轮袑⒁吹降哪菢樱琂STL xml 庫中的標(biāo)記利用了 <c:import> 這種能力,即將整個(gè)文檔存儲(chǔ)在一個(gè)限定了作用域的變量中。
還要注意的是,可以使用(可選的)嵌套的 <c:param> 標(biāo)記來為正在導(dǎo)入的 URL 指定請(qǐng)求參數(shù)。與在 <c:url> 中嵌套 <c:param> 標(biāo)記一樣,必要時(shí)也要對(duì)參數(shù)名稱和參數(shù)值進(jìn)行 URL 編碼。
請(qǐng)求重定向
最后一個(gè) core 庫標(biāo)記是 <c:redirect> 。該操作用于向用戶的瀏覽器發(fā)送 HTTP 重定向響應(yīng),它是 JSTL 中與 javax.servlet.http.HttpServletResponse 的 sendRedirect() 方法功能相當(dāng)?shù)臉?biāo)記。清單 19 中顯示了該標(biāo)記的 url 和 context 屬性,它們的行為分別等同于 <c:import> 的 url 和 context 屬性的行為,是嵌套任何 <c:param> 標(biāo)記的結(jié)果。
清單 19. <c:redirect> 操作的語法
<c:redirect url="
expression" context="
expression">
<c:param name="
expression" value="
expression"/>
...
</c:redirect>
|
清單 20 顯示了 <c:redirect> 操作,它用一個(gè)到指定錯(cuò)誤頁面的重定向代替了清單 18 中的錯(cuò)誤消息。在該示例中, <c:redirect> 標(biāo)記的用法與標(biāo)準(zhǔn) <jsp:forward> 操作的用法類似。不過請(qǐng)回憶一下:通過請(qǐng)求分派器進(jìn)行轉(zhuǎn)發(fā)是在服務(wù)器端實(shí)現(xiàn)的,而重定向卻是由瀏覽器來執(zhí)行的。從開發(fā)人員的角度來講,轉(zhuǎn)發(fā)比重定向更有效率,但 <c:redirect> 操作卻更靈活一些,因?yàn)?<jsp:forward> 只能分派到當(dāng)前 servlet 上下文內(nèi)的其它 JSP 頁面。
清單 20. 響應(yīng)異常的重定向
<c:catch var="exception">
<c:import url="ftp://ftp.example.com/package/README"/>
</c:catch>
<c:if test="${not empty exception}">
<c:redirect url="/errors/remote.jsp"/>
</c:if>
|
從用戶的角度來看,主要區(qū)別在于重定向會(huì)更新瀏覽器所顯示的 URL,并因此影響書簽的設(shè)置。轉(zhuǎn)發(fā)卻不這樣,它對(duì)最終用戶是透明的。這樣,選擇 <c:redirect> 還是 <jsp:forward> 還取決于所期望的用戶體驗(yàn)。
結(jié)束語
JSTL core 庫含有多種通用的定制標(biāo)記,廣大的 JSP 開發(fā)人員都會(huì)使用這些標(biāo)記。例如,URL 和異常處理標(biāo)記很好地補(bǔ)充了現(xiàn)有的 JSP 功能,如 <jsp:include> 和 <jsp:forward> 操作、 include 偽指令以及 page 偽指令的 errorpage 屬性。迭代和條件操作使得無需腳本編制元素就能夠?qū)崿F(xiàn)復(fù)雜的表示邏輯,尤其在將變量標(biāo)記( <c:set> 和 <c:remove> )與 EL 相結(jié)合使用時(shí)更是如此。
|