隨筆-57  評(píng)論-202  文章-17  trackbacks-0
          使用定制標(biāo)記進(jìn)行流控制和 URL 管理

          Mark A. Kolb
          軟件工程師
          2003 年 6 月 21 日

          顧名思義,JSP 標(biāo)準(zhǔn)標(biāo)記庫(kù)(JSP Standard Tag Library,JSTL) core 庫(kù)為一些基本功能(如,管理限定了作用域的變量和與 URL 交互等)和基本操作(如,迭代和條件化)提供了定制標(biāo)記。這些標(biāo)記不僅可以由頁(yè)面設(shè)計(jì)人員直接利用,而且還為與其它 JSTL 庫(kù)相結(jié)合從而提供更復(fù)雜的表示邏輯奠定了基礎(chǔ)。Mark Kolb 在本文中繼續(xù)對(duì) JSTL 和 core 庫(kù)進(jìn)行探討,研究用標(biāo)記來(lái)協(xié)助流控制和 URL 管理。

          通過(guò)閱讀本系列的 第一篇文章,您對(duì) JSTL 有了初步的了解。我們描述了使用其 表達(dá)式語(yǔ)言(EL)來(lái)訪問(wèn)數(shù)據(jù)和操作數(shù)據(jù)。正如您所了解的那樣,EL 用來(lái)為 JSTL 定制標(biāo)記的屬性賦予動(dòng)態(tài)值,因此,它所起的作用與 JSP 表達(dá)式一樣,為內(nèi)置操作及其它定制標(biāo)記庫(kù)指定請(qǐng)求時(shí)的屬性值。

          為了演示 EL 的用法,我們介紹了 core 庫(kù)中的三個(gè)標(biāo)記: <c:set><c:remove><c:out><c:set><c:remove> 用于管理限定了作用域的變量;而 <c:out> 用于顯示數(shù)據(jù),尤其是顯示用 EL 計(jì)算出的值。在此基礎(chǔ)上,接下來(lái)本文把注意力集中在 core 庫(kù)的其余標(biāo)記上,這些標(biāo)記可以大致歸為兩大類別:流控制和 URL 管理。

          示例應(yīng)用程序
          為了演示 JSTL 標(biāo)記,我們將使用來(lái)自一個(gè)工作應(yīng)用程序的示例,本系列中余下的文章都將使用此應(yīng)用程序。由于基于 Java 的 Weblog 日漸流行及為人們所熟悉,因此我們將出于此目的使用一個(gè)簡(jiǎn)單的基于 Java 的 Weblog;參閱 參考資料以下載該應(yīng)用程序的 JSP 頁(yè)面和源代碼。Weblog(也稱為 blog)是一種基于 Web 的簡(jiǎn)短注釋的日志,這些注釋是有關(guān) Weblog 的作者所感興趣的主題,通常帶有與 Web 上其它地方的相關(guān)文章及討論的鏈接。圖 1 中顯示了該應(yīng)用程序正在運(yùn)行時(shí)的抓屏。

          圖 1. Weblog 應(yīng)用程序
          Weblog 示例應(yīng)用程序的抓屏

          雖然完整的實(shí)現(xiàn)需要二十四個(gè) Java 類,但在表示層中卻只涉及 Weblog 應(yīng)用程序中的兩個(gè)類, EntryUserBean 。這樣,對(duì)于理解 JSTL 示例而言,只有這兩個(gè)類比較重要。圖 2 顯示了 EntryUserBean 的類圖。

          圖 2. Weblog 應(yīng)用程序的類圖
          Weblog 示例應(yīng)用程序的類圖

          Entry 類表示 Weblog 中一個(gè)標(biāo)有日期的項(xiàng)。其 id 屬性用于在數(shù)據(jù)庫(kù)中存儲(chǔ)及檢索該項(xiàng),而 titletext 屬性則表示該項(xiàng)的實(shí)際內(nèi)容。 createdlastModified 屬性引用了 Java 語(yǔ)言中 Date 類的兩個(gè)實(shí)例,分別用來(lái)表示最初創(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ù)庫(kù)進(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á)式來(lái)指定動(dòng)態(tài)屬性值,因此頁(yè)面創(chuàng)作人員無(wú)需使用腳本編制元素。因?yàn)槟_本編制元素可能是引起 JSP 頁(yè)面中維護(hù)問(wèn)題的主要原因,所以 JSTL 的主要優(yōu)點(diǎn)就在于提供了這樣簡(jiǎn)單(且標(biāo)準(zhǔn))的替代方法。

          EL 從 JSP 容器檢索數(shù)據(jù),遍歷對(duì)象層次結(jié)構(gòu),然后對(duì)結(jié)果執(zhí)行簡(jiǎn)單的操作。不過(guò),除了訪問(wèn)和操作數(shù)據(jù)之外,JSP 腳本編制元素的另一個(gè)常見(jiàn)用法是流控制。尤其是,頁(yè)面創(chuàng)作人員常借助 scriptlet 來(lái)實(shí)現(xiàn)迭代或條件內(nèi)容。然而,因?yàn)檫@樣的操作超出了 EL 的能力,所以 core 庫(kù)提供了幾個(gè)定制操作來(lái)管理流控制,其形式有 迭代條件化異常處理

          迭代
          在 Web 應(yīng)用程序環(huán)境中,迭代主要用于訪存和顯示數(shù)據(jù)集,通常是以列表或表中的一系列行的形式顯示。實(shí)現(xiàn)迭代內(nèi)容的主要 JSTL 操作是 <c:forEach> 定制標(biāo)記。該標(biāo)記支持兩種不同樣式的迭代:整數(shù)范圍上的迭代(類似 Java 語(yǔ)言的 for 語(yǔ)句)和集合上的迭代(類似 Java 語(yǔ)言的 IteratorEnumeration 類)。

          進(jìn)行整數(shù)范圍迭代用到了清單 1 中所示的 <c:forEach> 標(biāo)記的語(yǔ)法。 beginend 屬性要么是靜態(tài)整數(shù)值,要么是可以得出整數(shù)值的表達(dá)式。它們分別指定迭代索引的初始值以及迭代索引的終止值。當(dāng)使用 <c:forEach> 在整數(shù)范圍內(nèi)進(jìn)行迭代時(shí),這兩個(gè)屬性是必需的,而其它所有屬性都是可選的。

          清單 1. 通過(guò) <c:forEach> 操作進(jìn)行數(shù)字迭代的語(yǔ)法
          <c:forEach var="
                  name" varStatus="
                  name"
              begin="
                  expression" end="
                  expression" step="
                  expression">
            
                  body content
          </c:forEach>
          
                

          當(dāng)出現(xiàn) step 時(shí),它也必須是整數(shù)值。它指定每次迭代后索引的增量。這樣,迭代索引從 begin 屬性的值開(kāi)始,以 step 屬性的值為增量進(jìn)行遞增,在迭代索引超過(guò) end 屬性的值時(shí)停止迭代。注:如果省略了 step 屬性,那么步長(zhǎng)缺省為 1。

          如果指定了 var 屬性,那么將會(huì)創(chuàng)建一個(gè)帶有指定名稱的并限定了作用域的變量,并將每次迭代的當(dāng)前索引值賦給該變量。這一限定了作用域的變量具有嵌套式可視性 ― 只可以在 <c:forEach> 標(biāo)記體內(nèi)對(duì)其進(jìn)行訪問(wèn)。(我們很快將討論可選屬性 varStatus 的用法。)清單 2 顯示了對(duì)一組固定整數(shù)值進(jìn)行迭代的 <c:forEach> 操作示例。

          清單 2. 使用 <c:forEach> 標(biāo)記來(lái)生成表列數(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ù)及其平方。這是通過(guò)將 beginstep 屬性值指定為 2,而將 end 屬性值指定為 10 實(shí)現(xiàn)的。此外,用 var 屬性創(chuàng)建用于存儲(chǔ)索引值的限定了作用域的變量, <c:forEach> 標(biāo)記體內(nèi)引用了該變量。尤其是,使用了一對(duì) <c:out> 操作來(lái)顯示索引及其平方,其中索引的平方是使用一個(gè)簡(jiǎn)單的表達(dá)式計(jì)算得來(lái)的。

          圖 3. 清單 2 的輸出
          清單 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á)式指定值。如果變量名稱是通過(guò) <c:forEach> 標(biāo)記的 item 屬性指定的,那么對(duì)于每次迭代該已命名變量都將被綁定到集合后續(xù)元素上。

          清單 3. 通過(guò) <c:forEach> 操作對(duì)集合進(jìn)行迭代的語(yǔ)法
          <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)集合類型。此外,您可以使用該操作來(lái)迭代數(shù)組(包括基本類型數(shù)組)中的元素。表 1 列出了 items 屬性所支持的所有值。正如表的最后一行所指出的那樣,JSTL 定義了它自己的接口 javax.servlet.jsp.jstl.sql.Result ,用來(lái)迭代 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)過(guò)包裝的數(shù)組元素
          用逗號(hào)定界的 String 子字符串
          javax.servlet.jsp.jstl.sql.Result SQL 查詢所獲得的行

          可以使用 beginendstep 屬性來(lái)限定在迭代中包含集合中哪些元素。和通過(guò) <c:forEach> 進(jìn)行數(shù)字迭代的情形一樣,在迭代集合中的元素時(shí)同樣要維護(hù)一個(gè)迭代索引。 <c:forEach> 標(biāo)記實(shí)際上只處理那些與索引值相對(duì)應(yīng)的元素,這些索引值與指定的 beginendstep 值相匹配。

          清單 4 顯示了用來(lái)迭代集合的 <c:forEach> 標(biāo)記。對(duì)于該 JSP 代碼段, entryList 這一限定了作用域的變量被設(shè)置成了 Entry 對(duì)象列表(確切的說(shuō), ArrayList )。 <c:forEach> 標(biāo)記依次處理列表中的每個(gè)元素,將其賦給一個(gè)限定了作用域的變量 blogEntry ,然后生成兩個(gè)表行 ― 一個(gè)用于 Weblog 項(xiàng)的 title ,另一個(gè)則用于該項(xiàng) text 。這些特性是通過(guò)一對(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 的輸出
          清單 4 的輸出

          不論是對(duì)整數(shù)還是對(duì)集合進(jìn)行迭代, <c:forEach> 剩余的屬性 varStatus 所起的作用相同。和 var 屬性一樣, varStatus 用于創(chuàng)建限定了作用域的變量。不過(guò),由 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 開(kāi)始的迭代索引
          count getCount() 當(dāng)前這次迭代從 1 開(kāi)始的迭代計(jì)數(shù)
          first isFirst() 用來(lái)表明當(dāng)前這輪迭代是否為第一次迭代的標(biāo)志
          last isLast() 用來(lái)表明當(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)的表行。它是通過(guò)為 varStatus 屬性指定一個(gè)值,然后訪問(wèn)所生成的限定了作用域的變量的 count 特性來(lái)實(shí)現(xiàn)這一點(diǎn)的。結(jié)果顯示在圖 5 中。

          清單 5. 使用 varStatus 屬性來(lái)顯示 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 的輸出
          清單 5 的輸出

          <c:forEach> 以外, core 庫(kù)還提供了另一個(gè)迭代標(biāo)記: <c:forTokens> 。JSTL 的這個(gè)定制操作與 Java 語(yǔ)言的 StringTokenizer 類的作用相似。清單 6 中顯示的 <c:forTokens> 標(biāo)記除了比 <c:forEach> 的面向集合版本多一個(gè)屬性之外,其它屬性都相同。對(duì)于 <c:forTokens> 而言,通過(guò) items 屬性指定要標(biāo)記化的字符串,而通過(guò) delims 屬性提供用于生成標(biāo)記的一組定界符。和 <c:forEach> 的情形一樣,可以使用 beginendstep 屬性將要處理的標(biāo)記限定為那些與相應(yīng)索引值相匹配的標(biāo)記。

          清單 6. 使用 <c:forTokens> 操作來(lái)迭代字符串標(biāo)記的語(yǔ)法
          <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 頁(yè)面,您可能希望不同類別的用戶看到不同形式的內(nèi)容。例如,在我們的 Weblog 中,訪問(wèn)者應(yīng)該能夠閱讀各項(xiàng),也許還應(yīng)該能夠提交反饋,但只有經(jīng)過(guò)授權(quán)的用戶才能公布新項(xiàng),或編輯已有內(nèi)容。

          在同一個(gè) JSP 頁(yè)面內(nèi)實(shí)現(xiàn)這樣的功能,然后使用條件邏輯來(lái)根據(jù)每條請(qǐng)求控制所顯示的內(nèi)容,這樣做常常能夠改善實(shí)用性和軟件維護(hù)。 core 庫(kù)提供了兩個(gè)不同的條件化標(biāo)記 ― <c:if><c:choose> ― 來(lái)實(shí)現(xiàn)這些功能。

          <c:if> 是這兩個(gè)操作中較簡(jiǎn)單的一個(gè),它簡(jiǎn)單地對(duì)單個(gè)測(cè)試表達(dá)式進(jìn)行求值,接下來(lái),僅當(dāng)對(duì)表達(dá)式求出的值為 true 時(shí),它才處理標(biāo)記的主體內(nèi)容。如果求出的值不為 true ,就忽略該標(biāo)記的主體內(nèi)容。如清單 7 所示, <c:if> 可以通過(guò)其 varscope 屬性(它們所起的作用和在 <c:set> 中所起的作用一樣)選擇將測(cè)試結(jié)果賦給限定了作用域的變量。當(dāng)測(cè)試代價(jià)非常高昂時(shí),這種能力尤為有用:可以將結(jié)果高速緩存在限定了作用域的變量中,然后在隨后對(duì) <c:if> 或其它 JSTL 標(biāo)記的調(diào)用中檢索該結(jié)果。

          清單 7. <c:if> 條件操作的語(yǔ)法
          <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> 來(lái)為 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 的輸出

          如清單 8 所示, <c:if> 標(biāo)記為條件化內(nèi)容的一些簡(jiǎn)單情形提供了一種非常簡(jiǎn)潔的表示法。對(duì)于需要進(jìn)行互斥測(cè)試來(lái)確定應(yīng)該顯示什么內(nèi)容的情況下,JSTL core 庫(kù)還提供了 <c:choose> 操作。清單 9 中顯示了 <c:choose> 的語(yǔ)法。

          清單 9. <c:choose> 操作的語(yǔ)法
          <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)記來(lái)表示,至少要有一個(gè) <c:when> 標(biāo)記。只會(huì)處理第一個(gè)其 test 值為 true<c:when> 標(biāo)記體內(nèi)的內(nèi)容。如果沒(mé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 ,而且又沒(méi)有給出 <c:otherwise> 操作,那么不會(huì)處理任何 <c:choose> 標(biāo)記的主體內(nèi)容。

          清單 10 顯示了運(yùn)用 <c:choose> 標(biāo)記的示例。在這里,檢索請(qǐng)求對(duì)象而獲得協(xié)議信息(通過(guò) 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 頁(yè)面內(nèi)進(jìn)行初級(jí)的異常處理。更確切地說(shuō),在該標(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ì)將異常賦給(具有頁(yè)面作用域的)指定的變量,這使得能夠在頁(yè)面自身內(nèi)部進(jìn)行定制錯(cuò)誤處理。清單 11 顯示了 <c:catch> 的語(yǔ)法(稍后在 清單 18中給出一個(gè)示例)。

          清單 11. <c:catch> 操作的語(yǔ)法
          <c:catch var="
                  name">
            
                  body content
          </c:catch>
          
                

          URL 操作
          JSTL core 庫(kù)中的其余標(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ì)話管理重寫(xiě) URL
          • 請(qǐng)求參數(shù)名稱和值的 URL 編碼

          清單 12 顯示了 <c:url> 標(biāo)記的語(yǔ)法。 value 屬性用來(lái)指定基本 URL,然后在必要時(shí)標(biāo)記對(duì)其進(jìn)行轉(zhuǎn)換。如果這個(gè)基本 URL 以一個(gè)斜杠開(kāi)始,那么會(huì)在它前面加上 servlet 的上下文名稱。可以使用 context 屬性提供顯式的上下文名稱。如果省略該屬性,那么就使用當(dāng)前 servlet 上下文的名稱。這一點(diǎn)特別有用,因?yàn)?servlet 上下文名稱是在部署期間而不是開(kāi)發(fā)期間決定的。(如果這個(gè)基本 URL 不是以斜杠開(kāi)始的,那么就認(rèn)為它是一個(gè)相對(duì) URL,這時(shí)就不必添加上下文名稱。)

          清單 12. <c:url> 操作的語(yǔ)法
          <c:url value="
                  expression" context="
                  expression"
              var="
                  name" scope="
                  scope">
            <c:param name="
                  expression" value="
                  expression"/>
            ...
          </c:url>
          
                

          URL 重寫(xiě)是由 <c:url> 操作自動(dòng)執(zhí)行的。如果 JSP 容器檢測(cè)到一個(gè)存儲(chǔ)用戶當(dāng)前會(huì)話標(biāo)識(shí)的 cookie,那么就不必進(jìn)行重寫(xiě)。但是,如果不存在這樣的 cookie,那么 <c:url> 生成的所有 URL 都會(huì)被重寫(xiě)以編碼會(huì)話標(biāo)識(shí)。注:如果在隨后的請(qǐng)求中存在適當(dāng)?shù)?cookie,那么 <c:url> 將停止重寫(xiě) 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>
          

          最后,如果通過(guò)嵌套 <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)行了編碼(確切地說(shuō),空格被轉(zhuǎn)換成了 + ,而斜杠被轉(zhuǎn)換成了 %2F 序列)。

          清單 15. 有會(huì)話 cookie 時(shí)生成的 URL
          /blog/content/search.jsp?keyword=foo+bar&month=02%2F2003
          

          當(dāng)沒(méi)有會(huì)話 cookie 時(shí),生成的結(jié)果如清單 16 中所示。同樣,servlet 上下文被添加到了前面,而 URL 編碼的請(qǐng)求參數(shù)被附加到了后面。不過(guò),除此以外還重寫(xiě)了基本 URL 以包含指定的會(huì)話標(biāo)識(shí)。當(dāng)瀏覽器發(fā)送用這種方式重寫(xiě)的 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)用程序就無(wú)需依賴由應(yīng)用程序用戶啟用的 cookie 了。

          清單 16. 沒(méi)有會(huì)話 cookie 時(shí)生成的 URL
          /blog/content/search.jsp;jsessionid=233379C7CD2D0ED2E9F3963906DB4290
            ?keyword=foo+bar&month=02%2F2003
          

          導(dǎo)入內(nèi)容
          JSP 有兩種內(nèi)置機(jī)制可以將來(lái)自不同 URL 的內(nèi)容合并到一個(gè) JSP 頁(yè)面: include 偽指令和 <jsp:include> 操作。不過(guò),不管是哪種機(jī)制,要包含的內(nèi)容都必須屬于與頁(yè)面本身相同的 Web 應(yīng)用程序(或 servlet 上下文)。兩個(gè)標(biāo)記之間的主要區(qū)別在于: include 偽指令在頁(yè)面編譯期間合并被包含的內(nèi)容,而 <jsp:include> 操作卻在請(qǐng)求處理 JSP 頁(yè)面時(shí)進(jìn)行。

          從本質(zhì)上講, core 庫(kù)的 <c:import> 操作是更通用、功能更強(qiáng)大的 <jsp:include> 版本(好像是 <jsp:include> “服用了興奮劑”的)。和 <jsp:include> 一樣, <c:import> 也是一種請(qǐng)求時(shí)操作,它的基本任務(wù)就是將其它一些 Web 資源的內(nèi)容插入 JSP 頁(yè)面中。如清單 17 中所示,它的語(yǔ)法非常類似于 <c:url> 的語(yǔ)法。

          清單 17. <c:import> 操作的語(yǔ)法
          <c:import url="
                  expression" context="
                  expression"
              charEncoding="
                  expression" var="
                  name" scope="
                  scope">
            <c:param name="
                  expression" value="
                  expression"/>
            ...
          </c:import>
          
                

          通過(guò) url 屬性指定將要導(dǎo)入內(nèi)容的 URL,這個(gè)屬性是 <c:import> 的唯一一個(gè)必選屬性。這里允許使用相對(duì) URL,并且根據(jù)當(dāng)前頁(yè)面的 URL 來(lái)解析這個(gè)相對(duì) URL。但是,如果 url 屬性的值以斜杠開(kāi)始,那么它就被解釋成本地 JSP 容器內(nèi)的絕對(duì) URL。如果沒(méi)有為 context 屬性指定值,那么就認(rèn)為這樣的絕對(duì) URL 引用當(dāng)前 servlet 上下文內(nèi)的資源。如果通過(guò) context 屬性顯式地指定了上下文,那么就根據(jù)指定的 servlet 上下文解析絕對(duì)(本地)URL。

          <c:import> 操作并不僅僅限于訪問(wèn)本地內(nèi)容。也可以將包含協(xié)議和主機(jī)名的完整 URI 指定為 url 屬性的值。實(shí)際上,協(xié)議甚至不僅局限于 HTTP。 <c:import>url 屬性值可以使用 java.net.URL 類所支持的任何協(xié)議。清單 18 中顯示了這種能力。

          其中, <c:import> 操作用來(lái)包含通過(guò) FTP 協(xié)議訪問(wèn)的文檔內(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è)(可選的)屬性是 varscopevar 屬性會(huì)導(dǎo)致從指定 URL 獲取的內(nèi)容(作為 String 值)被存儲(chǔ)在一個(gè)限定了作用域的變量中,而不是包含在當(dāng)前 JSP 頁(yè)面中。 scope 屬性控制該變量的作用域,缺省情況下是頁(yè)面作用域。如同我們?cè)诮窈蟮奈恼轮袑⒁吹降哪菢樱琂STL xml 庫(kù)中的標(biāo)記利用了 <c:import> 這種能力,即將整個(gè)文檔存儲(chǔ)在一個(gè)限定了作用域的變量中。

          還要注意的是,可以使用(可選的)嵌套的 <c:param> 標(biāo)記來(lái)為正在導(dǎo)入的 URL 指定請(qǐng)求參數(shù)。與在 <c:url> 中嵌套 <c:param> 標(biāo)記一樣,必要時(shí)也要對(duì)參數(shù)名稱和參數(shù)值進(jìn)行 URL 編碼。

          請(qǐng)求重定向
          最后一個(gè) core 庫(kù)標(biāo)記是 <c:redirect> 。該操作用于向用戶的瀏覽器發(fā)送 HTTP 重定向響應(yīng),它是 JSTL 中與 javax.servlet.http.HttpServletResponsesendRedirect() 方法功能相當(dāng)?shù)臉?biāo)記。清單 19 中顯示了該標(biāo)記的 urlcontext 屬性,它們的行為分別等同于 <c:import>urlcontext 屬性的行為,是嵌套任何 <c:param> 標(biāo)記的結(jié)果。

          清單 19. <c:redirect> 操作的語(yǔ)法
          <c:redirect url="
                  expression" context="
                  expression">
            <c:param name="
                  expression" value="
                  expression"/>
            ...
          </c:redirect>
          
                

          清單 20 顯示了 <c:redirect> 操作,它用一個(gè)到指定錯(cuò)誤頁(yè)面的重定向代替了清單 18 中的錯(cuò)誤消息。在該示例中, <c:redirect> 標(biāo)記的用法與標(biāo)準(zhǔn) <jsp:forward> 操作的用法類似。不過(guò)請(qǐng)回憶一下:通過(guò)請(qǐng)求分派器進(jìn)行轉(zhuǎn)發(fā)是在服務(wù)器端實(shí)現(xiàn)的,而重定向卻是由瀏覽器來(lái)執(zhí)行的。從開(kāi)發(fā)人員的角度來(lái)講,轉(zhuǎn)發(fā)比重定向更有效率,但 <c:redirect> 操作卻更靈活一些,因?yàn)?<jsp:forward> 只能分派到當(dāng)前 servlet 上下文內(nèi)的其它 JSP 頁(yè)面。

          清單 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>
          

          從用戶的角度來(lái)看,主要區(qū)別在于重定向會(huì)更新瀏覽器所顯示的 URL,并因此影響書(shū)簽的設(shè)置。轉(zhuǎn)發(fā)卻不這樣,它對(duì)最終用戶是透明的。這樣,選擇 <c:redirect> 還是 <jsp:forward> 還取決于所期望的用戶體驗(yàn)。

          結(jié)束語(yǔ)
          JSTL core 庫(kù)含有多種通用的定制標(biāo)記,廣大的 JSP 開(kāi)發(fā)人員都會(huì)使用這些標(biāo)記。例如,URL 和異常處理標(biāo)記很好地補(bǔ)充了現(xiàn)有的 JSP 功能,如 <jsp:include><jsp:forward> 操作、 include 偽指令以及 page 偽指令的 errorpage 屬性。迭代和條件操作使得無(wú)需腳本編制元素就能夠?qū)崿F(xiàn)復(fù)雜的表示邏輯,尤其在將變量標(biāo)記( <c:set><c:remove> )與 EL 相結(jié)合使用時(shí)更是如此。

          參考資料

          關(guān)于作者
          Mark Kolb 是一位在德克薩斯州奧斯汀市(Austin)工作的軟件工程師。他常就服務(wù)器端 Java 這一主題在業(yè)界演講,而且還是 Web Development with JavaServer Pages, 第二版 一書(shū)的合著者。可以通過(guò) mak@taglib.com與 Mark 聯(lián)系。
          posted on 2005-06-06 15:25 小米 閱讀(401) 評(píng)論(0)  編輯  收藏 所屬分類: Java
          主站蜘蛛池模板: 堆龙德庆县| 保康县| 阳信县| 上饶市| 宁国市| 横峰县| 广宁县| 榆中县| 牡丹江市| 个旧市| 巴彦淖尔市| 从化市| 新宾| 甘南县| 夏邑县| 项城市| 木兰县| 固始县| 呼伦贝尔市| 女性| 卓尼县| 得荣县| 邹平县| 石屏县| 福鼎市| 十堰市| 左云县| 普兰县| 屏南县| 岚皋县| 兰溪市| 巴林右旗| 綦江县| 徐汇区| 仁怀市| 五原县| 溧水县| 鄂州市| 莱州市| 淮阳县| 抚州市|