 |
 |
內(nèi)容: |
 |
|
 |
相關(guān)內(nèi)容: |
 |
|
| 將scriptlet轉(zhuǎn)換成自定義標(biāo)記以獲得更好的JSP代碼
Brett McLaughlin (brett@oreilly.com) 作者,O'Reilly and Associates 2003年 9 月
Scriptlet對(duì)于快而雜( fast-and-dirty)的編碼來(lái)說(shuō)是一種不錯(cuò)的選擇,但是從長(zhǎng)遠(yuǎn)來(lái)看,您需要為您的JSP頁(yè)面選擇一種不那么雜亂的解決方案。在本期的JSP最佳實(shí)踐中,Brett McLaughlin 向您展示了如何將scriptlet轉(zhuǎn)化成JSP自定義標(biāo)記,然后將其用于您的JSP開(kāi)發(fā)中。
在JSP最佳實(shí)踐的上一期,您學(xué)習(xí)了一種基于scriptlet的技術(shù),這種技術(shù)被用來(lái)將上次修改的時(shí)間戳添加到JavaServer Page(JSP)文件中。不幸的是,比起它所提供的短期利益,scriptlet會(huì)將更多的長(zhǎng)期復(fù)雜性引入到您的頁(yè)面中來(lái)。這些scriptlet會(huì)用Java代碼將各種類型的HTML混雜在一起,從而使得 程序的調(diào)試和設(shè)計(jì)極其錯(cuò)綜復(fù)雜。scriptlet不能重用,這常常導(dǎo)致開(kāi)發(fā)者不得不在JSP頁(yè)面之間進(jìn)行復(fù)制-粘貼操作,進(jìn)而導(dǎo)致同一段代碼出現(xiàn)多個(gè)版本。而且,scriptlet還加大了錯(cuò)誤處理的難度,因?yàn)镴SP沒(méi)有提供干凈利落的方式來(lái)報(bào)告腳本錯(cuò)誤。
因此,這次我們將設(shè)計(jì)一種新的解決方案。在本期的JSP最佳實(shí)踐中,您將學(xué)習(xí)一些基礎(chǔ)知識(shí),主要是關(guān)于如何將scriptlet轉(zhuǎn)換成自定義標(biāo)記,并對(duì)其進(jìn)行設(shè)置以便在您的JSP開(kāi)發(fā)項(xiàng)目中使用。
為什么使用taglib? 所謂標(biāo)記庫(kù)(tag library),是指由在JSP頁(yè)面中使用的標(biāo)記所組成的庫(kù)。JSP容器推出時(shí)帶有一個(gè)小型的、默認(rèn)的標(biāo)記庫(kù)。而自定義標(biāo)記庫(kù)是人們?yōu)榱四撤N特定的用途或者目的,將一些標(biāo)記放到一起而形成的一種庫(kù)。在一個(gè)團(tuán)隊(duì)中協(xié)同工作的開(kāi)發(fā)者們可能會(huì)為各自的項(xiàng)目創(chuàng)建一些非常特定化的自定義標(biāo)記庫(kù),同時(shí)也會(huì)創(chuàng)建一個(gè)通用自定義標(biāo)記庫(kù),以供當(dāng)前使用。
JSP 標(biāo)記替代了scriptlet,并緩解了由scriptlet所招致的所有令人頭痛的事情。例如,您可以看到這樣的標(biāo)記:
<store:shoppingCart id="1097629"/>
|
或者這樣的標(biāo)記:
每個(gè)標(biāo)記都包含了指向一個(gè)Java類的引用,但是類中的代碼仍然在它該在的地方:在標(biāo)簽之外,一個(gè)編譯好的類文件之中。
從 scriptlet 到標(biāo)記 創(chuàng)建一個(gè)自定義標(biāo)記的第一步就是決定您想怎樣使用它,如何稱呼它,以及它允許使用或者需要什么屬性(如果有的話)。對(duì)于時(shí)間戳標(biāo)記,我們所需要的很簡(jiǎn)單:只要一個(gè)能夠輸出一個(gè)頁(yè)面的最后修改數(shù)據(jù)的簡(jiǎn)單標(biāo)記。
因?yàn)椴恍枰獙傩?,這個(gè)標(biāo)記看上去就是這個(gè)樣子:
<site-utils:lastModified />
|
這個(gè)標(biāo)記的名稱和前綴是一樣的:都是site-utils 。元素的內(nèi)容是空的,這意味著該元素中不允許有子元素存在。定義了這個(gè)標(biāo)記之后,接下來(lái)的一步就是實(shí)現(xiàn)它的行為。
實(shí)現(xiàn)行為 實(shí)現(xiàn)標(biāo)記行為的第一步是將scriptlet代碼從原先所在的地方移到一個(gè)Java類(LastModifiedTag )中,如清單 1 所示: 清單 1. 創(chuàng)建一個(gè)時(shí)間戳標(biāo)記
package com.newInstance.site.tags;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.tagext.TagSupport;
public class LastModifiedTag extends TagSupport {
public int doEndTag() {
try {
HttpServletRequest request =
(HttpServletRequest)pageContext.getRequest();
String path = pageContext.getServletContext().getRealPath(
request.getServletPath());
File file = new File(path);
DateFormat formatter = DateFormat.getDateInstance(
DateFormat.LONG);
pageContext.getOut().println(
formatter.format(new Date(file.lastModified())));
} catch (IOException ignored) { }
return EVAL_PAGE;
}
}
|
這個(gè)方法中的代碼看上去比較熟悉;實(shí)質(zhì)上,它正是我們?cè)谙惹坝玫降南嗤臅r(shí)間戳代碼。由于不需要用戶輸入,而且該標(biāo)記也沒(méi)有屬性或者嵌入的內(nèi)容,我們惟一需要關(guān)心的一個(gè)新方法就是doEndTag() ,在這個(gè)方法中該標(biāo)記可以輸出內(nèi)容(在這個(gè)例子中是最后修改數(shù)據(jù))到JSP頁(yè)面。
清單 1 中其他的更改更多地與作為一個(gè)JSP標(biāo)記的代碼有關(guān),而與在一個(gè)頁(yè)面內(nèi)運(yùn)行的scriptlet沒(méi)有多大關(guān)系。例如,所有的JSP標(biāo)記都應(yīng)該擴(kuò)展JSP類javax.servlet.jsp.tagext.TagSupport ,這個(gè)類為JSP標(biāo)記提供了基本框架。可能您還注意到 ,該標(biāo)記返回的 EVAL_PAGE . EVAL_PAGE 是一個(gè)預(yù)定義的整型常量,它指示容器處理頁(yè)面的剩下部分。另一種選項(xiàng)就是使用SKIP_PAGE ,它將中止對(duì)頁(yè)面剩下部分的處理。如果您要將控制轉(zhuǎn)移到另一個(gè)頁(yè)面,例如您要前進(jìn)(forward)或者重定向(redirect)用戶,那么只需要使用SKIP_PAGE 。剩下來(lái)的細(xì)節(jié)都是與時(shí)間戳自身有關(guān)的事情。
接下來(lái),編譯這個(gè)類,并將 LastModifiedTag.class 文件放到一個(gè)WEB-INF/classes 目錄下,注意要放到正確的路徑層次結(jié)構(gòu)中。這個(gè)路徑應(yīng)該匹配該標(biāo)記的包名,包名中的圓點(diǎn)(.)用斜杠(/)代替。在本例中,目錄的路徑是基路徑(WEB-INF/classes)再加上層次結(jié)構(gòu)com/newInstance/site/tags。如果有一個(gè)名為 foo.bar.tag.MyTag 的標(biāo)記,那么它將被放在 WEB-INF/classes/foo/bar/tag中。這種路徑層次結(jié)構(gòu)確保了Web容器在任何需要裝載該標(biāo)記的時(shí)候都能夠找到這個(gè)類。
創(chuàng)建TLD 接下來(lái)的一步是創(chuàng)建一個(gè)標(biāo)記庫(kù)描述符(tag library descriptor ,TLD)文件。TLD向容器和任何要使用該標(biāo)記庫(kù)的JSP頁(yè)面描述您的標(biāo)記庫(kù)。清單 2 顯示了一個(gè)非常標(biāo)準(zhǔn)的TLD,其中只包含了一個(gè)標(biāo)記。當(dāng)您將更多的標(biāo)記添加到庫(kù)中時(shí),TLD文件的長(zhǎng)度和復(fù)雜性將隨之增長(zhǎng)。 清單 2. 一個(gè)標(biāo)記庫(kù)描述符
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2/EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>site-utils</short-name>
<uri>http://www.newInstance.com/taglibs/site-utils</uri>
<tag>
<name>lastModified</name>
<tag-class>com.newInstance.site.tags.LastModifiedTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
|
TLD 文件頂部的信息應(yīng)用于整個(gè)標(biāo)記庫(kù)。在本例中,我提供了一個(gè)版本(這對(duì)于跟蹤某個(gè)標(biāo)記庫(kù)的JSP創(chuàng)建者擁有哪個(gè)版本很有用,尤其是在您需要經(jīng)常修改標(biāo)記庫(kù)的情況下更是如此);該標(biāo)記庫(kù)所依賴的JSP版本;一個(gè)為該標(biāo)記庫(kù)推薦的前綴;以及用于引用這個(gè)標(biāo)記庫(kù)的URI。注意,我使用了前綴short-name 作為URI的一部分,這樣 比較容易將前綴和標(biāo)記庫(kù)的URI看成一個(gè)整體。
剩下的信息用于一個(gè)特定的標(biāo)記,這些信息用 tag 元素表示。我指定了該標(biāo)記的名稱、用于該標(biāo)記的類(這個(gè)類應(yīng)該被編譯好并放在適當(dāng)?shù)牡胤?,以便容器能夠裝載),最后還指定了該標(biāo)記是否有嵌入的內(nèi)容。在本例中,標(biāo)記沒(méi)有嵌入的內(nèi)容,因此使用"empty"。
保存這個(gè)文件,并將其放到WEB-INF/tlds目錄下(您可能需要在您的容器中創(chuàng)建這個(gè)目錄)。我將這個(gè)文件保存為site-utils.tld,并在該標(biāo)記庫(kù)的URI(推薦的前綴)和TLD文件本身之間再次創(chuàng)建一個(gè)干凈的鏈接。對(duì)于這個(gè)特定的標(biāo)記庫(kù),要使其可以使用,最后一步要做的是讓您的Web應(yīng)用知道如何連接一個(gè)JSP頁(yè)面中的URI,如何請(qǐng)求使用一個(gè)標(biāo)記庫(kù)。這可以通過(guò)應(yīng)用的web.xml文件來(lái)做。清單 3 顯示了一個(gè)非常簡(jiǎn)單的web.xml片段,正是 它為我們的標(biāo)記庫(kù)做這樣的事情。 清單 3. 將一個(gè)URI與一個(gè)標(biāo)記庫(kù)鏈接起來(lái)
<taglib>
<taglib-uri>http://www.newInstance.com/taglibs/site-utils</taglib-uri>
<taglib-location>/WEB-INF/tlds/site-utils.tld</taglib-location>
</taglib>
|
包裝起來(lái) 如果您已經(jīng)按照上述步驟執(zhí)行了,那么現(xiàn)在您應(yīng)該能夠在JSP頁(yè)面中引用新標(biāo)記了。清單 4 向我們展示了新改進(jìn)的footer.jsp,這個(gè)文件中現(xiàn)在完全沒(méi)有scriptlet,也沒(méi)有指向具有scriptlet的JSP頁(yè)面的引用。 清單 4. 使用新的標(biāo)記庫(kù)
<%@ taglib prefix="site-utils"
uri="http://www.newInstance.com/taglibs/site-utils" %>
</td>
<td width="16" align="left" valign="top"> </td>
</tr>
<!-- End main content -->
<!-- Begin footer section -->
<tr>
<td width="91" align="left" valign="top" bgcolor="#330066"> </td>
<td align="left" valign="top"> </td>
<td class="footer" align="left" valign="top"><div align="center"><br>
© 2003
<a href="mailto:webmaster@newInstance.com">Brett McLaughlin</a><br>
Last Updated: <site-utils:lastModified />
</div></td>
<td align="left" valign="top"> </td>
<td width="141" align="right" valign="top" bgcolor="#330066"> </td>
</tr>
</table>
<!-- End footer section -->
|
在前面幾期中看了JSTL如何工作(參見(jiàn)“利用JSTL更新您的JSP頁(yè)面”和“導(dǎo)入內(nèi)容到您的Web站點(diǎn)”)之后,接下來(lái)該做什么您應(yīng)該很清楚了:我們通過(guò)使用web.xml文件中的URI來(lái)引用這個(gè)標(biāo)記庫(kù),為之分配一個(gè)前綴(來(lái)自TLD的 short-name 始終是最好的選擇),然后就像使用任何其他JSP標(biāo)記一樣使用這個(gè)標(biāo)記。最終得到的是一個(gè)簡(jiǎn)潔的、更好的JSP頁(yè)面,這個(gè)JSP頁(yè)面運(yùn)行起來(lái)不比有 scriptlet 的時(shí)候差。
當(dāng)前我們的自定義標(biāo)記還很簡(jiǎn)單,在下一期,我們將擴(kuò)展其功能。到時(shí)候網(wǎng)上見(jiàn)。
參考資料
關(guān)于作者
Brett McLaughlin 自Logo時(shí)期(還記得那個(gè)小三角形嗎?)起就從事計(jì)算機(jī)方面的工作。目前,他專攻使用Java和Java相關(guān)的技術(shù)構(gòu)建應(yīng)用基礎(chǔ)設(shè)施。最近幾年,他一直在Nextel Communications and Allegiance Telecom公司,致力于實(shí)現(xiàn)這些基礎(chǔ)設(shè)施。Brett是Java Apache項(xiàng)目Turbine的合創(chuàng)者之一,Turbine為使用Java servlets的Web應(yīng)用的開(kāi)發(fā)構(gòu)建了可重用組件基礎(chǔ)設(shè)施。對(duì)于EJBoss項(xiàng)目(一種開(kāi)源EJB應(yīng)用服務(wù)器)和Cocoon(一種開(kāi)源XML Web發(fā)布引擎),他也作出了貢獻(xiàn)。您可以通過(guò)brett@oreilly.com與Contact Brett 聯(lián)系。 | |