概覽:
XML和JSP是這些日子中最熱的東西。本文介紹如何聯合這兩
種技術來建設動態網站。你還可以同時看一下DOM,XPath,XSL,
和其它Java-XML技術的示例代碼。
我們在此假設你已經了解JavaServer Pages(JSP)和Extensible
Markup Language (XML)。但也許你對該如何綜合使用它們仍然有些
迷惑。
JSP的應用很容易,你可以用它設計網頁,使之看起來似乎和HTML一
樣。唯一的不同是JSP是動態執行的。例如,它們可以處理表單form
和讀寫數據庫。
XML的應用的說明則比較困難。似乎所有的產品都支持它,每個人也
好象都以各種不同目的在使用它。
在本文中,你可以看到如何使用一種相當先進的方式用XML來設計一
個系統。許多站點有巨量數據收集并以一種很標準或很不標準的方式
來顯示它們。我將設計一個系統,它使用XML文件在web服務器上進行
存儲,并用JSP來顯示數據。
XML vs 關系型數據庫
"等一下!"你可能問,"你用XML文件存儲數據嗎?為什么不使用數據庫?"
這個問題問的很好。我的回答是,對很多目的用途來說,用數據庫太
過浪費了。.要使用一個數據庫,你必須安裝和支持一個分離的服務器
處理進程(a separate server process),它常要求有安裝和支持它
的administrator。你必須學習SQL, 并用SQL寫查詢,然后轉換數據,
再返回。而如果你用XML文件存儲數據,將可減少額外的服務器的負
荷。還有,你還找到了一個編輯數據的簡單方法。你只要使用文本編
輯器,而不必使用復雜的數據庫工具。XML文件很容易備份,和朋友共
享,或下載到你的客戶端。同樣的,你可以方便地通過ftp上載新的
數據到你的站點。
XML還有一個更抽象的優點,即作為層次型的格式比關系型的更好。
它可以用一種很直接的方式來設計數據結構來符合你的需要。你不需
要使用一個實體-關系編輯器,也不需要使你的圖表(schema)標準
化。 如果你有一個元素(element)包含了另一個元素,你可以直接
在格式中表示它,而不需要使用表的關聯。
注意,在很多應用中,依靠文件系統是不夠充分的。如果更新很多,
文件系統會因為同時寫入而受到破壞。數據庫則通常支持事務處理,
可以應付所發生的請求而不至于損壞。對于復雜的查詢統計要有反復
、及時的更新,此時數據庫表現都很優秀。當然,關系型數據庫還
有很多優點,包括豐富的查詢語言,圖表化工具,可伸縮性,存取
控制等等。
(注意:你可以使用簡單的文件鎖定來提供一個事務處理服務器,你還
可以在java中執行一種 XML index-and-search工具,不過這已經是
另外一篇文章的主題了。)
在下面這樣的案例中,正如大多數中小規模的、基于發布信息的站
點一樣,你可能涉及的大多數數據存取都是讀,而不是寫,數據雖
然可能很大,但相對來說并沒有經常的更新變化,你也不需要做很
復雜的查詢,即使你需要做,也將用一個獨立的查詢工具,那么成
熟的RDBMS的優點消失了,而面向對象型的數據模型的優點則可以得
到體現。
最后,為你的數據庫提供一個查詢器外殼來進行SQL查詢并將他們轉
化進入XML stream也是完全有可能的。
所以你可以選擇這二種方式之一。XML正變成一種非常健壯的,便于
編程的工具,作為某個成熟的數據庫的前端工具來進行存儲和查詢。
(Oracle的XSQL servlet即是這種技術的一個很好的例子。)
應用篇:一個在線相冊
所有人都喜歡照相!他們喜歡展示自己的,親人的,朋友的,度假
時的照片,而 Web 是他們展示的好地方。-- 即使千里之外的親戚
都可以看到。我將著重于定義一個單獨的Picture對象。(這一應用的
源代碼在Resources中可以取得) 。該對象描述了表示一張照片所需
要的字段:title,date,一個可選的標題,以及對圖片來源的一個指
向。
一個圖象,需要它自己的一些字段:源文件( GIF/JPEG)的定位,寬
度和高度像素(以協助建立<img> 標記。 這里可以看到一個很簡單
優點,即使用文件系統來代替數據庫的時候,你可以將圖形文件存
放在與數據文件相同的目錄中。
最后,讓我們來用一個元素擴展圖片記錄,該元素定義了一套縮略
圖(thumbnail)來用于內容表或其它地方。這里我用了和先前同樣
定義的圖片內容。
一張圖片的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>
注意,通過使用XML, 你將一張單獨圖片的全部信息放到了一個單獨
的文件中,而不是將它分散放入3-4個表中。
我們將這稱為 .pix file
-- 于是你的文件系統會是這樣的:
summer99/alex-beach.pix
summer99/alex-beach.jpg
summer99/alex-beach-sm.jpg
summer99/alex-beach-med.jpg
summer99/alex-snorkeling.pix
etc.
技術篇
俗話說,要剝下貓的皮的方法何止一種。同樣,將XML數據放到JSP
中也不止一種辦法。這里列舉了其中一些方法,(其實,很多其它工
具也可以做得同樣出色。)
DOM: 你可以使用類(classes)來調用DOM接口(interface)對XML
文件進行分析檢查。
XMLEntryList: 你可以使用我的代碼來將XML加載到name-value
pairs 的java.util.List中。
XPath: 你可以使用一個 XPath處理器(如Resin)通過路徑名在XML
文件中定位元素。
XSL:你可以使用某種XSL處理器將XML轉換成為HTML。
Cocoon: 你可以使用開放源碼的Cocoon framework
運行你自己的bean: 你可以寫一個外殼類(wrapper class),使用某
種其它技術來將數據加載到字定義的JavaBean中。
請注意這些技術將和一個你從另外來源取得的XML stream執行得同樣
出色,例如一個客戶端或者一個應用服務器。
JavaServer Pages
JSP規范有很多替身,不同的JSP產品表現也不盡相同,不同版本之間
也有差別。我選擇了Tomcat,這基于以下原因:
它支持大多數最新的JSP/servlet規范
它受到 Sun和Apache認同
你可以獨立運行它而不需要另外配置一個Web服務器。
它是開放源碼的
你可以選擇任何你喜歡的JSP引擎,但要自己配置它,它必須至少支
持JSP 1.0規范。0.91和1.0之間有了許多區別。而JSWDK
(Java Server Web Development Kit) 可能剛剛好地適合要求。
JSP結構
當創建一個jsp網站 (Webapp), 我喜歡將公用的函數、導入、常量、
變量聲明都放入到一個單獨的文件init.jsp中。 然后用
<%@include file="init.jsp"%>加載到每一個文件中去。
<%@include%> 就象C語言的 #include, include在編譯時使其中的
文本作為一個部分被加入并一起進行編譯,相對地, <jsp:include>
標記則是使其中的文件被獨立地進行編譯,然后在文件中嵌入一個對
它的調用。
查找文件
當JSP啟動時,初始化后第一件事情就是查找你要的XML文件。它是怎
么知道在眾多文件中你要找的是哪一個? 它來自與一個參數,使用者
會在調用jsp的URL中加入參數: picture.jsp?file=summer99/alex-beach.pix
(或者通過HTML表單來傳遞文件參數)。
但是,當JSP接受此參數以后,你仍然只完成了一半工作,因為還要
知道文件系統的根目錄在哪里。例如,在Unix系統中,實際文件可能
在這樣的路徑:
/home/alex/public_html/pictures/summer99/alex-beach.pix。
JSP文件在執行狀態時沒有當前路徑概念。所以你為java.io包要給出
一個絕對路徑。
Servlet API可以提供一個方法來將一個URL路徑,從相對于當前JSP
或Servlet的路徑轉化成為一個絕對的文件系統路徑。方法是:
ServletContext.getRealPath(String)。
每一個JSP有一個叫做application的 ServletContext對象。所以代
碼可以是:
String picturefile =
application.getRealPath("/" + request.getParameter("file"));
或者
String picturefile =
getServletContext().getRealPath("/" + request.getParameter("file"));
它也可以在servlet中工作。(你必須加上 / 因為此方法需要傳遞request.getPathInfo
()的結果。)
這里有一個重要的提示:每當你存取本地的資源,要非常小心地檢查輸入數據的合法性
。
黑客或者粗心的用戶,可能發送偽造的或錯誤的數據來破壞你的站
點。例如,請想一下以下的表達會發生什么結果:
如果輸入了file=../../../../etc/passwd。這樣用戶回讀到你的
服務器的password文件!
DOM (Document Object Model)
DOM 代表文檔對象模型Document Object Model。它是瀏覽XML文檔
的一種標準API,由World Wide Web Consortium (W3C)發展。 接口
在org.w3c.dom包中,文檔參見W3C站點。
有許多可用的DOM分析器工具。我選擇了 IBM的XML4J。但你可以使用
任何其它的DOM分析器。這是因為DOM是一套接口,而不是類 --所有的
DOM分析器(parser)必須返回同樣地處理這些接口的對象。
遺憾的是,雖然很標準,DOM還是有兩大缺陷:
1 API雖然也是面向對象的,還是相當笨重。
DOM parser并沒有標準的API,所以, 當每一個分析器返回一
個org.w3c.dom對象,文檔對象--分析器初始化和文件自身加載的方
式,對應于不同分析器通常總是特定的。
這個簡單的上面已描述的圖片文件在DOM中可以在一個樹結構中通
過一些對象表示如下:
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”,你要做一些方法調用,游歷DOM
樹,而且,分析器可能選擇分散“whitespace”文本nodes的一些數
據,你不得不使用循環和串聯等 (你可以通過調用normalize()來
糾正這個問題。)分析器可能還包含了分離的XML實體(如 &),
CDATA nodes或者其它實體nodes (如<b>big<b>會被變成至少三個
node。也沒有辦法在DOM中簡單表示"get me the text value of
the title element." 總之,在DOM中游歷有一點笨重。(參見本文
用XPath取代DOM章節。)
2 從更高處看,DOM的問題是XML對象無法象Java對象一樣可以直接得
到,它們需要通過 DOM API一個一個地得到。
你可以參考我的為Java-XML Data Binding技術討論做的一些歸納,
那里也用了這種直接使用Java的方法來存取XML數據。
我寫了一個小的工具類,叫做DOMUtils,包含了靜態方法來執行公用
的DOM任務。例如,要獲得根(圖片)元素的title子元素的文本內
容,你可以編寫如下代碼:
Document doc = DOMUtils.xml4jParse(picturefile);
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);
等等。
一旦你需要將Java變量用于每一個相關的元素,你必須做的是將變
量嵌入到 HTML 標記中去:
<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>
用JSP bean進行model/view分離
所有上面的picture-dom.jsp代碼是很不吸引人的。雖然你可以在jsp中加入上萬條
java代碼,但這樣就沒有使用JSP JavaBeans方法看上去來得簡單。
javabean存放了大量Java代碼,并在JSP腳本中加以調用。
為了制作一個模型,將所有java代碼放在JSP中來開始一個項目,這樣
相對比較簡單。一旦有什么新的想法可以直接回去展開代碼,然后改寫
為一些JavaBeans。雖然投資較高,但長遠來看回報更好,因為你的應
用更為模型化。 你可以在多個頁面中重復使用bean而不用擔心剪貼帶
來的不良后果。
在我們的案例中,一個典型的JSP JavaBean是從XML中展開字符串。
你可以定義Picture, Image和Thumbnails類,來表示主要的XML文件
中的元素。 這些beans帶有構造器constructors或者setter方法,它
們從展開值中帶入一個DOM node或一個文件名。你可以參
考picturebeans包或picture-beans.jsp.
在看代碼時請注意以下幾點:
* 我將接口的定義獨立于類的執行,這樣你可以很自由地選擇在將來
進行替代,你可以將值存放在一個List中,或者在DOM自身,甚至可
以在數據庫中。
* bean在一個自定義的包中被定義--picturebeans。所有JSP bean必
須要在某一個包中。大多數JSP引擎都不能從缺省包中發現類。
* 除了get方法,我也提供了set方法,當前你只是讀取,但在將來,
你要讓用戶編輯圖片,所以你要規劃用語修改寫入屬性的功能。
* <%=picture.getCaption()%>取代了 <%=caption%>, 因為值是存放
在一個bean中而不是本地變量中。但是,如果你需要,你可以將本地
變量定義為
String caption = picture.getCaption(); 這也是允許的,這可以使
代碼更容易閱讀和理解。
* 通過thumbnails放大縮小
你可能已經注意到我的第一個的 JSP的輸出, picture-dom.html,
使用了全部大小的源圖片文件。我們可以稍微修改一下代碼,使它顯
示一個縮略圖,我將把縮略圖列表存放在XML數據文件中。
讓我們定義一個參數,zoom,它的值決定了要顯示哪一個縮略圖。點
擊這個縮略圖后回顯示全部大小的圖片;點擊Zoom In或Zoom Out按鈕
將選擇列表中下/上一種縮略圖。
* 由于縮略圖對象返回一個Image對象的java.util.List,所以要找到
正確的縮略圖并不容易,如果用(Image)picture.getThumbnails().get(i)的話。
* 要制作Zoom In和 Zoom Out連接,你必須建立一個對同一頁面的遞歸的引用,但
使用不同的參數。為了做到這一點,你要使用request.getRequestURI()方法。這
只為你提供了該servlet的路徑,沒有參數,所以你可以在此補上你要的參數。
<%
if (zoom < (thumbnails.size() -1)) {
out.print("<a href='" +
request.getRequestURI() +
"?file=" + request.getParameter("file") +
"&zoom=" + (zoom+1) +
"'>");
out.print("Zoom In</a>");
}
%>
這里是一個HTML的屏幕拷貝。
http://www.javaworld.com/jw-03-2000/jspxml/picture-dom.html
使用JSP bean
JSP規范定義了 <jsp:useBean>標記來自動實例化和使用JavaBeans。useBean標記可以總
是
被嵌入的Java代碼取代,這里我也是這么做的。也是由于這樣的原因
人們有時質問使用 useBean 和setProperty標記還有什么必要。這種
做法的優點是:
標記語法有利于HTML設計者獨立工作。
useBean有一個scope參數,可以自動地決定bean是否必須存儲為一個
本地變量,一個 session變量或一個application屬性。
如果這個變量是持久的(session或application),useBean必要時可以
將它初始化,并切在它確實存在的時候才去取得變量。
長遠看標記對今后的JSP規范版本來說更為便攜(portable)或者更
改執行(例如,, 一個假定的JSP引擎在一個數據庫中存儲了變量,或
者跨服務器共享數據。)
這個應用中對應的useBean語句為:
<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>
或者,如果你在DOMBean中定義一個setFile(String)方法:
<jsp:useBean id="picture" scope="request" class="picturebeans.DOMPicture">
<jsp:setProperty name="picture" property="file" value="<%=picturefile%>"/>
</jsp:useBean>
使用XMLEntryList
為了克服DOM APIs的一些不足,我創建了一個類,叫做XMLEntryList。這個類
執行了 Java Collections接口java.util.List,以及java.util.Map的
get和put方法,它提供了一套更直觀的方法來在一個簡單的XML樹結構
中往返移動。你可以使用Collections API的標準抽象(abstraction)來
進行象獲得 迭代或子視圖等。在EntryList的每一個入口都有一個鍵
key和一個值,就象Map一樣;鍵就是子結點(child nodes)的名字
而值要么是字符串,要么是下一級(child)XMLEntryLists。
XMLEntryList并不意味著可以完全代替DOM。它還無法執行某些DOM的
功能。但是,它是一個方便的外殼(wrapper)在你的XML數據結構上
用于執行基本的getting, setting和 list-oriented 功能。例如,
你可以使用這樣的寫法來得到圖片node的caption元素:
String caption = (String)picturelist.get("caption");
caption字段的值早已被作為一個字符串分析和存儲了。
緩存 Caching
盡管有很多優點,分析一個XML文件總是需要耗費時間。為了改進基
于XML的應用的性能,你需要使用某種緩存技術。這種緩存必須在內存
中保存XML對象,記住它們來自哪一個文件。如果對象被加載以后文件
被修改了,那么對象需要重新加載。我開發過一個用于這種數據結構
的簡單方法,叫做CachedFS.java。你可以供給一個CachedFS 調用返
回功能(function),使用實際執行xml分析的內部類,將文件轉為一
個對象。cache于是可以在內存中存儲那個對象。
這里是創建cache的代碼,這一對象有application scope,所以此后的
請求可以使用同一對象cache。我將把這些代碼放到init.jsp,這樣你
就不必將這些初始化的代碼剪貼到其他JSP文件中去了。總之,你必須
在一個公共的地方定義application-scope對象。
<jsp:useBean id="cache" class="com.purpletech.io.CachedFS" scope="applicatio
n">
<% 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中是一個簡單的用于定位node的語法。它比 DOM更
容易使用,因
當你要轉入另一個node時不必每次都要產生方法調用,你可以把整
個路徑嵌入到一個字符串中去,例如:
/picture/thumbnails/image[2].
Resin產品包含了一個XPath處理器,你可以將它加入到自己的應用
中去。你可以使用 Caucho XPath對象加載于其自身,不必購買Resin
體系的其它產品。
Node verse = XPath.find("chapter/verse", node);
Resin也包含有一個腳本語言,與JavaScript兼容,允許在jsp中對
XPath和XSL的簡單存取。
XSL
這篇文章討論了在JSP中嵌入Java來從XML node中展開數據。完成做同
樣工作還可以有另外一種常見的模型:Extensible Stylesheet Language (XSL)。
這一模型和JSP模型有著根本的不同。在JSP中,主要內容是HTML,它包含了
一些Java代碼片段;而在XSL中,主要內容是XSL文檔,它包含了一些
HTML片段。如果要討論XSL和 Java/JSP之間的關系,這里的空間已經
不夠了。在JavaWorld雜志中將有一篇文章來探討如何同時使用XSL和
JSP。
目前的結論和未來的發展之路
在讀完這篇文章后,相信你應該有了一個JSP-XML應用及其強大威力
的很好的思路及結構認識。然而你也要知道一些它的局限。
開發JSP-XML應用中最令人煩悶的是為每一個 XML schema中的元素
element創建JavaBean。XML Data Binding 組織正在開發一種技術,
可以為每一個給定的schema自動生成Java類。同樣的,我也開發了
一種原型-開放源碼的Java-XML data binding技術。另外,IBM
alphaWorks最近也推出了XML Master, 或稱為XMas,這是另一
種XML-Java data binding系統。
另外一種可能性是擴展文件系統的功能,建立一些更加強大的功能,
如查詢和事務處理。自然地,我也開始期望這種功能類型也可以作為
開放源碼工程來得到發展。
posted on 2006-09-29 09:12
圣域飛俠 閱讀(1445)
評論(4) 編輯 收藏 所屬分類:
轉載