用XML和JSP來建立動態網站
最後更新:2017-02-28
來源:互聯網
上載者:User
js|xml|建立|動態|網站 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就可以將Object Storage Service在記憶體。
下面是產生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: