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)
致谢
非常感谢 JSR 172 规范负责人 Jon Ellis 和 Mark Young 对本文的反馈。
http://gceclub.sun.com.cn/staticcontent/html/2004-05-20/j2me.html