内容


Web 服务编程技巧与窍门

用 SAAJ 和 JAX-RPC 构建 SOAP 响应信封

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Web 服务编程技巧与窍门

敬请期待该系列的后续内容。

此内容是该系列的一部分:Web 服务编程技巧与窍门

敬请期待该系列的后续内容。

在技巧“将 <xsd:any/> 元素用于自定义序列化”中(请参阅 参考资料),我向您描述了如何将 <xsd:any/> 元素用于自定义序列化,以及如何将这个元素映射到 javax.xml.soap.SOAPElement 类型的 Java 对象。代码示例显示了客户端如何使用这个接口来手动解析从现有的 Web 服务中返回的响应。在本技巧中,我把重点放在了服务器端。我向您描述了如何使用 SOAP API for Attachments in Java (SAAJ) 在 Web 服务实现类中创建响应消息。

回顾 <xsd:any/> 元素

我再简要的讨论一下 <xsd:any/> 元素是怎样来帮助自定义创建和解析 SOAP 消息的。这个元素代表 XML 模式中任意的 XML 内容。换而言之,您可以把它当作您的模式中的通配符。JAX-RPC 引擎不知道这个内容在运行时是什么样,因此将它作为核心 SAAJ 接口中的一个实例 —— javax.xml.soap.SOAPElement ,传送给服务器或者客户端。您可以通过将您的模式中想通过 SAAJ 处理的元素替换为 <xsd:any/> 来利用这种做法。通常,您只在需要生成正确形式的 JAX-RPC 帮助代码时才要做这样的替换,并且最后能保持 WSDL 文件中模式的最初版本。

JAX-RPC 版本 1.1 要求符合规范的引擎必需提供工具,让用户选择是否进行 类型映射 (type-mapping)。这样做和使用 <xsd:any/> 元素产生的效果相似,即引擎不用试图将 XML 内容映射到 JAVA 内容,反之亦然,但使用的是 javax.xml.soap.SOAPElement 接口。

下面描述了如何平衡运用这两种方法以利用 SAAJ 构建 SOAP 响应消息。

服务端点接口

在 JAX-RPC 中,每个 Web 服务都由一个服务端点接口 (Service Endpoint Interface,SEI) 表示。它主要是将 WSDL portType 映射到 Java 类型,从而让您通过本地 Java 代理类消费 Web 服务,或者是通过执行 SEI 来提供 Web 服务。

通过 javax.xml.soap.SOAPElement 在 SEI 中的出现情况,您很容易就可以检测到 SAAJ 是否用于 Web 服务。如果 SEI 的一个方法使用这个接口作为输入参数,说明传入的 SOAP 请求消息没有被映射到 Java 类型。如果方法返回一个 javax.xml.soap.SOAPElement 的实例,说明响应消息没有被分别映射。

在本实例中,您可以生成两个不同的 SEI,一个用于服务器端,使用 javax.xml.soap.SOAPElement ,另一个用于客户端,使用 Java 类型映射。在这里我只重点讨论服务器端,但是您可以通过单击本文顶部或底部的 Code 图标下载整个实例,包括客户端。

下面是从 WSDL 文件中抽取的用于生成服务端点接口的一段代码:

清单 1.使用 <xsd:any/> 元素的 WSDL 摘录
<wsdl:types>
   <schema elementFormDefault="qualified" 
           targetNamespace="http://any.webservices.ibm.com" 
           xmlns="http://www.w3.org/2001/XMLSchema">
      <element name="getOrder">
         <complexType>
            <sequence>
               <element name="searchCriteria" nillable="true" type="xsd:string"/>
            </sequence>
         </complexType>
      </element>
      <element name="getOrderResponse">
         <complexType>
            <sequence>
               
        <xsd:any maxOccurs="unbounded"/>
            </sequence>
         </complexType>
      </element>
   </schema>
</wsdl:types>

注意,响应消息只包含一个元素,即 <xsd:any/> 。这里是 SEI:

清单 2.服务端点接口
package com.ibm.webservices.any;
import javax.xml.soap.SOAPElement;
public interface OrderManager extends java.rmi.Remote {
    public SOAPElement[] getOrder(java.lang.String searchCriteria) throws 
    java.rmi.RemoteException;
}

正如您所看到的,响应作为一组 javax.xml.soap.SOAPElement 实例被返回。

响应消息

在进一步研究服务实现之前,我描述一下 SOAP 消息的结构,您能够在实现中创建 SOAP 消息。切记,不能从我上面列出的 WSDL 摘录中导出消息的结构。这个定义包含在最初的 XML 模式中,也就是在生成客户端代码前向 WSDL 文件中添加的那段代码:

清单 3.完整的 XML Schema
 <wsdl:types>
  <schema elementFormDefault="qualified" targetNamespace=
  "http://any.webservices.ibm.com" xmlns="http://www.w3.org/2001/XMLSchema">
   <element name="getOrder">
    <complexType>
     <sequence>
      <element name="searchCriteria" nillable="true" type="xsd:string"/>
     </sequence>
    </complexType>
   </element>
   <complexType name="Order">
    <sequence>
     <element name="createDate" nillable="true" type="xsd:dateTime"/>
     <element name="customer" nillable="true" type="xsd:string"/>
     <element maxOccurs="unbounded" name="lineItems" nillable="true" type=
     "impl:LineItem"/>
    </sequence>
   </complexType>
   <complexType name="LineItem">
    <sequence>
     <element name="itemDesc" nillable="true" type="xsd:string"/>
     <element name="itemNumber" nillable="true" type="xsd:string"/>
    </sequence>
   </complexType>
   <element name="getOrderResponse">
    <complexType>
     <sequence>
      
        <element name="getOrderReturn" nillable="true" type="impl:Order"/>
     </sequence>
    </complexType>
   </element>
  </schema>
 </wsdl:types>

这里是符合上面模式的一个 SOAP 响应消息的例子:

清单 4.一个样本 SOAP 消息
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<getOrderResponse xmlns="http://any.webservices.ibm.com">
<getOrderReturn>
      <createDate>2004-07-03T22:57:45.359Z</createDate>
      <customer>Bill Smith</customer>
      <lineItems>
<itemDesc>This is a line item</itemDesc>
<itemNumber>12345</itemNumber>
      </lineItems>
</getOrderReturn>
</getOrderResponse>
</soapenv:Body>
</soapenv:Envelope>

这是您在服务实现类中创建的消息。

Web 服务实现

通常,JAX-RPC 工具从 WSDL 文件生成 SEI 的同时也为实际服务实现生成一个框架。在实现中,您需要创建 SEI 定义的 javax.xml.soap.SOAPElement 实例。这将引导您深入到 SAAJ 编程的领域中去。

这里,我将讨论构建适当的响应元素所必需的每一个步骤。 javax.xml.soap.SOAPElement 的实例是通过 javax.xml.soap.SOAPFactory 类型的工厂来创建的,所有您首先需要创建工厂。而且,每个元素需要一个名称,在 SAAJ 中,名称由 javax.xml.soap.Name 接口表示。 Name 实例不是通过工厂创建的,但是,相反您还需要为它准备一个 javax.xml.soap.SOAPEnvelope 实例。这个实例也是通过工厂创建的,即一个 javax.xml.soap.MessageFactory 类型实例。对您可能提出的疑问做一个回答:确实如此,在开始创建实际的元素之前,您的确需要三个不同的工厂。

清单 5.创建 SAAJ 工厂实例
...
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage message = messageFactory.createMessage();
SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();	
		
SOAPFactory factory = SOAPFactory.newInstance();
...

现在,开始准备创建响应消息的内容。注意,这段代码大部分都是重复性的,您很容易就可以将它们写得比这里显示的更高效些。而且我没有在这个例子中插入任何错误处理,但是,忽略它可能它将不适合于任何产品级别代码:

清单 6.创建 SAAJ 元素
...
// use the factory to create a new element
// the Name is created via the SOAPEnvelope, and we must define a 
// namespace for each element in the body (to be WS-I compliant)
SOAPElement getOrderReturn = factory.createElement(envelope.createName(
"getOrderReturn", "","http://any.webservices.ibm.com"));
// now start adding children and build the full response message
// hardcode the text content for the sakes of simplicity
SOAPElement createDate = getOrderReturn.addChildElement(envelope.createName(
"createDate", "", "http://any.webservices.ibm.com"));
createDate.addTextNode("2004-07-03T22:57:45.359Z");
SOAPElement customer = 
getOrderReturn.addChildElement(envelope.createName(
"customer", "", "http://any.webservices.ibm.com"));
customer.addTextNode("Bill Smith");
SOAPElement lineItems = 
getOrderReturn.addChildElement(envelope.createName(
"lineItems", "", "http://any.webservices.ibm.com"));
SOAPElement itemDesc = 
lineItems.addChildElement(envelope.createName(
"itemDesc", "", "http://any.webservices.ibm.com"));
itemDesc.addTextNode("This is a line item");
SOAPElement itemNumber = 
lineItems.addChildElement(envelope.createName(
"itemNumber", "", "http://any.webservices.ibm.com"));
itemNumber.addTextNode("12345");
...

最后,向 javax.xml.soap.SOAPElement 数组中添加一个名为 getOrderReturn 的元素(记住将 <xsd:any/> 元素的 maxOccurs 属性定义为“unbounded”):

清单 7.构建返回数组
...
SOAPElement[] elements = new SOAPElement[1];
elements[0] = getOrderReturn;
return elements;
...

从现有的 DOM 文档创建响应

在很多情况下,您可能想创建服务实现,它不是一次只构建响应消息的一个元素,而宁可利用已有的 DOM 文档,这个文档可能是从现有的终端检索到的。在这种情况下,您可能不想解析现有的 XML 片断,而是只想把它构造成一个新的 XML 消息。

SAAJ 1.2 规范在 javax.xml.soap.SOAPBody 接口上提出了一个名为 addDocument() 的新方法。而且,和 SAAJ 1.2(J2EE 版本 1.4 中也包括该规范)一样,好几个 SAAJ 接口都是继承于各自的 DOM 接口。例如, javax.xml.soap.SOAPElement 继承于 org.w3c.dom.Element 接口,这使得您可以使用通常的 DOM 编程方法来构建您的消息。由于现在在市场上对于 SAAJ 1.2 还没有普遍的支持,我暂时使用上面的 SAAJ 1.1 接口。

总结

SAAJ 规范定义了接口和类,使您从头开始构建 SOAP 消息。您可以利用这一点避免使用从 XML 到任何类型的映射,以及从 Java 类型到任何类型的映射,如在 JAX-RPC 中所定义的。这种方法的典型用例是假设客户序列化(反序列化)是必需的(例如,如果您的 JAX-RPC 引擎不完全支持所需消息的 XML 模式),或者当现有的代码已经处理了 XML 产物,使得类型映射不再需要了或者至少是多余的时候。

本技巧举了一个用于服务器端实现的例子,以聪明元素的方式构建它的响应信息。SAAJ 1.2 提出了另外的方法,可以更容易处理现有的 XML 部分。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=SOA and web services
ArticleID=23119
ArticleTitle=Web 服务编程技巧与窍门: 用 SAAJ 和 JAX-RPC 构建 SOAP 响应信封
publish-date=09012004