Apache SOAP 类型映射,第 1 部分: 深入研究 Apache 的序列化 API
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-ENV和 SOAP-ENC将分别引用名称空间 http://schemas.xmlsoap.org/soap/envelope/和 http://schemas.xmlsoap.org/soap/encoding/。
[*]除非另有明确声明,一般我们假定前缀 xsd和 xsi分别引用名称空间 http://www.w3.org/2001/XMLSchema和 http://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中的 FooJavaBean 在清单 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 服务。元素 fooParam、 i和 s被称为访问器(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 类),要么是通过顺序的位置来识别(例如,象 Array或 Vector这样的 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异常,因为当时不存在反序列化空引用的反序列化器。为实现这个目的,引入了 UrTypeDeserializer。 VectorSerializer将一个空引用映射到 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}base64Binary― SOAP-ENC:base64的一个超类型(supertype)。
包装器类 org.apache.soap.encoding.Hex被用于封装十六进制字符串。这个就是被(反)序列化的类。这个包装器类将接受十六进制值从 a到 f的小写和大写字母。只需确保您的十六进制字符串包含的字符数目是偶数。
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的虚包(请参阅下面的列表)。
[*]序列化器的注册表
[*]反序列化器的注册表
[*]Java2XML 的注册表
[*]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,那么就要强制使用 QName和 Class参数。清单 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元素的属性;它的属性反映了 SOAPMappingRegistry的 mapTypes方法的参数。唯一的不同是对于 map, qname是必需的。
清单 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_XSD和 Constants.NS_URI_CURRENT_SCHEMA_XSI(它们指向 1999 名称空间)获取名称空间前缀映射。这个缺点在最新的 CVS 源代码发行版中已经被修正(请参阅参考资料
)。但从反序列化的角度来看,Apache SOAP 能够适当地处理列出的三个版本中任何一个的模式类型。
编码风格和类型映射
根据我们对类型映射表内部工作机制的了解,很明显, encodingStyleURI在确定应该如何进行序列化和反序列化方面起着很大的作用。在这一部分,我将重点讨论一些影响 encodingStyleURI的使用的编程问题。
首先,SOAP 1.1 规范明确声明“没有为 SOAP 消息定义任何缺省编码”。于是,在 Apache SOAP 中缺省 encodingStyleURI为空。如果您没有在 Call对象或者每个参数的声明中把它初始化为一个值,您可以通过调用 SOAPMappingRegistry的 setDefaultEncodingStyle方法做到这一点。这相当于向您的所有参数访问器添加本地范围的 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的算法:
[*]如果方法包装器访问器的 encodingStyle属性可用的话,请使用它。
[*]如果没有这个属性,请从第一个参数推出 encodingStyle。
所以,如果您的方法接受多个参数,当没有显式设置 Call的 encodingStyle属性时,您可能希望重新组织它们的顺序。为保证绝对安全,在 Call对象中设置 encodingStyle来响应远程方法的返回类型。
SOAP 消息中混合的 encodingStyle
首先,也是最重要的,SOAP 规范不会不接受参数内的混合 encodingStyle; encodingStyle
属性的作用域很像名称空间声明。这种混合编码类型的一个有用的案例是一个 SVG 段数组。首先,使用 Section 5
对该数组进行序列化和使用文字 XML 序列化 SVG 成员看起来好像是合理的。不幸的是,这样是行不通的,因为所有的 Section 5
序列化器都被硬编码为只处理在 Section 5 encodingStyle注册的类型。换句话说就是以 Section 5 编码的 XML 流中的任何内容都不可变,并且无法包含使用不同 encodingStyle编码的 XML 段。而且,最近的错误修正(请参阅参考资料
)也确保了混合数据结构(如 Array、 Hashtable和 Vector)内的值不可以是 Section 5 之外的任何 encodingStyle。
混合类型映射问题
我们将用您在 SOAP 类型映射中可能遇到的几个问题和怪现象结束本文。
反序列化期间缺少 xsi:type
在处理 Microsoft 的 SOAP 工具箱生成的入站消息时,您很可能会遇到下列错误:
No Deserializer found to deserialize a ':Result' using encoding style 'yyy'
页:
[1]