XML和JSP是這些日子中最熱的東西。本文介紹如何聯(lián)合這兩種技術(shù)來(lái)建設(shè)動(dòng)態(tài)網(wǎng)站。你還可以同時(shí)看一下DOM,XPath,XSL,和其它Java-XML技術(shù)的示例代碼。
我們?cè)诖思僭O(shè)你已經(jīng)了解JavaServer Pages(JSP)和Extensible Markup Language (XML)。但也許你對(duì)該如何綜合使用它們?nèi)匀挥行┟曰蟆?/div>
"等一下!"你可能問(wèn),"你用XML文件存儲(chǔ)數(shù)據(jù)嗎?為什么不使用數(shù)據(jù)庫(kù)?"
這個(gè)問(wèn)題問(wèn)的很好。我的回答是,對(duì)很多目的用途來(lái)說(shuō),用數(shù)據(jù)庫(kù)太過(guò)浪費(fèi)了。.要使用一個(gè)數(shù)據(jù)庫(kù),你必須安裝和支持一個(gè)分離的服務(wù)器處理進(jìn)程(a separate server process),它常要求有安裝和支持它的administrator。你必須學(xué)習(xí)SQL, 并用SQL寫查詢,然后轉(zhuǎn)換數(shù)據(jù),再返回。而如果你用XML文件存儲(chǔ)數(shù)據(jù),將可減少額外的服務(wù)器的負(fù)荷。還有,你還找到了一個(gè)編輯數(shù)據(jù)的簡(jiǎn)單方法。你只要使用文本編輯器,而不必使用復(fù)雜的數(shù)據(jù)庫(kù)工具。XML文件很容易備份,和朋友共享,或下載到你的客戶端。同樣的,你可以方便地通過(guò)ftp上載新的數(shù)據(jù)到你的站點(diǎn)。
所有人都喜歡照相!他們喜歡展示自己的,親人的,朋友的,度假時(shí)的照片,而 Web 是他們展示的好地方。-- 即使千里之外的親戚都可以看到。我將著重于定義一個(gè)單獨(dú)的Picture對(duì)象。(這一應(yīng)用的源代碼在Resources中可以取得) 。該對(duì)象描述了表示一張照片所需要的字段:title,date,一個(gè)可選的標(biāo)題,以及對(duì)圖片來(lái)源的一個(gè)指向。
注意,通過(guò)使用XML, 你將一張單獨(dú)圖片的全部信息放到了一個(gè)單獨(dú)的文件中,而不是將它分散放入3-4個(gè)表中。
我們將這稱為 .pix file
請(qǐng)注意這些技術(shù)將和一個(gè)你從另外來(lái)源取得的XML stream執(zhí)行得同樣出色,例如一個(gè)客戶端或者一個(gè)應(yīng)用服務(wù)器。
JSP規(guī)范有很多替身,不同的JSP產(chǎn)品表現(xiàn)也不盡相同,不同版本之間也有差別。我選擇了Tomcat,這基于以下原因:
它支持大多數(shù)最新的JSP/servlet規(guī)范
它受到 Sun和Apache認(rèn)同
你可以獨(dú)立運(yùn)行它而不需要另外配置一個(gè)Web服務(wù)器。
它是開放源碼的
DOM (Document Object Model)
DOM 代表文檔對(duì)象模型Document Object Model。它是瀏覽XML文檔的一種標(biāo)準(zhǔn)API,由World Wide Web Consortium (W3C)發(fā)展。 接口在org.w3c.dom包中,文檔參見W3C站點(diǎn)。
遺憾的是,雖然很標(biāo)準(zhǔn),DOM還是有兩大缺陷:
得到image子元素的值也同樣直接:
用JSP bean進(jìn)行model/view分離
所有上面的picture-dom.jsp代碼是很不吸引人的。雖然你可以在jsp中加入上萬(wàn)條java代碼,但這樣就沒(méi)有使用JSP JavaBeans方法看上去來(lái)得簡(jiǎn)單。javabean存放了大量Java代碼,并在JSP腳本中加以調(diào)用。
* bean在一個(gè)自定義的包中被定義--picturebeans。所有JSP bean必須要在某一個(gè)包中。大多數(shù)JSP引擎都不能從缺省包中發(fā)現(xiàn)類。
* 除了get方法,我也提供了set方法,當(dāng)前你只是讀取,但在將來(lái),你要讓用戶編輯圖片,所以你要規(guī)劃用語(yǔ)修改寫入屬性的功能。
* 由于縮略圖對(duì)象返回一個(gè)Image對(duì)象的java.util.List,所以要找到正確的縮略圖并不容易,如果用(Image)picture.getThumbnails().get(i)的話。
* 要制作Zoom In和 Zoom Out連接,你必須建立一個(gè)對(duì)同一頁(yè)面的遞歸的引用,但使用不同的參數(shù)。為了做到這一點(diǎn),你要使用request.getRequestURI()方法。這只為你提供了該servlet的路徑,沒(méi)有參數(shù),所以你可以在此補(bǔ)上你要的參數(shù)。
使用JSP bean
JSP規(guī)范定義了 <jsp:useBean>標(biāo)記來(lái)自動(dòng)實(shí)例化和使用JavaBeans。useBean標(biāo)記可以總是被嵌入的Java代碼取代,這里我也是這么做的。也是由于這樣的原因,人們有時(shí)質(zhì)問(wèn)使用 useBean 和setProperty標(biāo)記還有什么必要。這種做法的優(yōu)點(diǎn)是:
標(biāo)記語(yǔ)法有利于HTML設(shè)計(jì)者獨(dú)立工作。
或者,如果你在DOMBean中定義一個(gè)setFile(String)方法:
為了克服DOM APIs的一些不足,我創(chuàng)建了一個(gè)類,叫做XMLEntryList。這個(gè)類執(zhí)行了 Java Collections接口java.util.List,以及java.util.Map的get和put方法,它提供了一套更直觀的方法來(lái)在一個(gè)簡(jiǎn)單的XML樹結(jié)構(gòu)中往返移動(dòng)。你可以使用Collections API的標(biāo)準(zhǔn)抽象(abstraction)來(lái)進(jìn)行象獲得迭代或子視圖等。在EntryList的每一個(gè)入口都有一個(gè)鍵 key和一個(gè)值,就象Map一樣;鍵就是子結(jié)點(diǎn)(child nodes)的名字,而值要么是字符串,要么是下一級(jí)(child)XMLEntryLists。
盡管有很多優(yōu)點(diǎn),分析一個(gè)XML文件總是需要耗費(fèi)時(shí)間。為了改進(jìn)基于XML的應(yīng)用的性能,你需要使用某種緩存技術(shù)。這種緩存必須在內(nèi)存中保存XML對(duì)象,記住它們來(lái)自哪一個(gè)文件。如果對(duì)象被加載以后文件被修改了,那么對(duì)象需要重新加載。我開發(fā)過(guò)一個(gè)用于這種數(shù)據(jù)結(jié)構(gòu)的簡(jiǎn)單方法,叫做CachedFS.java。你可以供給一個(gè)CachedFS 調(diào)用返回功能(function),使用實(shí)際執(zhí)行xml分析的內(nèi)部類,將文件轉(zhuǎn)為一個(gè)對(duì)象。cache于是可以在內(nèi)存中存儲(chǔ)那個(gè)對(duì)象。
XPath
XPath在XML tree中是一個(gè)簡(jiǎn)單的用于定位node的語(yǔ)法。它比 DOM更容易使用,因?yàn)楫?dāng)你要轉(zhuǎn)入另一個(gè)node時(shí)不必每次都要產(chǎn)生方法調(diào)用,你可以把整個(gè)路徑嵌入到一個(gè)字符串中去,例如:
這篇文章討論了在JSP中嵌入Java來(lái)從XML node中展開數(shù)據(jù)。完成做同樣工作還可以有另外一種常見的模型:Extensible Stylesheet Language (XSL)。這一模型和JSP模型有著根本的不同。在JSP中,主要內(nèi)容是HTML,它包含了一些Java代碼片段;而在XSL中,主要內(nèi)容是XSL文檔,它包含了一些HTML片段。如果要討論XSL和 Java/JSP之間的關(guān)系,這里的空間已經(jīng)不夠了。在JavaWorld雜志中將有一篇文章來(lái)探討如何同時(shí)使用XSL和JSP。
在讀完這篇文章后,相信你應(yīng)該有了一個(gè)JSP-XML應(yīng)用及其強(qiáng)大威力的很好的思路及結(jié)構(gòu)認(rèn)識(shí)。然而你也要知道一些它的局限。
開發(fā)JSP-XML應(yīng)用中最令人煩悶的是為每一個(gè) XML schema中的元素element創(chuàng)建JavaBean。XML Data Binding 組織正在開發(fā)一種技術(shù),可以為每一個(gè)給定的schema自動(dòng)生成Java類。同樣的,我也開發(fā)了一種原型-開放源碼的Java-XML data binding技術(shù)。另外,IBM alphaWorks最近也推出了XML Master, 或稱為XMas,這是另一種XML-Java data binding系統(tǒng)。
JSP的應(yīng)用很容易,你可以用它設(shè)計(jì)網(wǎng)頁(yè),使之看起來(lái)似乎和HTML一樣。唯一的不同是JSP是動(dòng)態(tài)執(zhí)行的。例如,它們可以處理表單form和讀寫數(shù)據(jù)庫(kù)。
XML的應(yīng)用的說(shuō)明則比較困難。似乎所有的產(chǎn)品都支持它,每個(gè)人也好象都以各種不同目的在使用它。
XML的應(yīng)用的說(shuō)明則比較困難。似乎所有的產(chǎn)品都支持它,每個(gè)人也好象都以各種不同目的在使用它。
在本文中,你可以看到如何使用一種相當(dāng)先進(jìn)的方式用XML來(lái)設(shè)計(jì)一個(gè)系統(tǒng)。許多站點(diǎn)有巨量數(shù)據(jù)收集并以一種很標(biāo)準(zhǔn)或很不標(biāo)準(zhǔn)的方式來(lái)顯示它們。我將設(shè)計(jì)一個(gè)系統(tǒng),它使用XML文件在web服務(wù)器上進(jìn)行存儲(chǔ),并用JSP來(lái)顯示數(shù)據(jù)。
XML vs 關(guān)系型數(shù)據(jù)庫(kù)
"等一下!"你可能問(wèn),"你用XML文件存儲(chǔ)數(shù)據(jù)嗎?為什么不使用數(shù)據(jù)庫(kù)?"
這個(gè)問(wèn)題問(wèn)的很好。我的回答是,對(duì)很多目的用途來(lái)說(shuō),用數(shù)據(jù)庫(kù)太過(guò)浪費(fèi)了。.要使用一個(gè)數(shù)據(jù)庫(kù),你必須安裝和支持一個(gè)分離的服務(wù)器處理進(jìn)程(a separate server process),它常要求有安裝和支持它的administrator。你必須學(xué)習(xí)SQL, 并用SQL寫查詢,然后轉(zhuǎn)換數(shù)據(jù),再返回。而如果你用XML文件存儲(chǔ)數(shù)據(jù),將可減少額外的服務(wù)器的負(fù)荷。還有,你還找到了一個(gè)編輯數(shù)據(jù)的簡(jiǎn)單方法。你只要使用文本編輯器,而不必使用復(fù)雜的數(shù)據(jù)庫(kù)工具。XML文件很容易備份,和朋友共享,或下載到你的客戶端。同樣的,你可以方便地通過(guò)ftp上載新的數(shù)據(jù)到你的站點(diǎn)。
XML還有一個(gè)更抽象的優(yōu)點(diǎn),即作為層次型的格式比關(guān)系型的更好。它可以用一種很直接的方式來(lái)設(shè)計(jì)數(shù)據(jù)結(jié)構(gòu)來(lái)符合你的需要。你不需要使用一個(gè)實(shí)體-關(guān)系編輯器,也不需要使你的圖表(schema)標(biāo)準(zhǔn)化。如果你有一個(gè)元素(element)包含了另一個(gè)元素,你可以直接在格式中表示它,而不需要使用表的關(guān)聯(lián)。
注意,在很多應(yīng)用中,依靠文件系統(tǒng)是不夠充分的。如果更新很多,文件系統(tǒng)會(huì)因?yàn)橥瑫r(shí)寫入而受到破壞。數(shù)據(jù)庫(kù)則通常支持事務(wù)處理,可以應(yīng)付所發(fā)生的請(qǐng)求而不至于損壞。對(duì)于復(fù)雜的查詢統(tǒng)計(jì)要有反復(fù)、及時(shí)的更新,此時(shí)數(shù)據(jù)庫(kù)表現(xiàn)都很優(yōu)秀。當(dāng)然,關(guān)系型數(shù)據(jù)庫(kù)還有很多優(yōu)點(diǎn),包括豐富的查詢語(yǔ)言,圖表化工具,可伸縮性,存取控制等等。
(注意:你可以使用簡(jiǎn)單的文件鎖定來(lái)提供一個(gè)事務(wù)處理服務(wù)器,你還可以在java中執(zhí)行一種 XML index-and-search工具,不過(guò)這已經(jīng)是另外一篇文章的主題了。)
在下面這樣的案例中,正如大多數(shù)中小規(guī)模的、基于發(fā)布信息的站點(diǎn)一樣,你可能涉及的大多數(shù)數(shù)據(jù)存取都是讀,而不是寫,數(shù)據(jù)雖然可能很大,但相對(duì)來(lái)說(shuō)并沒(méi)有經(jīng)常的更新變化,你也不需要做很復(fù)雜的查詢,即使你需要做,也將用一個(gè)獨(dú)立的查詢工具,那么成熟的RDBMS的優(yōu)點(diǎn)消失了,而面向?qū)ο笮偷臄?shù)據(jù)模型的優(yōu)點(diǎn)則可以得到體現(xiàn)。
最后,為你的數(shù)據(jù)庫(kù)提供一個(gè)查詢器外殼來(lái)進(jìn)行SQL查詢并將他們轉(zhuǎn)化進(jìn)入XML stream也是完全有可能的。
所以你可以選擇這二種方式之一。XML正變成一種非常健壯的,便于編程的工具,作為某個(gè)成熟的數(shù)據(jù)庫(kù)的前端工具來(lái)進(jìn)行存儲(chǔ)和查詢。(Oracle的XSQL servlet即是這種技術(shù)的一個(gè)很好的例子。)
應(yīng)用篇:一個(gè)在線相冊(cè)
所有人都喜歡照相!他們喜歡展示自己的,親人的,朋友的,度假時(shí)的照片,而 Web 是他們展示的好地方。-- 即使千里之外的親戚都可以看到。我將著重于定義一個(gè)單獨(dú)的Picture對(duì)象。(這一應(yīng)用的源代碼在Resources中可以取得) 。該對(duì)象描述了表示一張照片所需要的字段:title,date,一個(gè)可選的標(biāo)題,以及對(duì)圖片來(lái)源的一個(gè)指向。
一個(gè)圖象,需要它自己的一些字段:源文件( GIF/JPEG)的定位,寬度和高度像素(以協(xié)助建立標(biāo)記。這里可以看到一個(gè)很簡(jiǎn)單的優(yōu)點(diǎn),即使用文件系統(tǒng)來(lái)代替數(shù)據(jù)庫(kù)的時(shí)候,你可以將圖形文件存放在與數(shù)據(jù)文件相同的目錄中。
最后,讓我們來(lái)用一個(gè)元素?cái)U(kuò)展圖片記錄,該元素定義了一套縮略圖(thumbnail)來(lái)用于內(nèi)容表或其它地方。這里我用了和先前同樣定義的圖片內(nèi)容。
一張圖片的XML表示可以是這樣的:
<picture>
<title>Alex On The Beach</title>
<date>1999-08-08</date>
<caption>Trying in vain to get a tan</caption>
<image>
<src>alex-beach.jpg</src>
<width>340</width>
<height>200</height>
</image>
<thumbnails>
<image>
<src>alex-beach-sm.jpg</src>
<width>72</width>
<height>72</height>
</image>
<image>
<src>alex-beach-med.jpg</src>
<width>150</width>
<height>99</height>
</image>
</thumbnails>
</picture>
<title>Alex On The Beach</title>
<date>1999-08-08</date>
<caption>Trying in vain to get a tan</caption>
<image>
<src>alex-beach.jpg</src>
<width>340</width>
<height>200</height>
</image>
<thumbnails>
<image>
<src>alex-beach-sm.jpg</src>
<width>72</width>
<height>72</height>
</image>
<image>
<src>alex-beach-med.jpg</src>
<width>150</width>
<height>99</height>
</image>
</thumbnails>
</picture>
注意,通過(guò)使用XML, 你將一張單獨(dú)圖片的全部信息放到了一個(gè)單獨(dú)的文件中,而不是將它分散放入3-4個(gè)表中。
我們將這稱為 .pix file
-- 于是你的文件系統(tǒng)會(huì)是這樣的:
summer99/alex-beach.pix
summer99/alex-beach.jpg
summer99/alex-beach-sm.jpg
summer99/alex-beach-med.jpg
summer99/alex-snorkeling.pix
etc.
summer99/alex-beach.jpg
summer99/alex-beach-sm.jpg
summer99/alex-beach-med.jpg
summer99/alex-snorkeling.pix
etc.
技術(shù)篇
俗話說(shuō),要?jiǎng)兿仑埖钠さ姆椒ê沃挂环N。同樣,將XML數(shù)據(jù)放到JSP中也不止一種辦法。這里列舉了其中一些方法,(其實(shí),很多其它工具也可以做得同樣出色。)
DOM: 你可以使用類(classes)來(lái)調(diào)用DOM接口(interface)對(duì)XML文件進(jìn)行分析檢查。
XMLEntryList: 你可以使用我的代碼來(lái)將XML加載到name-value pairs 的java.util.List中。
XMLEntryList: 你可以使用我的代碼來(lái)將XML加載到name-value pairs 的java.util.List中。
XPath: 你可以使用一個(gè) XPath處理器(如Resin)通過(guò)路徑名在XML文件中定位元素。
XSL:你可以使用某種XSL處理器將XML轉(zhuǎn)換成為HTML。
Cocoon: 你可以使用開放源碼的Cocoon framework
運(yùn)行你自己的bean: 你可以寫一個(gè)外殼類(wrapper class),使用某種其它技術(shù)來(lái)將數(shù)據(jù)加載到字定義的JavaBean中。
請(qǐng)注意這些技術(shù)將和一個(gè)你從另外來(lái)源取得的XML stream執(zhí)行得同樣出色,例如一個(gè)客戶端或者一個(gè)應(yīng)用服務(wù)器。
JavaServer Pages
JSP規(guī)范有很多替身,不同的JSP產(chǎn)品表現(xiàn)也不盡相同,不同版本之間也有差別。我選擇了Tomcat,這基于以下原因:
它支持大多數(shù)最新的JSP/servlet規(guī)范
它受到 Sun和Apache認(rèn)同
你可以獨(dú)立運(yùn)行它而不需要另外配置一個(gè)Web服務(wù)器。
它是開放源碼的
你可以選擇任何你喜歡的JSP引擎,但要自己配置它,它必須至少支持JSP 1.0規(guī)范。0.91和1.0之間有了許多區(qū)別。而JSWDK (Java Server Web Development Kit) 可能剛剛好地適合要求。
JSP結(jié)構(gòu)
當(dāng)創(chuàng)建一個(gè)jsp網(wǎng)站 (Webapp), 我喜歡將公用的函數(shù)、導(dǎo)入、常量、變量聲明都放入到一個(gè)單獨(dú)的文件init.jsp中。然后用 <%@include file="init.jsp"%>加載到每一個(gè)文件中去。 <%@include%> 就象C語(yǔ)言的 #include, include在編譯時(shí)使其中的文本作為一個(gè)部分被加入并一起進(jìn)行編譯,相對(duì)地, <jsp:include>標(biāo)記則是使其中的文件被獨(dú)立地進(jìn)行編譯,然后在文件中嵌入一個(gè)對(duì)它的調(diào)用。
查找文件
當(dāng)JSP啟動(dòng)時(shí),初始化后第一件事情就是查找你要的XML文件。它是怎么知道在眾多文件中你要找的是哪一個(gè)? 它來(lái)自與一個(gè)參數(shù),使用者會(huì)在調(diào)用jsp的URL中加入?yún)?shù): picture.jsp?file=summer99/alex-beach.pix (或者通過(guò)HTML表單來(lái)傳遞文件參數(shù))。
但是,當(dāng)JSP接受此參數(shù)以后,你仍然只完成了一半工作,因?yàn)檫€要知道文件系統(tǒng)的根目錄在哪里。例如,在Unix系統(tǒng)中,實(shí)際文件可能在這樣的路徑:
當(dāng)JSP啟動(dòng)時(shí),初始化后第一件事情就是查找你要的XML文件。它是怎么知道在眾多文件中你要找的是哪一個(gè)? 它來(lái)自與一個(gè)參數(shù),使用者會(huì)在調(diào)用jsp的URL中加入?yún)?shù): picture.jsp?file=summer99/alex-beach.pix (或者通過(guò)HTML表單來(lái)傳遞文件參數(shù))。
但是,當(dāng)JSP接受此參數(shù)以后,你仍然只完成了一半工作,因?yàn)檫€要知道文件系統(tǒng)的根目錄在哪里。例如,在Unix系統(tǒng)中,實(shí)際文件可能在這樣的路徑:
/home/alex/public_html/pictures/summer99/alex-beach.pix。
JSP文件在執(zhí)行狀態(tài)時(shí)沒(méi)有當(dāng)前路徑概念。所以你為java.io包要給出一個(gè)絕對(duì)路徑。
Servlet API可以提供一個(gè)方法來(lái)將一個(gè)URL路徑,從相對(duì)于當(dāng)前JSP或Servlet的路徑轉(zhuǎn)化成為一個(gè)絕對(duì)的文件系統(tǒng)路徑。方法是:
ServletContext.getRealPath(String)。
每一個(gè)JSP有一個(gè)叫做application的 ServletContext對(duì)象。所以代碼可以是:
String picturefile =
application.getRealPath("/" + request.getParameter("file"));
String picturefile =
application.getRealPath("/" + request.getParameter("file"));
或者
String picturefile =
getServletContext().getRealPath("/" + request.getParameter("file"));
String picturefile =
getServletContext().getRealPath("/" + request.getParameter("file"));
它也可以在servlet中工作。(你必須加上 / 因?yàn)榇朔椒ㄐ枰獋鬟frequest.getPathInfo()的結(jié)果。)
這里有一個(gè)重要的提示:每當(dāng)你存取本地的資源,要非常小心地檢查輸入數(shù)據(jù)的合法性。黑客或者粗心的用戶,可能發(fā)送偽造的或錯(cuò)誤的數(shù)據(jù)來(lái)破壞你的站點(diǎn)。例如,請(qǐng)想一下以下的表達(dá)會(huì)發(fā)生什么結(jié)果:
如果輸入了file=../../../../etc/passwd。這樣用戶回讀到你的服務(wù)器的password文件!
DOM (Document Object Model)
DOM 代表文檔對(duì)象模型Document Object Model。它是瀏覽XML文檔的一種標(biāo)準(zhǔn)API,由World Wide Web Consortium (W3C)發(fā)展。 接口在org.w3c.dom包中,文檔參見W3C站點(diǎn)。
有許多可用的DOM分析器工具。我選擇了 IBM的XML4J。但你可以使用任何其它的DOM分析器。這是因?yàn)镈OM是一套接口,而不是類 --所有的DOM分析器(parser)必須返回同樣地處理這些接口的對(duì)象。
遺憾的是,雖然很標(biāo)準(zhǔn),DOM還是有兩大缺陷:
1 API雖然也是面向?qū)ο蟮模€是相當(dāng)笨重。
DOM parser并沒(méi)有標(biāo)準(zhǔn)的API,所以,當(dāng)每一個(gè)分析器返回一個(gè)org.w3c.dom對(duì)象,文檔對(duì)象--分析器初始化和文件自身加載的方式,對(duì)應(yīng)于不同分析器通常總是特定的。
這個(gè)簡(jiǎn)單的上面已描述的圖片文件在DOM中可以在一個(gè)樹結(jié)構(gòu)中通過(guò)一些對(duì)象表示如下:
Document Node
--> Element Node "picture"
--> Text Node "\n " (whitespace)
--> Element Node "title"
--> Text Node "Alex On The Beach"
--> Element Node "date"
--> ... etc.
為了取得“Alex On The Beach”,你要做一些方法調(diào)用,游歷DOM樹,而且,分析器可能選擇分散“whitespace”文本nodes的一些數(shù)據(jù),你不得不使用循環(huán)和串聯(lián)等(你可以通過(guò)調(diào)用normalize()來(lái)糾正這個(gè)問(wèn)題。)分析器可能還包含了分離的XML實(shí)體(如 &), CDATA nodes或者其它實(shí)體nodes (如big會(huì)被變成至少三個(gè)node。也沒(méi)有辦法在DOM中簡(jiǎn)單表示"get me the text value of the title element." 總之,在DOM中游歷有一點(diǎn)笨重。(參見本文用XPath取代DOM章節(jié)。)
這個(gè)簡(jiǎn)單的上面已描述的圖片文件在DOM中可以在一個(gè)樹結(jié)構(gòu)中通過(guò)一些對(duì)象表示如下:
Document Node
--> Element Node "picture"
--> Text Node "\n " (whitespace)
--> Element Node "title"
--> Text Node "Alex On The Beach"
--> Element Node "date"
--> ... etc.
為了取得“Alex On The Beach”,你要做一些方法調(diào)用,游歷DOM樹,而且,分析器可能選擇分散“whitespace”文本nodes的一些數(shù)據(jù),你不得不使用循環(huán)和串聯(lián)等(你可以通過(guò)調(diào)用normalize()來(lái)糾正這個(gè)問(wèn)題。)分析器可能還包含了分離的XML實(shí)體(如 &), CDATA nodes或者其它實(shí)體nodes (如big會(huì)被變成至少三個(gè)node。也沒(méi)有辦法在DOM中簡(jiǎn)單表示"get me the text value of the title element." 總之,在DOM中游歷有一點(diǎn)笨重。(參見本文用XPath取代DOM章節(jié)。)
2 從更高處看,DOM的問(wèn)題是XML對(duì)象無(wú)法象Java對(duì)象一樣可以直接得到,它們需要通過(guò) DOM API一個(gè)一個(gè)地得到。
你可以參考我的為Java-XML Data Binding技術(shù)討論做的一些歸納,那里也用了這種直接使用Java的方法來(lái)存取XML數(shù)據(jù)。
我寫了一個(gè)小的工具類,叫做DOMUtils,包含了靜態(tài)方法來(lái)執(zhí)行公用的DOM任務(wù)。例如,要獲得根(圖片)元素的title子元素的文本內(nèi)容,你可以編寫如下代碼:
Document doc = DOMUtils.xml4jParse(picturefile);
Element nodeRoot = doc.getDocumentElement();
Node nodeTitle = DOMUtils.getChild(nodeRoot, "title");
String title = (nodeTitle == null) ? null : DOMUtils.getTextValue(nodeTitle);
Element nodeRoot = doc.getDocumentElement();
Node nodeTitle = DOMUtils.getChild(nodeRoot, "title");
String title = (nodeTitle == null) ? null : DOMUtils.getTextValue(nodeTitle);
得到image子元素的值也同樣直接:
Node nodeImage = DOMUtils.getChild(nodeRoot, "image");
Node nodeSrc = DOMUtils.getChild(nodeImage, "src");
String src = DOMUtils.getTextValue(nodeSrc);
Node nodeSrc = DOMUtils.getChild(nodeImage, "src");
String src = DOMUtils.getTextValue(nodeSrc);
等等。
一旦你需要將Java變量用于每一個(gè)相關(guān)的元素,你必須做的是將變量嵌入到 HTML 標(biāo)記中去:
<table bgcolor="#FFFFFF" border="0" cellspacing="0" cellpadding="5">
<tr>
<td align="center" valign="center">
<img src="<%=src%>" width="<%=width%>" height="<%=height%>" border="0" alt="<%=src%>"></td>
</tr>
</table>
<tr>
<td align="center" valign="center">
<img src="<%=src%>" width="<%=width%>" height="<%=height%>" border="0" alt="<%=src%>"></td>
</tr>
</table>
用JSP bean進(jìn)行model/view分離
所有上面的picture-dom.jsp代碼是很不吸引人的。雖然你可以在jsp中加入上萬(wàn)條java代碼,但這樣就沒(méi)有使用JSP JavaBeans方法看上去來(lái)得簡(jiǎn)單。javabean存放了大量Java代碼,并在JSP腳本中加以調(diào)用。
為了制作一個(gè)模型,將所有java代碼放在JSP中來(lái)開始一個(gè)項(xiàng)目,這樣相對(duì)比較簡(jiǎn)單。一旦有什么新的想法可以直接回去展開代碼,然后改寫為一些JavaBeans。雖然投資較高,但長(zhǎng)遠(yuǎn)來(lái)看回報(bào)更好,因?yàn)槟愕膽?yīng)用更為模型化。你可以在多個(gè)頁(yè)面中重復(fù)使用bean而不用擔(dān)心剪貼帶來(lái)的不良后果。
在我們的案例中,一個(gè)典型的JSP JavaBean是從XML中展開字符串。你可以定義Picture, Image和Thumbnails類,來(lái)表示主要的XML文件中的元素。這些beans帶有構(gòu)造器constructors或者setter方法,它們從展開值中帶入一個(gè)DOM node或一個(gè)文件名。你可以參考picturebeans包或picture-beans.jsp.
在看代碼時(shí)請(qǐng)注意以下幾點(diǎn):
* 我將接口的定義獨(dú)立于類的執(zhí)行,這樣你可以很自由地選擇在將來(lái)進(jìn)行替代,你可以將值存放在一個(gè)List中,或者在DOM自身,甚至可以在數(shù)據(jù)庫(kù)中。
* bean在一個(gè)自定義的包中被定義--picturebeans。所有JSP bean必須要在某一個(gè)包中。大多數(shù)JSP引擎都不能從缺省包中發(fā)現(xiàn)類。
* 除了get方法,我也提供了set方法,當(dāng)前你只是讀取,但在將來(lái),你要讓用戶編輯圖片,所以你要規(guī)劃用語(yǔ)修改寫入屬性的功能。
* <%=picture.getCaption()%>取代了 <%=caption%>, 因?yàn)橹凳谴娣旁谝粋€(gè)bean中而不是本地變量中。但是,如果你需要,你可以將本地變量定義為
String caption = picture.getCaption(); 這也是允許的,這可以使代碼更容易閱讀和理解。
* 通過(guò)thumbnails放大縮小
你可能已經(jīng)注意到我的第一個(gè)的 JSP的輸出, picture-dom.html,使用了全部大小的源圖片文件。我們可以稍微修改一下代碼,使它顯示一個(gè)縮略圖,我將把縮略圖列表存放在XML數(shù)據(jù)文件中。
讓我們定義一個(gè)參數(shù),zoom,它的值決定了要顯示哪一個(gè)縮略圖。點(diǎn)擊這個(gè)縮略圖后回顯示全部大小的圖片;點(diǎn)擊Zoom In或Zoom Out按鈕將選擇列表中下/上一種縮略圖。
你可能已經(jīng)注意到我的第一個(gè)的 JSP的輸出, picture-dom.html,使用了全部大小的源圖片文件。我們可以稍微修改一下代碼,使它顯示一個(gè)縮略圖,我將把縮略圖列表存放在XML數(shù)據(jù)文件中。
讓我們定義一個(gè)參數(shù),zoom,它的值決定了要顯示哪一個(gè)縮略圖。點(diǎn)擊這個(gè)縮略圖后回顯示全部大小的圖片;點(diǎn)擊Zoom In或Zoom Out按鈕將選擇列表中下/上一種縮略圖。
* 由于縮略圖對(duì)象返回一個(gè)Image對(duì)象的java.util.List,所以要找到正確的縮略圖并不容易,如果用(Image)picture.getThumbnails().get(i)的話。
* 要制作Zoom In和 Zoom Out連接,你必須建立一個(gè)對(duì)同一頁(yè)面的遞歸的引用,但使用不同的參數(shù)。為了做到這一點(diǎn),你要使用request.getRequestURI()方法。這只為你提供了該servlet的路徑,沒(méi)有參數(shù),所以你可以在此補(bǔ)上你要的參數(shù)。
<%
if (zoom < (thumbnails.size() -1)) {
out.print("<a href=\" +
request.getRequestURI() +
"?file=" + request.getParameter("file") +
"&zoom=" + (zoom+1) +
">");
out.print("Zoom In");
}
%>
if (zoom < (thumbnails.size() -1)) {
out.print("<a href=\" +
request.getRequestURI() +
"?file=" + request.getParameter("file") +
"&zoom=" + (zoom+1) +
">");
out.print("Zoom In");
}
%>
這里是一個(gè)HTML的屏幕拷貝。
使用JSP bean
JSP規(guī)范定義了 <jsp:useBean>標(biāo)記來(lái)自動(dòng)實(shí)例化和使用JavaBeans。useBean標(biāo)記可以總是被嵌入的Java代碼取代,這里我也是這么做的。也是由于這樣的原因,人們有時(shí)質(zhì)問(wèn)使用 useBean 和setProperty標(biāo)記還有什么必要。這種做法的優(yōu)點(diǎn)是:
標(biāo)記語(yǔ)法有利于HTML設(shè)計(jì)者獨(dú)立工作。
useBean有一個(gè)scope參數(shù),可以自動(dòng)地決定bean是否必須存儲(chǔ)為一個(gè)本地變量,一個(gè) session變量或一個(gè)application屬性。
如果這個(gè)變量是持久的(session或application),useBean必要時(shí)可以將它初始化,并切在它確實(shí)存在的時(shí)候才去取得變量。
長(zhǎng)遠(yuǎn)看標(biāo)記對(duì)今后的JSP規(guī)范版本來(lái)說(shuō)更為便攜(portable)或者更改執(zhí)行(例如,, 一個(gè)假定的JSP引擎在一個(gè)數(shù)據(jù)庫(kù)中存儲(chǔ)了變量,或者跨服務(wù)器共享數(shù)據(jù)。)
這個(gè)應(yīng)用中對(duì)應(yīng)的useBean語(yǔ)句為:
這個(gè)應(yīng)用中對(duì)應(yīng)的useBean語(yǔ)句為:
<jsp:useBean id="picture" scope="request" class="picturebeans.DOMPicture">
<%
Document doc = DOMUtils.xml4jParse(picturefile);
Element nodeRoot = doc.getDocumentElement();
nodeRoot.normalize();
picture.setNode(nodeRoot);
%>
</jsp:useBean>
<%
Document doc = DOMUtils.xml4jParse(picturefile);
Element nodeRoot = doc.getDocumentElement();
nodeRoot.normalize();
picture.setNode(nodeRoot);
%>
</jsp:useBean>
或者,如果你在DOMBean中定義一個(gè)setFile(String)方法:
<jsp:useBean id="picture" scope="request" class="picturebeans.DOMPicture">
<jsp:setProperty name="picture" property="file" value="<%=picturefile%>"/>
</jsp:useBean>
<jsp:setProperty name="picture" property="file" value="<%=picturefile%>"/>
</jsp:useBean>
使用XMLEntryList
為了克服DOM APIs的一些不足,我創(chuàng)建了一個(gè)類,叫做XMLEntryList。這個(gè)類執(zhí)行了 Java Collections接口java.util.List,以及java.util.Map的get和put方法,它提供了一套更直觀的方法來(lái)在一個(gè)簡(jiǎn)單的XML樹結(jié)構(gòu)中往返移動(dòng)。你可以使用Collections API的標(biāo)準(zhǔn)抽象(abstraction)來(lái)進(jìn)行象獲得迭代或子視圖等。在EntryList的每一個(gè)入口都有一個(gè)鍵 key和一個(gè)值,就象Map一樣;鍵就是子結(jié)點(diǎn)(child nodes)的名字,而值要么是字符串,要么是下一級(jí)(child)XMLEntryLists。
XMLEntryList并不意味著可以完全代替DOM。它還無(wú)法執(zhí)行某些DOM的功能。但是,它是一個(gè)方便的外殼(wrapper)在你的XML數(shù)據(jù)結(jié)構(gòu)上用于執(zhí)行基本的getting, setting和 list-oriented 功能。例如,你可以使用這樣的寫法來(lái)得到圖片node的caption元素:
String caption = (String)picturelist.get("caption");
caption字段的值早已被作為一個(gè)字符串分析和存儲(chǔ)了。
緩存 Caching
盡管有很多優(yōu)點(diǎn),分析一個(gè)XML文件總是需要耗費(fèi)時(shí)間。為了改進(jìn)基于XML的應(yīng)用的性能,你需要使用某種緩存技術(shù)。這種緩存必須在內(nèi)存中保存XML對(duì)象,記住它們來(lái)自哪一個(gè)文件。如果對(duì)象被加載以后文件被修改了,那么對(duì)象需要重新加載。我開發(fā)過(guò)一個(gè)用于這種數(shù)據(jù)結(jié)構(gòu)的簡(jiǎn)單方法,叫做CachedFS.java。你可以供給一個(gè)CachedFS 調(diào)用返回功能(function),使用實(shí)際執(zhí)行xml分析的內(nèi)部類,將文件轉(zhuǎn)為一個(gè)對(duì)象。cache于是可以在內(nèi)存中存儲(chǔ)那個(gè)對(duì)象。
這里是創(chuàng)建cache的代碼,這一對(duì)象有application scope,所以此后的請(qǐng)求可以使用同一對(duì)象cache。我將把這些代碼放到init.jsp,這樣你就不必將這些初始化的代碼剪貼到其他JSP文件中去了。總之,你必須在一個(gè)公共的地方定義application-scope對(duì)象。
<jsp:useBean id="cache" class="com.purpletech.io.CachedFS" scope="application">
<% cache.setRoot(application.getRealPath("/"));
cache.setLoader( new CachedFS.Loader() {
// load in a single Picture file
public Object process(String path, InputStream in) throws IOException
{
try {
Document doc = DOMUtils.xml4jParse
(new BufferedReader(new InputStreamReader(in)));
Element nodeRoot = doc.getDocumentElement();
nodeRoot.normalize();
Picture picture = new DOMPicture(nodeRoot);
return picture;
}
catch (XMLException e) {
e.printStackTrace();
throw new IOException(e.getMessage());
}
}
});
%>
</jsp:useBean>
<% cache.setRoot(application.getRealPath("/"));
cache.setLoader( new CachedFS.Loader() {
// load in a single Picture file
public Object process(String path, InputStream in) throws IOException
{
try {
Document doc = DOMUtils.xml4jParse
(new BufferedReader(new InputStreamReader(in)));
Element nodeRoot = doc.getDocumentElement();
nodeRoot.normalize();
Picture picture = new DOMPicture(nodeRoot);
return picture;
}
catch (XMLException e) {
e.printStackTrace();
throw new IOException(e.getMessage());
}
}
});
%>
</jsp:useBean>
XPath
XPath在XML tree中是一個(gè)簡(jiǎn)單的用于定位node的語(yǔ)法。它比 DOM更容易使用,因?yàn)楫?dāng)你要轉(zhuǎn)入另一個(gè)node時(shí)不必每次都要產(chǎn)生方法調(diào)用,你可以把整個(gè)路徑嵌入到一個(gè)字符串中去,例如:
/picture/thumbnails/image[2].
Resin產(chǎn)品包含了一個(gè)XPath處理器,你可以將它加入到自己的應(yīng)用中去。你可以使用 Caucho XPath對(duì)象加載于其自身,不必購(gòu)買Resin體系的其它產(chǎn)品。
Node verse = XPath.find("chapter/verse", node);
Resin也包含有一個(gè)腳本語(yǔ)言,與JavaScript兼容,允許在jsp中對(duì)XPath和XSL的簡(jiǎn)單存取。
XSL
這篇文章討論了在JSP中嵌入Java來(lái)從XML node中展開數(shù)據(jù)。完成做同樣工作還可以有另外一種常見的模型:Extensible Stylesheet Language (XSL)。這一模型和JSP模型有著根本的不同。在JSP中,主要內(nèi)容是HTML,它包含了一些Java代碼片段;而在XSL中,主要內(nèi)容是XSL文檔,它包含了一些HTML片段。如果要討論XSL和 Java/JSP之間的關(guān)系,這里的空間已經(jīng)不夠了。在JavaWorld雜志中將有一篇文章來(lái)探討如何同時(shí)使用XSL和JSP。
目前的結(jié)論和未來(lái)的發(fā)展之路
在讀完這篇文章后,相信你應(yīng)該有了一個(gè)JSP-XML應(yīng)用及其強(qiáng)大威力的很好的思路及結(jié)構(gòu)認(rèn)識(shí)。然而你也要知道一些它的局限。
開發(fā)JSP-XML應(yīng)用中最令人煩悶的是為每一個(gè) XML schema中的元素element創(chuàng)建JavaBean。XML Data Binding 組織正在開發(fā)一種技術(shù),可以為每一個(gè)給定的schema自動(dòng)生成Java類。同樣的,我也開發(fā)了一種原型-開放源碼的Java-XML data binding技術(shù)。另外,IBM alphaWorks最近也推出了XML Master, 或稱為XMas,這是另一種XML-Java data binding系統(tǒng)。
另外一種可能性是擴(kuò)展文件系統(tǒng)的功能,建立一些更加強(qiáng)大的功能,如查詢和事務(wù)處理。自然地,我也開始期望這種功能類型也可以作為開放源碼工程來(lái)得到發(fā)展。那么,有沒(méi)有誰(shuí)愿意寫一個(gè)XML搜索引擎?
只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。 | ||
![]() |
||
網(wǎng)站導(dǎo)航:
博客園
IT新聞
Chat2DB
C++博客
博問(wèn)
管理
|
||
相關(guān)文章:
|
||