XML 和 JSp 是當今最流行的話題。 這篇文章引導你如何運用這兩種技術去創建動態Web站點。用XML文件去儲存數據,用JSP文件去顯示它。同時還可以了解DOM, XPath, XSL等其它 Java-XML技術。。
作者 Alex Chaffee
我先假設讀者與其他大多數Java 程序員一樣,對JSP(JavaServer Pages )和XML(Extensible Markup Language)有一定的了解,但是不清楚如何運用它們。在這篇文章中,您將學習如何用XML設計一個系統。許多網站都有大量的格式各異的數據,它們的表現方式或多或少的都沒有遵循一定的標准。而我在此推薦用這樣一種方法來設計網站,即用XML文件保存數據,用JSP文件顯示這些數據。
想必大家都已經知道了Html的局限性了把。隨著站點的發展,需要一種方法來共享和交換數據。不管是內容出售,訂單處理,還是報表生成,都需要對數據進行定義。XML正好可以發揮作用,其他的應用只能閱讀和翻譯信息,而XML可以賦予數據以意義。也許你會覺得奇怪,為什麼要用XML來儲存數據,而不用數據庫呢?答案是在很多應用方面,數據庫顯的太“猛”了一點。為了要使用數據庫,你必須要安裝和支持一個獨立的服務器處理,安裝DBMS,建立DBA,並且還要去學習SQL語言,學會如何寫查詢語句並把結果返回。但是如果你使用XML文件,你不必去為准備額外的數據庫Server而花費,且好處是可以很簡單的編輯處理數據,就象是用一個文本編輯器一樣,而不是復雜的數據庫管理工具。XML文件也十分容易備份,共享和下載,以及通過FTP上傳新數據至網站。
XML的另一個稍稍抽象的優點是采用了層次結構而不是關系結構來定義數據,可以根據需要直接了當的設計應用的數據結構,也不必使用實體關系設計器去進行模式的規范化操作。如果您有一個成員包含另一個成員,您可以通過層次結構直接表示出來,而不用使用連接表。從這種意義上說XML有利於信息的表達和結構化組織,可以准確定義數據,從而使數據搜索更有效。
對於許多應用來說,文件系統是沒有足夠的能力滿足需要。當有大量的數據需要更新時,它的弱點就暴露了。並發寫操作的沖突等是大問題。數據庫有良好的事務處理機能,豐富的索引與復雜查詢功能,完全可以為數據庫提供一個包裝器,以便將創建查詢並將它們轉化成XML流。這樣XML就變成了一個強大的且編程友好的數據庫前端部件(Oracles XSQL servlet是一個實現的例子)。
用XML來定義數據 :在線相冊的例子
每個人都喜歡照片。互聯網是個工具可以展示自己,朋友,充物及各種活動。這個例子以定義一個簡單照片對象為主(源代碼可以從參考資料處獲得)。展示了定義一個照片對象所需要的屬性有:標題,日期,簡短說明以及圖像的位置。
定義一幅圖像需要的屬性:
圖像文件(GIF or JPEG)的位置
圖像的高度(像素)
圖像的寬度(像素)
在這裡把文件系統當作數據庫來存儲信息有一個簡潔的優點,即圖像文件和數據描敘文件可以保存在同一位置下。
最後用一個元素來定義小照片圖像(thumbnail images)集來擴充相片記錄的定義,以便在任何地方使用。
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,可以把所有的關於一幅照片的信息放入一個簡單的文件,而不放在分散的表裡。這個文件稱為pix文件。這樣文件系統就如下面的樣子所示。
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: 用類實現DOM接口來分析與檢視XML文件
XMLEntryList: 把XML加載進 Java.util.List 的name-value對
XPath: 用 XPath 處理器 (就象Resin一樣) 通過路徑名來定位XML中的元素
XSL: 用 XPath 處理器來轉化XML為Html
Cocoon: 使用開放代碼的Cocoon框架
Roll your own bean: 可以寫一個包裝類加載數據進入一個的定制JavaBean
這些方法能很好地等同運用在客戶端或應用程序服務器端接受XML流時。
Java Server Pages
JSP有很多實現版本, 不同的JSP版本實現了不同的且互不兼容的規格.作者在這裡選用的是Tomcat, 基於如下的幾點理由:
它支持最新的JSP 和 Servlet 規格
Sun和apache認可它
你可以單獨運行它而不用單獨配置一個Web服務器
它的源碼開放
(關於Tomcat的更多信息, 請看參考資料.)
你可以隨便選用你喜歡的JSP引擎,但需要你自己配置它。一定要確認所選用的引擎支持JSP1.0規格。從0.91到1.0的版本有很多地方不一樣。只要JSWDK (Java Server Web Development Kit) 能正常工作就可以了。
JSP 構造
當構造一個基於JSP驅動的WEB站點時,建議將公用函數,導入模塊,常量和變量定義放在一個獨立的名為init.jsp的文件裡。然後用<%@include file="init.jsp"%>的方式把它添進每個JSP文件中。<%@include%>指示的作用類似於C語言的#include定義符,將被包含文件(例如init.jsp)作為包含文件(例如picture.jsp)的內容一起進行編譯。注意,作為比較, <jsp:include>標志指示編譯器將它作為一個獨立的JSP文件編譯,並在被編譯好的JSP中嵌入對它的調用。
查找文件
當啟動JSP時,在初始化工作結束之後的第一件事是查找所需要的XML文件。怎麼知道需要哪些文件呢?答案是以CGI程序方式來傳遞所需要的參數。用戶可以通過URL picture.JSP?file=summer99/alex-beach.pix的方式或用Html的form對象來傳遞文件參數。
當JSP收到參數之後,你還得知道 filesystem的根目錄位於什麼地方。在UNIX系統之下,文件位置是以/home/alex/public_Html/pictures/summer99/alex-beach.pix方式來指定的。JSP在執行時是沒有相對路徑的概念的,所以必須在Java.io包內提供絕對路徑。
根據所采用JSP 或 Servlet的版本,Servlet APT函數提供了轉換URL路徑為絕對路徑的方法。ServletContext.getRealPath(String)是實現的竅門。每個JSP版本都有個 ServletContext對象(稱為application)。因此代碼可以象下面的樣子:
String picturefile =
application.getRealPath("/" + request.getParameter("file"));
or
String picturefile =
getServletContext().getRealPath("/" + request.getParameter("file"));
它們在servlet中也能工作。注意必須增加"/",因為request.getPathInfo()的結果將被傳遞給這個方法。
另外重要的是,當訪問本地資源時,必須特別小心所傳過來的數據,黑客或粗心的用戶可能發送偽造的數據來黑了你的站點。例如用file=../../../../etc/passwd時用戶是可以看服務器的口令的。
文檔對象模型
DOM代表Document Object Model,它是一組用來浏覽XML文檔的標准API。由World Wide Web Consortium (W3C)所制定。其接口位於包org.w3c.dom內,其文檔位於W3C站點(參考資料)。
有許多DOM解析器的實現。因為DOM是一個接口集合而不是類的定義,每一種解析器都必須返回忠實地實現這些接口的對象。我選用了IBM所實現的XML4J。
盡管接口集是標准的,但是DOM有兩個主要的缺陷:
1. API函數,盡管是面向對象的實現,但還是相當煩瑣
2. 沒有為DOM解析器設計標准的API,雖然每一種解析器都返回org.w3c.dom.Document對象,但是解析器的初始化以及文件的載入一直都是各種解析器所獨有的。
上面所描述的相片文件,如果用DOM來表達,則可用樹結構中的幾個對象來描述:
Document Node
--> Element Node "picture"
--> Text Node " " (whitespace)
--> Element Node "title"
--> Text Node "Alex On The Beach"
--> Element Node "date"
--> ... etc.
為了獲得值“Alex On The Beach”,必須調用幾個方法,遍歷DOM樹。進一步的說,解析器可以選擇為去訪問任意數量的空白結點。解析器也可以包括區別XML實體(例如 &),CDATA結點,或其他元素結點(例如:<b>big<b> bear可以至少被轉化成3個結點。其中之一是元素b,包含一個 文本結點,文本值為big)。在DOM中是沒有辦法簡單的說一句:"get me the text value of the title element."就可以了的。簡言之,遍歷DOM樹不怎麼愉快。
從另外一個較高的視角來看,DOM問題就是訪問XML對象不能象訪問Java對象那樣直接。對它們的訪問必須通過DOM API來分解。
請參照我在 Java-XML 數據綁定技術討論中所下的結論,在那裡采用了直接訪問Java對象的方法來訪問XML數據。
我寫過一個小小的工具類DOMUtils,包含一些靜態方法來實現普通DOM任務。例如獲得根結點 (picture)的子結點(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子元素的值的辦法,也很簡潔:
Document doc = DOMUtils.XML4jParse(picturefile);
Element nodeRoot = doc.getDocumentElement();
Node nodeTitle = DOMUtils.getChild(nodeRoot, "title");
String title = (nodeTitle == null) ? null : DOMUtils.getTextValue(nodeTitle);
等等!
一旦為相關元素建立了Java變量,就必須用JSP標志將它們嵌入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>
完整的代碼如下。Html的輸出使用的是JSP文件。
<%
// invoke this file like: picture-dom.JSP?file=foo.pix
%>
<%@include file="init.JSP"%>
<%
String picturefile =
application.getRealPath("/" + request.getParameter("file"));
System.out.println(picturefile);
Document doc = DOMUtils.XML4jParse(picturefile);
Element nodeRoot = doc.getDocumentElement();
Node nodeTitle = DOMUtils.getChild(nodeRoot, "title");
String title = (nodeTitle == null) ? null : DOMUtils.getTextValue(nodeTitle);
Node nodeCaption = DOMUtils.getChild(nodeRoot, "caption");
String caption = (nodeCaption == null) ? null : DOMUtils.getTextValue(nodeCaption);
Node nodeDate = DOMUtils.getChild(nodeRoot, "date");
String date = (nodeDate == null) ? null : DOMUtils.getTextValue(nodeDate);
Node nodeImage = DOMUtils.getChild(nodeRoot, "image");
Node nodeSrc = DOMUtils.getChild(nodeImage, "src");
String src = DOMUtils.getTextValue(nodeSrc);
Node nodeWidth = DOMUtils.getChild(nodeImage, "width");
String width = DOMUtils.getTextValue(nodeWidth);
Node nodeHeight = DOMUtils.getChild(nodeImage, "height");
String height = DOMUtils.getTextValue(nodeHeight);
%>
<Html>
<head>
<title><%=title%></title>
</head>
<body>
<center>
<table bgcolor="#CCCCCC" border="0" cellspacing="0" cellpadding="8">
<tr>
<td align="left" bgcolor="#6666AA" width="100%">
<b><%=title%></b>
</td></tr>
<tr>
<td align="center" valign="top">
<table bgcolor="#FFFFFF" border="0" cellspacing="0" cellpadding="5">
<tr>
<td align="center" valign="center">
<a href="picture-sm-dom.JSP"><img src="<%=src%>" width="<%=width%>" height="<%=height%>" border="0" alt="<%=src%>"></a></td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" valign="center">
<% if (caption != null) { %>
<b><%=caption%></b><br/>
<% } %>
<% if (date != null) { %>
<font size="-1"><i><%=date%></i></font><br/>
<% } %>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</Html>
使用JSP beans 來分開模型和視圖
上面的代碼picture-dom.jsp並不吸引人。當你要在JSP內放置大量的Java代碼時,有一個簡潔的辦法:可以使用JSP JavaBeans來存儲大量的Java代碼。好處是在JSP頁面內可節省JSP 腳本 tags (<% and %>) ,以便於控制程序流與減少變量的操作。為了原型開發的目的,把所有的Java代碼放入JSP內可能開始覺得輕松愉快。也可以在有了好主意後,回頭將必要代碼抽出放入JavaBeans。這樣做的代價有點高,但是會在今後得到回報,如果你的應用需要模塊化的話。可以在不同的頁面內使用同一Beans,而不用做討厭的copy-and-paste,以支持代碼重用。
在以上的例子中,從XML文件中抽出字串的值可以作成一個JSP JavaBean。定義 Picture,Image,Thumbnails類來表達XML中的主要元素。這些Beans將有構造函數或setter方法,調用DOM結點或文件名來從中抽出需要的值。從參考資料或picture-beans.JSP中可以找到 picturebeans包的源碼。
閱讀源碼時請注意:
在實現類以外單獨定義了接口,因此今後可以自由地選擇改變實現。可以在列表中,在DOM裡甚至在database裡存儲數據。
beans被定義在一個定制包 picturebeans內。所有 JSP beans 都必須放在一個包內;大部分JSP引擎不能找到缺省包內的類。
除get方法之外還提供了set方法。現在,還只能讀而不能改變圖片的屬性。將來可以用來改變這些屬性以便用戶編輯圖片。
自從需要的值被存儲在beans中而不是在局部變量中後,就必須用<%=picture.getCaption()%>而不是<%=caption%>了。當然,也可定義局部變量比如String caption = picture.getCaption();.這樣使代碼較容易理解。
用thumbnails來進行縮放
在第一個JSP的輸出中,顯示了全尺寸的圖像文件。可以稍微修改代碼,讓它顯示由一些稍小一點的圖象組成的列表。這要使用到存儲在XML數據文件中的圖像列表。
首先定義一個參數 zoom,它可以決定顯示那一幅Thumbnails圖像。在小圖像上點擊鼠標,可以看到全尺寸的原來圖像。點擊Zoom In 和 Zoom Out按鈕可以選擇下一幅或前一幅Thumbnails圖像。
Thumbnails對象返回 Java.util.List圖像對象,找到正確的Thumbnails對象卻不怎麼容易,僅僅說(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>");
}
%>
Here is an Html screenshot of the working JSP page.
這裡有一個關於JSP頁面工作的例子。
使用JSP bean標記
JSP規范中定義了<JSp:useBean>標記,以在JSP頁面中自動實例化和使用JavaBeans。useBean標記通常都可被內嵌的Java代碼所替換。在這裡我已經替換了。基於此,很多人都在詢問useBean 和 setProperty標記何時需要使用的問題。關於這些標記有以下幾點:
標記語法不會對 Html 設計者構成干擾
useBean 有一個scope 參數,可以自動決定Beans是否應該存儲在局部變量或 session變量中或作為一個application的屬性。
如果變量是長期有效的(session或application),在必要時,useBean會初始化它,但是只有當變量已經存在時才能使用。
標記對於將來的JSP版本規范來說,潛在地具有很多優點,可以靈活地改變它的實現。(例如,一個假定的JSP引擎可以儲存變量於 database或通過server進程來共享)
對於這個應用來說等價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 API的一些困難,做了一個XMLEntryList類。它實現了Java集合接口java.util.List並包含Java.util.Map的get,put方法,提供了直接的方法集合去遍歷簡單XML樹結構。可以用集合API的標准抽出方法來做諸如查詢,迭代和子視圖。實體列表中的每個實體都有鍵和值,如同一個Map對象。鍵是子結點的名字,值或是字串或子XMLEntryLists.
XMLEntryList 並不是DOM的完全替代。有好幾個DOM功能它不能實現。可是,它是一個便利的包裝器,用來做基本的getting, setting,list-orIEnted功能來處理 XML數據結構。例如為了得到picture 結點的標題屬性的值,可以這樣寫:
String caption = (String)picturelist.get("caption");
標題屬性的值被解析出並被存入一個字串中。
Caching緩存
不論XML文件有什麼優點,解析它會花很多時間。為了提高基於XML的應用程序的性能,必須使用一些緩存技術。緩存必須基於XML文件名在內存中存儲XML對象。如果上次加載以來,XML文件被更新了,則必須重新加載。我做了關於實現這個數據結構的一個簡單工具CachedFS.Java。可以使用內部類給把CachedFS作為一個回調函數來使用,就能實現XML解析,轉換文件為一個對象。cache就可以將對象存儲在內存。
下面是產生cache的代碼。這個對象具有應用程序的范圍,可以被反復使用。這段代碼被放入了init.JSP。
<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>
XPath
XPath 是一個查找定位XML樹結點的簡單句法單位。它比DOM易於使用。當你想從一個結點進入下一個結點時,不用調用making方法。可以把全部路徑嵌入一個字串,如/picture/thumbnails/image[2]。在Resin中,包括了XPath對象,可直接在應用中調用。
Node verse = XPath.find("chapter/verse", node);
XSL
這篇文章討論了如何將Java代碼嵌入JSP,用來提取 XML結點的數據。另外還有一個流行的模型來完成這個工作。就是XSL (Extensible Stylesheet Language)。它完全不同於已討論過的JSP模型。在JSP中,主文檔模型是HTML,包含有Java代碼;在XSL中文檔模型是XSL,包含有Html。對於XSL和Java/JSP的關系,有許多值得討論的地方。這裡限於篇幅,就不深入了。將來在JavaWorld 中要討論XSL 和JSP一起用的問題。
結論與展望未來之路
讀完這篇文章,讀者應該對於 JSP-XML應用的結構與強大之處以及缺陷有教好的理解了吧。
在開發JSP-XML應用中,最單調乏味的事是為每個 XML模式的元素構造JavaBeans。 XML Data Binding group正在開發能從給定的模式自動生成Java類的技術。我也開發了一個公開源代碼的Java-XML數據綁定的原型。IBM alphaWorks最近也發布了XML Master 或(XMas),這是另外一個Java-XML數據綁定系統。
另外一個可能就是拓展文件系統的功能,構造一些更強大的特征,比如查詢或事物處理。自然,我也在考慮開發源代碼公開的工程來實現這種功能。有誰想寫 XML搜尋引擎呢?
(c) 版權所有 2000 ITworld.com, Inc., an IDG Communications company
Resources
文章中的源代碼可以從下面找到:
http://www.Javaworld.com/jw-03-2000/JSpXML/JSPXML-webapp.zip
For future updates to that source code:
http://www.purpletech.com/JSPXML/
XML:
http://www.XML.org/
http://www.XML.com/
http://www.jguru.com/faq/XML
SML, a controversial simplification of XML syntax:
http://www.XML.com/pub/1999/11/sml/index.Html
http://www.XML.com/pub/1999/12/sml/responses.Html
JSP:
http://Java.sun.com/prodUCts/JSP
http://www.jguru.com/faq/JSP
DOM specification:
http://www.w3.org/TR/1998/REC-DOM-Level-1-19981001/Java-language-binding.Html
http://www.w3.org/TR/REC-DOM-Level-1/
Java-XML data binding: