JSTL 入門: 表示式語言
JSP 標準標記庫(JSP Standard Tag Library,JSTL)是一個實現 Web 應用程式中常見的通用功能的定製標記庫集,這些功能包括迭代和條件判斷、資料管理格式化、XML 操作以及資料庫訪問。在 developerWorks 上其新系列的第一篇文章中,軟體工程師 Mark Kolb 向您展示瞭如何使用 JSTL 標記來避免在 JSP 頁面中使用指令碼編制元素。您還將瞭解如何通過從表示層刪除原始碼來簡化軟體維護。最後,您將瞭解 JSTL 經過簡化的表示式語言,它允許在不必使用功能齊全的程式語言的情況下對 JSTL 操作指定動態屬性值。
JavaServer Pages(JSP)是用於 J2EE 平臺的標準表示層技術。JSP 技術提供了用於執行計算(這些計算用來動態地生成頁面內容)的指令碼編制元素和操作。指令碼編制元素允許在 JSP 頁面中包括程式原始碼,在為響應使用者請求而呈現頁面時可以執行這些原始碼。操作將計算操作封裝到很象 HTML 或 XML 標記的標記中,JSP 頁面的模板文字通常包含這些標記。JSP 規範只將幾種操作定義成了標準,但從 JSP 1.1 開始,開發人員已經能夠以定製標記庫的方式建立其自己的操作了。
JSP 標準標記庫(JSTL)是 JSP 1.2 定製標記庫集,這些標記庫實現大量伺服器端 Java 應用程式常用的基本功能。通過為典型表示層任務(如資料格式化和迭代或條件內容)提供標準實現,JSTL 使 JSP 作者可以專注於特定於應用程式的開發需求,而不是為這些通用操作“另起爐灶”。
當然,您可以使用 JSP 指令碼編制元素(scriptlet、表示式和宣告)來實現此類任務。例如,可以使用三個 scriptlet 實現條件內容,清單 1 中著重顯示了這三個 scriptlet。但是,因為指令碼編制元素依賴於在頁面中嵌入程式原始碼(通常是 Java 程式碼),所以對於使用這些指令碼編制元素的 JSP 頁面,其軟體維護任務的複雜度大大增加了。例如,清單 1 中的 scriptlet 示例嚴格地依賴於花括號的正確匹配。如果不經意間引入了一個語法錯誤,則條件內容中的巢狀其它 scriptlet 可能會造成嚴重破壞,並且在 JSP 容器編譯該頁面時,要使所產生的錯誤資訊有意義可能會很困難。
清單 1. 通過 scriptlet 實現條件內容
<% if (user.getRole() == "member")) { %>
<p>Welcome, member!</p>
<% } else { %>
<p>Welcome, guest!</p>
<% } %>
修 正此類問題通常需要相當豐富的程式設計經驗。儘管通常會由十分精通頁面佈局和圖形設計的設計人員來開發和維護 JSP,但是同一頁面中的指令碼編制元素出現問題時,需要程式設計師的介入。這種狀況將單個檔案中程式碼的責任分擔給多人,因而使得開發、除錯和增強此類 JSP 頁面成為很麻煩的任務。通過將常用功能包裝到定製標記庫的標準集合中,JSTL 使 JSP 作者可以減少對編制指令碼元素的需求,甚至可以不需要它們,並避免了相關的維護成本。
JSTL 1.0
JSTL 1.0 釋出於 2002 年 6 月,由四個定製標記庫( core 、 format 、 xml 和 sql )和一對通用標記庫驗證器( ScriptFreeTLV 和 PermittedTaglibsTLV )組成。 core 標記庫提供了定製操作,通過限制了作用域的變數管理資料,以及執行頁面內容的迭代和條件操作。它還提供了用來生成和操作 URL 的標記。顧名思義, format 標記庫定義了用來格式化資料(尤其是數字和日期)的操作。它還支援使用本地化資源束進行 JSP 頁面的國際化。 xml 庫包含一些標記,這些標記用來操作通過 XML 表示的資料,而 sql 庫定義了用來查詢關聯式資料庫的操作。
兩個 JSTL 標記庫驗證器允許開發人員在其 JSP 應用程式中強制使用編碼標準。可以配置 ScriptFreeTLV 驗證器以在 JSP 頁面中禁用各種型別的 JSP 指令碼元素 ― scriptlet、表示式和宣告。類似地, PermittedTaglibsTLV 驗證器可以用來限制可能由應用程式的 JSP 頁面訪問的定製標記庫集(包括 JSTL 標記庫)。
儘管 JSTL 最終將會成為 J2EE 平臺的必需元件,但目前只有少數應用程式伺服器包括它。JSTL 1.0 的參考實現可作為 Apache 軟體基金會(Apache Software Foundation)的 Jakarta Taglibs 專案(請參閱 參考資料)的一部分而獲得。可以將該參考實現中的定製標記庫合併到任何支援 JSP 1.2 和 Servlet 2.3 規範的伺服器,以新增對 JSTL 的支援。
表示式語言
在 JSP 1.2 中,可以使用靜態字串或表示式(如果允許的話)指定 JSP 操作的屬性。例如,在清單 2 中,對 <jsp:setProperty> 操作的 name 和 property 屬性指定了靜態值,而用表示式指定了其 value 屬性。這個操作的效果是將請求引數的當前值賦予命名的 bean 特性。以這種形式使用的表示式被稱為 請求時屬性值(request-time attribute value),這是構建到 JSP 規範中的用於動態指定屬性值的唯一機制。
清單 2. 合併請求時屬性值的 JSP 操作
<jsp:setProperty name="user" property="timezonePref"
value='<%= request.getParameter("timezone") %>'/>
因 為請求時屬性值是用表示式指定的,所以它們往往有和其它指令碼元素一樣的軟體維護問題。因此,JSTL 定製標記支援另一種用於指定動態屬性值的機制。可以用簡化的 表示式語言(EL)而不使用完整的 JSP 表示式來指定 JSTL 操作的屬性值。EL 提供了一些識別符號、存取器和運算子,用來檢索和操作駐留在 JSP 容器中的資料。EL 在某種程度上以 EcmaScript(請參閱 參考資料)和 XML 路徑語言(XML Path Language,XPath)為基礎,因此頁面設計人員和程式設計師都應該熟悉它的語法。EL 擅長尋找物件及其特性,然後對它們執行簡單操作;它不是程式語言,甚至不是指令碼編制語言。但是,與 JSTL 標記一起使用時,它就能使用簡單而又方便的符號來表示複雜的行為。EL 表示式的格式是這樣的:用美元符號($)定界,內容包括在花括號({})中,如清單 3 所示。
清單 3. 說明 EL 表示式定界符的 JSTL 操作
<c:out value="${user.firstName}"/>
此 外,您可以將多個表示式與靜態文字組合在一起以通過字串並置來構造動態屬性值,如清單 4 所示。單獨的表示式由識別符號、存取器、文字和運算子組成。識別符號用來引用儲存在資料中心中的資料物件。EL 有 11 個保留識別符號,對應於 11 個 EL 隱式物件。假定所有其它識別符號都引用 限制了作用域的變數。存取器用來檢索物件的特性或集合的元素。文字表示固定的值 ― 數字、字元、字串、布林型或空值。運算子允許對資料和文字進行組合以及比較。
清單 4. 組合靜態文字和多個 EL 表示式以指定動態屬性值
<c:out value="Hello ${user.firstName} ${user.lastName}"/>
限制了作用域的變數
JSP API 通過 <jsp:useBean> 操作允許從 JSP 容器內的四個不同作用域中儲存和檢索資料。JSTL 通過提供用於指定和除去這些作用域中的物件的附加操作來擴充套件這一能力。此外,EL 提供將這些物件作為限制了作用域的變數進行檢索的內建支援。特別地,任何出現在 EL 表示式中但不對應於任何 EL 隱式物件的識別符號,都被自動假定為引用儲存在四個 JSP 作用域的其中某個中的物件,這四個作用域是:
* 頁面作用域
* 請求作用域
* 會話作用域
* 應用程式作用域
您 可能還記得,只有在為特定請求處理頁面期間才能檢索儲存在該頁面作用域中的物件。如果物件是儲存在請求作用域中的,可以在處理所有參與處理某請求的頁面期 間檢索這些物件(譬如在對某個請求的處理中遇到了一個或多個 <jsp:include> 或 <jsp:forward> 操作)。如果物件是儲存在會話作用域中的,則在與 Web 應用程式的互動式會話期間,可以由使用者訪問的任何頁面檢索它(即,直到與該使用者互動相關聯的 HttpSession 物件無效為止)。可以由任何使用者從任何頁面訪問儲存在應用程式作用域中的物件,直到解除安裝 Web 應用程式本身為止(通常是由於關閉 JSP 容器所致)。
通過將字串對映為期望作用域中的物件來將物件儲存到該作用域。然後,就可以通 過提供相同字串來從該作用域檢索該物件。在作用域的對映中查詢字串,並返回被對映的物件。在 Servlet API 中,將此類物件稱為相應作用域的 屬性。但是,在 EL 的上下文中,也將與屬性相關聯的字串看作變數的名稱,該變數通過屬性對映的方式獲得特定的值。
在 EL 中,與隱式物件無關聯的識別符號被認為是儲存在四個 JSP 作用域中的名稱物件。首先對頁面作用域檢查是否存在這樣的識別符號,其次對請求作用域、然後對會話作用域、最後對應用程式作用域依次進行這樣的檢查,然後測 試該識別符號的名稱是否與儲存在該作用域中的某個物件的名稱匹配。第一個這樣的匹配作為 EL 識別符號的值被返回。通過這種方法,可以將 EL 識別符號看作引用限制了作用域的變數。
從更技術的方面來說,沒有對映到隱式物件的識別符號是用 PageContext 例項的 findAttribute() 方法求值的,該例項表示對頁面的處理,在該頁面上,當前正在處理用於請求的表示式。識別符號的名稱作為引數傳遞給這個方法,然後該方法依次在四個作用域中搜 索具有相同名稱的屬性。並將所找到的第一個匹配項作為 findAttribute() 方法的值返回。如果未在這四個作用域中找到這樣的屬性,則返回 null 。
最終,限制了作用域的變數是四個 JSP 作用域的屬性,這些屬性具有可以用作 EL 識別符號的名稱。只要對限制了作用域的變數賦予由字母數字組成的名稱,就可以通過 JSP 中提供的用於設定屬性的任何機制來建立它們。這包括內建的 <jsp:useBean> 操作,以及由 Servlet API 中的幾個類定義的 setAttribute() 方法。此外,四個 JSTL 庫中定義的許多定製標記本身就能夠設定作為限制了作用域的變數使用的屬性值。
隱式物件
表 1 中列出了 11 個 EL 隱式物件的識別符號。不要將這些物件與 JSP 隱式物件(一共只有九個)混淆,其中只有一個物件是它們所共有的。
表 1. EL 隱式物件
盡 管 JSP 和 EL 隱式物件中只有一個公共物件( pageContext ),但通過 EL 也可以訪問其它 JSP 隱式物件。原因是 pageContext 擁有訪問所有其它八個 JSP 隱式物件的特性。實際上,這是將它包括在 EL 隱式物件中的主要理由。
其餘所有 EL 隱式物件都是對映,可以用來查詢對應於名稱的物件。前四個對映表示先前討論的各種屬性作用域。可以用它們來查詢特定作用域中的識別符號,而不用依賴於 EL 在預設情況下使用的順序查詢過程。
接 下來的四個對映用來獲取請求引數和請求頭的值。因為 HTTP 協議允許請求引數和請求頭具有多個值,所以它們各有一對對映。每對中的第一個對映返回請求引數或頭的主要值,通常是恰巧在實際請求中首先指定的那個值。每 對中第二個對映允許檢索引數或頭的所有值。這些對映中的鍵是引數或頭的名稱,但這些值是 String 物件的陣列,其中的每個元素都是單一引數值或頭值。
cookie 隱式物件提供了對由請求設定的 cookie 名稱的訪問。這個物件將所有與請求相關聯的 cookie 名稱對映到表示那些 cookie 特性的 Cookie 物件。
最後一個 EL 隱式物件 initParam 是一個對映,它儲存與 Web 應用程式相關聯的所有上下文的初始化引數的名稱和值。初始化引數是通過 web.xml 部署描述符檔案指定的,該檔案位於應用程式的 WEB-INF 目錄中。
存取器
因 為 EL 識別符號是作為隱式物件或限制了作用域的變數(通過屬性來實現)解析的,因此有必要將它們轉換成 Java 物件。EL 可以自動包裝和解包其相應的 Java 類中的基本型別(例如,可以在後臺將 int 強制轉換成 Integer 類,反之亦可),但大多數的識別符號將成為指向完整的 Java 物件的指標。
結果是,對這些物件的特性或(在物件是陣列和集合的情況下)對其元素的訪問通常是令人滿意的。就為了實現這種用途,EL 提供了兩種不同的存取器(點運算子( . )和方括號運算子( [] )),也支援通過 EL 操作特性和元素。
點 運算子通常用於訪問物件的特性。例如,在表示式 ${user.firstName} 中,使用點運算子來訪問 user 識別符號所引用物件的名為 firstName 的特性。EL 使用 Java bean 約定訪問物件特性,因此必須定義這個特性的 getter 方法(通常是名為 getFirstName() 的方法),以便表示式正確求值。當被訪問的特性本身是物件時,可以遞迴地應用點運算子。例如,如果我們虛構的 user 物件有一個實現為 Java 物件的 address 特性,那麼也可以用點運算子來訪問這個物件的特性。例如,表示式 ${user.address.city} 將會返回這個地址物件巢狀的 city 特性。
方括號運算子用來檢索陣列和集合的元素。在數 組和有序集合(也即,實現了 java.util.List 介面的集合)的情況下,把要檢索的元素的下標放在方括號中。例如,表示式 ${urls[3]} 返回 urls 識別符號所引用的陣列或集合的第四個元素(和 Java 語言以及 JavaScript 中一樣,EL 中的下標是從零開始的)。
對於實現 java.util.Map 介面的集合,方括號運算子使用關聯的鍵查詢儲存在對映中的值。在方括號中指定鍵,並將相應的值作為表示式的值返回。例如,表示式 ${commands["dir"]} 返回與 commands 識別符號所引用的 Map 中的 "dir" 鍵相關聯的值。
對於上述 兩種情況,都可允許表示式出現在方括號中。對巢狀表示式求值的結果將被作為下標或鍵,用來檢索集合或陣列的適當元素。和點運算子一樣,方括號運算子也可以 遞迴應用。這使得 EL 能夠從多維陣列、巢狀集合或兩者的任意組合中檢索元素。此外,點運算子和方括號運算子還可以互操作。例如,如果陣列的元素本身是物件,則可以使用方括號運 算符來檢索該陣列的元素,並結合點運算子來檢索該元素的一個特性(例如 ${urls[3].protocol} )。
假定 EL 充當指定動態屬性值的簡化語言,EL 存取器有一個有趣的功能(與 Java 語言的存取器不同),那就是它們在應用於 null 時不丟擲異常。如果應用 EL 存取器的物件(例如, ${foo.bar} 和 ${foo["bar"]} 中的 foo 識別符號)是 null ,那麼應用存取器的結果也是 null 。事實證明,在大多數情況下,這是一個相當有用的行為,不久您就會了解這一點。
最後,點運算子和 方括號運算子可能實現某種程度的互換。例如,也可以使用 ${user["firstName"]} 來檢索 user 物件的 firstName 特性,正如可以用 ${commands.dir} 獲取與 commands 對映中的 "dir" 鍵相關聯的值一樣。
運算子
EL 還可以通過使用識別符號和存取器,遍歷包含應用程式資料(通過限制了作用域的變數公開)或關於環境的資訊(通過 EL 隱式物件)的物件層次結構。但是,只是訪問這些資料,通常不足以實現許多 JSP 應用程式所需的表示邏輯。
最終,EL 還包括了幾個用來操作和比較 EL 表示式所訪問資料的運算子。表 2 中彙總了這些運算子。
表 2. EL 運算子
類別 運算子
算術運算子 + 、 - 、 * 、 / (或 div )和 % (或 mod )
關係運算子 == (或 eq )、 != (或 ne )、 < (或 lt )、 > (或 gt )、 <= (或 le )和 >= (或 ge )
邏輯運算子 && (或 and )、 || (或 or )和 ! (或 not )
驗證運算子 empty
算 術運算子支援數值的加法、減法、乘法和除法。還提供了一個求餘運算子。注:除法和求餘運算子都有替代的、非符號的名稱(為的是與 XPath 保持一致)。清單 5 中顯示了一個演示算術運算子用法的示例表示式。對幾個 EL 表示式應用算術運算子的結果是將該算術運算子應用於這些表示式返回的數值所得的結果。
清單 5. 利用算術運算子的 EL 表示式
${item.price * (1 + taxRate[user.address.zipcode])}
關係運算子允許比較數字或文字資料。比較的結果作為布林值返回。邏輯運算子允許合併布林值,返回新的布林值。因此,可以將 EL 邏輯運算子應用於巢狀的關係或邏輯運算子的結果,如清單 6 所示。
清單 6. 利用關係和邏輯運算子的 EL 表示式
${(x >= min) && (x <= max)}
最 後一種 EL 運算子是 empty ,它對於驗證資料特別有用。 empty 運算子采用單個表示式作為其變數(也即, ${empty input} ),並返回一個布林值,該布林值表示對錶達式求值的結果是不是“空”值。求值結果為 null 的表示式被認為是空,即無元素的集合或陣列。如果引數是對長度為零的 String 求值所得的結果,則 empty 運算子也將返回 true 。
表 3 顯示了 EL 運算子的優先順序。正如清單 5 和 6 所示,可以用圓括號對錶達式分組,高於普通的優先順序規則。
表 3. EL 運算子優先順序(自頂到底,從左到右)
[] , .
()
unary - 、 not 、 ! 、 empty
* 、 / 、 div 、 % 、 mod
+ 、binary -
() <</code> 、 > 、 <= 、 >= 、 lt 、 gt 、 le 、 ge
== 、 != 、 eq 、 ne
&& 、 and
|| 、 or
文字
在 EL 表示式中,數字、字串、布林值和 null 都可以被指定為文字值。字串可以用單引號或雙引號定界。布林值被指定為 true 和 false 。
Taglib 偽指令
正 如我們先前討論的,JSTL 1.0 包括四個定製標記庫。為了演示 JSTL 標記和表示式語言的互動,我們將研究幾個來自 JSTL core 庫的標記。和使用任何 JSP 定製標記庫一樣,必須在您想要使用這個庫標記的任何頁面中包括 taglib 偽指令。清單 7 顯示了用於這個特定庫的偽指令。
清單 7. 用於 JSTL core 庫 EL 版本的 taglib 偽指令
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
實 際上,對應於 JSTL core 庫的 taglib 偽指令有兩種,因為在 JSTL 1.0 中,EL 是可選的。所有四個 JSTL 1.0 定製標記庫都有使用 JSP 表示式(而不是 EL)指定動態屬性值的備用版本。因為這些備用庫依賴於 JSP 的更傳統的請求時屬性值,所以它們被稱為 RT庫,而那些使用表示式語言的則被稱為 EL 庫。開發人員用不同的 taglib 偽指令來區分每個庫的這兩個版本。清單 8 顯示了使用 core 庫的 RT 版本的偽指令。但是,由於現在我們討論的重點是 EL,所以首先需要這些偽指令。
清單 8. 用於 JSTL core 庫 RT 版本的 taglib 偽指令
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c_rt" %>
變數標記
我 們首先要考慮的 JSTL 定製標記是 <c:set> 操作。正如已經說明的,限制了作用域的變數在 JSTL 中起關鍵作用, <c:set> 操作提供基於標記的機制來建立和設定限制了作用域的變數。清單 9 中顯示了該操作的語法,其中 var 屬性指定了限制了作用域的變數的名稱, scope 屬性表明了該變數駐留在哪個作用域中, value 屬性指定了分配給該變數的值。如果指定變數已經存在,則簡單地將所指明的值賦給它。如果不存在,則建立新的限制了作用域的變數,並用該值初始化這個變數。
清單 9. <c:set> 操作的語法
<c:set var="
name" scope="
scope" value="
expression"/>
scope 屬性是可選的,其預設值是 page 。
清單 10 中顯示了 <c:set> 的兩個示例。在第一個示例中,將會話作用域變數設定成 String 值。在第二個示例中,用表示式來設定數值:將頁面作用域內名為 square 的變數賦值為名為 x 的請求引數的值的平方。
清單 10. <c:set> 操作示例
<c:set var="timezone" scope="session" value="CST"/>
<c:set var="square" value="${param['x'] * param['x']}"/>
您 還可以將限制了作用域的變數的值指定為 <c:set> 操作的主體內容,而不是使用屬性。使用這種方法,您可以重新編寫清單 10 中的第一個示例,如清單 11 所示。此外,正如我們馬上可以看到的, <c:set> 標記的主體內容本身也可以使用定製標記。 <c:set> 主體內生成的所有內容都將作為一個 String 值賦給指定變數。
清單 11. 通過主體內容指定 <c:set> 操作的值
<c:set var="timezone" scope="session">CST</c:set>
JSTL core 庫包含第二個用於管理限制了作用域的變數的標記 ― <c:remove> 。顧名思義, <c:remove> 操作是用來刪除限制了作用域的變數的,它獲取兩個屬性。 var 屬性指定待刪除變數的名稱, scope 屬性是可選的,它表示待刪除變數來自哪個作用域,預設為 page ,如清單 12 所示。
清單 12. <c:remove> 操作示例
<c:remove var="timezone" scope="session"/>
輸出
盡 管 <c:set> 操作允許將表示式結果賦給限制了作用域的變數,但開發人員通常會希望只顯示錶達式的值,而不儲存它。JSTL <c:out> 定製標記承擔這一任務,其語法如清單 13 所示。該標記對由其 value 屬性指定的表示式進行求值,然後列印結果。如果指定了可選屬性 default ,那麼,在對 value 屬性的表示式求值所得結果為 null 或空 String 的情況下, <c:out> 將列印其值。
清單 13. <c:out> 操作的語法
<c:out value="
expression" default="
expression" escapeXml="
boolean"/>
escapeXml 屬性也是可選的。它控制當用 <c:out> 標記輸出諸如“<”、“>”和“&”之類的字元(在 HTML 和 XML 中具有特殊意義)時是否應該進行轉義。如果將 escapeXml 設定為 true,則會自動將這些字元轉換成相應的 XML 實體(此處提到的字元分別轉換成 < 、 > 和 & )。
例如,假定有一個名為 user 的會話作用域變數,它是一個類的例項,該類為使用者定義了兩個特性: username 和 company 。每當使用者訪問站點時,這個物件被自動分配給會話,但直到使用者實際登入後,才會設定這兩個特性。假定是這種方案,請考慮清單 14 中的 JSP 片段。在使用者登入之後,這個片段將顯示單詞“Hello”,其後是他/她的使用者名稱和一個驚歎號。但是,在使用者登入之前,由這個片段生成的內容則是短語 “Hello Guest!”。在這種情況下,因為 username 特性還有待初始化,所以 <c:out> 標記將轉而列印出 default 屬性的值(即字串“Guest”)。
清單 14. 帶預設內容的 <c:out> 操作示例
Hello <c:out value="${user.username}" default=="Guest"/>!
接 下來,考慮清單 15,它使用了 <c:out> 標記的 escapeXml 屬性。如果在這種情況下已經將 company 特性設定成 Java String 值 "Flynn & Sons" ,那麼,實際上該操作生成的內容將是 Flynn & Sons 。如果這個操作是生成 HTML 或 XML 內容的 JSP 頁面的一部分,那麼,這個字串中間的“&”符號最終可能被解釋為 HTML 或 XML 控制字元,從而妨礙了對該內容的顯示或解析。但是,如果將 escapeXml 屬性值設定成 true ,則所生成的內容將是 Flynn & Sons 。瀏覽器或解析器不會因在解釋時遇到這種內容而出問題。假定 HTML 和 XML 是 JSP 應用程式中最常見的內容型別,所以 escapeXml 屬性的預設值是 true 就不足為奇了。
清單 15. 禁用轉義的 <c:out> 操作示例
<c:out value="${user.company}" escapeXml=="false"/>
用預設值設定變數
除 了簡化動態資料的顯示之外,當通過 <c:set> 設定變數值時, <c:out> 指定預設值的能力也很有用。正如 清單 11 所示,用來賦給限制了作用域的變數的值可以指定為 <c:set> 標記的主體內容,也可以通過其值屬性來指定。通過將 <c:out> 操作巢狀在 <c:set> 標記的主體內容中,變數賦值就可以利用其預設值能力。
清單 16 中說明了這種方法。外部 <c:set> 標記的行為非常簡單:它根據其主體內容設定會話作用域 timezone 變數的值。但是,在這種情況下,主體內容是通過 <c:out> 操作生成的。這個巢狀操作的值屬性是表示式 ${cookie['tzPref'].value} ,它嘗試通過 cookie 隱式物件返回名為 tzPref 的 cookie 值。( cookie 隱式物件將 cookie 名稱對映到相應的 Cookie 例項,這意味著必須通過物件的 value 特性使用點運算子來檢索儲存在 cookie 中的實際資料。)
清單 16. 合併 <c:set> 和 <c:out> 以提供預設變數值
<c:set var="timezone" scope=="session">
<c:out value="${cookie['tzPref'].value}" default=="CST"/>
</c:set>
但 是,請考慮以下情況,使用者是第一次嘗試使用這段程式碼的 Web 應用程式。結果是,請求中沒有提供名為 tzPref 的 cookie。這意味著使用隱式物件的查詢將返回 null ,在這種情況下整個表示式將返回 null 。因為對 <c:out> 標記的 value 屬性求值的結果是 null ,所以 <c:out> 標記會轉而輸出對其 default 屬性求值的結果。在這裡是字串 CST 。因此,實際的結果是將 timezone 限制了作用域的變數設定成使用者的 tzPref cookie 中儲存的時區,或者,如果沒有,則使用預設時區 CST 。
EL 和 JSP 2.0
目前,表示式語言僅可用於 指定 JSTL 定製標記中的動態屬性值。但 JSTL 1.0 表示式語言的一個擴充套件已經被提出,會把它包括到 JSP 2.0 中去,眼下正在進行最後評審。這個擴充套件將允許開發人員通過自己的定製標記來使用 EL。頁面作者將可以在目前允許使用 JSP 表示式的任何地方使用 EL 表示式,譬如將動態值插入模板文字中: <p>Your preferred time zone is ${timezone}</p> 。
這個 JSP 2.0 功能(就象 JSTL 本身一樣)將支援頁面作者進一步減少對 JSP 編制指令碼元素的依賴,從而改進 JSP 應用程式的可維護性。
結束語
EL (與四個 JSTL 定製標記庫提供的操作結合起來)允許頁面作者不使用指令碼元素即可實現表示層邏輯。例如,對比本文開頭 清單 1 中的 JSP 程式碼和清單 17 中顯示的通過 JSTL 實現的同樣功能。(JSTL core 庫中其餘的標記,包括 <c:choose> 及其子標記,將在本系列的下一篇文章中討論。)儘管顯然執行了條件邏輯,但是 JSTL 版本中沒有 Java 語言原始碼,並且標記之間的關係(尤其是關於巢狀需求)對於任何精通 HTML 語法的人都應該是熟悉的。
清單 17. 合併 <c:set> 和 <c:out> 以提供預設變數值
<c:choose><c:when test="${user.role == 'member'}">
<p>Welcome, member!</p>
</c:when><c:otherwise>
<p>Welcome, guest!</p>
</c:otherwise></c:choose>
通過提供大多數 Web 應用程式常用功能的標準實現,JSTL 有助於加速開發週期。與 EL 結合起來,JSTL 可以不需要對錶示層程式編寫程式碼,這極大地簡化了 JSP 應用程式的維護。
JavaServer Pages(JSP)是用於 J2EE 平臺的標準表示層技術。JSP 技術提供了用於執行計算(這些計算用來動態地生成頁面內容)的指令碼編制元素和操作。指令碼編制元素允許在 JSP 頁面中包括程式原始碼,在為響應使用者請求而呈現頁面時可以執行這些原始碼。操作將計算操作封裝到很象 HTML 或 XML 標記的標記中,JSP 頁面的模板文字通常包含這些標記。JSP 規範只將幾種操作定義成了標準,但從 JSP 1.1 開始,開發人員已經能夠以定製標記庫的方式建立其自己的操作了。
JSP 標準標記庫(JSTL)是 JSP 1.2 定製標記庫集,這些標記庫實現大量伺服器端 Java 應用程式常用的基本功能。通過為典型表示層任務(如資料格式化和迭代或條件內容)提供標準實現,JSTL 使 JSP 作者可以專注於特定於應用程式的開發需求,而不是為這些通用操作“另起爐灶”。
當然,您可以使用 JSP 指令碼編制元素(scriptlet、表示式和宣告)來實現此類任務。例如,可以使用三個 scriptlet 實現條件內容,清單 1 中著重顯示了這三個 scriptlet。但是,因為指令碼編制元素依賴於在頁面中嵌入程式原始碼(通常是 Java 程式碼),所以對於使用這些指令碼編制元素的 JSP 頁面,其軟體維護任務的複雜度大大增加了。例如,清單 1 中的 scriptlet 示例嚴格地依賴於花括號的正確匹配。如果不經意間引入了一個語法錯誤,則條件內容中的巢狀其它 scriptlet 可能會造成嚴重破壞,並且在 JSP 容器編譯該頁面時,要使所產生的錯誤資訊有意義可能會很困難。
清單 1. 通過 scriptlet 實現條件內容
<% if (user.getRole() == "member")) { %>
<p>Welcome, member!</p>
<% } else { %>
<p>Welcome, guest!</p>
<% } %>
修 正此類問題通常需要相當豐富的程式設計經驗。儘管通常會由十分精通頁面佈局和圖形設計的設計人員來開發和維護 JSP,但是同一頁面中的指令碼編制元素出現問題時,需要程式設計師的介入。這種狀況將單個檔案中程式碼的責任分擔給多人,因而使得開發、除錯和增強此類 JSP 頁面成為很麻煩的任務。通過將常用功能包裝到定製標記庫的標準集合中,JSTL 使 JSP 作者可以減少對編制指令碼元素的需求,甚至可以不需要它們,並避免了相關的維護成本。
JSTL 1.0
JSTL 1.0 釋出於 2002 年 6 月,由四個定製標記庫( core 、 format 、 xml 和 sql )和一對通用標記庫驗證器( ScriptFreeTLV 和 PermittedTaglibsTLV )組成。 core 標記庫提供了定製操作,通過限制了作用域的變數管理資料,以及執行頁面內容的迭代和條件操作。它還提供了用來生成和操作 URL 的標記。顧名思義, format 標記庫定義了用來格式化資料(尤其是數字和日期)的操作。它還支援使用本地化資源束進行 JSP 頁面的國際化。 xml 庫包含一些標記,這些標記用來操作通過 XML 表示的資料,而 sql 庫定義了用來查詢關聯式資料庫的操作。
兩個 JSTL 標記庫驗證器允許開發人員在其 JSP 應用程式中強制使用編碼標準。可以配置 ScriptFreeTLV 驗證器以在 JSP 頁面中禁用各種型別的 JSP 指令碼元素 ― scriptlet、表示式和宣告。類似地, PermittedTaglibsTLV 驗證器可以用來限制可能由應用程式的 JSP 頁面訪問的定製標記庫集(包括 JSTL 標記庫)。
儘管 JSTL 最終將會成為 J2EE 平臺的必需元件,但目前只有少數應用程式伺服器包括它。JSTL 1.0 的參考實現可作為 Apache 軟體基金會(Apache Software Foundation)的 Jakarta Taglibs 專案(請參閱 參考資料)的一部分而獲得。可以將該參考實現中的定製標記庫合併到任何支援 JSP 1.2 和 Servlet 2.3 規範的伺服器,以新增對 JSTL 的支援。
表示式語言
在 JSP 1.2 中,可以使用靜態字串或表示式(如果允許的話)指定 JSP 操作的屬性。例如,在清單 2 中,對 <jsp:setProperty> 操作的 name 和 property 屬性指定了靜態值,而用表示式指定了其 value 屬性。這個操作的效果是將請求引數的當前值賦予命名的 bean 特性。以這種形式使用的表示式被稱為 請求時屬性值(request-time attribute value),這是構建到 JSP 規範中的用於動態指定屬性值的唯一機制。
清單 2. 合併請求時屬性值的 JSP 操作
<jsp:setProperty name="user" property="timezonePref"
value='<%= request.getParameter("timezone") %>'/>
因 為請求時屬性值是用表示式指定的,所以它們往往有和其它指令碼元素一樣的軟體維護問題。因此,JSTL 定製標記支援另一種用於指定動態屬性值的機制。可以用簡化的 表示式語言(EL)而不使用完整的 JSP 表示式來指定 JSTL 操作的屬性值。EL 提供了一些識別符號、存取器和運算子,用來檢索和操作駐留在 JSP 容器中的資料。EL 在某種程度上以 EcmaScript(請參閱 參考資料)和 XML 路徑語言(XML Path Language,XPath)為基礎,因此頁面設計人員和程式設計師都應該熟悉它的語法。EL 擅長尋找物件及其特性,然後對它們執行簡單操作;它不是程式語言,甚至不是指令碼編制語言。但是,與 JSTL 標記一起使用時,它就能使用簡單而又方便的符號來表示複雜的行為。EL 表示式的格式是這樣的:用美元符號($)定界,內容包括在花括號({})中,如清單 3 所示。
清單 3. 說明 EL 表示式定界符的 JSTL 操作
<c:out value="${user.firstName}"/>
此 外,您可以將多個表示式與靜態文字組合在一起以通過字串並置來構造動態屬性值,如清單 4 所示。單獨的表示式由識別符號、存取器、文字和運算子組成。識別符號用來引用儲存在資料中心中的資料物件。EL 有 11 個保留識別符號,對應於 11 個 EL 隱式物件。假定所有其它識別符號都引用 限制了作用域的變數。存取器用來檢索物件的特性或集合的元素。文字表示固定的值 ― 數字、字元、字串、布林型或空值。運算子允許對資料和文字進行組合以及比較。
清單 4. 組合靜態文字和多個 EL 表示式以指定動態屬性值
<c:out value="Hello ${user.firstName} ${user.lastName}"/>
限制了作用域的變數
JSP API 通過 <jsp:useBean> 操作允許從 JSP 容器內的四個不同作用域中儲存和檢索資料。JSTL 通過提供用於指定和除去這些作用域中的物件的附加操作來擴充套件這一能力。此外,EL 提供將這些物件作為限制了作用域的變數進行檢索的內建支援。特別地,任何出現在 EL 表示式中但不對應於任何 EL 隱式物件的識別符號,都被自動假定為引用儲存在四個 JSP 作用域的其中某個中的物件,這四個作用域是:
* 頁面作用域
* 請求作用域
* 會話作用域
* 應用程式作用域
您 可能還記得,只有在為特定請求處理頁面期間才能檢索儲存在該頁面作用域中的物件。如果物件是儲存在請求作用域中的,可以在處理所有參與處理某請求的頁面期 間檢索這些物件(譬如在對某個請求的處理中遇到了一個或多個 <jsp:include> 或 <jsp:forward> 操作)。如果物件是儲存在會話作用域中的,則在與 Web 應用程式的互動式會話期間,可以由使用者訪問的任何頁面檢索它(即,直到與該使用者互動相關聯的 HttpSession 物件無效為止)。可以由任何使用者從任何頁面訪問儲存在應用程式作用域中的物件,直到解除安裝 Web 應用程式本身為止(通常是由於關閉 JSP 容器所致)。
通過將字串對映為期望作用域中的物件來將物件儲存到該作用域。然後,就可以通 過提供相同字串來從該作用域檢索該物件。在作用域的對映中查詢字串,並返回被對映的物件。在 Servlet API 中,將此類物件稱為相應作用域的 屬性。但是,在 EL 的上下文中,也將與屬性相關聯的字串看作變數的名稱,該變數通過屬性對映的方式獲得特定的值。
在 EL 中,與隱式物件無關聯的識別符號被認為是儲存在四個 JSP 作用域中的名稱物件。首先對頁面作用域檢查是否存在這樣的識別符號,其次對請求作用域、然後對會話作用域、最後對應用程式作用域依次進行這樣的檢查,然後測 試該識別符號的名稱是否與儲存在該作用域中的某個物件的名稱匹配。第一個這樣的匹配作為 EL 識別符號的值被返回。通過這種方法,可以將 EL 識別符號看作引用限制了作用域的變數。
從更技術的方面來說,沒有對映到隱式物件的識別符號是用 PageContext 例項的 findAttribute() 方法求值的,該例項表示對頁面的處理,在該頁面上,當前正在處理用於請求的表示式。識別符號的名稱作為引數傳遞給這個方法,然後該方法依次在四個作用域中搜 索具有相同名稱的屬性。並將所找到的第一個匹配項作為 findAttribute() 方法的值返回。如果未在這四個作用域中找到這樣的屬性,則返回 null 。
最終,限制了作用域的變數是四個 JSP 作用域的屬性,這些屬性具有可以用作 EL 識別符號的名稱。只要對限制了作用域的變數賦予由字母數字組成的名稱,就可以通過 JSP 中提供的用於設定屬性的任何機制來建立它們。這包括內建的 <jsp:useBean> 操作,以及由 Servlet API 中的幾個類定義的 setAttribute() 方法。此外,四個 JSTL 庫中定義的許多定製標記本身就能夠設定作為限制了作用域的變數使用的屬性值。
隱式物件
表 1 中列出了 11 個 EL 隱式物件的識別符號。不要將這些物件與 JSP 隱式物件(一共只有九個)混淆,其中只有一個物件是它們所共有的。
表 1. EL 隱式物件
類別 | 識別符號 | 描述 | ||||||||
JSP | pageContext | PageContext 例項對應於當前頁面的處理 | ||||||||
作用域 | pageScope | 與頁面作用域屬性的名稱和值相關聯的 Map 類 | ||||||||
requestScope | 與請求作用域屬性的名稱和值相關聯的 Map 類 | |||||||||
sessionScope | 與會話作用域屬性的名稱和值相關聯的 Map 類 | |||||||||
applicationScope | 與應用程式作用域屬性的名稱和值相關聯的 Map 類 | |||||||||
請求引數 | param | 按名稱儲存請求引數的主要值的 Map 類 | ||||||||
paramValues | 將請求引數的所有值作為 String 陣列儲存的 Map 類 | |||||||||
請求頭 | header | 按名稱儲存請求頭主要值的 Map 類 | ||||||||
headerValues | 將請求頭的所有值作為 String 陣列儲存的 Map 類 | |||||||||
Cookie | cookie | 按名稱儲存請求附帶的 cookie 的 Map 類 | ||||||||
初始化引數 | initParam | 按名稱儲存 Web 應用程式上下文初始化引數的 Map 類 |
盡 管 JSP 和 EL 隱式物件中只有一個公共物件( pageContext ),但通過 EL 也可以訪問其它 JSP 隱式物件。原因是 pageContext 擁有訪問所有其它八個 JSP 隱式物件的特性。實際上,這是將它包括在 EL 隱式物件中的主要理由。
其餘所有 EL 隱式物件都是對映,可以用來查詢對應於名稱的物件。前四個對映表示先前討論的各種屬性作用域。可以用它們來查詢特定作用域中的識別符號,而不用依賴於 EL 在預設情況下使用的順序查詢過程。
接 下來的四個對映用來獲取請求引數和請求頭的值。因為 HTTP 協議允許請求引數和請求頭具有多個值,所以它們各有一對對映。每對中的第一個對映返回請求引數或頭的主要值,通常是恰巧在實際請求中首先指定的那個值。每 對中第二個對映允許檢索引數或頭的所有值。這些對映中的鍵是引數或頭的名稱,但這些值是 String 物件的陣列,其中的每個元素都是單一引數值或頭值。
cookie 隱式物件提供了對由請求設定的 cookie 名稱的訪問。這個物件將所有與請求相關聯的 cookie 名稱對映到表示那些 cookie 特性的 Cookie 物件。
最後一個 EL 隱式物件 initParam 是一個對映,它儲存與 Web 應用程式相關聯的所有上下文的初始化引數的名稱和值。初始化引數是通過 web.xml 部署描述符檔案指定的,該檔案位於應用程式的 WEB-INF 目錄中。
存取器
因 為 EL 識別符號是作為隱式物件或限制了作用域的變數(通過屬性來實現)解析的,因此有必要將它們轉換成 Java 物件。EL 可以自動包裝和解包其相應的 Java 類中的基本型別(例如,可以在後臺將 int 強制轉換成 Integer 類,反之亦可),但大多數的識別符號將成為指向完整的 Java 物件的指標。
結果是,對這些物件的特性或(在物件是陣列和集合的情況下)對其元素的訪問通常是令人滿意的。就為了實現這種用途,EL 提供了兩種不同的存取器(點運算子( . )和方括號運算子( [] )),也支援通過 EL 操作特性和元素。
點 運算子通常用於訪問物件的特性。例如,在表示式 ${user.firstName} 中,使用點運算子來訪問 user 識別符號所引用物件的名為 firstName 的特性。EL 使用 Java bean 約定訪問物件特性,因此必須定義這個特性的 getter 方法(通常是名為 getFirstName() 的方法),以便表示式正確求值。當被訪問的特性本身是物件時,可以遞迴地應用點運算子。例如,如果我們虛構的 user 物件有一個實現為 Java 物件的 address 特性,那麼也可以用點運算子來訪問這個物件的特性。例如,表示式 ${user.address.city} 將會返回這個地址物件巢狀的 city 特性。
方括號運算子用來檢索陣列和集合的元素。在數 組和有序集合(也即,實現了 java.util.List 介面的集合)的情況下,把要檢索的元素的下標放在方括號中。例如,表示式 ${urls[3]} 返回 urls 識別符號所引用的陣列或集合的第四個元素(和 Java 語言以及 JavaScript 中一樣,EL 中的下標是從零開始的)。
對於實現 java.util.Map 介面的集合,方括號運算子使用關聯的鍵查詢儲存在對映中的值。在方括號中指定鍵,並將相應的值作為表示式的值返回。例如,表示式 ${commands["dir"]} 返回與 commands 識別符號所引用的 Map 中的 "dir" 鍵相關聯的值。
對於上述 兩種情況,都可允許表示式出現在方括號中。對巢狀表示式求值的結果將被作為下標或鍵,用來檢索集合或陣列的適當元素。和點運算子一樣,方括號運算子也可以 遞迴應用。這使得 EL 能夠從多維陣列、巢狀集合或兩者的任意組合中檢索元素。此外,點運算子和方括號運算子還可以互操作。例如,如果陣列的元素本身是物件,則可以使用方括號運 算符來檢索該陣列的元素,並結合點運算子來檢索該元素的一個特性(例如 ${urls[3].protocol} )。
假定 EL 充當指定動態屬性值的簡化語言,EL 存取器有一個有趣的功能(與 Java 語言的存取器不同),那就是它們在應用於 null 時不丟擲異常。如果應用 EL 存取器的物件(例如, ${foo.bar} 和 ${foo["bar"]} 中的 foo 識別符號)是 null ,那麼應用存取器的結果也是 null 。事實證明,在大多數情況下,這是一個相當有用的行為,不久您就會了解這一點。
最後,點運算子和 方括號運算子可能實現某種程度的互換。例如,也可以使用 ${user["firstName"]} 來檢索 user 物件的 firstName 特性,正如可以用 ${commands.dir} 獲取與 commands 對映中的 "dir" 鍵相關聯的值一樣。
運算子
EL 還可以通過使用識別符號和存取器,遍歷包含應用程式資料(通過限制了作用域的變數公開)或關於環境的資訊(通過 EL 隱式物件)的物件層次結構。但是,只是訪問這些資料,通常不足以實現許多 JSP 應用程式所需的表示邏輯。
最終,EL 還包括了幾個用來操作和比較 EL 表示式所訪問資料的運算子。表 2 中彙總了這些運算子。
表 2. EL 運算子
類別 運算子
算術運算子 + 、 - 、 * 、 / (或 div )和 % (或 mod )
關係運算子 == (或 eq )、 != (或 ne )、 < (或 lt )、 > (或 gt )、 <= (或 le )和 >= (或 ge )
邏輯運算子 && (或 and )、 || (或 or )和 ! (或 not )
驗證運算子 empty
算 術運算子支援數值的加法、減法、乘法和除法。還提供了一個求餘運算子。注:除法和求餘運算子都有替代的、非符號的名稱(為的是與 XPath 保持一致)。清單 5 中顯示了一個演示算術運算子用法的示例表示式。對幾個 EL 表示式應用算術運算子的結果是將該算術運算子應用於這些表示式返回的數值所得的結果。
清單 5. 利用算術運算子的 EL 表示式
${item.price * (1 + taxRate[user.address.zipcode])}
關係運算子允許比較數字或文字資料。比較的結果作為布林值返回。邏輯運算子允許合併布林值,返回新的布林值。因此,可以將 EL 邏輯運算子應用於巢狀的關係或邏輯運算子的結果,如清單 6 所示。
清單 6. 利用關係和邏輯運算子的 EL 表示式
${(x >= min) && (x <= max)}
最 後一種 EL 運算子是 empty ,它對於驗證資料特別有用。 empty 運算子采用單個表示式作為其變數(也即, ${empty input} ),並返回一個布林值,該布林值表示對錶達式求值的結果是不是“空”值。求值結果為 null 的表示式被認為是空,即無元素的集合或陣列。如果引數是對長度為零的 String 求值所得的結果,則 empty 運算子也將返回 true 。
表 3 顯示了 EL 運算子的優先順序。正如清單 5 和 6 所示,可以用圓括號對錶達式分組,高於普通的優先順序規則。
表 3. EL 運算子優先順序(自頂到底,從左到右)
[] , .
()
unary - 、 not 、 ! 、 empty
* 、 / 、 div 、 % 、 mod
+ 、binary -
() <</code> 、 > 、 <= 、 >= 、 lt 、 gt 、 le 、 ge
== 、 != 、 eq 、 ne
&& 、 and
|| 、 or
文字
在 EL 表示式中,數字、字串、布林值和 null 都可以被指定為文字值。字串可以用單引號或雙引號定界。布林值被指定為 true 和 false 。
Taglib 偽指令
正 如我們先前討論的,JSTL 1.0 包括四個定製標記庫。為了演示 JSTL 標記和表示式語言的互動,我們將研究幾個來自 JSTL core 庫的標記。和使用任何 JSP 定製標記庫一樣,必須在您想要使用這個庫標記的任何頁面中包括 taglib 偽指令。清單 7 顯示了用於這個特定庫的偽指令。
清單 7. 用於 JSTL core 庫 EL 版本的 taglib 偽指令
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
實 際上,對應於 JSTL core 庫的 taglib 偽指令有兩種,因為在 JSTL 1.0 中,EL 是可選的。所有四個 JSTL 1.0 定製標記庫都有使用 JSP 表示式(而不是 EL)指定動態屬性值的備用版本。因為這些備用庫依賴於 JSP 的更傳統的請求時屬性值,所以它們被稱為 RT庫,而那些使用表示式語言的則被稱為 EL 庫。開發人員用不同的 taglib 偽指令來區分每個庫的這兩個版本。清單 8 顯示了使用 core 庫的 RT 版本的偽指令。但是,由於現在我們討論的重點是 EL,所以首先需要這些偽指令。
清單 8. 用於 JSTL core 庫 RT 版本的 taglib 偽指令
<%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c_rt" %>
變數標記
我 們首先要考慮的 JSTL 定製標記是 <c:set> 操作。正如已經說明的,限制了作用域的變數在 JSTL 中起關鍵作用, <c:set> 操作提供基於標記的機制來建立和設定限制了作用域的變數。清單 9 中顯示了該操作的語法,其中 var 屬性指定了限制了作用域的變數的名稱, scope 屬性表明了該變數駐留在哪個作用域中, value 屬性指定了分配給該變數的值。如果指定變數已經存在,則簡單地將所指明的值賦給它。如果不存在,則建立新的限制了作用域的變數,並用該值初始化這個變數。
清單 9. <c:set> 操作的語法
<c:set var="
name" scope="
scope" value="
expression"/>
scope 屬性是可選的,其預設值是 page 。
清單 10 中顯示了 <c:set> 的兩個示例。在第一個示例中,將會話作用域變數設定成 String 值。在第二個示例中,用表示式來設定數值:將頁面作用域內名為 square 的變數賦值為名為 x 的請求引數的值的平方。
清單 10. <c:set> 操作示例
<c:set var="timezone" scope="session" value="CST"/>
<c:set var="square" value="${param['x'] * param['x']}"/>
您 還可以將限制了作用域的變數的值指定為 <c:set> 操作的主體內容,而不是使用屬性。使用這種方法,您可以重新編寫清單 10 中的第一個示例,如清單 11 所示。此外,正如我們馬上可以看到的, <c:set> 標記的主體內容本身也可以使用定製標記。 <c:set> 主體內生成的所有內容都將作為一個 String 值賦給指定變數。
清單 11. 通過主體內容指定 <c:set> 操作的值
<c:set var="timezone" scope="session">CST</c:set>
JSTL core 庫包含第二個用於管理限制了作用域的變數的標記 ― <c:remove> 。顧名思義, <c:remove> 操作是用來刪除限制了作用域的變數的,它獲取兩個屬性。 var 屬性指定待刪除變數的名稱, scope 屬性是可選的,它表示待刪除變數來自哪個作用域,預設為 page ,如清單 12 所示。
清單 12. <c:remove> 操作示例
<c:remove var="timezone" scope="session"/>
輸出
盡 管 <c:set> 操作允許將表示式結果賦給限制了作用域的變數,但開發人員通常會希望只顯示錶達式的值,而不儲存它。JSTL <c:out> 定製標記承擔這一任務,其語法如清單 13 所示。該標記對由其 value 屬性指定的表示式進行求值,然後列印結果。如果指定了可選屬性 default ,那麼,在對 value 屬性的表示式求值所得結果為 null 或空 String 的情況下, <c:out> 將列印其值。
清單 13. <c:out> 操作的語法
<c:out value="
expression" default="
expression" escapeXml="
boolean"/>
escapeXml 屬性也是可選的。它控制當用 <c:out> 標記輸出諸如“<”、“>”和“&”之類的字元(在 HTML 和 XML 中具有特殊意義)時是否應該進行轉義。如果將 escapeXml 設定為 true,則會自動將這些字元轉換成相應的 XML 實體(此處提到的字元分別轉換成 < 、 > 和 & )。
例如,假定有一個名為 user 的會話作用域變數,它是一個類的例項,該類為使用者定義了兩個特性: username 和 company 。每當使用者訪問站點時,這個物件被自動分配給會話,但直到使用者實際登入後,才會設定這兩個特性。假定是這種方案,請考慮清單 14 中的 JSP 片段。在使用者登入之後,這個片段將顯示單詞“Hello”,其後是他/她的使用者名稱和一個驚歎號。但是,在使用者登入之前,由這個片段生成的內容則是短語 “Hello Guest!”。在這種情況下,因為 username 特性還有待初始化,所以 <c:out> 標記將轉而列印出 default 屬性的值(即字串“Guest”)。
清單 14. 帶預設內容的 <c:out> 操作示例
Hello <c:out value="${user.username}" default=="Guest"/>!
接 下來,考慮清單 15,它使用了 <c:out> 標記的 escapeXml 屬性。如果在這種情況下已經將 company 特性設定成 Java String 值 "Flynn & Sons" ,那麼,實際上該操作生成的內容將是 Flynn & Sons 。如果這個操作是生成 HTML 或 XML 內容的 JSP 頁面的一部分,那麼,這個字串中間的“&”符號最終可能被解釋為 HTML 或 XML 控制字元,從而妨礙了對該內容的顯示或解析。但是,如果將 escapeXml 屬性值設定成 true ,則所生成的內容將是 Flynn & Sons 。瀏覽器或解析器不會因在解釋時遇到這種內容而出問題。假定 HTML 和 XML 是 JSP 應用程式中最常見的內容型別,所以 escapeXml 屬性的預設值是 true 就不足為奇了。
清單 15. 禁用轉義的 <c:out> 操作示例
<c:out value="${user.company}" escapeXml=="false"/>
用預設值設定變數
除 了簡化動態資料的顯示之外,當通過 <c:set> 設定變數值時, <c:out> 指定預設值的能力也很有用。正如 清單 11 所示,用來賦給限制了作用域的變數的值可以指定為 <c:set> 標記的主體內容,也可以通過其值屬性來指定。通過將 <c:out> 操作巢狀在 <c:set> 標記的主體內容中,變數賦值就可以利用其預設值能力。
清單 16 中說明了這種方法。外部 <c:set> 標記的行為非常簡單:它根據其主體內容設定會話作用域 timezone 變數的值。但是,在這種情況下,主體內容是通過 <c:out> 操作生成的。這個巢狀操作的值屬性是表示式 ${cookie['tzPref'].value} ,它嘗試通過 cookie 隱式物件返回名為 tzPref 的 cookie 值。( cookie 隱式物件將 cookie 名稱對映到相應的 Cookie 例項,這意味著必須通過物件的 value 特性使用點運算子來檢索儲存在 cookie 中的實際資料。)
清單 16. 合併 <c:set> 和 <c:out> 以提供預設變數值
<c:set var="timezone" scope=="session">
<c:out value="${cookie['tzPref'].value}" default=="CST"/>
</c:set>
但 是,請考慮以下情況,使用者是第一次嘗試使用這段程式碼的 Web 應用程式。結果是,請求中沒有提供名為 tzPref 的 cookie。這意味著使用隱式物件的查詢將返回 null ,在這種情況下整個表示式將返回 null 。因為對 <c:out> 標記的 value 屬性求值的結果是 null ,所以 <c:out> 標記會轉而輸出對其 default 屬性求值的結果。在這裡是字串 CST 。因此,實際的結果是將 timezone 限制了作用域的變數設定成使用者的 tzPref cookie 中儲存的時區,或者,如果沒有,則使用預設時區 CST 。
EL 和 JSP 2.0
目前,表示式語言僅可用於 指定 JSTL 定製標記中的動態屬性值。但 JSTL 1.0 表示式語言的一個擴充套件已經被提出,會把它包括到 JSP 2.0 中去,眼下正在進行最後評審。這個擴充套件將允許開發人員通過自己的定製標記來使用 EL。頁面作者將可以在目前允許使用 JSP 表示式的任何地方使用 EL 表示式,譬如將動態值插入模板文字中: <p>Your preferred time zone is ${timezone}</p> 。
這個 JSP 2.0 功能(就象 JSTL 本身一樣)將支援頁面作者進一步減少對 JSP 編制指令碼元素的依賴,從而改進 JSP 應用程式的可維護性。
結束語
EL (與四個 JSTL 定製標記庫提供的操作結合起來)允許頁面作者不使用指令碼元素即可實現表示層邏輯。例如,對比本文開頭 清單 1 中的 JSP 程式碼和清單 17 中顯示的通過 JSTL 實現的同樣功能。(JSTL core 庫中其餘的標記,包括 <c:choose> 及其子標記,將在本系列的下一篇文章中討論。)儘管顯然執行了條件邏輯,但是 JSTL 版本中沒有 Java 語言原始碼,並且標記之間的關係(尤其是關於巢狀需求)對於任何精通 HTML 語法的人都應該是熟悉的。
清單 17. 合併 <c:set> 和 <c:out> 以提供預設變數值
<c:choose><c:when test="${user.role == 'member'}">
<p>Welcome, member!</p>
</c:when><c:otherwise>
<p>Welcome, guest!</p>
</c:otherwise></c:choose>
通過提供大多數 Web 應用程式常用功能的標準實現,JSTL 有助於加速開發週期。與 EL 結合起來,JSTL 可以不需要對錶示層程式編寫程式碼,這極大地簡化了 JSP 應用程式的維護。
相關文章
- Lambda表示式入門--函數語言程式設計與函式式介面函數程式設計函式
- jstl表示式引用檔案JS
- EL表示式 與JSTL標籤JS
- JSP 表示式語言概述JS
- JSP表示式語言(EL)JS
- go語言入門之-函式和方法Go函式
- GO語言入門 - (六)函式和方法Go函式
- lambda表示式——快速入門
- 正規表示式入門
- 如何設計一門語言(十)——正規表示式與領域特定語言(DSL)
- C語言程式設計入門之--第五章C語言基本運算和表示式-part2C語言程式設計
- perl語言入門
- c語言入門C語言
- JS正規表示式入門JS
- R語言快速入門R語言
- Dart 語言入門 (四)Dart
- Go語言快速入門Go
- Groovy 語言快速入門
- Swift語言快速入門Swift
- 如何快速入門一門語言
- 孫海洋老師C語言零基礎自學入門 ----移位運算子及移位表示式C語言
- Go語言入門系列(六)之再探函式Go函式
- PLSQL Language Reference-PL/SQL語言基礎-表示式-BOOLEAN表示式SQLBoolean
- 【R語言入門】R語言環境搭建R語言
- 3分鐘入門lambda表示式
- Regex 正規表示式入門
- 【記錄】正規表示式入門
- jdk1.8 lambda表示式入門JDK
- 正規表示式入門學習
- Java入門:Lambda常用表示式解析Java
- 正規表示式基礎入門
- 正規表示式從入門到入坑
- Dart 語言極簡入門Dart
- [翻譯] Go 語言入門Go
- go語言快速入門教程Go
- ChainDesk : Go 語言入門指南AIGo
- Prolog 語言入門教程
- C語言入門基礎C語言