扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
作者:ibm 来源:ibm 2007年10月9日
关键字: 中间件
引言
由于 JAX-RPC 仅限于支持特定的 XML 模式构造,因此常常会出现这样一种情况,即某种基于 JAX-RPC 的 WSDL-to-Java 生成工具虽然生成了一组 Java Bean,但这些 Java Bean 并不能真正按您照所期望的那样工作。最明显的证据就是,在您期望构造出一个能表示您的模式类型的 Java 类型时,却获得一个 javax.xml.soap.SOAPElement。同填充通常的 Java Bean 相比,直接创建并填充这些 SOAPElement 可能会变得有点难以处理,并迅速使应用程序变得混乱不堪。
此外,您可能已在应用程序中使用了特殊的 XML 绑定技术,现在,您打算将其作为 Web 服务公开。在这种情况下,您可能并不愿意重新编写应用程序以处理现有的 Bean 和 JAX-RPC 可能需要的新 Bean 之间的重叠部分。
我们将通过示例向您展示 WebSphere 新的自定义数据绑定功能的工作方式,以及它可以如何帮助您应对这些场景(允许您将自己的映射技术用于 Web 服务数据)。该功能首先在 WebSphere Application Server Base 和 Network Deployment Version 6 中引入。
WebSphere 中的序列化和反序列化
在具体讨论自定义数据绑定之前,我们首先为您介绍 WebSphere 中现有的序列化和反序列化环境,这将有助于您理解它如何能扩展为自定义序列化。
类型映射和元素映射
现有的 Web 服务运行时(在 WebSphere V5.0.2 中引入)可支持基于类型映射注册表概念的 JAX-RPC 编程模型。也就是说,WSDL 或 XSD 文件中所存在的每种类型都将具有为其创建的特定序列化器或反序列化器,这些序列化器或反序列化器知道该如何处理这些类型。在开发您的自定义数据绑定应用程序时,必须牢记这种类型映射概念。它不同于以元素为中心的映射,在后一种映射方式中,Java 类型的生成是基于 WSDL 中特定的根元素定义。在以类型为中心的映射中,数据的用户视图基于同特定 WSDL 关联的 XSD 中所存在的类型。
将 WSDL 映射到 Java
现在我们来分析一个示例。清单 1 显示了从关联的 WSDL 文件抽取的 XSD 代码片段,其中两个元素分别对应某项操作的输入和输出。操作请求接收 OpType 类型的输入对象,而响应则返回一个简单的字符串。JAX-RPC 将复杂的 OpType 类型映射到名为 OpType 的 Java 对象,Java 对象中包含 c1 和 c2 两个属性,且均为字符串类型。
清单 1. 来自 WSDL 的 XSD 示例
<xsd:element name="Op1Response" type="xsd:string" /> <xsd:element name="Op1Request" type="tns:OpType" /> <xsd:complexType name="OpType"> <xsd:sequence> <xsd:element name="c1" type="xsd:string"/> <xsd:element name="c2" type="xsd:string"/> </xsd:sequence> </xsd:complexType> |
清单 2 显示了该类的一个示例。如果这些元素为更加复杂的类型,则将创建进一步的嵌套
清单 2. 示例 XSD 的 Java Bean 映射。
public class OpType { private String c1; private String c2; public String getC1() { return c1; } public void setC1(String val) { c1 = val; } public String getC2() { return c2; } public void setC2(String val) { c2 = val; } } |
另一个常见的示例使用包装 操作的概念,尽管该模式尚未被正式定义,但在业界已得到广泛接受。该模式定义了一种元素/复杂类型的映射方式,其中元素名称同操作名称相同。复杂类型中子元素表示操作的参数。
|
从清单 3 的示例 WSDL 文件代码片段中,您可看到定义的两个元素,接下来的两部分指向这些元素。第一个元素为一个操作包装,它接受两个参数(参数类型分别为 xsd:int 和 xsd:string)。第二个为响应元素,其中包含单个元素(类型为 xsd:string)。
这些元素被定义成 WSDL 消息的输入和输出部分(分别为 operation1Request 和 operation1Response)。
清单 3. 包装的 WSDL 代码片段示例
<element name="operation1"> <complexType> <sequence> <element name="arg_0_0" type="xsd:int"/> <element name="arg_1_0" nillable="true" type="xsd:string"/> </sequence> </complexType> </element> <element name="operation1Response"> <complexType> <sequence> <element name="operation1Return" nillable="true" type="xsd:string"/> </sequence> </complexType> </element> <wsdl:message name="operation1Request"> <wsdl:part element="impl:operation1" name="parameters"/> </wsdl:message> <wsdl:message name="operation1Response"> <wsdl:part element="impl:operation1Response" name="parameters"/> </wsdl:message> <wsdl:portType name="MyPortType"> <wsdl:operation name="operation1"> <wsdl:input message="impl:operation1Request" name="operation1Request"/> <wsdl:output message="impl:operation1Response" name="operation1Response"/> </wsdl:operation> </wsdl:portType> |
在 JAX-RPC 中,这些参数被反序列化为通常的 Java 对象和简单数据类型。清单 4 显示了通过 WebSphere 提供的 WSDL2Java 工具为该 WSDL 生成的 JAX-RPC 服务接口。请注意,在本例中运行时已解开操作包装以使其更具有 RPC 风格。
清单 4. JAX-RPC 服务端点接口示例
public interface MyPortType extends java.rmi.Remote { public java.lang.String operation1(int a, java.lang.String b) throws java.rmi.RemoteException; } |
如果在 WSDL2Java 工具中使用 -noWrappedOperations 标志,则 WSDL 将不会被视作具有包装的操作,在将元素内容返回至 Bean 之前也不会解开元素内容。在这种情况下,将对全部 复杂类型生成 Bean,而不是接受两个独立的参数(int 和 String),该接口将需要接受整个操作包装元素。从 portType 类型生成服务端点接口 (SEI),其类似于清单 5 中的内容。这在处理整个 XML 文档的映射(我们将在稍后讨论)时将变得非常重要。
清单 5. JAX-RPC 服务端点接口示例
public interface MyPortType extends java.rmi.Remote { public a.b.Operation1Response operation1( a.b.Operation1 parameters) throws java.rmi.RemoteException; } |
|
使您的服务变得通用
现在,您已对 WebSphere 中类型如何进行映射和序列化有了大致的了解,接下来我们将介绍如何对您的 Web 服务使用自定义数据绑定。这一部分讨论如何将特定 Web 服务的数据模型(您的自定义 Java Bean)与调用模型(您要调用的接口)分开。
要实现插入替代映射技术的目标,我们首先需要能够使用与调用模型分开的数据模型。我们需要使 Web 服务的参数具有更加通用的格式,这样就可以更方便地生成或使用对自定义对象的通用表示。要做到这一点,我们将使用 WebSphere 的 WSDL2Java 工具中的 –noDataBinding 选项。
|
如果在 WSDL2Java 工具中指定 -noDataBinding 标志,则 WSDL2Java 不会将任何模式构件绑定到它们通常的 Java Bean 表示上,而是将所有内容映射到一个 SAAJ SOAPElement。在 JAX-RPC 中,使用 SOAPElement API 作为通用格式来封装那些缺少对应映射的模式类型和细微差别(例如 xsd:choice)。
在清单 6 中,您可看到为此场景生成的 JAX-RPC 接口。该接口基于清单 3 中的示例 WSDL,其中所有输入和输出类型被映射到包含 XML 文本的 SOAPElement 树上。
清单 6. 无数据绑定的 JAX-RPC 接口示例
public interface MyPortType extends java.rmi.Remote { public javax.xml.soap.SOAPElement operation1( javax.xml.soap.SOAPElement parameters) throws java.rmi.RemoteException; } |
需要特别注意的是,这是特定于 WebSphere 的功能,因此并不存在相应的规范或标准来定义这种无数据绑定格式下从 WSDL 到 Java 的映射。但是,该工具使用的模式是在 JAX-RPC 接口中所生成方法的签名中包含 WSDL 的每个部分的 SOAPElement。换句话说,方法中的输入参数数目对应于“wsdl:message”中“wsdl:part/”的数目。回头再来看清单 3 中定义的 WSDL,您可以看到表示请求的 operation1Request 消息只有单个 parameters 部分。因此,我们看到只有单个 SOAPElement 元素来表示整个请求的有效负载。
如果您使用 Rational® Application Developer 工具来开发您的 Web 服务应用程序,则可以找到在生成的接口中禁用数据绑定的选项。要找到此选项,请选择 Preferences => Web Services => Code Generation => IBM WebSphere Runtime,然后选择 Disable data binding and use SOAPElement。
图 1. 在 Rational Application Developer 中禁用数据绑定
其他有用的 API
尽管通过正式的 SAAJ 1.2 SOAPElement API 可以实现使用无数据绑定的 Web 服务,不过 IBM 已在其 SOAPElement 实现中引入了其他一些方法来支持这些扩展。这些方法定义在 com.ibm.websphere.webservices.soap.IBMSOAPElement 中 IBM 特定的公共 SOAPElement 接口内。
在此接口中,有两个特殊的方法在编写无数据绑定应用程序时非常有用:toXMLString(boolean) 和 toInputSource(boolean)。
清单 7. IBMSOAPElement 接口
package com.ibm.websphere.webservices.soap; public interface IBMSOAPElement extends SOAPElement { public String toXMLString (boolean includeNSDecls); public InputSource toInputSource (boolean includeNSDecls) throws SAXException; ... } |
清单 7 中描述的 API 仅在您当前具有 SOAPElement 且需要通过简单的方式来获得数据时有用。这意味着您只能在 SOAPElement 进入您的系统时(发送到服务器的入站请求或发送到客户端的入站响应)使用这些 API。不过,您也可以使用其他 API 来帮助创建新的 SOAPElement。如果您的对象包含某种形式的 XML 数据并且您希望在其外部创建 SOAPElement,在这种出站场景下您可以使用这些 API。IBM 已通过 com.ibm.websphere.webservices.soap.IBMSOAPElementFactory 为该场景提供了优化解决方案。该接口允许您从 InputSource 或 XML 字符串来创建 SOAPElement。
清单 8. IBMSOAPElementFactory 接口
package com.ibm.websphere.webservices.soap; import javax.xml.soap.*; import org.xml.sax.InputSource; public interface IBMSOAPFactory { public SOAPElement createElementFromXMLString( String xmlString) throws SOAPException; public SOAPElement createElementFromInputSource( InputSource inputSource) throws SOAPException; } |
不过,您可能会问:为什么 IBM 要创建这些 API,我为什么要使用它们?这些 API 是作为方便 SOAPElement 使用的工具提供的,但它们并不是该场景下所必需使用的。对于那些需要处理 SOAPElement 本身而不是使用数据对象的场合,使用这些 API 可以在一定程度上简化代码。
|
自定义数据绑定的工作方式
现在,我们已经向您介绍了增强的 SOAPElement 支持和无数据绑定的概念,您需要使用全部所需的工具来使用 WebSphere 中新的自定义序列化功能。
什么是自定义数据绑定?
在分析 JAX-RPC 时,您将看到 WSDL/XML 模式到 Java 的映射存在一定的限制。对于某些较为复杂的模式概念,JAX-RPC 选择将它们(例如 xsd:anyAttribute、xsd:choice 等)映射到一个 SOAPElement。如果我们展望 J2EE Web 服务编程模型的新一代版本 JAX-WS 2.0,您将注意到它并不是定义自己的映射,而是使用 JAX-B 2.0 规范作为数据绑定。JAX-B 是一种比 JAX-RPC 提供的映射机制更完善的 XML 模式映射机制,但二者却截然不同。遗憾的是,要让 JAX-RPC 使用 JAX-B 2.0 作为其数据绑定机制是不可能的,因为该规范目前尚未完成。在许多情况下,您可能希望使用 JAX-B 或其他对您和您的应用程序更有意义的绑定技术来映射您的 Web 服务数据。或者,也许您已获得一组正在使用的 Java Bean,并且打算在 JAX-RPC 选定的不同 Java Bean 组上维持这种映射。
运用上面介绍的某些概念和功能,您可通过使用原始 SOAPElement 作为输入和输出参数并自行进行转换以完成处理。但是,这意味着 SOAPElement 将出现在您的服务端点接口中(或作为对象内部的属性),并且您向其提供 SOAPElement 的对方也必须同样懂得如何处理 SOAPElement。自定义数据绑定允许您将该逻辑隐藏在接口级别下,而提供更加以应用程序为中心的 API。
WebSphere 中专门引入了一个自定义绑定器接口,可允许从 XML 模式类型映射到 Java 类型(反之亦然)。自定义绑定器拥有可处理特定 XML 模式类型并将其转换成所需的 Java 对象的方法。同样,自定义绑定器也可将您的自定义 Java 对象序列化成对应的 XML 表示。
例如,清单 9 中的 WSDL 在某种复杂类型中包含 xsd:choice,并且您从中生成 SEI。在本例中,我们将该接口命名为 InventorySearch。如果不使用自定义绑定器,则生成的接口将类似于清单 10 中的内容,并以 SOAPElement 作为返回类型。但是,如果我们在讨论的模式类型中使用自定义绑定器,则生成的接口将更接近于您所期望的内容(如清单 11 中所示),其中会采用合适的数据类型作为输入和响应类型。
清单 9. 具有无法映射的复杂类型的 WSDL 示例
<?xml version="1.0" encoding="UTF-8"?> <wsdl:definitions targetNamespace="http://mycorp.com" xmlns:impl="http://mycorp.com" xmlns:intf="http://mycorp.com" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsi="http://ws-i.org/profiles/basic/1.1/xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <wsdl:types> <schema targetNamespace="http://mycorp.com" xmlns="http://www.w3.org/2001/XMLSchema"> <element name="findItemResponse"> <complexType> <sequence> <element name="findItemReturn" nillable="true" type="impl:InventoryItem"/> </sequence> </complexType> </element> <element name="findItem"> <complexType> <sequence> <element name="arg_0_0" type="xsd:int"/> </sequence> </complexType> </element> <complexType name="InventoryItem"> <sequence> <element name="productId" type="xsd:int"/> <element name="productName" nillable="true" type="xsd:string"/> <xsd:choice> <element name="reorderNeeded" type="xsd:boolean"/> <element name="stockCount" type="xsd:int"/> </xsd:choice> </sequence> </complexType> </schema> </wsdl:types> <wsdl:message name="findItemResponse"> <wsdl:part element="impl:findItemResponse" name="parameters"/> </wsdl:message> <wsdl:message name="findItemRequest"> <wsdl:part element="impl:findItem" name="parameters"/> </wsdl:message> <wsdl:portType name="InventorySearch"> <wsdl:operation name="findItem"> <wsdl:input message="impl:findItemRequest" name="findItemRequest"/> <wsdl:output message="impl:findItemResponse" name="findItemResponse"/> </wsdl:operation> </wsdl:portType> <wsdl:binding name="InventorySearchSoapBinding" type="impl:InventorySearch"> <wsdlsoap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="findItem"> <wsdlsoap:operation soapAction=""/> <wsdl:input name="findItemRequest"> <wsdlsoap:body use="literal"/> </wsdl:input> <wsdl:output name="findItemResponse"> <wsdlsoap:body use="literal"/> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="InventorySearchService"> <wsdl:port binding="impl:InventorySearchSoapBinding" name="InventorySearch"> <wsdlsoap:address location="file:undefined_location"/> </wsdl:port> </wsdl:service> </wsdl:definitions> |
清单 10 和 11 显示了不使用和使用自定义数据绑定的服务端点接口。
清单 10. 不使用自定义数据绑定的服务端点接口
import javax.xml.soap.SOAPElement; public interface InventorySearch extends java.rmi.Remote { public SOAPElement findItem(int productId) throws java.rmi.RemoteException; } |
import com.mycorp.InventoryItem; public interface InventorySearch extends java.rmi.Remote { public InventoryItem findItem(int productId) throws java.rmi.RemoteException; } |
在何处使用自定义绑定器
WebSphere 运行时和工具在开发时和运行时均使用自定义绑定器。在开发时,它将搜索可用的自定义绑定器,并分别进行查询以查找其支持的模式类型和 Java 类型。这些信息被用于更新类型映射注册表,当需要生成接口或存根时,将会生成使用正确映射类型而不是 SOAPElement 或其他类型的代码。
同样地,运行时也会定位到自定义绑定器并使用所需的绑定器来执行其功能。它将尝试查找 /META-INF/services/CustomBindingProvider.xml 的所有实例,并使用这些信息来建立其类型映射注册表以及获取每种自定义绑定器,以便进行序列化和反序列化。
在 WebSphere 运行时中,它将搜索 classpath 以获得所有可用的自定义绑定器。您可根据所需粒度级别在不同的 classpath 级别上定义您的自定义绑定器。例如,如果您在 EJB jar 或 web 应用程序 war 文件中绑定了您的自定义绑定器,则该自定义绑定器将仅在特定的模块中可用。或者,您也可以通过创建共享库来扩大其使用范围,以使任何使用共享库的模块都能进行使用。此外,您还可以将其放入您的 WebSphere 安装程序的 lib 目录中以使其对整个 WebSphere 均可用。
创建自定义绑定器和自定义绑定构件
一个自定义绑定器中包含两个您需要创建的构件。首先是一个 XML 文件 CustomBindingProvider.xml,该文件中声明了工具和运行时定位绑定器所需的全部信息。第二个构件是在 CustomBindingProvider.xml 中指定的绑定器类的实现。该类通过实现特定的接口来处理序列化和反序列化。稍后我们将用较大的篇幅来专门讨论自定义绑定器类。
您将在下面找到 CustomBindingProvider.xml 的某个实例中需要的不同元素的列表。这些元素都需要在您的文件中进行填充,它们必须具有值以便自定义绑定器能正确工作。
xmlQName
: 映射的源 XML 命名类型
javaName:
映射的目标 Java 类名称
qnameScope
: 类型的范围
binder
: 遇到此类型时所使用自定义绑定器的 Java 类名称 一旦完成创建之后,CustomBindingProvider.xml 通常会与自定义绑定器类一起被打包到同一 jar 文件中,并且必须保存在 /META-INF/services/CustomBindingProvider.xml 路径下。清单 12 中为 CustomBindingProvider.xml 的实例示例,该示例基于我们在上面所使用的 InventorySearch 示例。
清单 12. CustomBindingProvider.xml 文件示例
<?xml version="1.0" encoding="UTF-8"?> <provider xmlns="http://www.ibm.com/webservices/customdatabinding/2004/06" xmlns:mycorp="http://mycorp.com"> <mapping> <xmlQName>mycorp:IventoryItem</xmlQName> <javaName>com.mycorp.IventoryItem</javaName> <qnameScope>complexType</qnameScope> <binder>com.mycorp.InventoryItemBinder</binder> </mapping> </provider> |
在上面的示例中,您可看到我们指定了一个自定义绑定器来映射模式中定义的 mycorp:IventoryItem 复杂类型,并将其映射到 com.mycorp.InventoryItem Java 类型。
在 <binder/> 中所指定的自定义绑定器类为数据绑定所需接口 com.ibm.wsspi.webservices.binding.CustomBinder 的 Java 类实现。这些方法为运行时提供了 QNames 和/或它们要映射到的目标 Java 类型信息,以及对应的序列化和反序列化方法。这两种方法为:serialize(Object, SOAPElement, CustomBindingContext) 和 deserialize(SOAPElement, CustomBindingContext)。当自定义绑定器被运行时识别后,它将通过 SOAPElement 以下列方式与运行时进行通信:
清单 13 显示了 CustomBinder 接口。
清单 13. CustomBinder 接口
public interface CustomBinder { // the QName this binder targets public QName getQName(): // QName scope for this binder public String getQNameScope(); // the Java type name for unmarshalling public String getJavaName(); // serialize the Java object to SOAPElement public javax.xml.soap.SOAPElement serialize (Object bean, SOAPElement rootNode, CustomBindingContext context) throws SOAPException; // deserialize the SOAPElement to Java object public Object deserialize (javax.xml.soap.SOAPElement source, CustomBindingContext context) throws SOAPException; } |
清单 14 中包含我们的 InventorySearch 示例所对应的自定义绑定器类的类似框架。
清单 14. CustomBinder 框架实现示例
import com.ibm.wsspi.webservices.binding.CustomBinder; import com.ibm.wsspi.webservices.binding.CustomBindingContext; public class InventoryItemBinder implements CustomBinder { public QName getQName() { return new QName("http://mycorp.com", "InventoryItem"); } public String getQNameScope() { return CustomBinder.QNAME_SCOPE_COMPLEXTYPE; } public String getJavaName() { return com.mycorp.InventoryItem.getClass().getName(); } public javax.xml.soap.SOAPElement serialize (Object bean, SOAPElement rootNode, CustomBindingContext context) throws SOAPException; { // populate the SOAPElement based on the contents of the // object that was passed in. return rootNode; } public Object deserialize (javax.xml.soap.SOAPElement source, CustomBindingContext context) throws SOAPException { // create an instance of the appropriate Java object based on // the SOAPElement that was passed in. return inventoryItem; } } |
汇总结果
由于目前尚无工具可用于自动创建 CustomBindingProvider.xml 或自定义绑定器类,因此您需要手动创建它们。运用上面的示例,您可通过 Rational Application Developer 或文本编辑器将这些构件汇总在一起。
完成这两个构件的创建之后,接下来您需要将其打包成自定义绑定器 jar 文件,以便可以用它来开发和部署您的 Web 服务应用程序。您最好创建一个新的 jar 文件(手动创建或使用 Rational Application Developer),并将绑定器类和 CustomBindingProvider.xml 包含在其中。记住,XML 文件必须保存在 /META-INF/services/CustomBindingProvider.xml 路径下。
现在,自定义绑定器已完成组装,您可以开发应用程序的其余部分。如果您正在使用 WebSphere Web 服务命令行工具(Java2WSDL 和 WSDL2Java)来开发应用程序,则可运行下列命令:
WSDL2Java.sh [your normal options] -classpath /temp/mybinder.jar [wsdl file name] |
正如您所看到的那样,您需要在使用 WSDL2Java 时包含您创建的绑定器 jar 文件所在的路径。这样,WSDL2Java 工具将搜索 classpath 以获得所有可用的 CustomBindingProvider.xml 实例并进行相应的配置。生成的类和部署描述符应反映出这一点,并且其中应包含代替前面的 SOAPElement 的特定数据类型。
如果您正在使用 Rational Application Developer,则只需确保自定义绑定器 jar 文件被作为外部 jar 文件进行添加。Rational Application Developer 在调用 WSDL2Java 和 Java2WSDL 工具之前会将这些 jar 文件添加到 classpath 中。
图 2. 在 Rational Application Developer 中包含自定义绑定 Jar 文件
|
结束语
利用上面列出的信息,现在您应该可以创建能对 JAX-RPC 未涵盖的某些较为复杂的模式类型进行映射的基于 WebSphere 的 Web 服务应用程序。现在创建的接口应当接近于那些试图使用您的应用程序的人所期望的接口,并且不再要求用户必须深入了解 SAAJ 和期望的 SOAPElement 结构。
如前面所提到的,本文的目的是概要介绍自定义数据绑定以及其如何对您的应用程序有利。本系列中的下一篇文章将深入分析某个具体的自定义绑定器类,我们将根据用户定义的类型映射(即不使用特定的数据绑定技术)用它来序列化和反序列化数据类型。我们还将讨论在开发特定的自定义绑定器时需注意的一些有用提示。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者