當前,Java 2平台企業版(J2EE)架構在廠商市場和開發人員社區中倍受推崇。作為一種工具,可延伸標記語言 (XML)(XML)簡化了資料交換、進程間訊息交換這一類的事情,因而對開發人員逐漸層得有吸引力,並開始流行起來。自然,在J2EE架構中訪問或整合XML解決方案的想法也很誘人。因為這將是強大系統架構同高度靈活的資料管理方案的結合。
XML的應用似乎是無窮無盡的,但它們大致上可以分為三大類:
* 簡單資料的表示和交換(針對XML的簡單API(SAX)和文件物件模型(DOM)文法解析,不同的文件類型定義(DTDs)和概要(schemas))
* 面向訊息的計算(XML-RPC(遠端程序呼叫),SOAP協議,電子化業務XML(ebXML))
* 使用者介面相關、表示相關的上下文(可延伸樣式表語言 (XSL)(XSL),可延伸樣式表語言轉換(XSLT))
這幾類應用在J2EE架構中恰好有天然的對應:資料表示和交換功能是EJB組件模型中持久化服務(persistence services)的一部分,基於訊息的通訊由JavaMessage Service(JMS)API來處理,而介面表示正是Java伺服器頁面(JSP)和Java Servlets的拿手好戲。
在本文中,我們將看到當今基於J2EE的應用裡,XML是如何在上述幾個方面進行應用的,以及在相關標準的未來版本中這些應用將會如何發展。
基礎:資料的表示和交換
原型化的XML應用(假設有的話)的內容通常是:資料以XML格式存放,為了進行顯示、修改甚至寫入某個XML文檔而經常被讀入到某個物件模型中。作為例子,假定我們正處理多種類型的媒體(圖品、視頻、文字文件等等),並且用下面這個簡單的XML DTD來描述這些媒體的中繼資料:
<!-- DTD for a hypothetical media management system --> <!-- Media assets are the root of the object hierarchy. Assets are also hierarchical - they can contain other assets. --> <!ELEMENT media-asset (name, desc?, type*, media-asset*, urn)> <!-- Metadata about the asset --> <!ELEMENT name (#PCDATA)> <!ELEMENT desc (#PCDATA)> <!ELEMENT type (desc, mime-type?)> <!ELEMENT mime-type (#PCDATA)> <!ELEMENT urn (#PCDATA)> |
以下是一個基於上述媒體DTD的XML文檔,描述了與某個課程講座相關的內容:
<?xml version="1.0" ?><!DOCTYPE media-asset PUBLIC "-//Jim Farley//DTD Media Assets//EN" "http://localhost/Articles/Sun/dtds/media.dtd";> <media-asset> <name>第14講</name> <desc>與第14講相關的所有內容</desc> <!-- 內容對象"lecture 14"的一套子組件 --> <media-asset> <name>講座的投影片</name> <type> <desc>MS PowerPoint</desc> <mime-type>application/vnd.ms-powerpoint</mime-type> </type> <urn>http://javatraining.org/jaf/E123/lecture-14/slides.ppt<;/urn> </media-asset> <media-asset> <name>講座的視頻片斷</name> <type> <desc>RealPlayer streaming video</desc> <mime-type>video/vnd.rn-realvideo</mime-type> </type> <urn>http://javatraining.org/jaf/E123/lecture-14/lecture.rv<;/urn> </media-asset> <!-- 講座開始 --> <urn>http://javatraining.org/jaf/E123/lecture-14/index.jsp<;/urn> </media-asset> |
從Web或者企業級應用的角度看,能以這種方式訪問資料真是一種福音,因為它體現了高度的可移動性,使我們與中繼資料的實際資源本身隔離。這些資源可能來自一個關聯式資料庫系統、某種活動媒體伺服器或者Web伺服器上的一個靜態XML文檔,等等。如果想把這些資料載入到Java應用中,我們可以從當前眾多的Java語言XML解析器中選用一個,通過它將XML資料裝入一個DOM文檔,最後遍曆文檔,將所有這些資料轉換到我們應用系統的物件模型中。
下面是個簡單的基於DOM的解析程式,可對上述的媒體DTD進行解析。解析器用的是Apache Xerces:
package jaf.xml; import java.util.*; import java.io.IOException; import org.w3c.dom.*; import org.xml.sax.*;// XML文檔解析程式,使用上述媒體DTD. public class MediaParser implements ErrorHandler { /** 使用Apache Xerces解析器 */ org.apache.xerces.parsers.DOMParser mParser = new org.apache.xerces.parsers.DOMParser(); /** 建構函式 */ public MediaParser() { // 告訴解析器驗證並解析文檔 try { mParser.setFeature( "http://xml.org/sax/features/validation";, true); } catch (SAXException e) { System.out.println("Error setting validation on parser:"); e.printStackTrace(); } // 設定解析器的錯誤處理控制代碼 mParser.setErrorHandler(this); } /** 解析指定的URL,返回找到的XML文檔 */ public document.nbsp;parse(String url) throws SAXException, IOException { mParser.parse(url); document.nbsp;mediaDoc = mParser.getdocument.); return mediaDoc; } /** 解析指定URL的XML文檔,將內容轉換成 MediaAsset 對象 */ public Collection loadAssets(String url) throws SAXException, IOException { document.nbsp;doc = parse(url); Collection assets = new LinkedList(); NodeList assetNodes = doc.getElementsByTagName("media-asset"); for (int i = 0; i < assetNodes.getLength(); i++) { Node assetNode = assetNodes.item(i); MediaAsset asset = new MediaAsset(assetNode); assets.add(asset); } return assets; } /** * 錯誤處理代碼(為簡潔起見省略了) */ } |
MediaParser類的建構函式初始化了一個Xerces DOM解析器。parse()方法告訴解析器到哪個URL去找XML源,然後得到結果文檔並返回。loadAssets()方法調用parse()方法從某個XML源載入文檔,然後為文檔中找到的每個“media-asset”節點建立一個MediaAsset對象。
以下是一個使用MediaAsset類的例子:
package jaf.xml; import java.util.*; public class MediaAsset { // 資源中繼資料 private String mName = ""; private String mDesc = ""; private Collection mChildren = new LinkedList(); private Vector mTypes = new Vector(); private String mUrn = ""; protected MediaAsset(org.w3c.dom.Node assetNode) { // 為簡潔起見省略後面代碼 . . . } } |
因為篇幅的關係省略了MediaAsset類的詳細代碼,但應用模式依然是清晰的。MediaAsset類遍曆文檔的節點,當它碰到不同的子節點時,它用子節點的內容填充自己的成員資料。如果它發現了一個嵌套的子資源節點,它只需要建立一個新的MediaAsset對象,然後將子資源節點的資料填充到新對象的成員資料中。
實現上述處理的方法數不勝數。我們還可以使用其他的解析器或解析器架構,如Java API for XML Parsing (JAXP)。除了使用DOM模型外,事件驅動的SAX模型也可用於解析XML。類似的程式也可用來產生XML資料——前提是允許產生新的資料對象(在本例中是MediaAsset),它可將其相應的XML實體插入到DOM中,然後將DOM輸出到一個流中(諸如一個檔案,一個Socket,或者一個HTTP串連...)。還有其他更高層次的標準,可將XML映射到Java對象的過程進一步自動化(或簡化)。例如,使用XML概要(Schema)和XML綁定處理引擎,您可以半自動地將滿足某個XML 概要的XML資料轉變成Java資料對象。代表性的引擎是Castor,是由ExoLab小組管理的一個開放原始碼項目的產物。上述使用Xerces DOM的簡單例子僅僅是示範了這一處理過程的底層模型。
上述樣本表明,在Java環境中解析或產生XML是非常方便的,這與J2EE沒有必然關聯。格式化為XML的資料可以從應用程式的任何層次流入或輸出,這使得與外部系統的整合性無可限量。但我們能否以一種更為直接的方式將XML資料來源整合到J2EE架構中去呢?
駕馭訊息
J2EE架構包含了對JMS(JavaMessage Service)API的訪問,以實現面向訊息的通訊(J2EE 1.2.1版只需JMS API即可,在J2EE 1.3版中JMS基本定型,此時必須由某個相容J2EE平台的伺服器提供一個JMS API Provider)。這一類的非同步互動(與之相對的是:本地或遠程方法調用所代表的同步互動)被證明在某些應用環境中是非常有用的。某些時候,互動只需要通過間接的請求或回答來實現,即:在某些情況下,發出訊息後不可能立即收到回覆,但我們仍希望當訊息發出者重新線上時,確保他能收到回覆資訊。
面向訊息系統的實際應用之一就是企業之間的鬆散整合。類似於EDI(電子文檔交換)時代的文檔交換,兩個企業由於業務的需要而交換訊息,此時通常不能為了使用RPC或者RMI、CORBA、DCOM之類的遠程方法互動而在兩者之間進行緊密整合。象JMS API這樣的訊息系統允許雙方交換基於JMS API的訊息載荷,前提是雙方在會話的時候均能提供相容的JMS API服務。當前仍然存在的困難是:雙方是否能尊從相同的格式或協議。
這正是XML大顯身手的時候。XML明確地被設計來解決此類資料交換問題——靈丹妙藥就是“面向訊息的概要表”(Message-Oriented Communication Scheme),實質就是基於一個雙方認同的DTD或schema,用XML格式來交換訊息載荷。
JMS API支援好幾種訊息,其中的TextMessage代表簡訊載荷。一個簡單而有效XML訊息交換方案是,在一端將我們的XML文檔插入TextMessage,然後在另一端用自製的XML解析程式(如前面的MediaParser)解開資料並(可選地)將其轉換成Java對象。這使得我們既可以用JMS API支援的公開預訂的訊息模型,也可以用JMS API支援的點對點的訊息模型來發送XML訊息。
上述方法有一些局限,因為對於JMS運行時處理而言,XML的內容基本上是不透明的。例如,JMS API允許使用基於特定訊息頭的路由。這很容易理解,尤其當我們希望XML訊息根據其內容採取不同走向時。例如在我們的MediaAsset例子中,我們希望公開講座內容,但只想把特定的內容傳送給那些預訂了課程的人,或傳送給那些表明可以接受某些媒體格式(如視頻流)的人。為了發揮JMS API的價值,以便實現上述基於內容的訊息路由,我們有必要從XML資料中解析出關鍵資訊,然後在構造標準JMS API訊息頭時插入這些資訊。這是可行的,但要實現XML資訊我們就得額外地寫很多代碼(交換訊息的雙方均如此)。
為了在XML和JMS API之間架起橋樑,一些廠商提供了自訂的JMS擴充,以便直接支援XML訊息機制。例如,BEA系統公司基於J2EE的WebLogic應用伺服器特別為TextMessage提供了XMLMessage子類,允許用XPath運算式來過濾XML訊息。不過這是一種專有的擴充,這要求交換訊息的雙方必須都能處理這類訊息。
為此,Sun公司目前正在開發用於XML訊息的Java API(JAXM)。其目標是提供一個進階別的標準服務,以實現基於ebXML的訊息的合成與傳送。一個JAXM服務提供者可以將這類訊息映射到適當的物理訊息系統(諸如JMS API)中去。
讓XML看得見
將XML同Web系統的使用者介面進行整合顯然是一種有益的嘗試。絕大多數的介面程式,無論是基於還是不基於Web,都是將資料進行轉換,然後用易讀的格式展現給使用者。用諸如XML這種“易消化”的格式存放資料將簡化上述工作,同時它還大大提高了內容的可管理性,接下來我們就可看到這一點。不過首先要大書一筆的是,XML在Web介面層的應用得益於JSP技術的發展。
一直以來大家都希望能清晰地區分Web應用程式的展示層與底層物件模型,JSP架構誕生於這些努力之中(包括早期JHTML嘗試)。JSP架構允許將Java代碼嵌入到HTML內容中,這樣既可以實現動態內容,又不必經常修改Java Servlets的代碼。在頁面中包含Java技術的途徑是通過JSP標記(JSP Tags),這些標記以XML風格出現。在JSP中,Java程式以程式碼片段、伺服器端JavaBeans組件、在伺服器端觸發特定操作的不透明標記(標準的或自訂的)等形式存在。當某個使用者通過瀏覽器請求JSP頁面時,一個Java應用伺服器解析該JSP頁面,將其編譯成一個Java Servlet,然後執行該Servlet以產生回覆頁面。
一種直接將XML資料來源整合到JSP的介面中去的方法是,將XML載入到JavaBeans組件中(如同我們在MediaAsset例子中所做的),然後在JSP中直接引用這些JavaBeans組件。
下面是一個嵌入Java代碼片斷的例子:
<html> <head> <title>第14講的媒體資源</title> </head> <body> <!-- 引入我們的類 --> <%@ page import="jaf.xml.*" %> <center><H3>Media Assets for Lecture 14:</H3></center> <!-- 定義一個資來源物件,以便用於顯示 --> <jsp:useBean class="jaf.xml.MediaAsset" id="asset" /> <!-- 從一個先前定義的位置裝載資源 --> <% MediaParser parser = new MediaParser(); Collection assets = parser.loadAssets("http://javaschool.org /jaf/E162/lecture14-assets.xml"); Iterator iter = assets.iterator(); %> <table border=0> <tr><th>Name</th><th>Type</th><th>URN</th></tr> <% while (iter.hasNext()) { asset = (MediaAsset)iter.next(); %> <tr><td><jsp:getProperty name="asset" property="name" /></td> <td><jsp:getProperty name="asset" property="type" /></td> <td><jsp:getProperty name="asset" property="URN" /></td> </tr> <% } %> </table> </body> </html> |
其中粗體部分為JSP代碼片斷和標記,其餘部分是標準的HTML文本。
上述程式還有一種更簡潔的寫法,那就是使用自訂JSP頁面標記。這樣我們就可以從JSP頁面中剔出程式碼片段,只使用JavaBeans組件和自訂的JSP標記即可。比如說,為了去掉建立解析器、載入資源資料到集合中的那段代碼,我們可建立一個自己的標記,由它在幕後完成這些工作。以下是例子:
.
.
.
<!-- 引入我們的類 -->
<%@ page import="jaf.xml.*" %>
<center><H3>Media Assets for Lecture 14:</H3></center>
<!-- 載入我們自訂的標記庫 -->
<%@ taglib uri="http://javaschool.org/taglib"; prefix="media" %>
<!-- 從一個先前定義的位置裝載資源 -->
<media:load url="http://javaschool.org/jaf/E162/lecture14-assets.xml";
collectionName="assets" cursorName="asset" />
<table border=0>
.
.
.
使用自訂標籤的最大好處是使我們的程式碼集中在一個地方(對Java技術而言,一般是指在“類”中),易於管理。這樣可以將程式中對象層同介面層的整合關係定義得很清晰,修改代碼所造成的影響是可以預測和管理的。
直接將XML資料轉換成Web顯示內容的另一種方法是使用XSL和XSLT。在這種方案中,將XML資料對應成HTML(或WML等)的邏輯由XSL樣式表(XSL StyleSheet)來定義。樣式表描述了每個特定XML資料實體應該怎樣轉換成介面資料實體(如HTML表格、內聯標記等)。在JSP架構中,XSL轉換隻能應用於特定的XML資料來源,最理想的是採用一套自訂的JSP標記並引用某個XSLT處理常式。這方面的典型樣本請參考java.sun.com中關於XML同JSP構架整合的白皮書。
同前面那個JSP自訂標籤加XML解析器組件的方案相比,XSLT方案的伸縮性要好一些,而且具有更好的可管理性。在這種情形下,我們的轉換邏輯是編寫在一個XSL樣式表中,而不是在Java代碼中。這意味著當需要修改介面時,大多數情況下只是編輯樣式表或者HTML,代碼不受影響。不過在決定選用何種方案之前,還是要根據實際狀況仔細權衡。如果選用XSLT方案,那麼就得有人負責維護這些XSL樣式表(要麼是負責介面的人,要麼是編寫程式的人)。XSLT既像內容,又像程式,因此雙方都不能把責任推給對方,結果大家可能都被這不倫不類的XSLT弄得矛盾百出。從這點上考慮,採用自訂標籤並由介面開發人員將其嵌入展示層的方法似乎更有吸引力,因為這樣軟體工程師只考慮Java代碼,而內容工程師也只操心內容標記。
Java servlet過濾器是J2EE 1.3版在其Web層最新發行的一種Web組件。當Sevelet將請求寫入某個資源或者從某個資源中讀取回答資訊時,過濾器可以非常方便地轉換其中的頭資訊和內容資訊。這裡所說的資源可以是一個Java servlet、一個JSP頁面,甚至一個靜態Web頁。過濾器的確很“酷”,因為它允許開發人員從轉換內容的代碼中分離出產生內容的那部分代碼,並加以重用。當需要通過XSLT方式將XML資料轉換到不同的XML應用目標時,Java servlet過濾器尤其有用。
在J2EE應用程式中使用Java servlet過濾器轉換其輸出,以便相容任何類型用戶端的前景呼之欲出。servlet過濾器能夠偵測到來自使用WAP協議(無線應用通訊協定)的移動用戶端的呼叫,並且將回覆內容轉換成WML(無線傳輸標記語言)格式。servlet過濾器也能檢測到來自iMode無線客戶的呼叫,並將其轉變成cHTML(緊湊HTML)格式。當然,servlet過濾器也能夠分辨出傳統的HTML瀏覽器客戶的請求,並用正確的格式進行回複。
結束語
在J2EE 1.2.1規範中,XML“整合”僅指組件或應用程式的XML格式的部署描述。在J2EE 1.3規範中,對XML的支援被擴充為要求具備SAX 2和DOM 2解析器,以及在相容J2EE的伺服器平台上提供XSLT轉換處理常式。您可以毋庸置疑地相信,將來在J2EE架構中還會整合進更多的XML特性,因為J2EE規範的定義者們會認真傾聽開發人員社區中對在企業級應用中使用更多XML的渴求呼聲。例如,JSR(Java定義請求)處理小組中與JAXM規範相關的部分(JSR 000067)承諾在J2EE後續規範中整合進JAXM。可以預見,在JSP架構、EJB和JDBC規範中均會有類似的變化。J2EE平台中上述組件的變革,將使Java技術開發人員目前用的XML更為規