Apache SOAP 类型映射,第 1 部分: 深入研究 Apache 的序列化 API

来源:互联网 发布:局域网网络测速工具 编辑:程序博客网 时间:2024/05/19 21:01

2002 年 4 月 01 日

SOAP 定义了一个简单的有线协议来传输应用程序级的数据。因为有丰富而又可扩展的类型系统,这个协议可以很容易地将任意 Java 类型作为序列化的 XML 进行传送。在本文,即关于 Apache SOAP 工具箱中的类型系统支持的系列文章的第一部分(共两部分)中,Gavin Bong 将向您介绍 SOAP 的类型系统的理论基础。您还将了解到更多关于 SOAP 对序列化和反序列化的编程支持,最后,深入研究一下工具箱的内部机制。更好地理解这个过程是如何运作的将有助于您构建自己的分布式系统。

当以电子方法交换数据时,进行交换的端点需要预先在两方面达成一致: 交互模式类型系统。前者与通信通道的体系结构(例如,点对点和多对一,或分块对异步)有关。而另一方面,后者是要在对消息进行编码和解码的过程中使用的达成一致的数据格式。

在 本文中,我将描述 SOAP 中可应用于 Apache SOAP 工具箱的类型系统。虽然 SOAP 工具箱的当前版本同时支持消息传递和 RPC 交互两种模式,但本文只重点讨论后者。除非另有声明,否则本文中讨论的功能一般适用于 2001 年 5 月发行的 Apache SOAP 版本 2.2(请参阅 参考资料)。在适当的地方,我将突出显示自该发行版之后发生的错误修正和接口变化。

除术语之外,让我来介绍一下整个系列中使用的用来表示 QName(qualified name,限定名)的名称空间前缀(适用于 SOAP 和 W3C 的 XML Schema)和约定。

  • 2001 年发布了一个 SOAP 1.2 工作草案,但我们将只讨论 SOAP 1.1 的细节问题。这样,前缀 SOAP-ENVSOAP-ENC 将分别引用名称空间 http://schemas.xmlsoap.org/soap/envelope/http://schemas.xmlsoap.org/soap/encoding/
  • 除非另有明确声明,一般我们假定前缀 xsdxsi 分别引用名称空间 http://www.w3.org/2001/XMLSchemahttp://www.w3.org/2001/XMLSchema-instance
  • 我们将以下面的格式编写带外部 URI 的 QName:
    {uri}localpart.

    一个示例: {http://xml.apache.org/xml-soap}Map

SOAP 和 RPC

当用 SOAP 执行 RPC 调用时,在 Web 服务请求者和提供者之间交换两种类型的有效负载:

  • 远程方法的参数(RPC 请求)
  • 返回值(RPC 响应)

从技术上来说,SOAP 附件和 SOAP 报头也可以组成 SOAP RPC 调用中的有效负载,但现在我们可以安全地忽略它们。通常是使用俗称 Section 5的方法对有效负载编码。虽然 SOAP 规范没把它指定为缺省的编码方法,但目前可用的所有 SOAP 框架都支持这个编码方法。

Section 5 编码描述了将对象图转换为 XML 的方法。如果一直坚持使用这种方法,就可以将 SOAP XML 重新构造回它的原始形态。另一种编码方法通过使用模式语言(如 W3C 的 XML Schema)约束有效负载。在前一个方法学中,事务中感兴趣的各方对序列化规则都持一致意见,而在后一个方法学中,他们是对消息的文字格式意见一致。在 这个序列的第 2 部分中,您会看到一个示例,它演示由模式约束的 SOAP。

清单 1和 2详细演示并深入研究了 Section 5 编码。 清单 1 中的 Foo JavaBean 在 清单 2中被序列化。


清单 1. Foo JavaBean
class Foo{
int i;
String s;
public Foo() {}
.... /* property mutators and accessors not shown for brevity */
}


清单 2. Foo 对象的 SOAP 表示
  <SOAP-ENV:Body>
<ns1:eatFoo
xmlns:ns1="urn:ibmdw:myservice"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<fooParam
xmlns:ns2="http://foo/type"
xsi:type="ns2:Foo">
<i xsi:type="xsd:int">1000</i>
<s xsi:type="xsd:string">Hello World</s>
</fooParam>
</ns1:eatFoo>
</SOAP-ENV:Body>

清单 2 中的 SOAP XML 实例表示一个 RPC 调用,该调用将方法调用 eatFoo 调度到由 URI urn:ibmdw:myservice 标出的 Web 服务。元素 fooParamis 被称为 访问器(accessor);它们是值的容器。多引用(Multireference)访问器不在这个定义的范围内;这些是一些空元素,使用“XML 指针语言”(XML Pointer Language)之类的机制(关于更多信息,请参阅下面的 参考资料)来引用其它包含实际值的元素。

清单 2 中遍布的 xsi:type 属性提供访问器的子类型。通常,Web 服务提供者和请求者已经预先就每个 RPC 调用的参数数据类型达成了一致。要对 SOAP XML 实例进行正确的反序列化,有了这个预先达成的一致就够了,即使没有 xsi:type 属性也可以。但请考虑一下这样一个用例,在其中有一个特殊的方法既接受字符串也接受整数(例如可以将参数定型为接受 java.lang.Object 参数)。在这个案例中,为使对象正确进行反序列化,需要一个显式的 xsi:type 声明所包含值的类型。带有显式的 xsi:type 属性的访问器被称为 多态访问器(polymorphic accessor)

Apache SOAP 被设计为把所有的访问器都当作多态访问器看待。版本 2.2 继续使用多态访问器生成 SOAP XML 实例,但编程时它被设计为不用多态访问器就可以进行数据编出。稍后,在本文中我将简要说明如何执行这种操作。

xsi:type 属性将 QName 作为它的值。 xsi:type 属性可接受的值有:

“XML 模式第 2 部分:数据类型”规范中的内置类型。

Apache SOAP 支持大多数内置类型并向后兼容该模式第 2 部分规范的所有版本的类型,这些版本中包括:

  • 推荐版 (2001)
  • 候选推荐版 (2000 年 10 月)
  • 工作草案 (1999)

准确地说,Apache SOAP 并没有涵盖所有的内置类型,但它的确支持那些最常用的类型。在下一部分,我将看一下 Apache SOAP 支持的 Section 5 编码类型的详细清单。

表示一个用户定义的类型的任意 QName

这些用于表示混合类型如 清单 2 中的 ns2:Foo 。这种类型的序列化格式可以由模式文档来定义,或者,象 Apache SOAP 中那样,由定制的(反)序列化器来定义。

SOAP 1.1 扩展类型。


这些包括 SOAP-ENC:Array (表示数组成员的一个有序序列)和 SOAP-ENC:base64 (表示一个 base-64 编码的字符串)。

清单 2 中的 encodingStyle 属性被用于指出所用的序列化模式。例如,Section 5 编码由 URI http://schemas.xmlsoap.org/soap/encoding/ 指出。Apache SOAP 有三个 encodingStyle 的内置支持:Section 5、XMI 和文字 XML(literal XML)。同时也支持定制的 encodingStyle

但是,Apache SOAP 的 Section 5 编码支持并不全面。它仍然缺少一些功能,如稀疏(sparse)支持、部分传输支持和多维数组支持。

直 到现在,我一直都在暗示进行数据交换的端点将能够魔术般地(反)序列化以 Section 5 编码的类型;但前提条件实际上依赖于对被来回传送的实体的数据模型要有某些非常规的一致。这基本上意味着,端点需要在一些“众所周知”的类型上达成一致。 然而,这里有一个替代方案。不想受 Section 5 限制的软件开发者可以求助于 基于模式的序列化(schema-based serialization)。将 SOAP 服务的接口和请求/响应消息的模式(一个或多个)一起发布,这种方法就可以生效。“Web 服务定义语言”(Web Services Definition Language,WSDL)是当前用于这种目的的实际标准。Apache SOAP 是无 WSDL 意识的,但 Apache SOAP 的后继工具箱 Axis(请参阅 参考资料)有这种意识。





回页首

Section 5 编码

SOAP 没有为 Section 5 编码描述的 SOAP 类型定义任何语言绑定。相反,它的类型已足够通用,能够为 Java 编程语言和大多数其它主流编程语言中的一些典型数据类型建模。在这部分中,我们将深入研究 Apache SOAP 对编码和解码 Java 基本数据类型和任意 Java 类的支持。

但首先让我们来定义一些术语。 序列化(Serialization)是将 Java 对象转换为 XML 实例的过程,而 反序列化(deserialization)是从 XML 重新构造 Java 对象的过程。Apache SOAP 利用被称为 序列化器(serializer)反序列化器(deserializer)的构造来执行这些操作。在整篇文章中,我将使用简称 (反)序列化器表示短语 反序列化器和序列化器

包含在 Apache SOAP 工具箱内的(反)序列化器的基集(base set)可以处理三组数据类型: 简单类型(simple type)、混合类型(compound type)特殊类型(special type)。术语 简单类型混合类型直接引自 SOAP 1.1 规范,它们在本文中的意思和在 SOAP 1.1 规范中的意思相同。我已经杜撰了 特殊类型这个术语来表示与前面两组类型不太相符的 Java 类型。

简单类型

Apache SOAP 直接将这些类型作为访问器来序列化,只有文本节点作为子节点。一般情况下,XML 实例具有下列格式:

<accessor xsi:type="qname">
<!-- data -->
</accessor>


表 1. Apache SOAP 中支持的简单类型

Java SOAP 序列化器 反序列化器 String xsd:string 内置 * StringDeserializer * Boolean xsd:boolean 内置 * BooleanObjectDeserializer * boolean xsd:boolean 内置 * BooleanDeserializer * Double xsd:double 内置 * DoubleObjectDeserializer * double xsd:double 内置 * DoubleDeserializer * Long xsd:long 内置 * LongObjectDeserializer * long xsd:long 内置 * LongDeserializer * Float xsd:float 内置 * FloatObjectDeserializer * float xsd:float 内置 * FloatDeserializer * Integer xsd:int 内置 * IntObjectDeserializer * int xsd:int 内置 * IntDeserializer * Short xsd:short 内置 * ShortObjectDeserializer * short xsd:short 内置 * ShortDeserializer * Byte xsd:byte 内置 * ByteObjectDeserializer * byte xsd:byte 内置 * ByteDeserializer * BigDecimal xsd:decimal 内置 DecimalDeserializer GregorianCalendar xsd:timeInstant ! CalendarSerializer CalendarSerializer Date xsd:date DateSerializer DateSerializer QName xsd:QName QNameSerializer QNameSerializer
  • 内置: org.apache.soap.encoding.SOAPMappingRegistry 中实现的内部类。
  • * :从 Apache SOAP 版本 2.1 以后。
  • ! :在 2001 年 1 月,当“W3C XML 模式数据类型”规范(请参阅 参考资料)转变为提议推荐状态时, timeInstant 被重命名为 dateTime 。Apache 仍不支持 xsd:dateTime (请参阅 参考资料)。

在继续往下讨论之前,让我们更仔细地检查一下 表 1 中三点。首先,机灵的读者会注意到 char 不受支持(请参阅 参考资料)。幸运的是,为它写一个定制的(反)序列化器很容易。其次,对 BooleanDeserializer 的更新使它能够分辨出 { "1", "t", "T" } 这组值表示布尔文字 true ,而 { "0", "f", "F" } 表示 false 。第三,缺省情况下 RPC 调用的接收方将反序列化为基本数据类型。为演示这一点,让我们假设 SOAP 服务器公开一个单独的方法,如下所示:

echoBoolean( Boolean b ) { .. }

当您使用 Boolean 参数调用 echoBoolean 时,将生成一个 SOAP Fault。

         Exception while handling service request: 
com.raverun.bool.Service.echoBool(boolean) -- no signature match

幸运的是,有一组反序列化器( xxxObjectDeserializer )将返回相应的包装器对象。为使它返回相应的包装器对象,您需要覆盖它的缺省反序列化行为。稍后,在本文中您将看到如何执行这种操作。

混合类型

从 Java 的角度来说,混合类型是带有成分(constituent)元素的类型。这些元素要么是只通过名称识别(例如,带有几个成员属性的 Java 类),要么是通过顺序的位置来识别(例如,象 ArrayVector 这样的 List 数据结构)。 清单 3显示了混合类型的一个 XML 实例。


清单 3. java.util.Hashtable 的 XML 实例
     <hash xmlns:ns2="http://xml.apache.org/xml-soap" xsi:type="ns2:Map">
<item>
<key xsi:type="xsd:string">2</key>
<value xsi:type="xsd:string">two</value>
</item>
<item>
<key xsi:type="xsd:string">1</key>
<value xsi:type="xsd:string">one</value>
</item>
</hash>


表 2. 混合类型的 Section 5 编码

Java SOAP 序列化器/反序列化器 Java 数组 SOAP-ENC:Array ArraySerializer java.util.Vector
java.util.Enumeration {http://xml.apache.org/xml-soap}Vector * VectorSerializer java.util.Hashtable {http://xml.apache.org/xml-soap}Map HashtableSerializer java.util.Map {http://xml.apache.org/xml-soap}Map MapSerializer # 任意 JavaBean 用户定义的 Qname BeanSerializer
  • * :Apache 2.1 中的 VectorSerializer 序列化为一个 SOAP 数组。但在 2.2 中,是把它作为它自己的 Apache SOAP 所属类型引入的。
  • #MapSerializer 实际上把调用委托给 HashtableSerializer

在 表 2 中您可以看到,多复杂的 Java 类都可以用 BeanSerializer 来发送。但被序列化的 Java 类必须遵守 JavaBeans 设计模式;特别是,对于每个您想序列化的属性,它都必须有一个公有的、无参数的构造函数和 getter/setter 方法。在您的 getter/setter 方法不满足 JavaBeans 命名约定的情况下,您可以将它们通过 BeanInfo 类公开给 BeanSerializer 。例如,如果您需要序列化类 Foo ,请创建一个名为 FooBeanInfo 的类,它实现接口 java.beans.BeanInfo 。请参阅 清单 4中的示例。


清单 4. FooBeanInfo 类
import java.lang.reflect.*; 
import java.beans.*;
/*
* Foo has a single String property by the name of "s".
* And its setter and getter methods are called "_setS" and "_getS"
* respectively, thus violating the JavaBean spec. Passing Foo to BeanSerializer
* will cause this exception to be thrown:
* Error: Unable to retrieve PropertyDescriptor for property 's'
* of class "Foo".
*/
public class FooBeanInfo extends SimpleBeanInfo{
private final static Class c = Foo.class;
public PropertyDescriptor[] getPropertyDescriptors()
{
try{
Class[] sp = new Class[] {String.class};
Method s_setter = c.getMethod( "_setS", sp );
Method s_getter = c.getMethod( "_getS", null );
PropertyDescriptor spd = new PropertyDescriptor("s", s_getter, s_setter);
PropertyDescriptor[] list = { spd };
return list;
}catch (IntrospectionException iexErr){
throw new Error(iexErr.toString());
}catch (NoSuchMethodException nsme){
throw new Error(nsme.toString());
}
}
}

特殊类型

有许多 Java 类型不是十分符合上述类别的任何一种;SOAP 也可以处理这些类型。

Null 和 void
Apache SOAP 将通过添加 xsi:null 属性,将其设为 "true" 来序列化带空值的对象引用。它的缺省行为是忽略其它可能的 xsi:null 属性变体,例如:

  • xsi:nil="true" (按 XML Schema 第 1 部分)(请参阅 参考资料)
  • xsi:null="1" (按 SOAP 1.1)(请参阅 参考资料)

清单 5包含一个带空值的、被序列化的对象引用的示例。


清单 5. 稀疏向量的 SOAP 表示
<myvec xmlns:ns2="http://xml.apache.org/xml-soap" xsi:type="ns2:Vector">
<item xsi:type="xsd:string">Hello World</item>
<item xsi:type="xsd:anyType" xsi:null="true"/>
</myvec>

在 Apache SOAP 2.1 中,发送带空成员的向量会导致 SOAPException 异常,因为当时不存在反序列化空引用的反序列化器。为实现这个目的,引入了 UrTypeDeserializerVectorSerializer 将一个空引用映射到 xsd:anyType (在 2001 XML Schema 推荐版中不推荐 xsd:ur-type )― XML Schema 内置数据类型层中的根类。

在处理字符串参数时,如果访问器是一个不带 xsi:null 属性的空元素,那么 Apache SOAP 将不会反序列化为一个空对象引用。例如,如果一个远程方法需要一个 String 参数:

public void eat(String s) //1

并且您假设这个请求被发送到一个 Web 服务,如:

<ns1:eat>                       
<s xsi:type="xsd:string"/>
</ns1:eat>

它将会反序列化为一个空 String

二进制数据
如果您需要传输二进制大对象(blob),您有三个选择:

  • 字节数组
  • 十六进制字符串
  • MIME 附件

表 3. 发送二进制大对象

Java SOAP 序列化器/反序列化器 字节数组 SOAP-ENC:base64 # Base64Serializer 十六进制字符串 xsd:hexBinary HexDeserializer javax.activation.DataSource xsd:anyType MimePartSerializer javax.activation.DataHandler xsd:anyType MimePartSerializer
  • #:在反序列化期间, SOAPMappingRegistry 的最新版本支持 {http://www.w3.org/2001/XMLSchema}base64BinarySOAP-ENC:base64 的一个超类型(supertype)。

包装器类 org.apache.soap.encoding.Hex 被用于封装十六进制字符串。这个就是被(反)序列化的类。这个包装器类将接受十六进制值从 af 的小写和大写字母。只需确保您的十六进制字符串包含的字符数目是偶数。

Apache SOAP 支持将 SOAP 信封嵌入 MIME/multipart related 消息。所以二进制数据可作为 MIME 附件传送。这一点与“带附件的 SOAP 消息”(SOAP Messages with Attachment,SwA)规范一致(请参阅 参考资料)。

文字 XML

如果将一个 XML 段作为字符串发送,内置的字符串序列化器将转义所有作为元素内容不合法的字符,其手段是用它们预定义的字符串实体替换它们 ― 例如 < 将代替 < 。要发送嵌入在 SOAP RPC 构造中的文字 XML,您有两个选择。您可以将 XML 字符串包在 CDATA 部分中。这样还允许您发送格式不好的 XML。第二种方法是将 XML 段加载到一个 DOM 中并将文档元素作为参数发送。Apache SOAP 将通过 XMLParameterSerializer 序列化 org.w3c.dom.Element 实例。





回页首

类型映射模式

在使用 Apache SOAP 编写 SOAP 客户机和服务器程序时,您很可能会遇到可怕的 java.lang.IllegalArgumentException 异常,并伴随有 表 4的列表中的消息。

表 4. 常见类型映射器错误消息

消息   No Serializer found to serialize a 'xxx' using encoding style 'yyy'   No Deserializer found to deserialize a ':Result' using encoding style 'yyy'   No mapping found for 'xxx' using encoding style 'yyy'

Apache SOAP 中的序列化和反序列化技术要依赖于注册表中定义的 类型映射的可用性。类型映射定义如何在 Java 类(或基本数据类型)和 XML 之间进行来回转换。注册表是类 org.apache.soap.encoding.SOAPMappingRegistry 的一个实例。缺省情况下,SOAP 客户机和 SOAP 服务器将继承相同的类型映射集。


图 1. 上下文中的序列化/反序列化
上下文中的序列化/反序列化

对于下面的讨论,我将使用术语 入站(inbound)出站(outbound)来描述 SOAP 消息处理的方向。例如,在 SOAP 服务器的上下文中,序列化指的是生成 出站消息的技术(mechanic)。这个简单的模型忽略了中间的 SOAP 服务器,但对我们的讨论没什么影响。 图 1描述了正在讨论的体系结构。

在描述与类型映射注册表交互的编程语法之前,了解一些关于注册表内部实现的知识会比较有帮助。开发者与之交互的注册表实际上是四个内部 Hashtable 的虚包(请参阅下面的列表)。

  1. 序列化器的注册表
  2. 反序列化器的注册表
  3. Java2XML 的注册表
  4. XML2Java 的注册表

序列化和反序列化是对称的操作;因此我将只讨论如何存储序列化类型映射,希望您能够根据我的说明推知如何对反序列化类型映射进行相应的操作。序列化类型映射由上面列表中的三个散列表 ― A、C 和 D 中的条目表示。将一个序列化器 FooSerializer 作为示例考虑一下,它将 Foo.class 对象序列化为 SOAP 类型 "{http://someuri}foo" 。如果您考虑用字符串 k 代表这个类型映射的唯一键,下面这些就是为序列化目的而创建的映射:

  • 散列表 A: k 映射到 FooSerializer.class
  • 散列表 C: k 映射到 {http://someuri}foo

散列表 A 和 C 都是以 Java 运行时类型和 encodingStyleURI 连接在一起生成的共享字符串作为键。散列表 A 维护实现接口 org.apache.soap.util.xml.Serializer 的 Java 对象集合,而散列表 C 管理一个 QName 集合。在序列化过程中,使用两个 API 调用查询注册表:

  Serializer querySerializer( javaType, encodingStyleURI )
QName queryElementType (javaType, encodingStyleURI )

当 Apache SOAP 被定向为序列化一个 Foo 实例时,它调用 querySerializer 方法,并得到一个 FooSerializer 。然后调用 FooSerializer 对对象进行数据编入,这时它将调用 queryElementType 来检索 xsi:type 属性值。

如果 querySerializer 无法返回任何 Serializer 实例,您会得到 表 4 中的出错消息 1。另一方面,如果 queryElementType 不返回一个匹配的 QName,出错消息 3 将被返回。

在 SOAP 客户机上定义类型映射

您可以加强注册表,通过调用 SOAPMappingRegistry 实例的 mapTypes() 方法来处理特殊类型。方法说明如下所示:

  mapTypes( String, QName, Class, Serializer, Deserializer );

如果您希望映射被同时注册到散列表 C 和 D,那么就要强制使用 QNameClass 参数。 清单 6包含 Apache SOAP 中缺省类型映射的示例。


清单 6. 示例客户机映射类型
HashtableSerializer hashtableSer = new HashtableSerializer();
mapTypes("http://schemas.xmlsoap.org/soap/encoding/",
new QName("http://xml.apache.org/xml-soap", "Map"),
Hashtable.class,
hashtableSer,
hashtableSer);
mapTypes("http://schemas.xmlsoap.org/soap/encoding/",
new QName("http://schemas.xmlsoap.org/soap/encoding/", "Array"),
null,
null,
new ArrayDeserializer());
mapTypes( encStyleURI, Qname, Foo.class, null, null );

清单 6 中的 Hashtable 映射是 对称映射(symmetric mapping)的一个示例,在这种映射中入站和出站处理都已注册。第二个映射是 不对称映射(asymmetric mapping);它只将 ArrayDeserializer 注册到散列表 B。数组序列化发生在 SOAPMappingRegistry.querySerializer() 内,它用内省指出 Java 类型是不是一个数组,然后当时就返回 ArraySerializer 的一个新实例。最后的映射只是一个关联。

在 SOAP 服务器上定义类型映射

在服务器端,类型映射是在一个名为 部署描述符的 XML 文件中定义的。Apache SOAP 实际上是根据部署描述符的内容构建一个 SOAPMappingRegistry 实例。 随本文一起提供的源代码分发包提供了一个 XML 语法模式(请参阅 参考资料)。在 清单 7 中您可以看到从这个模式抽取的内容,它显示了 map 元素的属性;它的属性反映了 SOAPMappingRegistrymapTypes 方法的参数。唯一的不同是对于 mapqname 是必需的。


清单 7. Apache SOAP 部署描述符中映射元素的模式片段
    <complexType name="mapType">
<attribute name="encodingStyle" type="xsd:anyURI"/>
<attribute name="qname" type="xsd:string"/>
<attribute name="javaType" type="xsd:string" use="optional"/>
<attribute name="java2XMLClassName" type="xsd:string" use="optional"/>
<attribute name="xml2JavaClassName" type="xsd:string" use="optional"/>
</complexType>

被编译的定制类型映射

通过将单调乏味的类型映射手工声明固定在 SOAPMappingRegistry 的子类中,就有可能绕过它们。于是这个子类既可以用于客户机也可以用于服务器。 清单 8 包含 SOAPMappingRegistry 子类的代码样本,这个子类提供了自己的数组(反)序列化器。


清单 8. 为继承 SOAPMappingRegistry 的公有类 MySmr 建立子类
  public MySmr()
{
super();
registerMyMappings();
}
public MySmr(String schemaURI)
{
super(schemaURI);
registerMyMappings();
}
private void registerMyMappings()
{
MyArraySerializer arraySer = new MyArraySerializer();
mapTypes(Constants.NS_URI_SOAP_ENC,
new QName(Constants.NS_URI_SOAP_ENC, "Array"),
null,
arraySer,
arraySer);
}
}

要使用调用者端的这个定制的 SOAPMappingRegistry ,请将其作为参数发送到 Call 对象的 setSOAPMappingRegistry() 方法。在提供者端,删除所有的独立映射元素,并按 清单 9所示修改部署描述符。


清单 9. 部署描述符的更改
<isd:service ...>
// other elements here
<isd:mappings defaultRegistryClass="com.raverun.array.MySmr" />
</isd:service>

Apache SOAP 如何处理不同的模式名称空间?

在 清单 9 中,您会注意到 SOAPMappingRegistry 类有一个重载的构造函数,这个构造函数使用一个 String 参数。该参数实际上是一个 URI,指出 W3C 的 XML Schema 语言的特定版本。Apache SOAP 接受的版本是在 org.apache.soap.Constants 中预定义的最终版本:

  • Constants.NS_URI_1999_SCHEMA_XSD = http://www.w3.org/1999/XMLSchema
  • Constants.NS_URI_2000_SCHEMA_XSD = http://www.w3.org/2000/10/XMLSchema
  • Constants.NS_URI_2001_SCHEMA_XSD = http://www.w3.org/2001/XMLSchema

如果您用 SOAPMappingRegistry 的无参数构造函数对它进行了实例化,缺省情况下,Apache SOAP 将把类型序列化为 1999 名称空间。调用重载的构造函数实际上只覆盖了 xsd 名称空间;SOAP 信封中声明的 xsi 名称空间仍未改变。这是因为该信封直接从 Constants.NS_URI_CURRENT_SCHEMA_XSDConstants.NS_URI_CURRENT_SCHEMA_XSI (它们指向 1999 名称空间)获取名称空间前缀映射。这个缺点在最新的 CVS 源代码发行版中已经被修正(请参阅 参考资料)。但从反序列化的角度来看,Apache SOAP 能够适当地处理列出的三个版本中任何一个的模式类型。

编码风格和类型映射

根据我们对类型映射表内部工作机制的了解,很明显, encodingStyleURI 在确定应该如何进行序列化和反序列化方面起着很大的作用。在这一部分,我将重点讨论一些影响 encodingStyleURI 的使用的编程问题。

首先,SOAP 1.1 规范明确声明“没有为 SOAP 消息定义任何缺省编码”。于是,在 Apache SOAP 中缺省 encodingStyleURI 为空。如果您没有在 Call 对象或者每个参数的声明中把它初始化为一个值,您可以通过调用 SOAPMappingRegistrysetDefaultEncodingStyle 方法做到这一点。这相当于向您的所有参数访问器添加本地范围的 encodingStyleURI 属性。

第二,考虑一下需要 SOAP 响应和 SOAP 请求的 encodingStyle 不一样这种情形。为便于讨论,请考虑 清单 10 中的方法 countKids() 。它的入站和出站 encodingStyles 分别是文字 XML 和 Section 5。


清单 10. 示例 SOAP 服务器的代码段
public int countKids( Element el ){
return( DOMUtils.countKids(el, Node.ELEMENT_NODE) );
}

如果您想用 清单 11 中的代码调用 countKids() ,就会返回一个 SOAP 错误,同时伴随着一条消息: java.lang.IllegalArgumentException: I only know how to serialize an 'org.w3c.dom.Element'


清单 11. RPC 调用的代码段
Call call = new Call();
Vector params = new Vector();
params.addElement( new Parameter("xmlfile",
Element.class,
e,
Constants.NS_URI_LITERAL_XML) );
call.setParams( params );
call.invoke ( url, "" );

要理解发生这种错误的原因,您需要理解 Apache SOAP(在 SOAP 服务器上)用来确定在出站消息上使用何种 encodingStyle 的算法:

  1. 如果方法包装器访问器的 encodingStyle 属性可用的话,请使用它。
  2. 如果没有这个属性,请从第一个参数推出 encodingStyle

所以,如果您的方法接受多个参数,当没有显式设置 CallencodingStyle 属性时,您可能希望重新组织它们的顺序。为保证绝对安全,在 Call 对象中设置 encodingStyle 来响应远程方法的返回类型。

SOAP 消息中混合的 encodingStyle

首先,也是最重要的,SOAP 规范不会不接受参数内的混合 encodingStyleencodingStyle 属性的作用域很像名称空间声明。这种混合编码类型的一个有用的案例是一个 SVG 段数组。首先,使用 Section 5 对该数组进行序列化和使用文字 XML 序列化 SVG 成员看起来好像是合理的。不幸的是,这样是行不通的,因为所有的 Section 5 序列化器都被硬编码为只处理在 Section 5 encodingStyle 注册的类型。换句话说就是以 Section 5 编码的 XML 流中的任何内容都不可变,并且无法包含使用不同 encodingStyle 编码的 XML 段。而且,最近的错误修正(请参阅 参考资料)也确保了混合数据结构(如 ArrayHashtableVector )内的值不可以是 Section 5 之外的任何 encodingStyle

混合类型映射问题

我们将用您在 SOAP 类型映射中可能遇到的几个问题和怪现象结束本文。

反序列化期间缺少 xsi:type
在处理 Microsoft 的 SOAP 工具箱生成的入站消息时,您很可能会遇到下列错误:

No Deserializer found to deserialize a ':Result' using encoding style 'yyy'

这个典型的互操作性问题是由于访问器中缺少 xsi:type 属性引起的。Apache SOAP 2.1 发行版实现的解决方案是按照这个规则动态创建 xsi:type 值: 如果参数的访问器不在任何名称空间的范围内,就使用下面的 QName:

{""}/X

其中 X 代表访问器的 tagName"" 代表一个空 URI。如果反序列化的访问器的名称空间 URI 存在的话,请使用它来代替这个空 URI。这项工作完成后,就会生成类型映射将这些特别的 QName 与(反)序列化器匹配。 清单 12演示了客户机和服务器的样本类型映射。


清单 12. RPC 调用的代码段
    [Client]

SOAPMappingRegistry smr = new SOAPMappingRegistry ();
StringDeserializer sd = new StringDeserializer ();
smr.mapTypes (Constants.NS_URI_SOAP_ENC,
new QName ("", "Result"), null, null, sd);
[Server]

<isd:mappings>
<isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:x="" qname="x:Book"
javaType="com.raverun.Book"
xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/>
<isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:x="" qname="x:price"
xml2JavaClassName="org.apache.soap.encoding.soapenc.FloatObjectDeserializer"/>
<isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:x="" qname="x:isbn"
xml2JavaClassName="org.apache.soap.encoding.soapenc.StringDeserializer"/>
</isd:mappings>

类型映射需要遵守下列约定:

  • 对于基本数据类型以及它们的包装器对象(例如, intInteger ),不要在类型映射中指定 javaType
  • 对于混合类型,如果您的反序列化器在执行它的任务时要求了解 javaType (例如 org.apache.soap.encoding.soapenc.BeanSerializer ),那么您可以指定 javaType

关于这种行为的更多详细信息可以从 Sanjiva Weeraweena 发到 soap-dev 的帖子列表中收集,也可以从 Jim Snell 写的一篇文章中收集(请参阅 参考资料获得这两者的链接)。

多引用类型
多引用类型并不是一种新的类型分组,但应该看作是简单和混合类型的一个方面。对于保持参数(特别是那些显示 DCE 风格的 [ptr] 语义的参数)间的一致性,这种类型很有用。Apache SOAP 能够反序列化多引用类型,但不能序列化为多引用类型。

多引用类型被序列化为两部分: 参数访问器(parameter accessor)(一个空元素)和 值访问器(value accessor)。 在 清单 13 中,元素 varString 是参数访问器,而 greeting 是值访问器。注意,值访问器是方法包装器访问器 ns1:echoString 的同级元素。这一点很重要,因为 Apache SOAP 假设序列化图的根(方法包装器访问器)是 SOAP-ENV:Body 的直接子元素。如果值访问器排在第一位,那么您将遇到一个 Server.BadTargetObjectURI 错误。SOAP 规范提供了一个属性 SOAP-ENC:root 指出一个元素不是序列化的根,但 Apache SOAP 无法识别它。


清单 13. 多引用参数的一个示例
<SOAP-ENV:Body>
<ns1:echoString
xmlns:ns1="http://soapinterop.org"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<varString href="#String-0" />
</ns1:echoString>
<greeting
xsi:type="xsd:string"
id="String-0">Hello World</greeting>
</SOAP-ENV:Body>

XMI 编码
XMI (XML Metadata Interchange,XML 元数据交换)是一个在应用程序间共享 UML 模型的 OMG 标准。它在 UML 建模应用程序中最常用,这种应用程序要从 XMI 文件导进自己的模型,还要把自己的模型导出到 XML 文件。“IBM XMI 框架”(IBM XMI Framework)中的“Java 对象桥”(Java Object Bridge,JOB)允许把 Java 对象(反)序列化为 XMI 格式。这样,就可以把 XMI 看作 Section 5 编码的备选方案,虽然 XMI 很少用到。





回页首

总结和展望

在本文中,我已经深入研究了 Apache SOAP 的大多数用于(反)序列化 Java 类型的取出即可用的 API 支持。我还讨论了一些可能会禁止与其它基于 Java 或基于非 Java 的 SOAP 工具箱进行交互的特性。

在下一部分中,我将介绍一个详尽的说明书,它会指导您编写自己的(反)序列化器。您还将看到如何利用模式语言进行非 Section 5 编码。



参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.

  • 请单击文章顶部或底部的 讨论参与本文的 讨论论坛。

  • Apache SOAP是 Java 编程语言的一个开放源代码 SOAP 工具箱。

  • Apache Axis是取代 Apache SOAP 的下一代工具箱。

  • Brendan Macmillan 的 Java Serialization to XML (JSX)工具箱包含一些有用的工具。

  • 下载 Sun 的 Java Architecture for XML Binding (JAXB)的早期访问发行版。

  • Castor是用于 Java 编程语言的一个开放源代码的数据绑定框架。

  • Schema2Java是 Creative Science Systems 的一个商业 XML 数据绑定产品。

  • 为 Apache SOAP 的部署描述符下载 xsd 模式。

  • 下载随本系列一起提供的样本代码: PurchaseOrder.zip或 PurchaseOrder.tar.gz。在下一部分,我将详细讨论这些代码;但是,这个包里包含的一个 XML 语法的模式,您可能现在就想看。

  • 要了解更多关于 XMI(XML 元数据交换)的信息,请查阅 IBM XMI Framework。

  • SOAP 1.1 规范作为 W3C Technical Note提供。

  • Sanjiva Weerawarana 的这个帖子描述了 Apache SOAP 中放松的 xsi:type 要求。

  • James Snell 写的 “The Web services insider, Part 3”(developerWorks,2001 年 5 月)讨论了 Apache SOAP 与其它工具箱的互操作性。

  • “ SOAP Messages with Attachments,一个 W3C 说明,它描述了 SOAP 是如何嵌入到 MIME 中的。

  • W3C XML Schema, Part 1,描述了核心 XML Schema 概念和语法。

  • W3C XML Schema, Part 2,描述了 XML Schema 中支持的数据类型。

  • Apache SOAP 错误报告 #2865 描述了不支持 char 这种基本数据类型。

  • Apache SOAP 错误报告 #3000 描述了 timeInstant 错误。

  • Apache SOAP 错误报告 #2388描述使用了旧 XML Schema 名称空间 URI。

  • Apache SOAP 错误报告 #2470 描述了混合 SOAP 和文字 XML 编码风格的 HashtableSerializer 问题。


关于作者

小白同学   MSN:zhoujianguo_leo@hotmail.com  
原创粉丝点击