posts - 495,comments - 227,trackbacks - 0

理解 SOAP

现在已经安装了软件,接下来可以开始着手处理实际的 Web 服务了。正如我在其他类型的 Web 服务中提到的,您可以选择多种格式。在本系列中,将使用 SOAP。

XML 简介

进行传递的所有这些消息都基于可扩展标记语言(Extensible Markup Language,XML)。如果完全不熟悉 XML,在深入了解各个 Web 服务主题前,真的应该进行一些相关研究。不过,以下提供了继续学习本教程所需的基本知识。

XML 是一种“标记语言”,即给出了一种提供实际内容的附加信息的方式。此信息以“标记”的形式提供,这些标记用于指示“元素”。例如,考虑一下清单 5 中所示的简单 XML 文档。


清单 5. 包含基本内容的 XML 文件
                    
<article articleId="88271" categoryId="classifieds" subcategoryId="forsale">
  <articleHeadline>Fun, fun, fun</articleHeadline>
  <articleText>Vintage 1963 T-Bird.  Less than 300 miles.
Driven by my daughter until I took it away.  Serious 
inquires only.  555-3264 after 7 PM.</articleText>
</article>

请留意此文本中的几个值得注意的地方。首先,这是文本。这就使其可以供任何人阅读,或在其中包含关于任何事物的内容。其次,标记使用 > 和 < 指示,开始标记具有一个名称,并可能带有各种属性(如文章 ID),而结束标记以反斜杠 (/) 表示。元素必须为自包含的,并进行了恰当嵌套。也就是说,不能使用与清单 6 所示类似的 XML 文档。


清单 6. 无效 XML 文档示例
                    
<article articleId="88271" categoryId="classifieds" subcategoryId="forsale">
  <articleHeadline>Fun, fun, fun
  <articleText></articleHeadline>Vintage 1963 T-Bird.  
Less than 300 miles. Driven by my daughter until I 
took it away.  Serious inquires only.  555-3264 after 
7 PM.</articleText>
</article>

XML 还提供了将内容划分为不同“命名空间”的方法,以便由应用程序对其进行不同的处理。例如,SOAP 消息可能与以下的清单 7 类似。


清单 7. 示例 SOAP 消息
                    
<?xml version='1.0' ?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/SOAP-envelope"> 
 <env:Header>
 </env:Header>
 <env:Body>
  <cms:getNumberOfArticles xmlns:cms="http://www.daily-moon.com/cms">
    <cms:category>classifieds</cms:category>
    <cms:subcategory>forsale</cms:subcategory>
  </cms:getNumberOfArticles>
 </env:Body>
</env:Envelope>

不要担心消息的实际结构,但要注意存在两种不同的“前缀”,每个前缀与特定的命名空间对应。在这种情况下,我们是为了将 SOAP“信封”与实际的有效负载进行区分。

再次说明,关于 XML 有很多需要学习,但这些只是本教程需要了解的基础知识。





回页首


SOAP 信封

Web 服务消息的基本单元是实际的 SOAP 信封。这是包含处理消息所必需的所有信息的 XML 文档(请参见清单 8)。


清单 8. 示例 SOAP 信封
                    
<?xml version='1.0' ?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/SOAP-envelope"> 
 <env:Header>
</env:Header>
<env:Body>
</env:Body>
</env:Envelope>

在本例中,获得了一个简单的 Envelope,其命名空间指定为 SOAP 1.2 版本。其中包含两个子元素 HeaderBody。让我们了解一下这两个子元素所起的作用。





回页首


SOAP Header

SOAP 消息中的 Header 用于提供有关消息本身的信息,与用于应用程序的信息相对。例如,Header 可以包括路由信息,像清单 9 中的示例类似。


清单 9. Header 中的路由信息
                    
<?xml version='1.0' ?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/SOAP-envelope"> 
 <env:Header>
  <wsa:ReplyTo xmlns:wsa=
        "http://schemas.xmlSOAP.org/ws/2004/08/addressing">
   <wsa:Address>
http://schemas.xmlSOAP.org/ws/2004/08/addressing/role/anonymous
   </wsa:Address>
  </wsa:ReplyTo>
  <wsa:From>
   <wsa:Address>
http://localhost:8080/axis2/services/MyService</wsa:Address>
  </wsa:From>
  <wsa:MessageID>ECE5B3F187F29D28BC11433905662036</wsa:MessageID> 
  </env:Header>
<env:Body>
</env:Body>
</env:Envelope>

本例中有一个 WS-Addressing 元素,其中包含有关消息将送达何处以及应将应答送达何处的信息。Header 可包含关于消息本身的所有类型的消息。事实上,SOAP 规范中使用了大量篇幅说明哪些元素可以放入 Header以及应由“SOAP 中间层”如何对其进行处理。也就是说,SOAP 规范并不假定消息将直接从一个点传递到另一个点(从客户机到服务器)。规范考虑了 SOAP 消息在送达最终目的地的过程中可能实际由多个中间层处理的情况,很清楚地说明了中间层应如何对待在 Header 中找到的信息。不过,对此的讨论不在本教程的范围之内。因此,目前只要知道 Header 可以提供许许多多的功能(如果您需要)即可。

接下来让我们看看实际的有效负载。





回页首


SOAP 体

发送 SOAP 消息时,都是有目的性的。您在尝试告诉接收者执行某种操作,或尝试向服务器传递相关信息。此信息称为“有效负载”。有效负载位于 EnvelopeBody 中。它还具有自己的命名空间,在本例中其命名空间与内容管理系统对应。在此情况下,可以完全随意地选择命名空间。只需要与 SOAP 命名空间相异即可(请参见清单 10)。


清单 10. 有效负载示例
                    
<?xml version='1.0' ?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/SOAP-envelope"> 
 <env:Header>
...
</env:Header>
 <env:Body>
  <cms:addArticle xmlns:cms="http://www.daily-moon.com/cms">
    <cms:category>classifieds</category>
    <cms:subcategory>forsale</cms:subcategory>
    <cms:articleHeadline></cms:articleHeadline>
    <cms:articleText>Vintage 1963 T-Bird.  Less than 300 miles.  
Driven by my daughter until I took it away.  Serious inquires only.  
555-3264 after 7 PM.</cms:articleText>
  </cms:addArticle>
 </env:Body>
</env:Envelope>

在此例中,有效负载很简单,其中包含将文章添加到 Daily Moon 的内容管理系统的指令。

如何设计有效负载的选择过程将涉及到样式和编码的内容。





回页首


样式和编码

本系列教程的第 2 部分将更深入地了解此主题的内容(该部分讨论 Web 服务描述语言——WSDL),但在创建应用程序时,您将需要确定要发送和接收的实际有效负载的结构。为此,让我们花点时间讨论一下编程样式和编码。

简单来说,有两种不同的主流 SOA 消息编程样式。第一种是 RPC 样式,基于使用 SOAP 消息创建远程过程调用(Remote Procedure Call)的概念。在此样式中,基本思路是在向服务器发送命令(如“添加文章”),并将该命令的参数(如要添加的文章和应该添加到的类别)作为整个方法的子元素包含在其中,如清单 11 中所示。


清单 11. RPC 样式
                    
<env:Envelope xmlns:env="http://www.w3.org/2003/05/SOAP-envelope">
 <env:Header> 

RPC 样式的替代方法将数据直接作为 SOAP 体的内容处理,并在应用服务器对消息进行路由的信息中包含有关其所属的过程或函数的信息。(请参见清单 12)。


清单 12. 将数据作为 SOAP 体中的内容
                    
<env:Envelope xmlns:env="http://www.w3.org/2003/05/SOAP-envelope"> 
 <env:Header>
 </env:Header>
 <env:Body>
  <cms:addArticle xmlns:cms="http://www.daily-moon.com/cms">
    <cms:category>classifieds</category>
    <cms:subcategory>forsale</cms:subcategory>
    <cms:articleHeadline></cms:articleHeadline>
    <cms:articleText>Vintage 1963 T-Bird.  Less than 300 
miles.  Driven by my daughter until I took it away.  
Serious inquires only.  555-3264 after 7 PM.</cms:articleText>
  </cms:addArticle>
 </env:Body>
</env:Envelope>

RPC 样式的一个变体就是与上面看到的 RPC/literal 相对的 RPC/encoded。在这种情况下,消息中包含类型信息,如清单 13 中所示。


清单 13. RPC/encoded 示例
                    
<?xml version='1.0' ?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/SOAP-envelope"> 
 <env:Header>
 </env:Header>
 <env:Body>
  <cms:addArticle xmlns:cms="http://www.daily-moon.com/cms">
    <cms:category xsi:type="xsd:string">classifieds</category>
    <cms:subcategory xsi:type="xsd:string">forsale
</cms:subcategory>
    <cms:articleHeadline xsi:type="xsd:string" />
    <cms:articleText xsi:type="xsd:string">Vintage 1963 
T-Bird.  Less than 300 miles.  Driven by my daughter until
I took it away.  Serious inquires only.  555-3264 after 7 
PM.</cms:articleText>
  </cms:addArticle>
 </env:Body>
</env:Envelope>

第二个样式称为 document/literal 样式,即将相应的数据直接添加到消息中,如清单 14 中所示。


清单 14. Document/literal 样式示例
                    
<?xml version='1.0' ?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/SOAP-envelope"> 
 <env:Header>
 </env:Header>
 <env:Body>
    <category>classifieds</category>
    <subcategory>forsale</subcategory>
    <articleHeadline></articleHeadline>
    <articleText>Vintage 1963 T-Bird.  Less than 300 miles.
  Driven by my daughter until I took it away.  Serious 
inquires only.  555-3264 after 7 PM.</articleText>
</env:Body>
</env:Envelope>

在这种情况下,消息本身并不包含有关数据所提交到的进程的信息,此工作由路由软件进行。例如,所有对特定 URL 或端点的调用都可能指向特定的操作。另外,从技术上讲,可以使用 document/encoded 样式,但目前还没有人这样做,因此可以将其忽略。

在每个样式中都涉及到不同的折衷,本系列的第 2 部分将进一步对此进行详细讨论。不过,务必知道还有第三种样式“document wrapped”,并未正式地确定此样式,但由于各种互操作性原因而大受欢迎。在此情况下,有效负载的内容包装为单个元素,但元素并不据数据所属的过程或函数进行命名。从肉眼来看,这些消息几乎与 RPC/literal 消息完全相同。





回页首


消息交换模式

谈到发送消息,您有很多选择,可以发送请求并等待响应,发送请求但不等待响应,发送请求并在到达最终的目的地前通过多个中间层。但就实质而言,只有两个选择:

  • 请求/响应:在请求/响应模式种,以 SOAP 消息的形式发送请求,然后直接等待发送回响应。请求可以为同步的,也可以是异步的。
  • 单向消息传递:这种情况也称为“Fire and Forget”方法,发送请求但并不等待响应。可以在仅传递信息时或并不关心接收者对此如何响应时使用此方法。

现在,请注意并没有使用术语“客户机”和“服务器”。之所以这样,是因为这些消息交换模式几乎可以用于创建与上面提到的方法类似的任意数量的不同备选方法。例如,可以发送一条请求,然后依靠接收者对其进行处理,并在将来完成应完成的工作时发送一条消息。为此,将使用多个单向消息的组合,因此谈“客户机”和“服务器”并不合理,因为每个消息都有其接收方和发送方,所谓的客户机和服务器的位置会发生对换。





回页首


构建 SOAP 客户机

上面已经对理论进行了介绍,现在让我们开始构建实际的实现。首先从客户机开始。

以前的老方式

最初出现用于使用 SOAP 消息的 Java API 时,其用途非常特定化。它们的用途相当直接,用于创建 SOAP 消息。需要创建消息、EnvelopeHeaderBody 等等。例如,可以构建“旧式”客户机来访问前面安装的 MyService 服务的 echo 函数(请参见清单 15)。

注意:为了编译并运行此客户机,将需要 SAAJ 实现(如原始 Axis 软件)。可以从 http://ws.apache.org/axis/ 下载 Axis。据说 Axis2 0.95 也包含此实现,但本教程未针对其进行测试。


清单 15. 旧式 SOAP 客户机
                    
import javax.xml.SOAP.*; 
import javax.xml.transform.*;
import java.io.FileInputStream;
import javax.xml.transform.stream.*;
import org.w3c.dom.*;

public class SendSOAP {
    
   public static void main(String args[]) {
        
      try {
      
         MessageFactory messageFactory = MessageFactory.newInstance();
         SOAPMessage message = messageFactory.createMessage();
         
         //Create objects for the message parts            
         SOAPPart SOAPPart = message.getSOAPPart();
         SOAPEnvelope envelope = SOAPPart.getEnvelope();
         SOAPBody body = envelope.getBody();

         SOAPElement bodyElement = 
           body.addChildElement(envelope.createName("echo", 
            "req", "http://localhost:8080/axis2/services/MyService/"));
         bodyElement.addChildElement("category")
                             .addTextNode("classifieds");
         message.saveChanges();

         SOAPPart SOAPpartbefore = message.getSOAPPart();
         SOAPEnvelope reqenv = SOAPpartbefore.getEnvelope();

         System.out.println("REQUEST:");
         System.out.println(reqenv.toString());

         //Now create the connection
         SOAPConnectionFactory SOAPConnFactory = 
                           SOAPConnectionFactory.newInstance();
         SOAPConnection connection = 
                           SOAPConnFactory.createConnection();
         
         SOAPMessage reply = connection.call(message, 
                   "http://localhost:8080/axis2/services/MyService");

         SOAPPart SOAPpart = reply.getSOAPPart();
         SOAPEnvelope replyenv = SOAPpart.getEnvelope();

         System.out.println("\nRESPONSE:");
         System.out.println(replyenv.toString());

         connection.close();
     
     } catch (Exception e){
         System.out.println(e.getMessage());
     }
  }
}

请注意,您要直接创建 SOAPEnvelopeSOAPBody 等内容。可以向消息体添加 echocategory 等元素。将从其中创建连接,进行调用,同样也能遍历 SOAP 消息的结构来获取实际的内容。如果要运行此客户机,应该看到与清单 16 中所示类似的响应。


清单 16. 当时的客户机
                    
REQUEST:
<SOAPenv:Envelope xmlns:SOAPenv=
       "http://schemas.xmlSOAP.org/SOAP/envelope/" 
     xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <SOAPenv:Body>
  <req:echo xmlns:req=
         "http://localhost:8080/axis2/services/MyService/">
     <req:category>classifieds</req:category>
  </req:echo>
 </SOAPenv:Body>
</SOAPenv:Envelope>

RESPONSE:
<SOAPenv:Envelope xmlns:SOAPenv=
      "http://schemas.xmlSOAP.org/SOAP/envelope/" xmlns:wsa=
      "http://schemas.xmlSOAP.org/ws/2004/08/addressing">
 <SOAPenv:Header>
  <wsa:ReplyTo>
   <wsa:Address>
http://schemas.xmlSOAP.org/ws/2004/08/addressing/role/anonymous
   </wsa:Address>
  </wsa:ReplyTo>
  <wsa:From>
   <wsa:Address>
http://localhost:8080/axis2/services/MyService</wsa:Address>
  </wsa:From>
  <wsa:MessageID>ECE5B3F187F29D28BC11433905662036</wsa:MessageID>
 </SOAPenv:Header>
 <SOAPenv:Body>
  <req:echo xmlns:req=
         "http://localhost:8080/axis2/services/MyService/">
     <req:category>classifieds</req:category>
  </req:echo>
 </SOAPenv:Body>
</SOAPenv:Envelope>

echo 服务所进行的所有工作实际就是对其所接收到的请求进行应答,通过这一点可以很好地了解旧式处理方法与新式处理方法间的差别。接下来让我们看看二者的差异。





回页首


新方式

现在越来越趋向于对程序员隐藏使用基于 XML 的 Web 服务消息的复杂性。为此进行了大量的工作,其中大部分的目标都是希望尽可能让 Web 服务编程与任何其他体系结构的编程工作一样进行。

在 Axis2 中,实际上并不仅限于此。Axis2 引入了一种全新的方式来使用表示 SOAP 消息的 XML(尽管表面看来与使用文档对象模型类似)。AxIs 对象模型(Axis Object Model,AXIOM)进行了一系列更改,但我暂时将仅提一下其对消息的信息集的关注,消息集是元素和属性中包含的实际信息,而不是通常看到的序列化版本。不过,更为重要的是,Axis2 将为您处理 SOAP 信封,从而可以将精力放在构建有效负载上(或者,如果是实际的服务,则是分析有效负载和创建响应)。接下来让我们看看如何进行此工作。





回页首


创建请求

要开始创建客户机,请确保 Axis2 lib 目录中的所有 *.jar 文件——指 Standard 分发版,而不是 War 分发版——都在您的 CLASSPATH 上,并创建名为 ClassifiedClient 的新类。创建有效负载,如清单 17 中所示。


17. 有效负载
                    
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.om.OMElement;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import java.io.StringWriter;
import org.apache.axis2.om.OMAbstractFactory;
import org.apache.axis2.SOAP.SOAPFactory;
import org.apache.axis2.om.OMFactory;
import org.apache.axis2.om.OMNamespace;

public class ClassifiedClient {

    public static OMElement getEchoOMElement() {
        SOAPFactory fac = OMAbstractFactory.getSOAP12Factory();
        OMNamespace omNs = fac.createOMNamespace(
                "http://daily-moon.com/cms", "cms");
        OMElement method = fac.createOMElement("echo", omNs);
        OMElement value = fac.createOMElement("category", omNs);
        value.addChild(fac.createText(value, "classifieds"));
        method.addChild(value);

        return method;
    }

    public static void main(String[] args) {
        try {
            OMElement payload = ClassifiedClient.getEchoOMElement();
        } catch (Exception e) { //(XMLStreamException e) {
            System.out.println(e.toString());
        }
    }
}

首先,创建工厂和命名空间,并使用其创建各个元素。在本例中,您将创建与前面示例完全相同的元素,因为将再次使用此客户机访问 echo 函数。(稍后将对其进行更改,以访问真正的服务。)

接下来,将创建请求。





回页首


创建请求

下一步将创建实际的请求。同样,这方面也体现出技术随时间的发展。这里不会直接将请求发送到 URL,而要设置“端点引用”,如清单 18 中所示。


清单 18. 请求中的端点引用
                    
...
public class ClassifiedClient {
    private static EndpointReference targetEPR = new
        EndpointReference(
           "http://localhost:8080/axis2/services/MyService");

    public static OMElement getEchoOMElement() {
...
    }

    public static void main(String[] args) {
        try {
            OMElement payload = ClassifiedClient.getEchoOMElement();
            Options options = new Options();
            options.setTo(targetEPR);
            options.setTransportInProtocol(Constants.TRANSPORT_HTTP);

        } catch (Exception e) { //(XMLStreamException e) {
            System.out.println(e.toString());
        }
    }
}

对 WS-Addressing 的全面讨论不在本教程的范畴之内,但完全可以说端点引用包含消息将定向到的 URL,还可以有选择地包含其他信息,如应答地址和其他资源属性等。

首先,为请求创建 Options,以便为请求设置 EPR 和其他信息(如打算使用的传输协议)。完成了这些后,就可以实际发送请求了。





回页首


发送请求

完成了设置后,就可以发送请求了。对于 Axis 的 0.94 版,首选的方式是通过 ServiceClient 类发送消息,如清单 19 中所示。


清单 19. 发送请求
                    
...
    public static void main(String[] args) {
        try {
            OMElement payload = ClassifiedClient.getEchoOMElement();
            Options options = new Options();
            options.setTo(targetEPR);
            options.setTransportInProtocol(Constants.TRANSPORT_HTTP);

            ServiceClient sender = new ServiceClient();
            sender.setOptions(options);
            OMElement result = sender.sendReceive(payload);

        } catch (Exception e) { //(XMLStreamException e) {
            System.out.println(e.toString());
        }
    }
}

将创建 ServiceClient 对象,并为其设置前面创建的 Options。然后就可以直接发送消息了。由于希望得到响应,因此将使用 sendReceive() 方法(该方法用于 in/out 类型的消息)。将在本教程的单向服务部分讨论单向消息的信息。sendReceive() 方法将在客户机接收到实际响应时将其返回。





回页首


输出结果

实际上,sendReceive() 并不会返回整个响应,而仅返回有效负载。如果将结果输出到命令行,应该清楚地看到清单 20 中的内容。


清单 20. sendReceive 输出
                    

...
            sender.setOptions(options);
            OMElement result = sender.sendReceive(payload);

            System.out.println(result.toString());

        } catch (Exception e) { //(XMLStreamException e) {
            System.out.println(e.toString());
        }
    }
}

运行此客户机将获得清单 21 中所示的响应。


清单 21. sendReceive 响应
                    
<cms:echo xmlns:cms="http://daily-moon.com/cms"><cms:catego
ry>classifieds</cms:category></cms:echo>

当然,接收到此数据后,可以使用其进行任何工作。接下来,我们将构建实际的 getNumberofArticles() 服务。





回页首

posted on 2006-12-29 18:35 SIMONE 阅读(1302) 评论(0)  编辑  收藏 所属分类: AXISJAVA

只有注册用户登录后才能发表评论。


网站导航: