簡介
WSA 設計用於與 J2ME 配置文件協同工作,J2ME 配置文件要麼基於 Connected Device Configuration (CDC),要麼基於 Connected Limited Device Configuration (CLDC 1.0 或 CLDC 1.1)。遠程調用 API 基於 J2SE 的 Java API for XML-Based RPC (JAX-RPC 1.1) 的一個完整子集,它包含了一些遠程方法調用(Remote Method Invocation,RMI)類,以滿足 JAX-RPC 依賴。XML 解析 API 基於 Simple API for XML, v2 (SAX2)的一個完整子集。
WSA 的目標是把對 Web 服務調用和 XML 解析的基本支持集成到設備的運行時環境,這樣開發人員就不必在每個應用程序中嵌入這樣的功能了——在像移動電話和個人數字助理這樣資源有限的設備中,這算是一個特別浪費資源的問題。
核心規范
Web Services InterOperability Organization (WS-I) 促進了定義 Web 服務的核心規范和應用層協議,並且它們受 World Wide Web Consortium (W3C) 和 Organization for the Advancement of Structured Information Standards (OASIS) 的指導。4 個關鍵標准規定了如何創建、部署、發現以及如何使用 Web 服務:
Web 服務標准 描述
Simple Object Access Protocol (SOAP) 1.1 定義了傳輸和數據編碼
Web Services Definition Language (WSDL) 1.1 定義了如何描述遠程服務
Universal Description, Discovery, & Integration (UDDI) 2.0 定義了如何發現遠程服務
Extensible Markup Language (XML) 1.0 和 XML Schema 定義了可擴展標記語言(Extensible Markup Language ,XML) 和 XML 模式(Schema)
這些主要規范往往非常廣泛,而且 Web 服務開發人員已發現難以實現完全互操作性。為了解決標准解釋中存在的差異,WS-I 已定義了一組稱作 WS-I Basic Profile, version 1.0 的一致性規則。JSR 172 符合基本配置文件(Basic Profile)。
J2ME 平台上的 Web 服務
JSR 172 規定了標准化客戶端技術,允許 J2ME 應用程序在典型 Web 服務架構上使用遠程服務,如圖 1 所示:
圖 1 在典型 Web 服務架構上的 J2ME Web 服務
在高層,該 Web 服務架構具有三個元素:
駐留在支持 WSA 無線設備上的網絡感知應用程序。該應用程序包括使用 JSR 172 運行庫與網絡進行通信的 JSR 172 存根。下文將會描述存根和運行庫所扮演的角色。
無線網絡和 Internet 以及對應的通信和數據編碼協議,包括二進制協議、HTTP 以及 SOAP/XML.
Web 服務器,扮演服務提供者的角色,通常在一個或多個防火牆和代理網關後面。Web 服務器通常提供對專用網絡上後端應用程序和服務器的訪問。
WSA 的第一個版本只解決了 Web 服務的使用,不支持服務端點的創建和開發;J2ME 設備可以是服務用戶,但不可以是服務提供者。JSR 172 還沒有規定使用 UDDI 的服務發現的 API。
了解 JSR 172
首先考查如何組織典型的基於 JSR 172 的應用程序,開始了解 J2ME Web Services 是如何運作的:
該應用程序本身是一個基於移動信息設備配置文件(Mobile Information Device Profile,MIDP)或個人基礎配置文件(Personal Basis Profile,PBP))的智能客戶機,具有特定於業務的邏輯、用戶界面、持久性邏輯以及生命周期和應用程序狀態管理。該應用程序可以使用 JAXP 子集 API 來處理 XML 文檔。還可以使用 JAX-RPC 子集 API 來使用 Web 服務,從而使用 JSR 172 存根和運行庫。
在像手提電話這樣的設備中,應用程序和 JSR 172 存根通常駐留在設備內存中,而所有 JSR 172 元素連同基礎配置文件和配置一起嵌入到設備本身。
JSR 172 運行庫和服務提供者接口
在 JSR 172 操作的中心是運行庫,帶有服務提供者接口,允許存根執行所有與調用 RPC 服務端點有關的任務:
設置特定於 RPC 調用的屬性。
描述 RPC 調用輸入和返回值。
編碼輸入值。
調用 RPC 服務端點。
對應用程序解碼並為其返回服務端點所返回的全部值。
圖 3 描繪出了客戶機應用程序、存根以及運行庫三者之間的關系:
圖 3 JSR 172 運行庫和 SPI
雖然 JSR 172 運行庫隱藏了像連接管理和數據編碼這樣的復雜性,但 SPI 從運行庫實現細節分離出了存根,從而允許存根在供應商實現之間的可移植性。
客戶機應用程序不直接與運行庫和 SPI 進行交互;而是使用存根。運行庫和 SPI 主要為打算開發 JSR 172 運行庫和自動化工具的第三方供應商所關心,比如開發人員用於生成存根的 WSDL 到 Java 映射工具。
由於應用程序開發人員不直接使用 SPI,因此本文不會較詳盡地介紹運行庫和 SPI。如果想了解關於它們的更多信息,可以查閱 JSR 172 規范。
JSR 172 JAX-RPC 子集 API
正如前面所提到的,JSR 172 Web 服務遠程調用 API 基於 J2SE 的 JAX-RPC 1.1 API 的一個完整子集。以下列表在高層描述了該子集。要了解詳細信息,請參閱 JSR 172 specification:
符合 WS-I Basic Profile。
支持 SOAP 1.1。
支持所有傳輸,比如 HTTP 1.1,它可以傳送 SOAP 消息,而且具有一個捆綁 SOAP 1.1 的已定義協議。
支持全套數據類型:邏輯型、字符型、短整型、整型、長整型、浮點型、雙精度型、字符串型(String)、基本類型數組以及復雜類型(包括基本或復雜類型的結構)。
注意,真正的浮點支持只在基於 CDC 或基於 CLDC 1.1. On CLDC 1.0 的軟件堆棧中出現,浮點型和雙精度型映射為 String。還要注意,所支持的數據類型會約束所支持的 WSDL 數據類型。後面的部分將討論 WSDL 到 Java 數據類型映射。
支持表示 RPC 調用或響應(使用 Document/Literal 消息傳遞模式的 WSDL 操作)的 SOAP 消息的 Literal 表示;不支持 Encoded 表示。
不支持帶有附件的 SOAP 消息。
不支持 SOAP 消息處理程序。
不支持服務端點;即不允許設備是 Web 服務提供者。
不提供發現支持(UDDI).
要減少對網絡帶寬的需求——以及要節省用戶的時間和每字節的開銷,請不要在設備本身上強制使用 XML 編碼。
注:實現可能會使用更高效的編碼,比如設備與無線網關間的二進制協議,只要它對消費者和生產者來說都是透明的。
下表描述了 JSR 172 JAX-RPC 子集中的軟件包:
Java\ 軟件包 描述
Javax.microedition.XML.rpc 定義了存根使用的 Type,它是合法簡單類型的枚舉:邏輯型、字節型、短整型、整型、長整型、浮點型、雙精度型、 String
定義了存根使用的 ComplexType,它是表示 WSDL xsd:complextype 定義的特殊類型
定義了存根使用的 Element,它表示 WSDL xsd:element 定義
定義了存根使用的 Operation,它表示服務端點的 WSDL wsdl:Operation
定義了 FaultDetailHandler,這是一個處理自定義故障的存根所實現的接口
定義了存根使用的 FaultDetailException,這是一個 Exception 類,表示運行時實現拋出的作為 JAXRPCException 結果的異常;為 service-specificDefines 返回存根異常詳細值所使用的 QName(限定名)類,並將相關的 QName 返回給 Stub 實例
Javax.XML.namespace 定義了存根所使用的 QName(限定名)類
Javax.XML.rpc 定義了 Stub 接口,這是所有 JAX-RPC 存根的基本接口;還定義了 NamespaceConstants 以及與 JAXRPCException 有關的類
java.rmi 標准 Java.rmi 軟件包子集,包含它以滿足 JAX-RPC 依賴性;只定義了 Remote 處理程序和三個異常類:MarshalException、 RemoteException 和 ServerException
除了像 RemoteException 這樣的異常,存根還使用大多數上述 API;應用程序本身使用存根。
在應用程序中使用 JAX-RPC 子集
創建 JSR 172 JAX-RPC 客戶機的一般步驟如下:
從描述遠程 Web 服務的 WSDL XML 文檔生成 JSR 172 JAX-RPC 存根類。
在自己的代碼中創建生成存根的實例。
實例化之後,調用生成存根的方法。這些方法與 WSDL XML 文檔中服務端點的 wsdl:操作相對應。
生成 JSR 172 JAX-RPC 存根
客戶機應用程序通過存根和設備的 JSR 172 運行庫與遠程服務進行交互。該運行庫隱藏了與連接、SOAP 以及數據編碼管理相關的復雜性。
要與運行庫進行交互,客戶機通常使用存根生成器所生成的存根或者 WSDL 到 Java 的映射工具,它們用於在 WSDL 文檔中輸入遠程服務接口定義,並生成使用 JAX-RPC 子集 API 和運行庫 SPI 的存根類。雖然也能手工開發這些存根,但使用該生成器效果會更佳。下圖概述了存根的生成:
生成 JSR 172 存根的一般步驟。 最後所得到的存根類實現了 Stub Interface 和服務的遠程接口,並使用了支持類。
生成 JSR 172 JAX-RPC 存根的步驟類似於創建基於 RMI 的應用程序的步驟。在標准 RMI 中,RMI 編譯器 (rmic) 從遠程接口定義生成存根。如圖 4 所示,創建 WSA 應用程序之後,WSDL 到 Java 的映射工具從 WSDL XML 文件生成存根。映射工具生成遠程接口 Java 文件和支持類,它們由生成的存根本身所使用。然後將 JSR 172 存根編譯為 Java 類,並同應用程序一起打包。
Sun 的 J2ME Wireless Toolkit V2.1 包含 JSR 172 JAX-RPC 存根生成器——本文稍後將講述如何使用它。第三方供應商提供了自己的存根生成器。
使用 JSR 172 JAX-RPC 存根
存根一旦生成了,應用程序就可以使用它了。以下代碼片段使用了稱作 PubService 的服務,以便檢索無線技術文章:
package J2MEdeveloper.pubwebservice
// MIDP
import Javax.microedition.midlet.MIDlet;
import Javax.microedition.lcdui.Display;
import Javax.microedition.lcdui.Form;
...
// JAX-RPC
import Java.rmi.RemoteException;
// JAX-RPC
String serviceURL = "www.J2MEdeveloper.com/pubwebservice";
String articleID = "IntroJSR172";
...
// Instantiate the service stub.
PubService_Stub service = new PubService_Stub();
// Set up the stub.
service._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, serviceURL);
service._setProperty(Stub.SESSION_MAINTAIN_PROPERTY, new Boolean(true));
...
try {
// Invoke the PubService method getArticleByID() to retrIEve
// a specific article by ID. A WirelessArticle represents an
// article and has methods to get the article's author, date,
// title, summary, and body. PubService also has a method to
// get the PubService's RSS feed.
WirelessArticle article = service.getArticleByID(articleID);
// Create a Form to display the article.
Javax.microedition.lcdui.Form form =
new Form(article.getShortTitle());
form.append(wrap(article.getSummary()));
...
...
display.setCurrent(form);
} catch (RemoteException e) {
// Handle RMI exception.
} catch (Exception e) {
// Handle exception.
}
...
使用存根會使遠程服務調用變得非常容易。該代碼用例子說明了 PubService_Stub,初始化了實例,並調用了它的其中一個方法 getArticleByID(),以便對文章本身進行檢索。然後,應用程序使用了文章的訪問方法來獲取文章的標題、摘要等等。
注意,因為調用 PubService_Stub.getArticleByID() 是分塊調用,因此在實際應用程序中,要用分離的線程來分派該調用。在 WSA 中,方法調用遵循常見的客戶機/服務器交互的同步請求-響應模型:客戶機向服務器發出請求,然後等待服務器的響應:
圖 5 同步請求/響應模型
如何對方法及其參數進行編碼、序列化和發送,以及如何接收、解碼和反序列化響應,對應用程序來說是完全透明的。JSR 172 存根和運行庫處理所有這些單調冗長的細節。
將 WSDL 數據類型映射到 Java
下面簡要考察一下 JSR 172 的 JAX-RPC 子集 API 中的 WSDL 到 Java 的數據類型映射:
WSDL 數據類型 Java 數據類型
xsd:string String
xsd:int int 或 Integer
xsd:long long 或 Long
xsd:short short 或 Short
xsd:boolean boolean 或 Boolean
xsd:byte byte 或 Byte
xsd:float String、float 或 Float
xsd:double String、double 或 Double
xsd:QName Javax.XML.namespace.QName
xsd:base64Binary byte[]
xsd:hexBinary byte[]
Arrays 取決於 XML 數組的模式
xsd:complexType 基本和類類型的序列
別忘了,因為 CLDC 1.0 不支持浮點類型,因此一定要將浮點型和雙精度型映射為 String 型。當目標既包含 CLDC 1.0 又包含 CLDC 1.1 平台時,開發人員應使用該默認映射。
要了解關於 WSDL 的更多信息,請參閱 JSR 172 specification 和 W3C's WSDL specification。
在應用程序中使用 JAXP 子集
現在將注意力轉移到 JAXP 子集——其他 JSR 172 API。記得 JAXP 和 JAX-RPC 都是可選軟件包;它們彼此獨立地存在。
JAXP XML 解析 API 基於 Simple API for XML v2(SAX2)的一個完整子集。雖然標准 SAX2 API 中的大多數類沒被包含在該子集中,但保留有所有必要的核心功能。
以下列表在高層描述了 JAXP 子集。要了解更多信息,請參閱 JSR 172 specification:
基於 SAX 2.0 的一個完整子集 不支持 SAX 1.0,因為 SAX 2.0 已將它取代了。
支持 XML 命名空間
既支持 UTF-8 又支持 UTF-16 字符編碼。
要麼不支持文檔對象模型(Document Object Model (DOM))的 Level 1.0 ,要麼不支持 DOM 的 Level 2.0 ,因為考慮到它太笨重。
不支持可擴展樣式表語言轉換(Extensible Stylesheet Language Transformations,XSLT)。
支持文檔類型定義(Document Type Definition,DTD)。
解析器可以根據 XML 文檔類型的指定內部或外部定義來對這些文檔進行驗證。 JSR 172 規定,文檔驗證是可選的,因為該操作開銷極大,一個文檔在限制較高的基於 CLDC 的環境不適合,但可能會在更高端的基於 CDC 的平台上獲得成功。如果跳過 DTD 處理,DefaultHandler.skippedEntity() 方法就會通知應用程序。該邏輯符合 XML 1.0 規范。
下表描述了 JSR 172 JAXP 子集中的軟件包:
Java 軟件包 描述
Javax.XML.parsers 定義了 SAXParser,這是一個表示簡單的基於 SAX 的解析器的 API。
定義了 SAXParserFactory,這是一個獲得和配置基於 SAX 的解析器的工廠。
定義了 ParserConfigurationException,拋出時指示配置錯誤。
定義了 FactoryConfigurationError,拋出時指示解析器工廠的問題。
org.XML.sax 定義了 Attributes,這是一個通過以下形式提供對屬性列表進行訪問的接口:
屬性索引
限定命名空間的名稱
限定(前綴)名稱
定義了 Locator,這是一個用於將 SAX 事件與文檔位置相關聯的接口。通過使用 Locator(該 Locator 通過調用 DefaultHandler 對象的 setDocumentLocator() 方法而指定)的實例,該應用程序可以獲取解析事件位置信息,比如事件行號、列號以及公共和系統標識符。
Locator 提供了獲取當前文檔事件結束位置的行號、列號的方法以及獲取當前文檔事件的公共和系統標識符的方法。
定義了 InputSource,它封裝輸入源。它提供了獲取或設置字節流、字符集、編碼以及公共和系統標識符的方法。
定義了通用 SAX 異常類、SAXException 以及三個子類: SAXNotRecognizedException、SAXNotSupportedException 和 SAXParseException
org.XML.sax.helpers 定義了 DefaultHandler,這是 SAX2 事件處理程序的基類。它提供了要擴展的應用程序的事件處理行為的缺省實現。應用程序開發人員只需覆蓋相關的方法,就可以設計解析行為。
在讀取 XML 流的時候,當解析器與 XML 元素或屬性發生沖突時,它就通過調用其中一個 DefaultHandler 方法來通知確定事件的應用程序;例如:
startDocument() 和 endDocument(),在 XML 文檔的開頭和末尾
startElement() 和 endElement(),在 XML 元素的開頭和末尾
characters(),當在元素內部發現字符數據時
skippedEntity(),當跳過一個實體時
JSR 172 specification 描述了 DefaultHandler 方法的其余部分。
以下方法定義顯示了如何使用 JAXP 來解析從 PubService 檢索的 RSS 提要:
/**
* Invoke the service and parse the RSS feed.
*/
private void parseRSSfeed() {
try {
// Create a RSS parser handler
parser = new RSSParserHandler();
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
saxParser = factory.newSAXParser();
} catch (Exception e) {
// Handle exception...
return;
}
// Invoke the remote service method getRSSFeed to
// retrIEve the RSS feed for the Pub service
// web site.
String rssFeed = service.getRSSFeed();
// Parse the RSS feed.
byte[] rssFeedByteArray = rssFeed.getBytes("UTF-8");
ByteArrayInputStream rssFeedByteArrayInputStream =
new ByteArrayInputStream(rssFeedByteArray);
saxParser.parse(rssFeedByteArrayInputStream, parser);
} catch (RemoteException e) {
// Handle RMI exception.
} catch (Exception e) {
// Handle exception.
}
}
parseRSSfeed() 方法使用 PubService_Stub.getRSSFeed() 遠程服務來檢索 PubService 的 RSS Content XML 文檔,然後將其傳遞到 JAXP RSS-feed XML 解析器。以下類定義顯示了如何實現負責解析 RSS 提要的處理程序:
/**
* RSS Parser handler class to parse the RSS feed from the
* server.
*/
class RSSParserHandler extends DefaultHandler {
// Stack to keep track of document parsing.
Stack stack;
// Current document element.
Object current;
/** Start of document processing. */
public void startDocument() {
rssParsingComplete = false;
title = link = description = null;
stack = new Stack();
}
/** Process the new element. */
public void startElement(
String uri, String localName, String qName,
Attributes attributes) throws SAXException {
if ("item".equals(qName)) {
title = link = description = null;
}
stack.push(qName);
}
/** Process the character data for current tag. */
public void characters(
char[] ch, int start, int length) {
Object current = stack.peek();
if ("title".equals(qName)) {
title = new String(ch, start, length));
} else if ("link".equals(qName)) {
link = new String(ch, start, length));
} else if ("description".equals(qName)) {
description = new String(ch, start, length));
}
}
/** Process the end element tag. */
public void endElement(
String uri, String localName, String qName) {
stack.pop();
if ("rss".equals(qName)) {
rssParsingComplete = true;
} else {
// Do something with title, link, description, such
// as displaying it to the user.
}
}
}
注意,startDocument()、startElement()、stopElement() 以及 characters() 方法的定義提供了 SAX2 XML 解析所需的最低功能。
支持 Web 服務的 J2ME 無線工具包
J2ME Wireless Toolkit v2.1 包含開發充分利用 J2ME Web Service 的 MIDlet 所需的庫。該工具包還包含 JAX-RPC 存根生成器,可以從命令行或 KToolbar 菜單運行它,如下圖所示:
圖 6 J2ME 無線工具包 2.1 實用程序和存根生成器
在 UtilitIEs 屏幕上選擇 Stub Generator。在出現的對話框中,輸入描述要使用服務的輸入 WSDL 文檔的 URL、生成文件的輸出路徑以及用於已生成存根文件的軟件包名。相應地選擇 CLDC 1.0 或 1.1。
J2ME 無線工具包的 v2.1 還包含一個示例應用程序 JSR172Demo。
結束語
J2ME Web 服務規范(J2ME Web Services Specification)使 J2ME 平台上的 Web 服務和 XML 解析編程接口標准化了。隨著 JAX-RPC 子集 API 的出現,開發人員可以使用 Java 編程語言和熟悉的 JAX-RPC API 來創建使用基於 XML 的遠程服務的應用程序。而不必處理 HTTP、SOAP 和數據轉換的底層細節。而且隨著 JAXP 子集 API 的引入,XML 解析目前已是 J2ME 平台本身不可分割的一部分。J2ME Web Services API 消除了開發人員在每個應用程序中包含用於遠程調用和 XML 解析代碼的需要。
這些功能強大的 API 允許移動應用程序更容易地訪問基於 Web 的服務,但是開發人員千萬別忘了 J2ME 設備提供的受限制的應用程序環境。僅僅移植現有的基於 XML 的桌面或企業應用程序不是開發 J2ME 平台的 Web 服務客戶機的適當方法。對設備處理能力、電池壽命、網絡帶寬以及安全性的適當考慮也是不可缺少的。
更多信息
JSR 172: J2ME Web Services Specification
Web Services InterOperability Organization (WS-I)
World Wide Web Consortium (W3C)
Organization for the Advancement of Structured Information Standards (OASIS)
The Complexity of Developing Mobile Networked Data Services, J2ME Wireless Connection Wizard For Sun Java System Studio
W3C WSDL website