2002 年 12 月 01 日
用
于基于 XML 的远程过程调用的 Java API(Java APIs for XML-Based Remote Procedure
Call,JAX-RPC)是实现 Web 服务互操作性的一个重要的促进步骤。在这第一篇文章中(共两篇),Joshy Joseph
将带您了解这种互操作性工作的核心:JAX-RPC 类型映射系统。您将了解如何把 XML 类型转换为 Java 类型,以确保 Web
服务客户机和基于 Java 的应用程序之间能够进行平稳的数据交换。
用于基于
XML 的远程过程调用的 Java API(JAX-RPC)在 Java 社区过程(Java Community
Process)已经达了最终推荐阶段,被命名为 JSR 101。XML Web 服务供应商已经开始把这个软件包作为一个核心 API,用以在
Java 平台上构建可互操作的 Web
服务。在这一系列文章中,我将使用样本代码在整个过程中对您进行指导,带您逐步了解该标准提供的主要功能。在这一系列文章的最后,您将熟悉
JAX-RPC 规范的核心功能;这些知识将有助于服务开发者、客户机开发者以及工具箱开发者设计尽可能可以互操作的 Web 服务。
对于这里的讨论,我将假定您熟悉基本的 Web 服务概念(如 SOAP、WSDL 和 XML)。(请参阅下面的
参考资料部
分,以获取关于这些概念以及其他相关主题的更多信息。)您将要碰到的代码样本都是用 Apache Axis 测试版 3 和 Sun 的 Web
Services Developer Pack 开发的 — 换言之,这些代码样本是用 JAX-RPC 规范的参考实现开发的。
本文是一个由两部分组成的系列文章的第一部分。我将从讨论 JAX-RPC
的最重要方面之一 — 即类型映射系统 — 开始讨论。这个系统使运行时系统能够将 WSDL 文档中定义的每种 XML 类型映射为 Java
服务接口所指定的相应 Java 类型,反之亦然。这是使 Web 服务可实现互操作的
一个主要步骤。JAX-RPC 为一组已扩展的 XML 类型和 Java 类型指定可扩展的类型映射支持。JAX-RPC
运行时系统实现序列化框架以支持该类型映射。
在随后的一篇文章中,我将根据在此处已经建立的基础进行讨论,深入介绍该规范其余部分的具体细节。
基本的 JAX-RPC 概念
作
为一名 Web 服务开发者,您应该已经熟悉 Web 服务标准(SOAP、WSDL、XML)所提供的优势和体系结构方面的灵活性了。Web
服务背后的概念帮助您使用抽象的接口设计松散耦合的服务,这样就可以把服务实现与服务定义分开。Web
服务社区面临的最重要的问题是可互操作的服务的开发。为了解决这个问题,各个标准小组(包括 WS-I、OASIS、W3C 以及
SOAPBuilders)正在非常努力地为
互操作性定义标准(请参阅 参考资料以获取更多信息)。
JAX-RPC
是 Java 社区的工作成果,用于彻底解决这一问题,并为客户机端和服务器端的 Web
服务实现提供一个众所周知的应用程序编程接口(application programming interface,API)。通过采用一个面向
Web 服务的标准 API,JAX-RPC
旨在帮助服务用户(客户机)和服务实现者获得最大程度的灵活性,方法是通过把服务互操作性的重担转移到运行时基础架构。通过采用标准组织提供的全局标准、
使用定义明确的概要文件(例如 WS-I)以及创建 IDE 和其他开发工具来与这些标准相匹配,运行时框架可以提供连接级别的互操作性。
就其本质而言,JAX-RPC 定义并使用了一种基于 XML 的远程过程调用机制。它使服务器(即服务提供者)能够用标准的 API 定义其服务并且能够用 WSDL 描述其服务;它使客户机(即服务消费者)能够用标准的 API 与服务器进行通信。
该规范内容详尽(大约 160 页)并且向 Java 开发者介绍了新的编程模式。下面就是我将在这一系列文章中讨论的概念的列表:
- 类型映射系统
- 服务端点
- 异常处理
- 服务端点上下文
- 消息处理程序
- 服务客户机和服务上下文
- 带附件的 SOAP
- 运行时服务
- JAX-RPC 客户机调用模型
如上所述,这第一篇文章将仅着重介绍第一个概念:JAX-RPC 的类型映射系统。在后面的一篇文章中,我将探讨该规范的其他关键概念。
在我介绍每个主题时,我将用一个虚构的书店(以下称 Acme Booksellers)所提供的样本 Web 服务代码来阐述重点部分。
该书店提供伙伴可以以各种方式使用的服务:
- 消费者可以根据某个搜索标准(ISBN、作者姓名等)搜索书籍。
- 经批准的作者可以上载书籍的更新。
- 评论者可以撰写书籍评论。
大家都知道,进入 Web 服务体系结构和实现有两种体系结构方式:
- 自顶向下(即从 WSDL 入手)
- 自底向上(即从 Java 实现类入手)
我将用 Acme Booksellers 样本解释如何使用与 JAX-RPC 编程模型一致的自顶向下的设计和实现。您可以在
附录中看到用于样本服务的 WSDL 文件。一旦研究过其代码后,您就可以继续进行下一部分了。
JAX-RPC 的类型映射系统:介绍
JAX-RPC
规范最重要的成就之一就是它定义了一个标准,该标准用于将 WSDL 文档(它表示了服务描述)映射为其 Java 表示(服务端点、存根、绑定以及
Java 类型),反之亦然。以前这是一个主要的易引起混淆的地方,因为每个 Web 服务工具箱供应商对于相关的标准(WSDL、SOAP 和
XML)以及这些标准之间的关系都有自己的解释。因为这些标准并行发展并且尚未成熟到足可依赖的程度,所以更增加了这种混淆。
让我们深入讨论细节问题以了解 JAX-RPC 是如何通过定义一个公共编程模型来设法彻底解决这些问题的。请注意,这个标准化成就是建立在 WSDL 1.1 和 SOAP 1.1 的基础上的。
JAX-RPC 规范中关于 WSDL 到 Java 的映射那部分解决了下列问题:
- 将 XML 类型映射为 Java 类型
- 将抽象的 WSDL 定义(端口类型、操作和消息)映射为 Java 接口和 Java 类。
- 将具体的 WSDL 定义(端口、绑定和服务)映射为 Java 类。
我将逐个讨论这些问题中的每一个。
将 XML 类型映射为 Java 类型
简单类型
XML Schema 和 SOAP 1.1 编码所定义的大多数简单的 XML 数据类型被映射为其对应的 Java 类型。您可以在 JAX-RPC 规范的表 4-1 中看到这种映射的详细信息。(请参阅
参考资料)。
作为一名开发者,在映射简单 XML 类型时,您应该记住以下几点。首先,JAX-RPC 映射规范没有为
xsd:anyType 指定特定的 Java 映射。
其次,内建的简单 XML 数据类型的
nillable 属性被设置为
true 的元素声明被映射为一个对应的 Java 基本类型的 Java 包装器类。例如,下面的代码被映射为
java.lang.Integer 。
<xsd:element name="code" type="xsd:int" nillable="true" />
|
这就为类型系统设计提供了将空(null)对象映射为其对应的 XML 类型的灵活性。您可以在 JAX-RPC 规范的表 4-2 中看到这些映射的完整列表(请参阅
参考资料)。
最后,SOAP 1.1 编码规范将其大多数的类型映射为
nillable 。JAX-RPC 使用 Java 封装的对象来映射这些类型。请参阅 JAX-RPC 规范的表 4-3 以获取更多信息。
|
元数据和 JSR 175
Java
社区中正在进行一些努力以解决 Java 语言中元数据信息的不可用性问题。JSR 175 中提出的元数据工具将使 Java
类、接口、方法和域能够用额外的属性标记。Web 服务领域中还有另一个项目(JSR 181)正在进行中,该项目使用这些元数据工具(属性)来扩展
Web 服务配置及部署模型。
|
|
复杂类型
XML Schema 复杂类型被映射为若干个带有 getter 和 setter 的 JavaBean 以访问复杂类型中的每个元素。当处理这些类型时,您需要记住几点。首先请注意,虽然复杂类型包含
xsd:sequence ,但是在这些 JavaBean 中没有保持元素的先后顺序,这一点很重要。
其次,元素属性
maxOccurs 值为正的复杂类型映射为一个相应类型的 Java 数组。这样一种类型并不定义
minOccurs 和
maxOccurs 所有可能的组合。对于那些试图映射所有与 XML Schema 相关联的语义的 Java 开发者来说,这可能是个问题。
最后,该映射的主要问题之一就是 XML 元素属性映射。因为没有与 Java 类相关联的元数据,所以当您试图将 JavaBean 转换为 WSDL 时,可能会碰到映射问题;因此,JAX-RPC 规范并未指出如何映射
xsd:attribute 。然而,在进行 SOAP 绑定时,您可以将
SOAPElement 用于此目的。Java 语言扩展将来可能会对此进行调整;请参阅标题为“
元数据和 JSR 175”的侧栏以获取更多关于这方面的信息。
下面的
清单 1向您展示了复杂类型映射是如何进行的。
清单 1. 正在进行的复杂类型映射
<xsd:complexType name ="Book"> <sequence> <element name="author" type="xsd:string" maxOccurs="10" /> <element name="price" type="xsd:float" /> </sequence> <xsd:attribute name="reviewer" type="xsd:string" /> </xsd:complexType>
|
在
清单 2中,您可以看到
清单 1中的类型如何被映射为带有 getter 和 setter 方法的 JavaBean。
清单 2. 来自清单 1 的类型的 Java 映射
Public class Book{ private float price; private String[] author; private String reviewer; // attribute //.... public String[] getAuthor() {.......} public setAuthor(String[] author) {.......} public float getPrice() {.......} public void setPrice(float price) {.......} public String getReviewer() {.....} public void setReviewer(String reviewer) {.....} }
|
数组
JAX-RPC 将 XML 数组映射为带操作符
[] 的 Java 数组。您可以在 WSDL 服务声明中找到几种不同种类的数组声明。
第一种类型是 restriction 使用 WSDL 1.1 指定的
wsdl:ArrayType 属性从
soapenc:Array 中派生出来的数组。
清单 3. 用 restriction 和 wsdl:ArrayType 定义数组
<complexType name = "ArrayOfString"> <complexContent> <restriction base="soapenc:Array"> <attribute ref="soapend:ArrayType" wsdlArrayType="xsd:string[]"> </restriction> </complexContent> </complexType>
|
JAX-RPC 将这些复杂数组类型映射为 Java
String[] ,
java.lang.String 作为数组中的元素。
下一种数组类型是 SOAP 1.1 规范所设置的 restriction 从
soapenc:Array 中派生出来的。
清单 4. 用 restriction 和 soapenc:Array 定义数组
<complexType name = "ArrayOfString"> <complexContent> <restriction base="soapenc:Array"> <sequence> <element name="stringArray" type="xsd:string" maxOccurs="unbounded" /> </sequence> </restriction> </complexContent> </complexType>
|
JAX-RPC 将这些复杂数组类型映射为 Java
String[] ,
java.lang.String 作为数组中的元素。
您在 WSDL 中可以找到的另一种数组声明如下所示。
<element name="myNumbers" type="soapenc:Array" />
|
清单 5展示了上述模式的一个实例。
清单 5. 映射为 Java Object 数组
<myNumbers soapend:arrayType="xsd:int[2]" > <number>1</number> <number>2</number> </myNumbers>
|
该声明被映射为一个 Java
Object 数组,这个数组中的各个元素对应于 XML Schema 类型。(此处它映射为
integer 。)
最后,您还可以找到由用户定义的 XML schema 类型构造而成的数组。清单 6 展示了这种类型的定义:
清单 6. 定义一种模式类型
<complexType name="Article"> <all> <element name="name" type="xsd:string"> <element name="author" type="xsd:string"> </all> </complexType>
|
现在您需要定义一个
Article 类型的数组。
清单 7. 用用户定义的模式类型定义一个数组
<complexType name="ArrayOfArticles"> <complexContent> <restriction base="soapenc:Array"> <sequence> <element name="article" type="tns:Article" maxOccurs="unbounded" /> </sequence> </restriction> </complexContent> </complexType>
|
WSDL 中的上述声明映射为一个
Article (Article[] article;) 类型的 Java 数组。
将抽象的 WSDL 类型映射为 Java 类型
既然您理解了如何将 XML 类型映射为 Java 类型,我就要考虑 WSDL 的特殊情况了。首先,我将快速讨论一下如何将抽象的 WSDL 类型映射为 Java 表示:
-
wsdl:porttype :
该类型被映射为服务端点接口;它扩展了
java.rmi.Remote 接口。在这一系列文章的下一篇文章中,我将讨论服务端点接口以及它们的要求。
-
wsdl:operation :
因
为 WSDL 1.1 并没指明操作名称需要是唯一的,所以可以有多个名称相同但参数不同的操作。您还应该记住,JAX-RPC 规范仅支持 WSDL
规范定义的单向操作和请求-响应操作;JAX-RPC
规范不支持要求-响应(solicit-response)操作样式或通知操作样式。您可以用可选的 parameterOrder 属性指定 WSDL 中的参数顺序。
我们来看一下参数传递样式。从
表 1 可以看出,这些参数传递样式可以是
in 、
out 和
inout ,这取决于您要怎样处理参数。
表 1. 参数传递样式
定义参数的位置
|
参数样式
|
可能的 JAX-RPC 映射类
|
样本 JAX-RPC 映射类
|
wsdl:input
|
in
|
包装器类或 JavaBean |
java.lang.Integer
|
wsdl:output
|
out
|
Holder 类
|
IntHolder 、
StringHolder 等
|
wsdl:input 和
wsdl:output
|
inout
|
Holder 类
|
IntHolder 、
StringHolder 等
|
如
表 1 所示,为了支持
out 参数和
inout 参数,定义了一个新的
Holder 类。服务客户机使用
Holder 类实例发送
out 参数或
inout 参数的值。
Holder 类的内容是通过远程方法调用修改的,且服务客户机在方法调用后可以使用这些修改过的内容。还要注意,JAX-RPC 规范定义了大量
Holder 类以支持基本类型。您可以定义自己的
Holder 类以支持从名为
Holder 的标准接口派生出来的复杂类型。
让我用一些样本代码来阐述这些要点。
清单 8包括一个样本 WSDL 定义。
清单 8. 样本 WSDL 定义
<xsd:complexType name="Authors"> <xsd:all> <xsd:element name="Authors" type="typens:AuthorArray"/> </xsd:all> </xsd:complexType> <message name="AuthorPresentRequest"> <part name="Authors" type="typens:Authors"/> </message> <message name="AuthorPresentResponse"> <part name="return" type="xsd:boolean"/> <part name="Authors" type="typens:Authors"/> </message> <portType name="AcmeAuthorPresentPortType"> <operation name="IsAuthorPresent"> <input message="typens:AuthorPresentRequest"/> <output message="typens:AuthorPresentResponse"/> </operation> </portType>
|
您可以看到输入消息和输出消息包含
Authors 类型作为参数。这是参数传递的
inout 样式。这需要一个用于
Authors 类型的
Holder 类,如
清单 9所示。
清单 9. 为 Authors 类型创建一个样本 Holder 类
public final class AuthorsHolder implements javax.xml.rpc.holders.Holder { public com.acme.www.Authors value; public AuthorsHolder() {} public AuthorsHolder(com.acme.www.Authors value) { this.value = value; } }
|
最后,
清单 10展示了如何实现 Java 端点。
清单 10. Java 端点实现
public interface AcmeAuthorPresentPortType extends java.rmi.Remote { public boolean isAuthorPresent(AuthorsHolder authors) throws java.rmi.RemoteException; }
|
您需要理解服务端点接口的名称以及端点方法的签名是怎样派生的。它们可以从
wsdl.portType 的名称属性和操作定义中派生,或者从
wsdl.binding 名称和操作声明中派生。这是您要记住的重要一点,因为可以有不同样式的多个绑定(例如 RPC/文档)指向同一个
portType 。这种绑定样式上的差异可能会导致创建出带有不同名字和签名的不同端点。因此,您应该谨防这种有混淆的映射。
|
SOAP 消息样式和操作编码
您已经知道存在两种 SOAP 消息样式,
文档和
RPC,它们定义了 SOAP 消息正文的格式。还存在两种对操作的参数进行编码的模式,即
文字的(literal)和
编码的(encoded)。
表 2列出了 SOAP 消息样式和模式并说明了 JAX-RPC 是如何对其进行定义的。
|
|
将具体的 WSDL 类型映射为 Java 类型
现在请把您的注意力转到具体的 WSDL 元素以及它们的 Java 映射上。
-
wsdl:binding :
JAX-RPC 没有为
wsdl.binding 定义任何标准的 Java 表示。这意味着运行时可以定义所需的绑定。至少,JAX-RPC 运行时系统应该为具有如下特征的 SOAP 绑定提供支持:
- 带有已编码的操作模式(RPC/编码的)的 RPC 样式。
- 带有文字的操作模式(文档/文字的)的文档样式。
在进行文字映射时,每个消息部件都被映射为一种 Java 类型,早些时候我在前面的
将 XML 类型映射为 Java 类型部分中已经描述过这一点。如果不存在这种映射,那么就使用一个
javax.xml.soap.SOAPElement Java 类来表示消息部件。这种映射的一个示例就是包含多个属性的复杂类型。因为没有为属性定义标准的映射,所以这种复杂类型就被表示为
SOAPElement 。
请参阅标题为
SOAP 消息样式和操作编码的侧栏以及
表 2以获取关于这些不同样式和模式的更多信息。
表 2. SOAP 消息样式和 JAX-RPC
样式(SOAP 消息正文格式)
|
模式(参数编码)
|
JAX-RPC 相关信息
|
文档(用一种模式定义正文格式) |
文字的(对每种参数编码使用 XML Schema) |
必需的 |
文档 |
编码的(使用 SOAP Section 5 编码) |
可选的 |
RPC(用 SOAP Section 7 规则定义正文格式) |
编码的 |
必需的 |
RPC |
文字的 |
可选的 |
-
wsdl:service :
这个 WSDL 具体定义对一组服务端点(或
wsdl:ports )进行分组。JAX-RPC 将
wsdl:service 映射为一个 Java 服务类。这个服务类实现
javax.xml.rpc.Service 接口或者一个生成的 Java 接口(该 Java 接口被映射为
wsdl.service 定义),它接着再实现
javax.xml.rpc.Service 。
这个服务类充当以下内容的工厂:
- 服务端点的动态代理(在这一系列文章的下一篇文章中对此有更多描述)。您可以在服务对象中使用
getPort(..) 方法创建动态代理。
- 一个
javax.xml.rpc.Call 类型的实例,它用于动态调用服务端点上的远程操作(在这一系列文章的下一篇文章中对此有更多描述)。该动态调用接口需要在运行时创建一个
Call 对象。为此,您可以使用服务类的
createCall(..) 操作。
- 一个生成的存根类实例(在这一系列文章的下一篇文章中对此有更多描述)。这些存根是由生成的类提供的,它们是由工具创建的。
实现
Service 接口是 JAX-RPC 运行时系统的责任。根据绑定要求可以存在不同的存根实现。还要注意,
Service 实现类应该实现
java.io.Serializable 接口或
javax.naming.Referenceable 接口以支持 JNDI 名称空间中的注册。
|
可扩展的类型映射支持
我
在这篇文章中已经讨论过,当前的 JAX-RPC 规范定义了简单类型的类型映射。因为这种类型映射不足以支持更复杂的数据类型,所以 JAX-RPC
规范已经增加了支持,以便用可插的序列化器和反序列化器来扩展它的类型映射系统。当前的规范提供应用程序编程模型以开发可插性,并将可插性构建到序列化器
和反序列化器中。请向您的 JAX-RPC
供应商咨询以获得更多关于它对定制类型映射的支持以及它所支持的类型这些方面的信息。还要记住,这种灵活性有一个缺点:它可能会导致 Web
服务实现间的互操作性问题。
|
|
将 Java 类型映射为 WSDL
当
然,JAX-RPC 规范中相当大一部分讨论了如何将 Java 类型映射为 XML 类型以及如何将 Java 服务端点接口映射为 WSDL
定义。这样的映射类似于我在此处所介绍的映射,但过程相反。在此我不打算详细解释该过程。请参考 JAX-RPC 规范的第 5
节以获取更多信息。这是用于 Web 服务设计的自底向上体系结构,它主要用于将现有的、基于 Java 的应用程序转换为 Web
服务或者用于创建包装这些应用程序的包装器。
结束语以及展望
在
本文中,我已经讨论了 JAX-RPC 对简单类型映射的支持。要获得 Web
服务实现间最大程度的互操作性,您需要理解这个核心概念。虽然当前的类型映射列表很详细,但它们还没详尽到足以支持数据类型的所有要求。因此,JAX-
RPC 运行时实现者可以通过定制的序列化器自由支持其他可扩展的类型映射机制。
我希望这个关于 JAX-RPC 中的类型映射支持的讨论有助于您领会 JAX-RPC 系统如何处理 XML 类型和 Java 类型以及如何将这些类型中的每一种类型转换成其他类型而不丧失其语义这些基本概念。
在这一系列文章的下一部分中,我将讨论 JAX-RPC 规范的其他方面(包括服务实现、服务客户机以及其他的核心概念)。在那之前,请努力掌握此处所概述的类型映射信息。
参考资料
关于作者
|
|
|
Joshy Joseph 是 IBM OGSA 开发小组的一名软件工程师。他的主要编程爱好是使用新兴技术(如 Web 服务、语义 Web、REST、网格计算)以及使用基于 UML、AOP 和 XP 的编程模型。您可以通过
joshy@us.ibm.com与他联系。
|
|