﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-　　　　　　　　　　　　　彬 ^_^ -文章分类-soap</title><link>http://www.blogjava.net/libin2722/category/27231.html</link><description>LuckyStar</description><language>zh-cn</language><lastBuildDate>Fri, 09 Nov 2007 16:04:45 GMT</lastBuildDate><pubDate>Fri, 09 Nov 2007 16:04:45 GMT</pubDate><ttl>60</ttl><item><title>SOAP协议规范</title><link>http://www.blogjava.net/libin2722/articles/159460.html</link><dc:creator>礼物</dc:creator><author>礼物</author><pubDate>Fri, 09 Nov 2007 14:11:00 GMT</pubDate><guid>http://www.blogjava.net/libin2722/articles/159460.html</guid><wfw:comment>http://www.blogjava.net/libin2722/comments/159460.html</wfw:comment><comments>http://www.blogjava.net/libin2722/articles/159460.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/libin2722/comments/commentRss/159460.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/libin2722/services/trackbacks/159460.html</trackback:ping><description><![CDATA[<h4>SOAP协议规范</h4>
<p>1. 简介</p>
<p>SOAP以XML形式提供了一个简单、轻量的用于在分散或分布环境中交换结构化和类型信息的机制。SOAP本身并没有定义任何应用程序语义，如编程模型或特定语义的实现；实际上它通过提供一个有标准组件的包模型和在模块中编码数据的机制，定义了一个简单的表示应用程序语义的机制。这使SOAP能够被用于从消息传递到RPC的各种系统。</p>
<p>SOAP包括三个部分
<ul>
    <li>SOAP封装（见第4节）结构定义了一个整体框架用来表示消息中包含什么内容，谁来处理这些内容以及这些内容是可选的或是必需的。
    <li>SOAP编码规则（见第5节）定义了用以交换应用程序定义的数据类型的实例的一系列机制。
    <li>SOAP RPC表示（见第7节）定义了一个用来表示远程过程调用和应答的协定。 </li>
</ul>
<p>虽然这三个部分都作为SOAP的一部分一起描述，但它们在功能上是相交的。特别的，封装和编码规则是在不同的名域中定义的，这种模块性的定义方法增加了简单性在SOAP封装，SOAP编码规则和SOAPRPC协定之外，这个规范还定义了两个协议的绑定，描述了在有或没有HTTP扩展框架[6]的情况下，SOAP消息如何包含在HTTP消息[5]中被传送。</p>
<h4>1.1 设计目标</h4>
<p>SOAP的主要设计目标是简单性和可扩展性，这意味着传统的消息系统和分布对象系统的某些性质不是SOAP规范的一部分。这些性质包括：
<ul>
    <li>分布式碎片收集
    <li>成批传送消息
    <li>对象引用（要求分布式碎片收集）
    <li>激活机制（要求对象引用） </li>
</ul>
<h4>1.2 符号约定</h4>
<p>这篇文章中的关键字 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT","SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", 和"OPTIONAL"的解释在RFC-2119 [2]中。 这篇文章中用到的名域前缀 "SOAP-ENV" 和"SOAP-ENC"分别与"http://schemas.xmlsoap.org/soap/envelope/" 和"http://schemas.xmlsoap.org/soap/encoding/"关联。整篇文档中，名域前缀&#8220;xsi&#8221;被假定为与URI"http://www.w3.org/1999/XMLSchema-instance&#8220;（在XMLSchema规范[11]定义）相连。类似的，名域前缀&#8221;xsd&#8220;被假定为与URI"http://www.w3.org/1999/XMLSchema"（在[10]中定义）相连。名域前缀&#8221;tns&#8220;用来表示任意名域。所有其它的名域前缀都只是例子。 <br />
名域URI的基本形式&#8221;some－URI&#8220;表示某些依赖于应用程序或上下文的URI[4]。这个规范用扩展BNF（在RFC－2616[5] 描述）描述某些结构。</p>
<h4>1.3 SOAP消息举例</h4>
<p>在这个例子中，GetLastTradePrice SOAP 请求被发往StockQuote服务。这个请求携带一个字符串参数和ticker符号，在SOAP应答中返回一个浮点数。XML名域用来区分SOAP标志符和应用程序特定的标志符。这个例子说明了在第6节中定义的HTTP绑定。如果SOAP中管理XML负载的规则完全独立于HTTP是没有意义的，因为事实上该负载是由HTTP携带的。在Appendix A中有更多的例子。</p>
<p>例1 在HTTP请求中嵌入SOAP消息</p>
<p>POST /StockQuote HTTP/1.1 <br />
Host: <br />
www.stockquoteserver.com <br />
Content-Type: text/xml; <br />
charset="utf-8" <br />
Content-Length: nnnn <br />
SOAPAction: <br />
"Some-URI" <br />
&lt;SOAP-ENV:Envelope <br />
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" <br />
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"&gt; <br />
&lt;SOAP-ENV:Body&gt; <br />
&lt;m:GetLastTradePrice xmlns:m="Some-URI"&gt; <br />
&lt;symbol&gt;DIS&lt;/symbol&gt; <br />
&lt;/m:GetLastTradePrice&gt; <br />
&lt;/SOAP-ENV:Body&gt; <br />
&lt;/SOAP-ENV:Envelope&gt; </p>
<p>下面是一条应答消息，包括HTTP消息，SOAP消息是其具体内容 ：</p>
<p>例2 在HTTP应答中嵌入SOAP消息 </p>
<p>HTTP/1.1 200 OK <br />
Content-Type: text/xml; <br />
charset="utf-8" <br />
Content-Length: <br />
nnnn <br />
&lt;SOAP-ENV:Envelope <br />
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" <br />
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/&gt; <br />
&lt;SOAP-ENV:Body&gt; <br />
&lt;m:GetLastTradePriceResponse xmlns:m="Some-URI"&gt; <br />
&lt;Price&gt;34.5&lt;/Price&gt; <br />
&lt;/m:GetLastTradePriceResponse&gt; <br />
&lt;/SOAP-ENV:Body&gt; <br />
&lt;/SOAP-ENV:Envelope&gt;</p>
<h4>2. SOAP消息交换模型</h4>
<p>SOAP消息从发送方到接收方是单向传送，但正如上面显示的，SOAP消息经常以请求/应答的方式实现。SOAP实现可以通过开发特定网络系统的特性来优化。例如，HTTP绑定（见第6节）使SOAP应答消息以HTTP应答的方式传输，并使用同一个连接返回请求。不管SOAP被绑定到哪个协议，SOAP消息采用所谓的&#8221;消息路径&#8220;发送，这使在终节点之外的中间节点可以处理消息。一个接收SOAP消息的SOAP应用程序必须按顺序执行以下的动作来处理消息：识别应用程序想要的SOAP消息的所有部分 （见4.2.2节）检验应用程序是否支持第一步中识别的消息中所有必需部分并处理它。如果不支持，则丢弃消息（见4.4节）。在不影响处理结果的情况下，处理器可能忽略第一步中识别出的可选部分。如果这个SOAP应用程序不是这个消息的最终目的地，则在转发消息之前删除第一步中识别出来的所有部分。为了正确处理一条消息或者消息的一部分，SOAP处理器需要理解：所用的交换方式（单向，请求/应答，多路发送等等），这种方式下接收者的任务，RPC机制（如果有的话）的使用（如第7节中所述），数据的表现方法或编码，还有其它必需的语义。尽管属性（比如SOAP encodingstyle，见4.1.1节）可以用于描述一个消息的某些方面，但这个规范并不 强制所有的接收方也必须有同样的属性并取同样的属性值。举个例子，某一特定的应用可能知道一个元素表示一条遵循第7节约定的RPC请求，但是另外一些应用可能认为指向该元素的所有消息都用单向传输，而不是类似第7节的请求应答模式。 <br />
（译者注：交互双方的SOAP消息并不一定要遵循同样的格式设定，而只需要以一种双方可理解的格式交换信息就可以了）</p>
<h4>3. 与XML的关系</h4>
<p>所有的SOAP消息都使用XML形式编码（更多有关XML的信息请见[7]）一个SOAP应用程序产生的消息中，所有由SOAP定义的元素和属性中必须包括正确的名域。SOAP应用程序必须能够处理它接收到的消息中的SOAP名域（见4.4节），并且它可以处理没有SOAP名域的SOAP消息，就象它们有正确的名域一样。SOAP定义了两个名域（更多有关XML名域的信息请见[8]）
<ul>
    <li>SOAP封装的名域标志符是"http://schemas.xmlsoap.org/soap/envelope/"
    <li>SOAP的编码规则的名域标志符是"http://schemas.xmlsoap.org/soap/encoding/" </li>
</ul>
<p>SOAP消息中不能包含文档类型声明，也不能包括消息处理指令。[7] SOAP使用"ID"类型"id"属性来指定一个元素的唯一的标志符，同时该属性是局部的和无需校验的。SOAP使用"uri-reference"类型的"href"属性指定对这个值的引用，同时该属性是局部的和无需校验的。这样就遵从了XML规范[7]，XMLSchema规范[11]和XML连接语言规范[9]的风格。除了SOAP mustUnderstand 属性(见4.2.3节）和SOAPactor属性(见4.2.2节）之外，一般允许属性和它们的值出现在XML文档实例或Schema中（两者效果相同）。也就是说，在DTD或Schema中声明一个缺省值或固定值和在XML文档实例中设置它的值在语义上相同。</p>
<h4>4. SOAP封装</h4>
<p>SOAP消息是一个XML文档，包括一个必需的SOAP封装，一个可选的SOAP头和一个必需的SOAP体。在这篇规范剩余部分中，提到SOAP消息时就是指这个XML文档。这一节中定义的元素和属性的名域标志符为：</p>
<p>"http://schemas.xmlsoap.org/soap/envelope/" 。一个SOAP消息包括以下部分：1.在表示这个消息的XML文档中，封装是顶层元素。2.应用SOAP交换信息的各方是分散的且没有预先协定，SOAP头提供了向SOAP消息中添加关于这条SOAP消息的某些要素(feature)的机制。SOAP定义了少量的属性用来表明这项要素（feature）是否可选以及由谁来处理。（见4.2节）3.SOAP体是包含消息的最终接收者想要的信息的容器（见4.3节）。SOAP为SOAP体定义了一个Fault元素用来报告错误信息。语法规则如下所示： </p>
<p>封装
<ol>
    <li>元素名是 "Envelope"
    <li>在SOAP消息中必须出现。
    <li>可以包含名域声明和附加属性。如果包含附加属性，这些属性必须限定名域。类似的，"Envelope"可以包含附加子元素，这些也必须限定名域且跟在SOAP体元素之后。 </li>
</ol>
<p>SOAP头 （见4.2节）
<ol>
    <li>元素名是"Header"
    <li>在SOAP消息中可能出现。如果出现的话，必须是SOAP封装元素的第一个直接子元素。
    <li>SOAP头可以包含多个条目，每个都是SOAP头元素的直接子元素。所有SOAP头的直接子元素都必须限定名域。 </li>
</ol>
<p>SOAP体 （见4.3节）
<ol>
    <li>元素名是"Body"
    <li>在SOAP消息中必须出现且必须是SOAP封装元素的直接子元素。它必须直接跟在SOAP头元素（如果有）之后。否则它必须是SOAP封装元素的第一个直接子元素。
    <li>SOAP体可以包括多个条目，每个条目必须是SOAP体元素的直接子元素。SOAP体元素的直接子元素可以限定名域。SOAP定义了SOAPFault元素来表示错误信息。 </li>
</ol>
<h4>4.1.1 SOAP encodingStyle属性</h4>
<p>EncodingStyle全局属性用来表示SOAP消息的序列化规则。这个属性可以在任何元素中出现，作用范围与名域声明的作用范围很相似，为这个元素的内容和它的所有没有重载此属性的子元素。SOAP消息没有定义缺省编码。属性值是一个或多个URI的顺序列表，每个URI确定了一种或多种序列化规则，用来不同程度反序列化SOAP消息，举例如下：</p>
<p>"http://schemas.xmlsoap.org/soap/encoding/" <br />
"http://my.host/encoding/restricted http://my.host/encoding/" <br />
"" </p>
<p>第5节中定义的序列化规则由URI"http://schemas.xmlsoap.org/soap/encoding/" 确定。使用这个特定序列化规则的消息应该用encodingStyle属性说明这一点。另外，所有以"http://schemas.xmlsoap.org/soap/encoding/"开头的URI中的序列化规则与第5节中定义的SOAP编码规则相一致。一个零长度的URI（""）明确显示所含元素没有任何编码形式。这可以用来取消上一级元素的所有编码声明。</p>
<h4>4.1.2 封装版本模型</h4>
<p>SOAP没有定义常规的基于主版本号和辅版本号的版本形式。SOAP消息必须有一个封装元素与名域"http://schemas.xmlsoap.org/soap/envelope/"关联。如果SOAP应用程序接收到的SOAP消息中的SOAP封装元素与其他的名域关联，则视为版本错误，应用程序必须丢弃这个消息。如果消息是通过HTTP之类的请求/应答协议收到的，应用程序必须回答一个SOAP VersionMismatch 错误信息（见4.4节）。</p>
<h4>4.2 SOAP头</h4>
<p>SOAP为相互通信的团体之间提供了一种很灵活的机制：在无须预先协定的情况下，以分散但标准的方式扩展消息。可以在SOAP头中添加条目实现这种扩展，典型的例子有认证，事务管理，支付等等。头元素编码为SOAP封装元素的第一个直接子元素。头元素的所有直接子元素称作条目。条目的编码规则如下：</p>
<p>一个条目有它的完整的元素名（包括名域URI和局部名）确定。SOAP头的直接子元素必须有名域限制。<br />
SOAP encodingStyle属性可以用来指示条目所用的编码形式（见4.1.1节）<br />
SOAP mustUnderstand属性（见4.2.3节）和SOAPactor属性（见4.2.2节）可以用来指示如何处理这个条目以及由谁来处理。（见4.2.1节）</p>
<h4>4.2.1 使用头属性</h4>
<p>这一节中定义的SOAP头属性确定了SOAP消息的接收者应该怎样按第2节中所述的方式处理消息。产生SOAP消息的SOAP应用程序，应该仅仅在SOAP头元素的直接子元素中使用这些SOAP头属性。SOAP消息的接收者必须忽略所有不在SOAP头元素的直接子元素中SOAP头属性。下面的例子是一个SOAP头，包括一个元素标志符"Transaction"，"mustUnderstand"取值为"1"和数值5。这应该以如下方式编码：</p>
<p>&lt;SOAP-ENV:Header&gt; <br />
&lt;t:Transaction <br />
xmlns:t="some-URI" SOAP-ENV:mustUnderstand="1"&gt; <br />
5 <br />
&lt;/t:Transaction&gt; <br />
&lt;/SOAP-ENV:Header&gt; </p>
<h4>4.2.2 SOAP actor属性</h4>
<p>一个SOAP消息从始节点到终节点的过程中，可能沿着消息路径经过一系列SOAP中间节点。一个SOAP中间节点是一个可以接收转发SOAP消息的应用程序。中间节点和终节点由URI区分。可能SOAP消息的终节点并不需要所有部分，而在消息路径上的一个和几个中间节点可能需要这些内容。头元素的接收者扮演的角色类似于一个过滤器，防止这些只发给本接受者的消息部分扩散到其它节点。即一个头元素的接收者必须不转发这些头元素到SOAP消息路径上的下一个应用程序。同样的，接收者可能插入一个相似的头元素。SOAP actor全局属性可以用于指示头元素的接收者。SOAP actor属性的值是一个URI。</p>
<p>URI "http://schemas.xmlsoap.org/soap/actor/next"指出了第一个处理这个消息的SOAP应用程序需要这个头元素。这类似于HTTP头中用Connection域表示hop-by-hop范围模型。省略SOAP actor属性表示接收者是SOAP消息的终节点。如果这个属性要生效，它必须出现在SOAP消息实例中。（见第3节和4.2.1节）</p>
<h4>4.2.3 SOAP mustUnderstand属性</h4>
<p>SOAP mustUnderstand全局属性用来指示接受者在处理消息时这个条目是否必须处理。条目的接收者由SOAP actor属性定义（见4.2.2节）。MustUnderstand属性的值是"1" 或 "0"。缺少SOAP mustUnderstand属性在语义上等同于它的值为"0"。如果一个头元素的SOAP mustUnderstand属性的值是"1",那么条目的接受者必须或者遵守语义（如以元素的全名传送）并按照语义正确的处理，或者放弃处理消息（见4.4节）。SOAP mustUnderstand 属性考虑了消息演变的准确性（robust evolution）。必须假定包含SOAP mustUnderstand属性且值为"1"的元素以某种方式修改了它们的父元素或同层元素的语义。以这种方式连接元素确保了语义上的变化不会被那些不能完全理解它的接收者忽略。如果这个属性要生效，它必须出现在SOAP消息实例中。（见第3节和4.2.1节）</p>
<h4>4.3 SOAP体</h4>
<p>SOAP体元素提供了一个简单的机制，使消息的最终接收者能交换必要的信息。使用体元素的典型情况包括配置RPC请求和错误报告。体元素编码为SOAP封装元素的直接子元素。如果已经有一个头元素，那么体元素必须紧跟在头元素之后，否则它必须是SOAP封装元素的第一个直接子元素。体元素的所有直接子元素称作体条目，每个体条目在SOAP体元素中编码为一个独立的元素。条目的编码规则如下：
<ul>
    <li>一个条目由它的元素全名（包括名域URI和局部名）确定。SOAP体元素的直接子元素可能是名域限制的。
    <li>SOAP encodingStyle属性可能用来指示条目（见4.1.1节）的编码方式。
    <li>SOAP定义了一个Fault条目用来报告错误信息。（见4.4节） </li>
</ul>
<h4>4.3.1 SOAP头和体的关系</h4>
<p>虽然头和体定义为独立的元素，它们实际上是有关系的。体条目和头条目的关系如下：体条目在语义上等同于actor属性为缺省值且mustUnderstand属性值为"1"的头条目。不使用actor属性则表示缺省的actor。（见4.2.2节）</p>
<h4>4.4 SOAP错误</h4>
<p>SOAP错误元素用于在SOAP消息中携带错误和（或）状态信息。如果有SOAP错误元素，它必须以以体条目的方式出现，并且在一个体元素中最多出现一次。SOAP错误元素定义了以下四个子元素：
<ul>
    <li>faultcode<br />
    faultcode元素给软件提供了一个识别此错误的算法机制。SOAP错误元素必须有faultcode子元素，并且它的值必须是一个合法的名（在[8]节定义）。SOAP定义一些SOAP faultcode描述基本的SOAP错误（见4.4.1节）。
    <li>faultstring<br />
    faultstring元素提供了一个错误解释，而不是为了软件处理。faultstring元素类似于HTTP中定义(见[5]，第6.1节）的'Reason-Phrase'。SOAP错误元素必须有faultstring子元素，并且它应该提供一些错误本质的解释信息。
    <li>faultactor<br />
    faultactor元素提供了在消息路径上是谁导致了错误发生的信息（见第2节）。它类似于SOAP actor属性（见4.2.2节），只是SOAP actor指的是头条目的目的地，faultactor指的是错误的来源。faultactor属性的值是用来区分错误来源的URI。不是SOAP消息的最终目的地的应用程序必须在SOAP Fault元素中包含faultactor元素。消息的最终目的地可以使用faultactor元素明确的指示是它产生了这个错误（参见下面的detail元素）
    <li>detail<br />
    detail元素用来携带与Body元素有关的应用程序所要的错误信息。如果Body元素的内容不能被成功的处理，则必须包含detail子元素。它不能用来携带属于头条目的错误信息。头条目的详细出错信息必须由头条目携带。Fault元素中没有detail元素表示这个错误与Body元素的处理无关。在有错误的时候，这可以用来区分Body元素有没有被正确的处理。detail元素的所有直接子元素称作detail条目，并且每个detail条目在detail元素中编码为独立的元素。detail条目的编码规则如下（参见例10）： 一个detail条目由它的元素全名（包括名域URI和局部名）确定。SOAP体元素的直接子元素可能是名域限制的。SOAP encodingStyle属性可能用来指示detail条目（见4.1.1节）的编码方式。也可以有其它的Fault子元素，只要它们是名域限制的。 </li>
</ul>
<h4>4.4.1 SOAP 错误代码</h4>
<p>在描述这个规范中定义的错误时，这一节中定义的Faultcode值必须用在faultcode元素中。这些faultcode值得名域标志符为"http://schemas.xmlsoap.org/soap/envelope/"。定义这个规范之外的方法时推荐（不要求）使用这个名域。缺省的SOAP faultcode值以可扩展的方式定义，允许定义新的SOAP faultcode值，并与现有的faultcode值向后兼容。使用的机制类似于HTTP中定义的1xx, 2xx,3xx等基本的状态类（见[5]第10节），不过，它们定义为XML合法名（见 [8] 第3节 ），而不是整数。 字符"."（点）作为faultcode的分隔符，点左边的错误代码比右边的错误代码更为普通。如：</p>
<p>Client.Authentication</p>
<p>这篇文档中定义的faultcode值是：</p>
<table cellspacing="0" cellpadding="0" width="100%" border="1">
    <tbody>
        <tr>
            <td width="23%">名称</td>
            <td width="77%">含义</td>
        </tr>
        <tr>
            <td width="23%">VersionMismatch</td>
            <td width="77%">处理方发现SOAP封装元素有不合法的名域（见4.1.2节）</td>
        </tr>
        <tr>
            <td width="23%">MustUnderstand</td>
            <td width="77%">处理方不理解或者不服从一个包含值为"1"的</td>
        </tr>
        <tr>
            <td width="23%">mustUnderstand</td>
            <td width="77%">属性的 SOAP头元素的直接子元素。（见4.2.3节）</td>
        </tr>
    </tbody>
</table>
<p>Client</p>
<p>Client错误类表示消息的格式错误或者不包含适当的正确信息。例如，消息可能缺少正确的认证和支付信息。一般地，它表示消息不能不作修改就重发。参见4.4节</p>
<p>SOAP Fault detail子元素的描述。</p>
<p>Server</p>
<p>Server错误类表示由于消息的处理过程而不是消息的内容本身使得消息消息不能正确的处理。例如，处理消息时可能要与其它处理器通信，但它没有响应。这个消息可能在迟一点的时间处理成功。 SOAP Fault子元素的详细信息参见4.4节</p>
<h4>5. SOAP编码</h4>
<p>SOAP编码格式基于一个简单的类型系统，概括了程序语言，数据库和半结构化数据等类型系统的共同特性。一个类型或者是一个简单的（标量的）类型，或者是由几个部分组合而成的复合类型，其中每个部分都有自己的类型。以下将详细描述这些类型。这一节定义了类型化对象的序列化规则。它分两个层次。首先，给定一个与类型系统的符号系统一致的Schema（译者注：这里的schema不是符合XML语法的schema，而仅仅表示广义的用于表示消息结构的定义方式），就构造了XML语法的Schema。然后，给定一个类型系统的Schema和与这个Schema一致的特定的值，就构造了一个XML文档实例。反之，给定一个依照这些规则产生的XML文档实例和初始的Schema，就可以构造初始值的一个副本。这一节中定义的元素和属性的名域标志符为"http://schemas.xmlsoap.org/soap/encoding/"。下面的例子都假定在上一层的元素中声明了名域。 <br />
鼓励使用这一节中描述的数据模型和编码方式，但也可以在SOAP中使用其他的数据模型和编码方式。（见4.1.1节）</p>
<h4>5.1 XML中的编码类型规则</h4>
<p>XML允许非常灵活的数据编码方式。SOAP定义了一个较小的规则集合。这一节在总的层次上定义了这些编码规则，下一节将描述特定类型的编码规则的细节。这一节定义的编码规则可以与第7节中所述的RPC调用和应答映射结合使用。下面的术语用来描述编码规则:
<ul>
    <li>一个"value"是一个字符串，类型（数字，日期，枚举等等）的名或是几个简单值的组合。所有的值都有特定的类型。
    <li>一个"simple value"没有名部分， 如特定的字符串，整数，枚举值等等。
    <li>一个"compound value"是相关的值的结合，如定单，股票报表，街道地址等等。在"compound value"中，每个相关的值都潜在的以名，序数或这两者来区分。这叫作"a ccessor"。复合值的例子有定单和股票报表等等。数组也是复合值。在复合值中，多个accessor有相同的名是允许的，例如RDF就是这样做的。
    <li>一个"array"是一个复合值，成员值按照在数组中的位置相互区分。
    <li>一个"struct"也是一个复合值，成员值之间的唯一区别是accessor名，accessor名互不相同。
    <li>一个"simple type"是简单值的类，如叫做"string" "integer"的类，还有枚举类等等。
    <li>一个"compound type"是复合值的类。复合类型的例子有定单类，它们有相同的accessor名（shipTo, totalCost等），但可能会有不同的值（可能以后被设置为确定的值）。 </li>
</ul>
<p>在复合类型中，如果类型内的accessor名互不相同，但是可能与其他类型中的accessor名相同，即，accessor名加上类型名形成一个唯一的标志符，这个名叫作"局部范围名"。如果名是直接或间接的基于URI的一部分，那么不管它出现在什么类型中，这个名本身就可以唯一标志这个accessor，这样的名叫作"全局范围名"。给定了schema中相关的值的序列化信息，就可能确定某些值只与某个accessor的一个实例有关。其它情况下则无法确定。当且仅当一个accessor引用一个值，这个值才能被视为"single-reference"，如果有不止一个accessor引用它，那么就将它视为"multi-reference"。注意，可能一个确定的值在一个schema中是"single-reference"，而在另一个schema中是"multi-reference"。在语句构成上，一个元素可能是"independent" 或 "embedded"。一个独立的元素指出现在序列化最顶层的任何元素。所有其它元素都是嵌入元素。虽然用xsi:type属性可以使值的结构和类型变为自描述的，但是序列化规则允许值的类型仅仅参照schema而定。这样的schema可能使用"XML Schema Part 1: Structures" [10]和"XML Schema Part 2: Datatypes" [11]中描述的符号系统，也可能使用其它符号系统。注意，虽然序列化规则可以用于除了数组和结构之外的复合类型，但是许多schema仅仅包含数组和结构类型。序列化规则如下: </p>
<p>所有的值以元素内容的形式表示。一个multi-reference值必须表示为一个独立元素的内容，而一个single-reference值最好不要这样表示（也可以这样表示）。对于每个具有值的元素，值的类型时必须用下述三种方式之一描述：
<ul>
    <li>所属元素实例有xsi:type属性
    <li>所属元素是一个有SOAP-ENC:arrayType 属性（该属性可能是缺省的）的元素的子元素，或者
    <li>所属元素的名具有特定的类型，类型可以由schema确定。 </li>
</ul>
<p>一个简单值表示为字符数据，即没有任何子元素。每个简单值必须具有一个类型，这个类型或者是XML Schemas Specification, part 2 [11]有的类型，或者具有源类型（参见5.2节）。一个复合值编码成一个元素的序列，每个accessor用一个嵌入元素表示，该元素的元素名和accessor的名一致。如果accessor的名是局部于其所属的类型的，则该元素的元素名不是合格的，否则对应的元素名是合格的。(参见5.4节) <br />
一个multi-reference的简单值或复合值编码成一个独立的元素，这个元素包含一个局部的无需校验的属性，属性名为"id"，类型为"ID"（依照XML Specification [7]）。值的每个accessor对应一个空元素，该元素有一个局部的，无需校验的属性，属性名为"href"，类型为" uri-reference "（依照XML Schema Specification [11]），"href"属性的值引用了相对应的独立元素的URI标志符。字符串和字符数组表示为multi-reference的简单类型，但是特殊的规则使它们在普通的情况下能被更有效的表示（参见5.2.1节和5.2.3节）。字符串和字符数组值的accessor可能有一个名字为"id"，类型为"ID"（依照XML Specification [7]）的属性。如果这样，所有这个值的所有其它accessor编码成一个空元素，这个元素有一个局部的，无需校验的属性，属性名为"href"，类型为" uri-reference "（依照XML Schema Specification [11]），"href"属性的值引用了包含这个值的元素的URI标志符。编码时允许一个值有多个引用，就像多个不同的值有多个引用一样，但这仅在从上下文可以知道这个XML文档实例的含义没有改变时才可使用。数组是复合值（参见5.4.2节）。SOAP数组定义为具有类型"SOAP-ENC:Array"或从它衍生的类型.</p>
<p>SOAP数组可以时一维或多维，它们的成员以序数位置相互区分。一个数组值表示为反映这个数组的一系列元素，数组成员按升序出现。对多维数组来说，右边的这一维变化最快。每个成员元素命名为一个独立元素。（见规则2）SOAP数组可以是single-reference 或multi-reference值，因此可以表示为嵌入元素或独立元素的内容。SOAP数组必须包含一个"SOAP-ENC:arrayType"属性，它的值指定了包含元素的类型和数组的维数。"SOAP-ENC:arrayType"属性的值定义如下：</p>
<p>arrayTypeValue = atype asize <br />
atype = QName *( rank ) <br />
rank = "[" *( "," ) "]" <br />
asize = "[" #length "]" <br />
length = 1*DIGIT
<ul>
    <li>"atype"结构是被包含元素的类型名，它表示为QName并且作为类型限制在XML元素声明的
    <li>"type"属性中出现（这意味着被包含元素的所有值都要与该类型一致，即在SOAP-ENC:a rrayType中引用的类型必须是每个数组成员的类型或超类型）。在arrays of arrays or "jagged arrays"的情况下，类型组件编码为"innermost"类型且在从第一层开始的嵌套数组的每一层中，类型名后都跟随一个rank结构。多维数组编码时从第一维起，每一维之间用逗号隔开。
    <li>"asize"结构包含一个以逗号分隔的列表，数值0，1或其它整数表示数组每一维的长度。整数0表示没有指定详细的大小，但是可能在检查数组实际成员的大小后确定。例如，一个5个成员的整型数组的arrayTypeValue值为"int[][5]"，它的atype值是int[]"，asize值是"[5]"。同样，一个3个成员的两维整型数组的arrayTypeValue值为"int[,][3]"，它的atype值是int[,]"，asize值是"[3]"。 </li>
</ul>
<p>一个SOAP数组成员可能包含一个"SOAP-ENC:offset"属性表示这一项在整个数组中的位置偏移值。这被用来指示一个部分储值数组（见5.4.2.1节）的位置偏移值。同样，一个数组成员可能包含一个"SOAP-ENC:position"属性表示这一项在整个数组中的位置，这被用来描述稀疏数组（见5.4.2.2节）的成员。"SOAP-ENC:offset" 和"SOAP-ENC:position"属性值的定义如下：</p>
<p>arrayPoint = "[" #length "]"<br />
偏移值和位置从0开始 <br />
NULL值或缺省值可能通过省略accssor元素来表示。NULL值也可能通过一个包含值为'1'的xsi:null属性的accssor元素来表示，其它的依赖于应用程序的属性和值也可能用来表示NULL值。注意，规则2允许独立的元素和数组成员名不同于值类型的元素。 </p>
<h4>5.2 简单类型</h4>
<p>SOAP采用了"XML Schema Part 2: Datatypes"规范[11]"Built-in datatypes"节中的所有类型作为简单类型，包括值和取值范围。例如：</p>
<div align="center">
<center>
<table cellspacing="0" cellpadding="0" width="42%" border="1">
    <tbody>
        <tr>
            <td width="23%">类型</td>
            <td width="77%">举例</td>
        </tr>
        <tr>
            <td width="23%">int</td>
            <td width="77%">58502</td>
        </tr>
        <tr>
            <td width="23%">float</td>
            <td width="77%">314159265358979E+1</td>
        </tr>
        <tr>
            <td width="23%">negativeInteger</td>
            <td width="77%">-32768</td>
        </tr>
        <tr>
            <td width="23%">string</td>
            <td width="77%">Louis "Satchmo" Armstrong</td>
        </tr>
    </tbody>
</table>
</center></div>
<p>在XML Schema规范中声明的数据类型可以直接用在元素schema中，也可以使用从这些类型衍生的新类型。一个schema和对应的具有这些类型的元素的数据实例的例子如下所示：</p>
<blockquote>
<p>&lt;element name="age" type="int"/&gt; <br />
&lt;element name="height" type="float"/&gt; <br />
&lt;element name="displacement" type="negativeInteger"/&gt; <br />
&lt;element name="color"&gt; <br />
&lt;simpleType base="xsd:string"&gt; <br />
&lt;enumeration value="Green"/&gt; <br />
&lt;enumeration value="Blue"/&gt; <br />
&lt;/simpleType&gt; <br />
&lt;/element&gt; <br />
&lt;age&gt;45&lt;/age&gt; <br />
&lt;height&gt;5.9&lt;/height&gt; <br />
&lt;displacement&gt;-450&lt;/displacement&gt; <br />
&lt;color&gt;Blue&lt;/color&gt;</p>
</blockquote>
<p>所有简单值必须编码为元素的内容，它的类型或者在"XML Schema Part 2: Datatypes"规范[11]中定义过，或者是基于一个用XML Schema规范提供的机制能推衍生出的类型。如果一个简单值编码为独立元素或异质数组成员，那么有一个对应于数据类型的元素声明将会很方便。因为"XML Schema Part 2: Datatypes"规范[11]包括了类型定义，但是不包括对应的元素声明，SOAP-ENC schema和名域为每个简单数据类型声明了一个元素，如&lt;SOAP-ENC:int id="int1"&gt;45&lt;/SOAP-ENC:int&gt;</p>
<h4>5.2.1 字符串</h4>
<p>字符串数据类型的定义在"XML Schema Part 2: Datatypes"规范[11]中。注意，这不同于许多数据库和程序语言中的"string"类型，特别的，字符串数据类型可能禁止某些在那些语言中允许的字符。（这些值必须用xsd:string之外的数据类型表示）一个字符串可能编码为一个single-reference 或 multi-reference值。包含字符串值的元素可能有一个"id"属性。附加的accessor元素可能有对应的"href"属性。 <br />
例如，同一字符串的两个accessor可能以如下形式出现：</p>
<blockquote>
<p>&lt;greeting id="String-0"&gt;Hello&lt;/greeting&gt; <br />
&lt;salutation href="#String-0"/&gt; </p>
</blockquote>
<p>但是，如果两个accessor参考同一字符串实例（或字符串的子类型），这不是一个实质问题，它们可以编码为两个single-reference值，如下所示：</p>
<blockquote>
<p>&lt;greeting&gt;Hello&lt;/greeting&gt; <br />
&lt;salutation&gt;Hello&lt;/salutation&gt;</p>
</blockquote>
<p>这个例子的schema片断如下所示：</p>
<blockquote>
<p>&lt;element name="greeting" type="SOAP-ENC:string"/&gt; <br />
&lt;element name="salutation" type="SOAP-ENC:string"/&gt;</p>
</blockquote>
<p>在这个例子中，SOAP-ENC:string类型用作元素的类型，这是声明数据类型是"xsd:string"且允许"id" 和"href"属性的元素的简便方法。精确定义参见SOAP编码schema。Schemas可以使用这些源自SOAP编码schema的声明，但也可以不这样做。</p>
<h4>5.2.2 Enumerations</h4>
<p>"XML Schema Part 2: Datatypes"规范 [11] 定义了"enumeration."机制。SOAP数据模型直接采用了这种机制。但是，由于程序语言和其它语言在定义枚举时通常有些不同，所以我们在这里详细阐述了它的概念并描述了一个列表成员的可能取的值是如何编码的。"Enumeration"作为一个概念表示不同的名字的集合。一个特定的枚举就是对应于特定的基类型的不同的值的列表。例如，颜色集合("Green", "Blue", "Brown")可以定义为基于字符串类型的枚举，("1", "3", "5")可能是一个基于整型数的枚举，等等。"XML Schema Part 2: Datatypes" [11]支持除了布尔型以外所有简单类型的枚举。"XML Schema Part 1: Structures"规范[10]的语言可以用来定义枚举类型。如果schema由另一个没有特定基类型适用的符号系统生成，就使用"string"。在下面schema的例子中，"EyeColor"定义为字符串，可能的值是"Green", "Blue", 或"Brown"的枚举，数据实例按照schema显示如下。</p>
<blockquote>
<p>&lt;element name="EyeColor" type="tns:EyeColor"/&gt; <br />
&lt;simpleType name="EyeColor" base="xsd:string"&gt; <br />
&lt;enumeration value="Green"/&gt; <br />
&lt;enumeration value="Blue"/&gt; <br />
&lt;enumeration value="Brown"/&gt; <br />
&lt;/simpleType&gt; <br />
&lt;Person&gt; <br />
&lt;Name&gt;Henry Ford&lt;/Name&gt; <br />
&lt;Age&gt;32&lt;/Age&gt; <br />
&lt;EyeColor&gt;Brown&lt;/EyeColor&gt; <br />
&lt;/Person&gt;</p>
</blockquote>
<h4>5.2.3 字符数组</h4>
<p>一个字符数组可能编码为single-reference 或multi-reference值。字符数组的编码规则与字符串的编码规则类似。特别的，包含字符数组的元素值可能由一个"id"属性，附加的accssor元素可能有相应的"href"属性。推荐使用定义在XML Schemas [10][11]中的'base64'编码（使用在2045 [13]中定义的base64编码算法）表示模糊字符数组。不过，由于行长度（line length）的限制，通常在MIME中应用base64编码，SOAP中一般不应用base64编码。但是提供了"SOAP-ENC:base64"子类型使之能用于SOAP。</p>
<blockquote>
<p>&lt;picture xsi:type="SOAP-ENC:base64"&gt; <br />
aG93IG5vDyBicm73biBjb3cNCg== <br />
&lt;/picture&gt;</p>
</blockquote>
<h4>5.3 多态accessor</h4>
<p>许多语言允许能够多态访问多种类型值的accessor，每种类型在运行时可用。一个多态accessor实例必须包含一个"xsi:type"属性描述实际值的类型。例如，一个名为"cost"类型值为"xsd:float"的多态accessor编码如下：</p>
<p>&lt;cost xsi:type="xsd:float"&gt;29.95&lt;/cost&gt;与之对比，类型值不变的accessor编码如下：</p>
<p>&lt;cost&gt;29.95&lt;/cost&gt;</p>
<h4>5.4 Compound types复合类型</h4>
<p>SOAP定义了与下列常在程序语言中出现的结构性模式对应的类型：
<ul>
    <li>结构:一个"struct"是一个复合值，它的成员值的唯一区别是accessor名称，任意两个accessor名称都不相同。
    <li>数组:一个"array"是一个复合值，它的成员值的唯一区别是序数位置。 </li>
</ul>
<p>SOAP也允许结构和数组之外的其它数据的序列化，例如Directed-Labeled-Graph Data Model之类的数据中，单个节点有许多不同的accssor，有些不止出现一次。SOAP序列化规则不要求底层的数据模型在accssor之间区分次序，但如果有这样的次序的话，这些accssor必须按照这个顺序编码。</p>
<h4>5.4.1 复合值，结构和值引用</h4>
<p>复合值的成员编码为accessor元素。当accessor由名区分时（如结构），accessor名即作为元素名。名局部于类型的accessor有不受限的名，其它的accessor则有受限的名。下面的例子是类型为"Book"的结构：</p>
<blockquote>
<p>&lt;e:Book&gt; <br />
&lt;author&gt;Henry Ford&lt;/author&gt; <br />
&lt;preface&gt;Prefatory text&lt;/preface&gt; <br />
&lt;intro&gt;This is a book.&lt;/intro&gt; <br />
&lt;/e:Book&gt;</p>
</blockquote>
<p>以下是描述上面结构的schema片断：</p>
<blockquote>
<p>&lt;element name="Book"&gt; <br />
&lt;complexType&gt; <br />
&lt;element name="author" type="xsd:string"/&gt; <br />
&lt;element name="preface" type="xsd:string"/&gt; <br />
&lt;element name="intro" type="xsd:string"/&gt; <br />
&lt;/complexType&gt; <br />
&lt;/e:Book&gt;</p>
</blockquote>
<p>以下是一个同时具有简单和复杂成员类型的例子。它显示两层引用。注意"Author"accssor元素的"href"属性是对相应具有"id"属性的值的引用。"Address"与之类似。</p>
<blockquote>
<p>&lt;e:Book&gt; <br />
&lt;title&gt;My Life and Work&lt;/title&gt; <br />
&lt;author href="#Person-1"/&gt; <br />
&lt;/e:Book&gt; <br />
&lt;e:Person id="Person-1"&gt; <br />
&lt;name&gt;Henry Ford&lt;/name&gt; <br />
&lt;address href="#Address-2"/&gt; <br />
&lt;/e:Person&gt; <br />
&lt;e:Address id="Address-2"&gt; <br />
&lt;email&gt;mailto:henryford@hotmail.com&lt;/email&gt; <br />
&lt;web&gt;http://www.henryford.com&lt;/web&gt; <br />
&lt;/e:Address&gt;</p>
</blockquote>
<p>当"Person"的值和"Address"的值是multi-reference时，上面的形式是正确的。如果它 <br />
们是single-reference，就必须用嵌入的形式，如下所示：</p>
<blockquote>
<p>&lt;e:Book&gt; <br />
&lt;title&gt;My Life and Work&lt;/title&gt; <br />
&lt;author&gt; <br />
&lt;name&gt;Henry Ford&lt;/name&gt; <br />
&lt;address&gt; <br />
&lt;email&gt;mailto:henryford@hotmail.com&lt;/email&gt; <br />
&lt;web&gt;http://www.henryford.com&lt;/web&gt; <br />
&lt;/address&gt; <br />
&lt;/author&gt; <br />
&lt;/e:Book&gt;</p>
</blockquote>
<p>如果添加一个限制，任意两个人都不会有相同的地址，并且地址可以是街道或Email地址，一本书可以有两个作者，编码如下：</p>
<blockquote>
<p>&lt;e:Book&gt; <br />
&lt;title&gt;My Life and Work&lt;/title&gt; <br />
&lt;firstauthor href="#Person-1"/&gt; <br />
&lt;secondauthor href="#Person-2"/&gt; <br />
&lt;/e:Book&gt; <br />
&lt;e:Person id="Person-1"&gt; <br />
&lt;name&gt;Henry Ford&lt;/name&gt; <br />
&lt;address xsi:type="m:Electronic-address"&gt; <br />
&lt;email&gt;mailto:henryford@hotmail.com&lt;/email&gt; <br />
&lt;web&gt;http://www.henryford.com&lt;/web&gt; <br />
&lt;/address&gt; <br />
&lt;/e:Person&gt; <br />
&lt;e:Person id="Person-2"&gt; <br />
&lt;name&gt;Samuel Crowther&lt;/name&gt; <br />
&lt;address xsi:type="n:Street-address"&gt; <br />
&lt;street&gt;Martin Luther King Rd&lt;/street&gt; <br />
&lt;city&gt;Raleigh&lt;/city&gt; <br />
&lt;state&gt;North Carolina&lt;/state&gt; <br />
&lt;/address&gt; <br />
&lt;/e:Person&gt;</p>
</blockquote>
<p>序列化可以包含对不在同一个资源的值的引用：</p>
<blockquote>
<p>&lt;e:Book&gt; <br />
&lt;title&gt;Paradise Lost&lt;/title&gt; <br />
&lt;firstauthor href="http://www.dartmouth.edu/~milton/"/&gt; <br />
&lt;/e:Book&gt;</p>
</blockquote>
<p>以下是描述上面结构的schema片断：</p>
<blockquote>
<p>&lt;element name="Book" type="tns:Book"/&gt; <br />
&lt;complexType name="Book"&gt; <br />
&lt;!-- Either the following group must occur or else the <br />
href attribute must appear, but not both. --&gt; <br />
&lt;sequence minOccurs="0" maxOccurs="1"&gt; <br />
&lt;element name="title" type="xsd:string"/&gt; <br />
&lt;element name="firstauthor" type="tns:Person"/&gt; <br />
&lt;element name="secondauthor" type="tns:Person"/&gt; <br />
&lt;/sequence&gt; <br />
&lt;attribute name="href" type="uriReference"/&gt; <br />
&lt;attribute name="id" type="ID"/&gt; <br />
&lt;anyAttribute namespace="##other"/&gt; <br />
&lt;/complexType&gt; <br />
&lt;element name="Person" base="tns:Person"/&gt; <br />
&lt;complexType name="Person"&gt; <br />
&lt;!-- Either the following group must occur or else the <br />
href attribute must appear, but not both. --&gt; <br />
&lt;sequence minOccurs="0" maxOccurs="1"&gt; <br />
&lt;element name="name" type="xsd:string"/&gt; <br />
&lt;element name="address" type="tns:Address"/&gt; <br />
&lt;/sequence&gt; <br />
&lt;attribute name="href" type="uriReference"/&gt; <br />
&lt;attribute name="id" type="ID"/&gt; <br />
&lt;anyAttribute namespace="##other"/&gt; <br />
&lt;/complexType&gt; <br />
&lt;element name="Address" base="tns:Address"/&gt; <br />
&lt;complexType name="Address"&gt; <br />
&lt;!-- Either the following group must occur or else the <br />
href attribute must appear, but not both. --&gt; <br />
&lt;sequence minOccurs="0" maxOccurs="1"&gt; <br />
&lt;element name="street" type="xsd:string"/&gt; <br />
&lt;element name="city" type="xsd:string"/&gt; <br />
&lt;element name="state" type="xsd:string"/&gt; <br />
&lt;/sequence&gt; <br />
&lt;attribute name="href" type="uriReference"/&gt; <br />
&lt;attribute name="id" type="ID"/&gt; <br />
&lt;anyAttribute namespace="##other"/&gt; <br />
&lt;/complexType&gt;</p>
</blockquote>
<h4>5.4.2 数组</h4>
<p>SOAP数组定义为具有"SOAP-ENC:Array"类型或一个从"SOAP-ENC:Array"衍生的类型（参见规则8）。数组表示为元素值，对元素的名没有特别的约束（正如元素值并不约束它们所属的元素）。数组可以包含任意类型的元素，包括嵌套数组。可以创建新的类型（受SOAP-ENC:Array <br />
类型限制）来表示数组，如整数数组或某些用户定义的枚举。数组值表示为组成这个数组的项的元素的规则序列。在数组值中，元素名对于区分accesor并不重要。元素可以有任意的名。实际上，元素常常用它们在schema中暗示或确定的数组类型来命名元素。并且一般情况下对于复合值来说，如果数组中数组项的值是single-reference值，则这个数组项包含它的值，否则，该数组项通过"href"属性引用这个值。下面的例子是一个整型数组的schema片断：</p>
<blockquote>
<p>&lt;element name="myFavoriteNumbers" <br />
type="SOAP-ENC:Array"/&gt; <br />
&lt;myFavoriteNumbers <br />
SOAP-ENC:arrayType="xsd:int[2]"&gt; <br />
&lt;number&gt;3&lt;/number&gt; <br />
&lt;number&gt;4&lt;/number&gt; <br />
&lt;/myFavoriteNumbers&gt;</p>
</blockquote>
<p>在这个例子中，数组"myFavoriteNumbers"包括几个成员，每个成员是一个类型为SOAP-ENC:int的值。注意SOAP-ENC:Array允许不受限制的元素名，它们不传达任何类型信息，所以在使用时，或者它们有xsi:type属性，或者它们所属的元素有SOAP-ENC:arrayType属性。自然，由SOAP-ENC:Array衍生的类型可以声明局部元素，但这种情况下要包括类型信息。上面已经提到，SOAP-ENC schema包含了元素的声明，元素名与"XML Schema Part 2: Datatypes"规范[11]中的简单类型一致。其中包括了对"Array"的声明。于是，我们可以这样写：</p>
<blockquote>
<p>&lt;SOAP-ENC:Array SOAP-ENC:arrayType="xsd:int[2]"&gt; <br />
&lt;SOAP-ENC:int&gt;3&lt;/SOAP-ENC:int&gt; <br />
&lt;SOAP-ENC:int&gt;4&lt;/SOAP-ENC:int&gt; <br />
&lt;/SOAP-ENC:Array&gt;</p>
</blockquote>
<p>数组可以包含特定arrayType的任意子类型的实例。即，数组成员可以是arryType属性值指定的类型的任意子类型，这个类型对于arrayType属性中指定的类型来说是可替换的（根据schema中的替换规则）。例如，一个整型数组可以包含从整型衍生的任意类型（如"int"或任意用户定义的从整型衍生的类型）。同样，一个"address"数组可能包含一个address的受限类型或扩展类型如"internationalAddress"。因为提供的SOAP-ENC:Array类型允许任意类型的成员，所以可以包含任意类型的混合除非使用arrayType属性加以特别的限制。在实例中，可以使用xsi:type指定成员元素的类型，或通过schema中成员元素的声明来指定。下面是两个例子。</p>
<blockquote>
<p>&lt;SOAP-ENC:Array SOAP-ENC:arrayType="SOAP-ENC:ur-type[4]"&gt; <br />
&lt;thing xsi:type="xsd:int"&gt;12345&lt;/thing&gt; <br />
&lt;thing xsi:type="xsd:decimal"&gt;6.789&lt;/thing&gt; <br />
&lt;thing xsi:type="xsd:string"&gt; <br />
Of Mans First Disobedience, and the Fruit <br />
Of that Forbidden Tree, whose mortal tast <br />
Brought Death into the World, and all our woe, <br />
&lt;/thing&gt; <br />
&lt;thing xsi:type="xsd:uriReference"&gt; http://www.dartmouth.edu/~milton/reading_room/ &lt;/thing&gt; <br />
&lt;/SOAP-ENC:Array&gt; <br />
&lt;SOAP-ENC:Array SOAP-ENC:arrayType="SOAP-ENC:ur-type[4]"&gt; <br />
&lt;SOAP-ENC:int&gt;12345&lt;/SOAP-ENC:int&gt; <br />
&lt;SOAP-ENC:decimal&gt;6.789&lt;/SOAP-ENC:decimal&gt; <br />
&lt;xsd:string&gt; <br />
Of Mans First Disobedience, and the Fruit <br />
Of that Forbidden Tree, whose mortal tast <br />
Brought Death into the World, and all our woe, <br />
&lt;/xsd:string&gt; <br />
&lt;SOAP-ENC:uriReference&gt; http://www.dartmouth.edu/~milton/reading_room/ &lt;/SOAP-ENC:uriReference &gt; <br />
&lt;/SOAP-ENC:Array&gt;</p>
</blockquote>
<p>数组值可以是结构或其它复合值。例如"xyz:Order"结构数组：</p>
<blockquote>
<p>&lt;SOAP-ENC:Array SOAP-ENC:arrayType="xyz:Order[2]"&gt; <br />
&lt;Order&gt; <br />
&lt;Product&gt;Apple&lt;/Product&gt; <br />
&lt;Price&gt;1.56&lt;/Price&gt; <br />
&lt;/Order&gt; <br />
&lt;Order&gt; <br />
&lt;Product&gt;Peach&lt;/Product&gt; <br />
&lt;Price&gt;1.48&lt;/Price&gt; <br />
&lt;/Order&gt; <br />
&lt;/SOAP-ENC:Array&gt;</p>
</blockquote>
<p>数组成员值也可以是数组。下例是两个字符串数组组成的数组：</p>
<blockquote>
<p>&lt;SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[][2]"&gt; <br />
&lt;item href="#array-1"/&gt; <br />
&lt;item href="#array-2"/&gt; <br />
&lt;/SOAP-ENC:Array&gt; <br />
&lt;SOAP-ENC:Array id="array-1" SOAP-ENC:arrayType="xsd:string[2]"&gt; <br />
&lt;item&gt;r1c1&lt;/item&gt; <br />
&lt;item&gt;r1c2&lt;/item&gt; <br />
&lt;item&gt;r1c3&lt;/item&gt; <br />
&lt;/SOAP-ENC:Array&gt; <br />
&lt;SOAP-ENC:Array id="array-2" SOAP-ENC:arrayType="xsd:string[2]"&gt; <br />
&lt;item&gt;r2c1&lt;/item&gt; <br />
&lt;item&gt;r2c2&lt;/item&gt; <br />
&lt;/SOAP-ENC:Array&gt;</p>
</blockquote>
<p>包含数组的元素无需命名为"SOAP-ENC:Array"。它可以有任意的名，只要元素的类型是SOAP-ENC:Array或由之衍生的类型。例如，下面是一个schema片断和与之一致的数组实例。</p>
<blockquote>
<p>&lt;simpleType name="phoneNumber" base="string"/&gt; <br />
&lt;element name="ArrayOfPhoneNumbers"&gt; <br />
&lt;complexType base="SOAP-ENC:Array"&gt; <br />
&lt;element name="phoneNumber" type="tns:phoneNumber" maxOccurs="unbounded" /&gt; <br />
&lt;/complexType&gt; <br />
&lt;anyAttribute/&gt; <br />
&lt;/element&gt; <br />
&lt;xyz:ArrayOfPhoneNumbers SOAP-ENC:arrayType="xyz:phoneNumber[2]"&gt; <br />
&lt;phoneNumber&gt;206-555-1212&lt;/phoneNumber&gt; <br />
&lt;phoneNumber&gt;1-888-123-4567&lt;/phoneNumber&gt; <br />
&lt;/xyz:ArrayOfPhoneNumbers&gt;</p>
</blockquote>
<p>数组可能是多维的。在这种情况下，在arrayType属性的asize部分将不止有一个值：</p>
<blockquote>
<p>&lt;SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[2,3]"&gt; <br />
&lt;item&gt;r1c1&lt;/item&gt; <br />
&lt;item&gt;r1c2&lt;/item&gt; <br />
&lt;item&gt;r1c3&lt;/item&gt; <br />
&lt;item&gt;r2c1&lt;/item&gt; <br />
&lt;item&gt;r2c2&lt;/item&gt; <br />
&lt;item&gt;r2c3&lt;/item&gt; <br />
&lt;/SOAP-ENC:Array&gt;</p>
</blockquote>
<p>虽然上面的例子把数组编码为独立的元素，但元素值也可以是嵌入形式，而且若元素值是single reference时，必须编码为嵌入形式。下例是一个schema片断，电话号码数组嵌入到一个类型为"Person"的结构中，并且通过accessor "phone-numbers"访问它：</p>
<blockquote>
<p>&lt;simpleType name="phoneNumber" base="string"/&gt; <br />
&lt;element name="ArrayOfPhoneNumbers"&gt; <br />
&lt;complexType base="SOAP-ENC:Array"&gt; <br />
&lt;element name="phoneNumber" type="tns:phoneNumber" maxOccurs="unbounded"/&gt; <br />
&lt;/complexType&gt; <br />
&lt;anyAttribute/&gt; <br />
&lt;/element&gt; <br />
&lt;element name="Person"&gt; <br />
&lt;complexType&gt; <br />
&lt;element name="name" type="string"/&gt; <br />
&lt;element name="phoneNumbers" type="tns:ArrayOfPhoneNumbers"/&gt; <br />
&lt;/complexType&gt; <br />
&lt;/element&gt; <br />
&lt;xyz:Person&gt; <br />
&lt;name&gt;John Hancock&lt;/name&gt; <br />
&lt;phoneNumbers SOAP-ENC:arrayType="xyz:phoneNumber[2]"&gt; <br />
&lt;phoneNumber&gt;206-555-1212&lt;/phoneNumber&gt; <br />
&lt;phoneNumber&gt;1-888-123-4567&lt;/phoneNumber&gt; <br />
&lt;/phoneNumbers&gt; <br />
&lt;/xyz:Person&gt;</p>
</blockquote>
<p>下面的例子中，数组值为single-reference，被编码为嵌入元素，包含它的元素名即为入口名：</p>
<blockquote>
<p>&lt;xyz:PurchaseOrder&gt; <br />
&lt;CustomerName&gt;Henry Ford&lt;/CustomerName&gt; <br />
&lt;ShipTo&gt; <br />
&lt;Street&gt;5th Ave&lt;/Street&gt; <br />
&lt;City&gt;New York&lt;/City&gt; <br />
&lt;State&gt;NY&lt;/State&gt; <br />
&lt;Zip&gt;10010&lt;/Zip&gt; <br />
&lt;/ShipTo&gt; <br />
&lt;PurchaseLineItems SOAP-ENC:arrayType="Order[2]"&gt; <br />
&lt;Order&gt; <br />
&lt;Product&gt;Apple&lt;/Product&gt; <br />
&lt;Price&gt;1.56&lt;/Price&gt; <br />
&lt;/Order&gt; <br />
&lt;Order&gt; <br />
&lt;Product&gt;Peach&lt;/Product&gt; <br />
&lt;Price&gt;1.48&lt;/Price&gt; <br />
&lt;/Order&gt; <br />
&lt;/PurchaseLineItems&gt; <br />
&lt;/xyz:PurchaseOrder&gt;</p>
</blockquote>
<h4>5.4.2.1 部分储值（partially transmitted）数组</h4>
<p>SOAP提供了对部分储值（partially transmitted）数组的支持，如某些上下文中的可变数组。一个partially transmitted 数组由一个"SOAP-ENC:offset"属性（从第一个transmitted的元素开始的偏移量，基于0）指示。如果省略，偏移量取0。下面的例子中数组的大小为5，但只有从0起，第三和第四个元素被储值。</p>
<blockquote>
<p>&lt;SOAP-ENC:Array ;SOAP-ENC:arrayType="xsd:string[5]" ;SOAP-ENC:offset="[2]"&gt; <br />
&lt;item&gt;The third element&lt;/item&gt; <br />
&lt;item&gt;The fourth element&lt;/item&gt; <br />
&lt;/SOAP-ENC:Array&gt;</p>
</blockquote>
<h4>5.4.2.2 稀疏数组Sparse Arrays</h4>
<p>SOAP提供了对稀疏数组的支持。每个表示成员值的元素包含一个"SOAP-ENC:position"属性，用来指示它在数组中的位置。下例是两维字符串稀疏数组的例子，数组大小是4，但只用到第2个。</p>
<blockquote>
<p>&lt;SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[,][4]"&gt; <br />
&lt;SOAP-ENC:Array href="#array-1" SOAP-ENC:position="[2]"/&gt; <br />
&lt;/SOAP-ENC:Array&gt; <br />
&lt;SOAP-ENC:Array id="array-1" SOAP-ENC:arrayType="xsd:string[10,10]"&gt; <br />
&lt;item SOAP-ENC:position="[2,2]"&gt;Third row, third col&lt;/item&gt; <br />
&lt;item SOAP-ENC:position="[7,2]"&gt;Eighth row, third col&lt;/item&gt; <br />
&lt;/SOAP-ENC:Array&gt;</p>
</blockquote>
<p>如果对array-1的引用仅发生在数组内部，上例也可以编码如下：</p>
<blockquote>
<p>&lt;SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[,][4]"&gt; <br />
&lt;SOAP-ENC:Array SOAP-ENC:position="[2]" SOAP-ENC:arrayType="xsd:string[10, 10]&gt; <br />
&lt;item SOAP-ENC:position="[2,2]"&gt;Third row, third col&lt;/item&gt; <br />
&lt;item SOAP-ENC:position="[7,2]"&gt;Eighth row, third col&lt;/item&gt; <br />
&lt;/SOAP-ENC:Array&gt; <br />
&lt;/SOAP-ENC:Array&gt;</p>
</blockquote>
<h4>5.4.3 一般复合类型</h4>
<p>在这里提到的编码规则不仅仅限于accessor名已知的情况，如果accessor名是运行环境下实时获得的，编码规则同样适用，也就是说accessor编码成一个元素名与accessor名匹配的元素，同时accessor可能包含或者引用该元素的值。如果accessor包含类型不能事先确定的值，它必须包含一个合适的属性xsi:type 。类似地，上述引用的规则已经足够用于复合类型的序列化，这些复合类型可能包含用名区分的accessors（结构）和用名及序数位置区分的accessors。（可能包含重复的accessor） 实际上这并不要求任何schema模式包含这些类型，但更为准确的说法是：一个类型模型（type-model）schema如果有这些类型，就可以构造一个符合XML句法规则的schema和XML文档实例。</p>
<blockquote>
<p>&lt;xyz:PurchaseOrder&gt; <br />
&lt;CustomerName&gt;Henry Ford&lt;/CustomerName&gt; <br />
&lt;ShipTo&gt; <br />
&lt;Street&gt;5th Ave&lt;/Street&gt; <br />
&lt;City&gt;New York&lt;/City&gt; <br />
&lt;State&gt;NY&lt;/State&gt; <br />
&lt;Zip&gt;10010&lt;/Zip&gt; <br />
&lt;/ShipTo&gt; <br />
&lt;PurchaseLineItems&gt; <br />
&lt;Order&gt; <br />
&lt;Product&gt;Apple&lt;/Product&gt; <br />
&lt;Price&gt;1.56&lt;/Price&gt; <br />
&lt;/Order&gt; <br />
&lt;Order&gt; <br />
&lt;Product&gt;Peach&lt;/Product&gt; <br />
&lt;Price&gt;1.48&lt;/Price&gt; <br />
&lt;/Order&gt; <br />
&lt;/PurchaseLineItems&gt; <br />
&lt;/xyz:PurchaseOrder&gt;</p>
</blockquote>
<p>类似地，将一个结构上类似数组但实际上不是一个 SOAP-ENC:Array类型或者 SOAP-ENC:Array子类型的复合值序列化同样是允许的，例如：</p>
<blockquote>
<p>&lt;PurchaseLineItems&gt; <br />
&lt;Order&gt; <br />
&lt;Product&gt;Apple&lt;/Product&gt; <br />
&lt;Price&gt;1.56&lt;/Price&gt; <br />
&lt;/Order&gt; <br />
&lt;Order&gt; <br />
&lt;Product&gt;Peach&lt;/Product&gt; <br />
&lt;Price&gt;1.48&lt;/Price&gt; <br />
&lt;/Order&gt; <br />
&lt;/PurchaseLineItems&gt;</p>
</blockquote>
<h4>5.5 缺省值</h4>
<p>省略accessor元素意味着或者有一个缺省值或者值不知道。具体细节依靠这个accessor，方法和上下文。例如，对于多态accessor，省略accessor一般意味着一个Null值。同样，省略布尔accessor一般意味着False值或者值不知道，省略数字accessor一般意味着值为零或者值不知道。</p>
<h4>5.6 SOAP root属性</h4>
<p>SOAP root 属性可用于标记一个序列化root，从而一个对象可以反序列化（deserialized），而实际上该root并不是真正的对象root。这个属性有两个可选值"1" or "0"。对象真正的roots属性值为&#8220;1&#8221; ，序列化root但不是真正的root属性值也为&#8220;1&#8221;，元素如果要显式地指定不能为序列化root，只需将该属性设置为&#8220;0&#8221; SOAP root属性可以出现在SOAP头和SOAP体元素的任意子元素中。（译者注：SOAP root属性为0的元素不是一个独立的实体，外部的应用不能访问到该元素，但该元素可以被SOAP文档本身的其它元素访问到）SOAP root属性可以出现在SOAP头和SOAP体元素的任意子元素中。这个属性没有缺省值。</p>
<h4>6. 在HTTP中使用SOAP</h4>
<p>这一节讲述了如何在HTTP中使用SOAP。把SOAP绑定到HTTP，无论使用或不用HTTP扩展框架，都有很大的好处：在利用SOAP的形式化和灵活性的同时，使用HTTP种种丰富的特性。在HTTP中携带SOAP消息，并不意味着SOAP改写了HTTP已有的语义，而是将构建在HTTP之上SOAP语义自然地对应到HTTP语义。SOAP自然地遵循HTTP的请求/应答消息模型使得SOAP的请求和应答参数可以包含在HTTP请求和应答中。注意，SOAP的中间节点与HTTP的中间节点并不等同，即，不要期望一个根据HTTP连接头中的域寻址到的HTTP中间节点能够检查或处理HTTP请求中的SOAP消息。 <br />
在HTTP消息中包含SOAP实体时，按照RFC2376[3] HTTP应用程序必须使用媒体类型 "text/xml"。</p>
<h4>6.1 SOAP HTTP请求</h4>
<p>虽然SOAP可能与各种HTTP请求方式相结合，但是绑定仅定义了在HTTP POST请求中包含SOAP消息。（第7节中描述了如何在RPC中使用SOAP，第6.3节描述了如何使用HTTP扩展框架）</p>
<h4>6.1.1 HTTP头中SOAPAction域</h4>
<p>一个HTTP请求头中的SOAPAction域用来指出这是一个SOAP HTTP请求，它的值是所要的URI。在格式、URI的特性和可解析性上没有任何限制。当HTTP客户发出SOAP HTTP请求时必须使用在HTTP头中使用这个域。</p>
<p>soapaction = "SOAPAction" ":" [ &lt;"&gt; URI-reference &lt;"&gt; ] <br />
URI-reference = &lt;as defined in RFC 2396 [4]&gt;</p>
<p>HTTP头中SOAPAction域使服务器（如防火墙）能正确的过滤HTTP中SOAP请求消息。如果这个域的值是空字符串（""），表示SOAP消息的目标就是HTTP请求的URI。这个域没有值表示没有SOAP消息的目标的信息。例子:</p>
<blockquote>
<p>SOAPAction: "http://electrocommerce.org/abc#MyMessage" <br />
SOAPAction: "myapp.sdl" <br />
SOAPAction: "" <br />
SOAPAction:</p>
</blockquote>
<h4>6.2 SOAP HTTP应答</h4>
<p>SOAP HTTP遵循HTTP 中表示通信状态信息的HTTP状态码的语义。例如，2xx状态码表示这个包含了SOAP组件的客户请求已经被成功的收到，理解和接受。在处理请求时如果发生错误，SOAP HTTP服务器必须发出应答HTTP 500 "Internal Server Error"，并在这个应答中包含一个SOAP Fault元素（见4.4节）表示这个SOAP处理错误。</p>
<h4>6.3 HTTP扩展框架</h4>
<p>一个SOAP消息可以与HTTP扩展框架 [6]一起使用以区分是否有SOAP HTTP请求和它的目标。是使用扩展框架或是普通的HTTP关系到通信各方的策略和能力。通过使用一个必需的扩展声明和"M-"HTTP方法名前缀，客户可以强制使用HTTP扩展框架。服务器可以使用HTTP状态码510 "Not Extended"强制使用HTTP扩展框架。也就是说，使用一个额外的来回，任何一方都可以发现另一方的策略并依照执行。用来表示SOAP使用了扩展框架的扩展标志符是：http://schemas.xmlsoap.org/soap/envelope/</p>
<h4>6.4 SOAP HTTP举例</h4>
<p>例3 使用POST的SOAP HTTP</p>
<blockquote>
<p>POST /StockQuote HTTP/1.1 <br />
Content-Type: text/xml; charset="utf-8" <br />
Content-Length: nnnn <br />
SOAPAction: "http://electrocommerce.org/abc#MyMessage" <br />
&lt;SOAP-ENV:Envelope... <br />
HTTP/1.1 200 OK <br />
Content-Type: text/xml; charset="utf-8" <br />
Content-Length: nnnn <br />
&lt;SOAP-ENV:Envelope... </p>
</blockquote>
<p>例4 使用扩展框架的SOAP HTTP</p>
<blockquote>
<p>M-POST /StockQuote HTTP/1.1 <br />
Man: "http://schemas.xmlsoap.org/soap/envelope/"; ns=NNNN <br />
Content-Type: text/xml; charset="utf-8" <br />
Content-Length: nnnn <br />
NNNN-SOAPAction: "http://electrocommerce.org/abc#MyMessage" <br />
&lt;SOAP-ENV:Envelope... <br />
HTTP/1.1 200 OK <br />
<br />
Ext: <br />
Content-Type: text/xml; charset="utf-8" <br />
Content-Length: nnnn <br />
&lt;SOAP-ENV:Envelope...</p>
</blockquote>
<h4>7. 在RPC中使用SOAP</h4>
<p>设计SOAP的目的之一就是利用XML的扩展性和灵活性来封装和交换RPC调用。这一节定义了远程过程调用和应答的统一表示形式。虽然可以预计到这种表示形式最可能被用于与第5节中定义的编码方式相结合，但也可能有其它的表示形式。SOAP的encodingStyle属性（见4.3.2节）可以用来表明方法调用和应答都使用这一节所指定的表示方式。在RPC中使用SOAP和SOAP协议绑定（见第6节）是紧密相关的。在使用HTTP作为绑定协议时，一个RPC调用自然地映射到一个HTTP请求，RPC应答同样映射到HTTP应答。但是，在RPC中使用SOAP并不限于绑定HTTP协议。 <br />
要进行方法调用，以下的信息是必需的：
<ul>
    <li>目标对象的URI
    <li>方法名
    <li>方法signature（可选）
    <li>方法的参数
    <li>头数据（可选） </li>
</ul>
<p>SOAP依靠协议绑定提供传送URI的机制。例如，对HTTP来说，请求的URI指出了调用的来源 。除了必须是一个合法的URI之外，SOAP对一个地址的格式没有任何限制。（更多URI的信息参见 [4]）</p>
<h4>7.1 RPC和SOAP体</h4>
<p>RPC方法调用和应答都包含在SOAP Body元素中（见4.3节），它们使用如下的表示形式：
<ul>
    <li>一个方法调用用一个结构表示
    <li>一个方法调用被看作一个单个的结构，每个[in]和[in/out]参数有一个accessor。结构的名和类型与方法相同。每个[in]和[in/out]参数都被看作一个accessor，这个accessor的名和类型与参数的名和类型相对应。它们的出现顺序和方法中定义的参数顺序相同。
    <li>一个方法应答用一个结构表示。
    <li>一个方法应答被看作一个单个的结构，返回值和每个[in]和[in/out]参数有一个accessor。第一个accessor是返回值，之后是参数accessor，参数accessor的出现顺序和方法中定义的参数顺序相同。每个参数accessor的名称和类型与参数的名称和类型相对应。返回值accessor的名称并不重要。同样，结构的名称也不重要，不过，通常在方法名称的后面加上字符串"Response"作为结构的名称。 </li>
</ul>
<p>方法错误使用SOAP Fault元素（见4.4节）表示。如果绑定的协议有额外的规则表示错误，则这些规则也必须要遵从。正如上面所述，方法调用和应答结构可以按照第5节中规则编码，或者用encodingStyle属性（见4.1.1节）指定编码方式。应用程序可以处理缺少参数的请求，但是可能返回一个错误。因为返回结果表示调用成功，错误表示调用失败，所以，在方法应答中同时包含返回结果和错误是错误的。</p>
<h4>7.2 RPC和SOAP头</h4>
<p>在RPC编码中，可能会有与方法请求有关但不是正规的方法signature的附加信息。如果这样，它必须作为SOAP头元素的子元素。使用这种头元素的一个例子是在消息中传递事务ID。由于事务ID不是方法signature的一部分，通常由底层的组件而不是应用程序代码控制，所以没有一种直接的方法在调用中传递这个必要的信息。通过在头中添加一个给定名字的条目，接收方的事务管理器就可以析取这个事务ID，而且不影响远程过程调用的代码。</p>
<h4>8. 安全性考虑</h4>
<p>这篇文档中没有涉及完整性和保密性，这些问题将在以后的版本中描述。</p>
<h4>9. 参考文献</h4>
<blockquote>
<p>[1] S. Bradner, "The Internet Standards Process -- Revision 3", RFC2026, Harvard University, October 1996 <br />
[2] S. Bradner, "Key words for use in RFCs to Indicate Requirement Levels", RFC 2119, Harvard University, March 1997 <br />
[3] E. Whitehead, M. Murata, "XML Media Types", RFC2376, UC Irvine, Fuji Xerox Info. Systems, July 1998 <br />
[4] T. Berners-Lee, R. Fielding, L. Masinter, "Uniform Resource Identifiers (URI): Generic Syntax", RFC 2396, MIT/LCS, U.C.Irvine, Xerox Corporation, A ugust 1998. <br />
[5] R. Fielding, J. Gettys, J. C. Mogul, H. Frystyk, T. Berners-Lee, "Hypert ext Transfer Protocol -- HTTP/1.1", RFC 2616, U.C. Irvine, DEC W3C/MIT, DEC,W3C/MIT, W3C/MIT, January 1997 <br />
[6] H. Nielsen, P. Leach, S. Lawrence, "An HTTP Extension Framework", RFC 2774, Microsoft, Microsoft, Agranat Systems <br />
[7] W3C Recommendation "The XML Specification"<br />
[8] W3C Recommendation "Namespaces in XML" <br />
[9] W3C Working Draft "XML Linking Language". This is work in progress. <br />
[10] W3C Working Draft "XML Schema Part 1: Structures". This is work in progress. <br />
[11] W3C Working Draft "XML Schema Part 2: Datatypes". This is work in progress. <br />
[12] Transfer Syntax NDR, in "DCE 1.1: Remote Procedure Call" <br />
[13] N. Freed, N. Borenstein, "Multipurpose Internet Mail Extensions (MIME)Part One: Format of Internet Message Bodies", RFC2045, Innosoft, First Virtu al, November 1996 </p>
</blockquote>
<h4>10。 附录</h4>
<p>A. SOAP封装举例</p>
<p>A.1 请求编码举例</p>
<p>例5 类似于例1，但有一个必要的头</p>
<blockquote>
<p>POST /StockQuote HTTP/1.1 <br />
Host: www.stockquoteserver.com <br />
Content-Type: text/xml; charset="utf-8" <br />
Content-Length: nnnn <br />
SOAPAction: "Some-URI" <br />
&lt;SOAP-ENV:Envelope <br />
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" <br />
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/&gt; <br />
&lt;SOAP-ENV:Header&gt; <br />
&lt;t:Transaction <br />
xmlns:t="some-URI" <br />
SOAP-ENV:mustUnderstand="1"&gt; <br />
5 <br />
&lt;/t:Transaction&gt; <br />
&lt;/SOAP-ENV:Header&gt; <br />
&lt;SOAP-ENV:Body&gt; <br />
&lt;m:GetLastTradePrice xmlns:m="Some-URI"&gt; <br />
&lt;symbol&gt;DEF&lt;/symbol&gt; <br />
&lt;/m:GetLastTradePrice&gt; <br />
&lt;/SOAP-ENV:Body&gt; <br />
&lt;/SOAP-ENV:Envelope&gt;</p>
</blockquote>
<p>例6 类似于例1，但有多个请求参数</p>
<blockquote>
<p>POST /StockQuote HTTP/1.1 <br />
Host: www.stockquoteserver.com <br />
Content-Type: text/xml; charset="utf-8" <br />
Content-Length: nnnn <br />
SOAPAction: "Some-URI" <br />
&lt;SOAP-ENV:Envelope <br />
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" <br />
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/&gt; <br />
&lt;SOAP-ENV:Body&gt; <br />
&lt;m:GetLastTradePriceDetailed <br />
xmlns:m="Some-URI"&gt; <br />
&lt;Symbol&gt;DEF&lt;/Symbol&gt; <br />
&lt;Company&gt;DEF Corp&lt;/Company&gt; <br />
&lt;Price&gt;34.1&lt;/Price&gt; <br />
&lt;/m:GetLastTradePriceDetailed&gt; <br />
&lt;/SOAP-ENV:Body&gt; <br />
&lt;/SOAP-ENV:Envelope&gt;</p>
</blockquote>
<p>A.2 应答编码举例</p>
<p>例7 与例2类似，但有必要的头部</p>
<blockquote>
<p>HTTP/1.1 200 OK <br />
Content-Type: text/xml; charset="utf-8" <br />
Content-Length: nnnn <br />
&lt;SOAP-ENV:Envelope <br />
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" <br />
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/&gt; <br />
&lt;SOAP-ENV:Header&gt; <br />
&lt;t:Transaction xmlns:t="some-URI" xsi:type="xsd:int" mustUnderstand="1"&gt; 5 &lt;/t:Transaction&gt; <br />
&lt;/SOAP-ENV:Header&gt; <br />
&lt;SOAP-ENV:Body&gt; <br />
&lt;m:GetLastTradePriceResponse xmlns:m="Some-URI"&gt; <br />
&lt;Price&gt;34.5&lt;/Price&gt; <br />
&lt;/m:GetLastTradePriceResponse&gt; <br />
&lt;/SOAP-ENV:Body&gt; <br />
&lt;/SOAP-ENV:Envelope&gt;</p>
</blockquote>
<p>例8 与例2类似，但有一个结构</p>
<blockquote>
<p>HTTP/1.1 200 OK <br />
Content-Type: text/xml; charset="utf-8" <br />
Content-Length: nnnn <br />
&lt;SOAP-ENV:Envelope <br />
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" <br />
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/&gt; <br />
&lt;SOAP-ENV:Body&gt; <br />
&lt;m:GetLastTradePriceResponse <br />
xmlns:m="Some-URI"&gt; <br />
&lt;PriceAndVolume&gt; <br />
&lt;LastTradePrice&gt; 34.5 &lt;/LastTradePrice&gt; <br />
&lt;DayVolume&gt; 10000 &lt;/DayVolume&gt; <br />
&lt;/PriceAndVolume&gt; <br />
&lt;/m:GetLastTradePriceResponse&gt; <br />
&lt;/SOAP-ENV:Body&gt; <br />
&lt;/SOAP-ENV:Envelope&gt;</p>
</blockquote>
<p>例9 与例2类似，但处理必要的头出错</p>
<blockquote>
<p>HTTP/1.1 500 Internal Server Error <br />
Content-Type: text/xml; charset="utf-8" <br />
Content-Length: nnnn <br />
&lt;SOAP-ENV:Envelope <br />
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"&gt; <br />
&lt;SOAP-ENV:Body&gt; <br />
&lt;SOAP-ENV:Fault&gt; <br />
&lt;faultcode&gt;SOAP-ENV:MustUnderstand&lt;/faultcode&gt; <br />
&lt;faultstring&gt;SOAP Must Understand Error&lt;/faultstring&gt; <br />
&lt;/SOAP-ENV:Fault&gt; <br />
&lt;/SOAP-ENV:Body&gt; <br />
&lt;/SOAP-ENV:Envelope&gt;</p>
</blockquote>
<p>例10 与例2类似，但处理Body出错</p>
<blockquote>
<p>HTTP/1.1 500 Internal Server Error <br />
Content-Type: text/xml; charset="utf-8" <br />
Content-Length: nnnn <br />
&lt;SOAP-ENV:Envelope <br />
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"&gt; <br />
&lt;SOAP-ENV:Body&gt; <br />
&lt;SOAP-ENV:Fault&gt; <br />
&lt;faultcode&gt;SOAP-ENV:Server&lt;/faultcode&gt; <br />
&lt;faultstring&gt;Server Error&lt;/faultstring&gt; <br />
&lt;detail&gt; <br />
&lt;e:myfaultdetails xmlns:e="Some-URI"&gt; <br />
&lt;message&gt; <br />
My application didn't work <br />
&lt;/message&gt; <br />
&lt;errorcode&gt; 1001 &lt;/errorcode&gt; <br />
&lt;/e:myfaultdetails&gt; <br />
&lt;/detail&gt; <br />
&lt;/SOAP-ENV:Fault&gt; <br />
&lt;/SOAP-ENV:Body&gt; <br />
&lt;/SOAP-ENV:Envelope&gt; </p>
</blockquote>
<img src ="http://www.blogjava.net/libin2722/aggbug/159460.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/libin2722/" target="_blank">礼物</a> 2007-11-09 22:11 <a href="http://www.blogjava.net/libin2722/articles/159460.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SOAP净化有线协议（四）：简化客户程序</title><link>http://www.blogjava.net/libin2722/articles/159459.html</link><dc:creator>礼物</dc:creator><author>礼物</author><pubDate>Fri, 09 Nov 2007 14:07:00 GMT</pubDate><guid>http://www.blogjava.net/libin2722/articles/159459.html</guid><wfw:comment>http://www.blogjava.net/libin2722/comments/159459.html</wfw:comment><comments>http://www.blogjava.net/libin2722/articles/159459.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/libin2722/comments/commentRss/159459.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/libin2722/services/trackbacks/159459.html</trackback:ping><description><![CDATA[Java 2平台1.3版本为Java映像API（Reflection API）增加了一个极其实用的扩展：动态代理类。一个动态代理类就是一个实现了一系列运行时指定的接口的类。这个代理可以象它真正实现了这些接口一样使用。换句话说，可以直接在代理对象上调用任意接口的任意方法——当然，必须先进行必要的类型定型（casting）。由此，我们可以用动态代理类为一组接口创建一个类型安全的代理对象，且不必象使用编译时工具一样预先生成代理（有关动态代理类更详细的说明，请参见本文最后的参考资源）。 <br />
<br />
接下来我将介绍一个以动态代理类为基础的框架，这个框架使得SOAP（简单对象访问协议）客户程序的创建更加简单和直观。SOAP是一种用XML编码数据的有线协议。在本系列文章的第二篇、第三篇构造SOAP服务的过程中，我们发现客户程序的开发者必须多做许多原来不必做的工作。为帮助回忆，你可以看一下第二篇文章中的SOAP服务代码，看看和客户程序代码相比较时，服务程序的SOAP代码是多么微不足道。本系列文章前几篇所创建的简单SOAP服务显示出，基于SOAP的服务只包含无论用不用SOAP都必须提供的代码。服务程序的开发者要编写的额外代码很少，而客户程序开发者却有许多额外工作要做。本文介绍的类将把这些额外工作减到最少。 <br />
<br />
<strong>一、介绍SOAP代理类</strong> <br />
首先，我要给出如果客户程序使用了本文创建的框架，它将变成什么样子： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">package hello;<br />
            import soapproxy.*;<br />
            public class Client<br />
            {<br />
            public static void main(String[] args)<br />
            {<br />
            try<br />
            {<br />
            Class[] interfaces = new Class[] {hello.Hello.class};<br />
            Hello hello = (Hello)(Proxy.newInstance("urn:Hello",interfaces));
            <p>// 调用sayHelloTo方法<br />
            // 这个sayHelloTo方法需要一个字符串参数<br />
            System.out.println(hello.sayHelloTo("John"));</p>
            <p>// 调用sayHelloTo方法<br />
            // 这个sayHelloTo方法需要一个Name JavaBean参数<br />
            Name theName = new Name();<br />
            theName.setName("Mala");<br />
            System.out.println(hello.sayHelloTo(theName));<br />
            }<br />
            catch(Exception e)<br />
            {<br />
            e.printStackTrace();<br />
            }<br />
            }<br />
            }</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
也许是出于我的个人爱好，我认为上面的客户代码比第二篇和第三篇文章中的客户代码更好。如果你现在不能理解上面的代码，这很正常，但我想待到本文结束时你会理解的。 <br />
<br />
要理解客户程序的代码，你必须深入了解SOAP Proxy类，它在soapproxy包内，可以在Proxy.java内找到（参见本文最后的参考资源）。Proxy类有一个私有的构造函数，它意味着Proxy实例不能从Proxy之外创建；新建Proxy实例的唯一方法是通过静态的newInstance()方法。newInstance()方法有两个参数：SOAP服务的对象ID，以及一个数组，数组中包含一组该代理要实现的接口的名字。对象ID很简单，但这些接口名字是什么？从哪里去得到这些接口的名字？SOAP服务的开发者直接把服务上所有可被客户程序调用的方法堆在一起得到一个接口。相当简单，不是吗？ <br />
<br />
现在我们为HelloWorld服务定义一个接口。第二篇文章中，这个服务的最终版本有sayHelloTo()方法的两个重载版本：一个版本的参数是一个字符串，另一个版本的参数是一个Name JavaBean。这两个方法就可以构成一个接口，称为Hello，如下所示： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">package hello;<br />
            public interface Hello<br />
            {<br />
            public String sayHelloTo(String name);<br />
            public String sayHelloTo(Name name);<br />
            }</td>
        </tr>
    </tbody>
</table>
<br />
服务开发者决定要创建多少接口，以及为这些接口取什么样的名字。例如，你可以为HelloWorld服务创建两个接口，每一个接口包含一个方法。一般地，你应该避免创建方法数量大于七个的接口。另外，注意只把那些看来有必要放在一起的方法用一个接口组织起来。例如，如果HelloWorld服务还有一个返回定制的Good-Bye消息给调用者的sayByeTo()方法，设计两个独立的接口也许比较明智：一个接口用于sayHelloTo()方法，一个接口用于sayByeTo()方法。 <br />
<br />
现在我们有了定义HelloWorld服务和客户程序之间契约的接口，下面返回来看newInstance()方法。如前所述，newInstance()方法创建Proxy类的一个新实例。newInstance()方法可以创建新实例是因为它属于Proxy类，能够访问私有的构造函数。newInstance()方法为新创建的实例调用initialize()方法。initialize()值得关注，因为动态代理就是在这里创建和返回。initialize()的代码如下所示： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">private Object initialize(Class[] interfaces)<br />
            {<br />
            return(java.lang.reflect.Proxy.newProxyInstance(getClass().getClassLoader()<br />
            ,interfaces,this));<br />
            }</td>
        </tr>
    </tbody>
</table>
<br />
注意newProxyInstance()方法的应用。创建动态代理类实例的唯一办法是调用该类（即java.lang.reflect.Proxy类）静态的newProxyInstance()方法。java.lang.reflect.Proxy类为创建动态代理类提供了静态方法，而且它还是所有由这些方法创建的动态代理类的超类。换句话说，它不仅是一个创建动态代理类的工厂，而且它本身也是一个动态代理类！因此，在我们的例子中，SOAP代理不是动态代理；相反，这个动态代理实际上是newProxyInstance静态方法返回的java.lang.reflect.Proxy类的一个实例。从本文后面可以看到，这个动态代理实际上通过SOAP代理实现的invoke()方法完成它的所有工作。那么，这个动态代理如何建立和SOAP代理的联系呢？因为有一个对SOAP代理的引用传递给了newProxyInstance()方法。也许现在这听起来有点费解，但只要你分析一下invoke()方法，这一切就很明白了。 <br />
<br />
java.lang.reflect.Proxy类构造函数的第一个参数是一个类装载器实例，第二个参数是需要动态实现的接口的数组（它就是客户程序传递给newInstance()的数组），第三个参数是一个实现了java.lang.reflect.InvocationHandler接口的类的实例。因为SOAP Proxy类实现了InvocationHandler接口，所以第三个参数是代理实例本身（即this）。InvocationHandler接口有一个方法invoke()。当动态代理的动态实现的接口被调用时，Java运行时环境调用invoke()方法。因此，举例来说，当客户程序调用动态代理的Hello接口的sayHelloTo()方法时，Java运行时环境将调用SOAP代理的invoke()方法。 <br />
<br />
你可能已经发现，SOAP代理的newInstance()方法不返回SOAP代理的实例；相反，它返回newInsance()刚刚创建的动态代理，而动态代理动态地实现客户程序传入的接口数组。客户程序可以将这个返回的动态代理定型为传入newInstance()的任意接口类型，在动态代理上调用接口所定义的各个方法，就象动态代理真地实现了那些接口一样。 <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">.<br />
            .<br />
            try<br />
            {<br />
            Class[] interfaces = new Class[] {hello.Hello.class};<br />
            Hello hello = (Hello)(Proxy.newInstance("urn:Hello",interfaces));<br />
            // 调用参数为字符串的sayHelloTo方法<br />
            System.out.println(hello.sayHelloTo("John"));<br />
            // 调用参数为Name JavaBean的sayHelloTo方法<br />
            Name theName = new Name();<br />
            theName.setName("Mala");<br />
            System.out.println(hello.sayHelloTo(theName));<br />
            }<br />
            .<br />
            .</td>
        </tr>
    </tbody>
</table>
<br />
在上面的代码中，invoke()方法将被调用两次，每次调用sayHelloTo()方法时执行一次。现在我们来看看invoke()方法。简而言之，invoke()方法的工作正是第二篇文章中每一个客户程序必须手工完成的工作，其中包括：用合适的调用参数设置一个Call对象，定制的调用参数所需要的类型映射。由于SOAP代理中的invoke()方法担负了所有这些任务，客户程序释放了这份负担。 <br />
<br />
在invoke()方法接收到的三个参数中，我们只对后面两个感兴趣。第二个参数，即Method对象，给出了被调用方法的名字。记住，被调用方法的名字对应着一个SOAP服务导出的已知方法。服务的对象ID作为参数传递给newInstance()方法，所以invoke()方法已经拥有该对象ID。invoke()方法利用这些信息，按照如下方式设置Call对象： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Call call = new Call();<br />
            call.setTargetObjectURI(urn);<br />
            call.setMethodName(m.getName());<br />
            call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);</td>
        </tr>
    </tbody>
</table>
<br />
现在要做的是为远程服务调用设置参数。为此，我们要用到invoke()方法的第三个参数：传入动态代理上被调用方法的一个参数数组。数组中索引为0的参数是方法调用中最左边的参数，索引为1的参数是方法的第二个参数，依此类推。举例来说，如果客户程序调用了sayHelloTo(String name)方法，那么参数数组就是包含一个字符串的数组。invoke()方法处理该数组的每一个元素，创建一个由Parameter对象构成的向量（Vector）（正如第二篇文章中客户程序所做的那样）： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">java.util.Vector params = new java.util.Vector();<br />
            for( int i=0; i&amp;lt;args.length; i++ )<br />
            {<br />
            if( isSimple(args[i]) || isSimpleArray(args[i]) )<br />
            {<br />
            params.add(new Parameter(_paramName+(i+1),<br />
            args[i].getClass(),args[i],null));<br />
            }<br />
            else if( isVector(args[i]) )<br />
            {<br />
            addMapping((java.util.Vector)args[i]);<br />
            params.add(new<br />
            Parameter(_paramName+(i+1),args[i].getClass(),args[i],null));<br />
            }<br />
            // 如果这个数组的元素不属于Java基本数据类型<br />
            // 则假定这是一个JavaBean的数组<br />
            else if( isArray(args[i]) )<br />
            {<br />
            if( smr == null )<br />
            smr = new SOAPMappingRegistry();<br />
            if( beanSer == null )<br />
            beanSer = new BeanSerializer();
            <p>ArraySerializer arraySer = new ArraySerializer();<br />
            smr.mapTypes(Constants.NS_URI_SOAP_ENC,<br />
            null, null, beanSer, beanSer);<br />
            smr.mapTypes(Constants.NS_URI_SOAP_ENC,<br />
            null,args[i].getClass(), arraySer, arraySer);<br />
            params.add(new Parameter(_paramName+(i+1),<br />
            args[i].getClass(),args[i],null));<br />
            }<br />
            // 假定这是一个Bean<br />
            else<br />
            {<br />
            if( smr == null )<br />
            smr = new SOAPMappingRegistry();<br />
            if( beanSer == null )<br />
            beanSer = new BeanSerializer();<br />
            String qnamePart = args[i].getClass().getName();<br />
            smr.mapTypes(Constants.NS_URI_SOAP_ENC,<br />
            new QName(urn, qnamePart),args[i].getClass(), beanSer,<br />
            beanSer);<br />
            params.add(new Parameter(_paramName+(i+1),args[i].getClass(),args[i],null));<br />
            }<br />
            }</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
invoke()方法用到了许多私有的辅助方法，比如用isSimple()来确定参数的类型。如果参数是一个JavaBean或者一个数组，那么，程序必须设置一个定制的SOAP映射注册项，并通过setSOAPMappingRegistry()方法对Call对象作相应的设置（参见第二篇文章）。SOAP代理假定，当出现JavaBean时，SOAP服务用到的所有JavaBean按照如下方式映射：NameSpace URI设置成对象ID，Local Part设置成JavaBean完整的类名。我们部署HelloWorld服务时正是按照这个要求进行，所以一切都不存在问题。 <br />
<br />
invoke()方法的剩余部分相当简单：设置Call对象参数，设置定制SOAP映射注册项（如果有必要的话），发出调用，接收方法调用的返回值。如下所示： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>if( params.size() != 0 )<br />
            call.setParams(params);<br />
            if( smr != null )<br />
            call.setSOAPMappingRegistry(smr);<br />
            // 发出调用<br />
            Response resp = call.invoke(serverURL, "");<br />
            if( !resp.generatedFault() )<br />
            {<br />
            Parameter ret = resp.getReturnValue();<br />
            return(ret.getValue());<br />
            }<br />
            else<br />
            {<br />
            Fault fault = resp.getFault();<br />
            throw new<br />
            SOAPException(fault.getFaultCode(),fault.getFaultString());<br />
            }</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
<strong>二、HelloWorld服务</strong> <br />
下面是HelloWorld服务的完整代码。有似曾相识的感觉吗？ <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>package hello;<br />
            public class HelloServer<br />
            {<br />
            public String sayHelloTo(String name)<br />
            {<br />
            System.out.println("sayHelloTo(String name)");<br />
            return "Hello " + name + ", How are you doing?";<br />
            }<br />
            public String sayHelloTo(Name theName)<br />
            {<br />
            System.out.println("sayHelloTo(Name theName)");<br />
            return "Hello " + theName.getName() + ", How are you doing?";<br />
            }<br />
            }</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
回忆一下，Name是一个简单的JavaBean，代码如下： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>package hello;</p>
            <p>public class Name<br />
            {<br />
            private String name;<br />
            public String getName()<br />
            {<br />
            return name;<br />
            }<br />
            public void setName(String name)<br />
            {<br />
            this.name = name;<br />
            }<br />
            }</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
事实上，这里服务的代码与第二篇文章中的服务程序代码完全一样。对于服务开发者来说，唯一增加的工作是创建Java接口。部署服务的方法也和第二篇文章中讨论的完全一样，所以这里我不再介绍。相同的地方还不止如此，编译和运行客户程序的方法也和第二篇文章介绍的一样。为什么有这么多相同之处呢？因为我们创建的代理是一个非插入式的框架，它不会修改和干涉任何Apache SOAP部件的内部工作——无论是客户端还是服务端。 <br />
<br />
<strong>三、其他说明</strong> <br />
本文讨论的SOAP代理（可以从文章后面下载）支持以下参数类型： <br />
<br />
⑴ 下面的Java基本数据类型及其对应的对象形式。 <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>boolean, Boolean,<br />
            double, Double,<br />
            float, Float,<br />
            long, Long,<br />
            int, Integer,<br />
            short, Short,<br />
            byte, Byte</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
注：服务器端总是接收基本数据类型。 <br />
<br />
⑵ 任何JavaBean <br />
<br />
注： <br />
<br />
<li>该JavaBean不能包含其他JavaBean。
<li>如果数组或向量包含除字符串或1列出数据类型之外的类型，则JavaBean不能包含这类数组或向量。
<ul></ul>
    </descript>⑶ 下面的类：String, Vector <br />
    <br />
    注： <br />
    <br />
    <li>Vector可以包含1、2列出的所有类型和字符串。
    <li>服务器端把Vector作为一个对象的数组接收。
    <ul></ul>
        </descript>⑷ 数组。数组元素可以是在1、2中列出的所有类型和字符串（上面已注明的除外）。 <br />
        <br />
        <strong>■ 结束语</strong> <br />
        在这个四篇文章构成的系列中，我不仅介绍了SOAP的基础知识，而且介绍了SOAP 1.1标准的一个优秀的实现：Apache SOAP。在本文中，我提供了一个以动态代理类为基础的框架，这个框架极大地简化了使用Apache SOAP的客户程序开发者的工作。 <br />
        <br />
        我深切地感到SOAP有着美好的前景，至少有两个理由使我这么认为：首先，SOAP以一些开放的标准为基础，比如XML。这使得无论是Microsoft，还是反Microsoft的企业，都广泛地接受了SOAP。对于开发者来说，这无疑是一个天大的好消息。第二，SOAP正在成为其他许多标准的基础，比如UDDI（Universal Description，Discovery，and Integration）。许多人认为，Web服务代表着下一代的Web应用开发，而SOAP和UDDI都是Web服务的关键组成部分。 <br />
        <br />
        <strong>■ 参考资源</strong> <br />
        <li>下载本文的完整代码：<a href="http://www.ccidnet.com/tech/focus/java_little_t/JavaAndSOAP4_code.zip">JavaAndSOAP4_code.zip</a><br />
        <li>W3C的SOAP 1.1规范：<br />
        <li><a href="http://www.w3.org/TR/SOAP/" target="_blank">http://www.w3.org/TR/SOAP/</a>
        <li>有关动态代理类的更多信息：<br />
        <li><a href="http://java.sun.com/j2se/1.3/docs/guide/reflection/proxy.html" target="_blank">http://java.sun.com/j2se/1.3/docs/guide/reflection/proxy.html</a>
        <li>关于IBM SOAP工程的更多信息：<br />
        <li><a href="http://www.alphaworks.ibm.com/tech/soap4j" target="_blank">http://www.alphaworks.ibm.com/tech/soap4j</a>
        <li>下载Apache SOAP：<br />
        <li><a href="http://xml.apache.org/dist/soap/" target="_blank">http://xml.apache.org/dist/soap/</a> </li>
<img src ="http://www.blogjava.net/libin2722/aggbug/159459.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/libin2722/" target="_blank">礼物</a> 2007-11-09 22:07 <a href="http://www.blogjava.net/libin2722/articles/159459.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SOAP净化有线协议（三）：用脚本语言编写服务</title><link>http://www.blogjava.net/libin2722/articles/159458.html</link><dc:creator>礼物</dc:creator><author>礼物</author><pubDate>Fri, 09 Nov 2007 14:05:00 GMT</pubDate><guid>http://www.blogjava.net/libin2722/articles/159458.html</guid><wfw:comment>http://www.blogjava.net/libin2722/comments/159458.html</wfw:comment><comments>http://www.blogjava.net/libin2722/articles/159458.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/libin2722/comments/commentRss/159458.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/libin2722/services/trackbacks/159458.html</trackback:ping><description><![CDATA[你是一个渴望永远站在技术最前沿的Java开发者吗？软件产业风云变幻，你渴望把握Web的未来，更重要的是，如何把自己数年的Java经验发挥到极致。要寻找这些问题的答案，你不必走得太远，答案就在于SOAP。 <br />
<br />
SOAP（简单对象访问协议）是一种利用XML编码数据的有线协议，它为Java的平台无关性、可移植性带来了更高层次的协同操作能力。在这个关于SOAP的系列文章的第二篇中，我介绍了Apache SOAP。作为SOAP规范的实现之一，Apache SOAP简化了SOAP应用的构造。我们曾经用Apache SOAP构造了两个简单的HelloWorld服务，以及调用这些服务的sayHelloTo()方法的客户程序。我们看到，虽然创建SOAP服务相当简单，但客户程序必须完成许多额外的工作，例如它必须设置Call对象，调用Call对象的invoke()方法，然后分析作为调用结果返回的Response对象。 <br />
<br />
其实，用Apache SOAP创建SOAP服务的过程还可以变得更简单，唯一的前提是：你必须懂得规定的几种脚本语言之一。对我来说——以及对大多数Java开发者来说，幸运的是，这些脚本语言中包含了JavaScript。确实不错，在Apache SOAP中，你可以用JavaScript创建SOAP服务。本文要介绍的就是用JavaScript创建SOAP服务的具体做法。 <br />
<br />
<strong>一、重新构造Apache SOAP</strong> <br />
Apache SOAP的脚本支持建立在Bean Scripting Framework（BSF，Bean脚本框架）的基础之上。BSF原先由IBM开发，现在作为一个源代码开放的工程发行，它使得Java程序能够运行用其他语言编写的脚本，也使得其他脚本语言能够使用已有的Java类。Apache SOAP利用了BSF的前一种能力。从Apache网站下载的标准二进制版本不支持脚本。具体地说，soap.jar不包含org.apache.soap.server.InvokeBSF类，而这个类是Apache SOAP和BSF的结合点和接口。Apache SOAP的开发者知道，并非每一个使用SOAP的人都需要BSF，也并非每一个人都安装了脚本引擎，所以在soap.jar中省略了脚本支持。要想用脚本编写SOAP服务，你必须从源代码重新构造以便引入InvokeBSF类。 <br />
<br />
首先要从http://xml.apache.org/dist/soap/下载源代码（soap-src-2.0.zip）。然后，把下载得到的文件解压缩到Apache SOAP所安装的目录。在我这里，它是E:驱动器的根目录。完成后，你将在soap_2-0目录下得到一个src子目录，子目录中包含Apache SOAP的所有源代码。重新从源代码构造Apache SOAP之前，你还必须下载必需的BSF jar文件。在ftp://ftp.mozilla.org/pub/js/可以找到一个。请结合Mozilla的JavaScript引擎Rhino使用它，Rhino可以从http://www.mozilla.org/rhino/download.html下载一个ZIP文件得到。我把这个文件解压缩到E:盘根目录下，最终得到一个包含了Rhino的E:\rhino目录，我们感兴趣的是它的js.jar。 <br />
<br />
接下来，你需要一个实际执行重新构造操作的工具，即Ant。Ant也是一个Apache的软件工程，它是一个基于Java的工具。Ant实际上和创建Web服务器Tomcat的工程同属一个工程，即Jakarta。在Ant中，所有构造信息，例如构造目标、依赖关系等，都通过XML配置文件指定，这是Ant独一无二的特点。此外，Ant是可扩展的。请参见本文最后&#8220;参考资源&#8221;部分收录的文章，了解如何充分发挥Ant的潜能。你可以从参考资源提供的链接下载Ant，然后解开压缩（我把它放入了C:盘的根目录）。 <br />
<br />
现在，从Apache SOAP安装目录执行如下命令： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>set<br />
            CLASSPATH=E:\jakarta-tomcat\lib\servlet.jar;E:\xerces-1_2_0\xerces.jar;<br />
            E:\soap-2_0\lib\bsf.jar<br />
            C:\build\bin\ant</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
由于上面的命令没有指定一个XML配置文件，Ant批命令文件将在当前目录（在我这里，是E:\soap_2-0）中寻找一个名为build.xml的文件。Apache SOAP提供了这个文件。打开这个文件可以看到，只有当com.ibm.bsf.BSFManager在classpath中时，InvokeBSF类才会编译。这就是我把bsf.jar（它包含了BSFManager类）放入类路径的原因。把新构造出来的soap.jar文件从build\lib子目录复制到lib子目录（我建议修改原来的soap.jar文件进行备份）。最后，把bsf.jar和js.jar加入到Web服务器的类路径。 <br />
<br />
大功告成！现在你可以开始用脚本编写SOAP服务了。 <br />
<br />
<strong>二、用JavaScript编写HelloWorld应用</strong> <br />
现在，我们用JavaScript重新编写第二篇文章的HelloWorld服务。服务程序的完整代码如下： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>function sayHelloTo(name)<br />
            {<br />
            return "Hello " + name + ", How are you?";<br />
            }</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
还有比这更容易的事情吗？不过，不要让这简单易行欺骗了你。事实上，你可以在服务程序里进行相当复杂的处理。例如，你可以从脚本代码访问任何标准的Java类。请看下面经过修改的脚本代码，它输出服务器的时间： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>function sayHelloTo(name)<br />
            {<br />
            var today = new java.util.Date();<br />
            java.lang.System.out.println("Today is " + today.toString());<br />
            return "Hello " + name + ", How are you?";<br />
            }</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
另外，你还可以导入和使用自己的任意Java类。例如，我们可以修改脚本代码，让它使用Name JavaBean： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>importClass(Packages.hello.Name);<br />
            function sayHelloTo(name)<br />
            {<br />
            var today = new java.util.Date();<br />
            java.lang.System.out.println("Today is " + today.toString());<br />
            var beanName = new Name();<br />
            beanName.setName("John");<br />
            <br />
            java.lang.System.out.println(beanName.getName());<br />
            return "Hello " + name + ", How are you?";<br />
            }</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
<strong>三、部署服务</strong> <br />
在使用JavaScript版本的服务之前，首先要部署它。正如本系列文章的第二篇所介绍的，在Apache SOAP中部署服务有两种办法：使用Web界面的管理工具，或者从命令行部署服务。下面我们来看看两种办法的具体操作过程。 <br />
<br />
<strong>3.1 使用Web界面的管理工具</strong> <br />
要使用Web界面的管理工具，用浏览器打开http://localhost:8080/apache-soap/admin。点击窗口左边的Deploy按钮。记住，ID输入框用来设置对象ID，SOAP基础设施利用对象ID把RPC（远程过程调用）请求关联到SOAP服务。每一个Apache SOAP服务都必须有一个对象ID，而且这个对象ID必须在该服务器上部署的所有服务之间唯一。把ID设置成urn:Hello，这个ID也就是我们在第二篇文章中为服务设置的对象ID。 <br />
<br />
把Scope输入框设置成application。回顾一下，Scope输入框用来指定响应调用请求的服务实例的生存时间（请参考第二篇文章中的更多说明）。 <br />
<br />
在Methods输入框中输入当前部署的服务允许调用的方法名字，多个方法名字之间以空白字符分隔。我们的服务只支持一个方法，即sayHelloTo()。 <br />
<br />
由于服务用JavaScript实现，而不是象第二篇文章那样用Java实现，所以Provider Type输入框应该填script。相应地，Java Provider输入框（包括Provider Class和Static输入框）不必填写。但现在必须填写Script Provider输入框，选择JavaScript（Rhino）作为脚本语言。由于我们将在Script文本输入框中提供脚本正文，所以现在不用填写Script Filename输入框。把下面的脚本代码复制到Script输入框： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>importClass(Packages.hello.Name);<br />
            function sayHelloTo(name)<br />
            {<br />
            var today = new java.util.Date();<br />
            java.lang.System.out.println("Today is " + today.toString());<br />
            var beanName = new Name();<br />
            beanName.setName("John");</p>
            <p>java.lang.System.out.println(beanName.getName());<br />
            return "Hello " + name + ", How are you?";<br />
            }</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
现在滚动到屏幕的最下面，点击表单下面的Deploy按钮（不是窗口左边的Deploy按钮）。要验证服务已经成功部署，点击窗口左边的List按钮。这时，urn:Hello服务应该在服务清单中出现。点击这个服务，确认所有信息都与刚才输入的吻合。 <br />
<br />
<strong>3.2 从命令行部署服务</strong> <br />
要从命令行部署服务，所有部署信息必须放入一个XML部署描述器文件。下面是我用来部署该服务的XML部署描述器文件： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>&lt;isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"<br />
            id="urn:Hello"&gt;<br />
            &lt;isd:provider type="script" scope="Application" methods="sayHelloTo"&gt;<br />
            &lt;isd:script language="javascript"&gt;<br />
            importClass(Packages.hello.Name);<br />
            function sayHelloTo(name)<br />
            {<br />
            var today = new java.util.Date();<br />
            java.lang.System.out.println("Today is " + today.toString());<br />
            var beanName = new Name();<br />
            beanName.setName("John");<br />
            <br />
            java.lang.System.out.println(beanName.getName());<br />
            return "Hello " + name + ", How are you?";<br />
            }<br />
            &lt;/isd:script&gt;<br />
            &lt;/isd:provider&gt;<br />
            &lt;/isd:service&gt;</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
和第二篇文章用到的部署描述器文件相比，这里主要的不同在于把提供者类型设置成了script而不是java。由于这个原因，部署描述器文件不再指定一个Java类，而是提供了服务的脚本代码。 <br />
<br />
部署服务之前应当确保Web服务器已经启动。下面的代码显示了如何部署服务： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>java org.apache.soap.server.ServiceManagerClient<br />
            http://localhost:8080/apache-soap/servlet/rpcrouter deploy<br />
            DeploymentDescriptor.xml</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
DeploymentDescriptor.xml是前面介绍的包含部署描述信息的XML文件。要验证服务已经成功部署，执行下面的命令： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>java org.apache.soap.server.ServiceManagerClient<br />
            http://localhost:8080/apache-soap/servlet/rpcrouter query urn:Hello</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
这时，我们应该看到和DeploymentDescriptor.xml文件一样的内容。 <br />
<br />
<strong>四、测试</strong> <br />
我们用第二篇文章提供的客户程序Client.java来测试HelloWorld服务。为什么可以用同一个客户程序访问JavaScript编写的服务呢？因为客户程序完全不会在乎服务用什么语言编写。只要服务能够理解SOAP请求，能够返回SOAP应答，客户程序不会关注服务用什么方式实现。回顾一下，下面就是我用来运行hello.Client的批命令文件： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>set<br />
            CLASSPATH=E:\soap-2_0\samples\;E:\soap-2_0\lib\soap.jar;<br />
            E:\xerces-1_2_0\xerces.jar<br />
            java hello.Client Tarak</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
观察Web服务器的控制台窗口，每次运行客户程序的时候，我们都可以看到当前的日期和输出&#8220;John&#8221;。 <br />
<br />
<strong>■ 结束语</strong> <br />
在这篇文章中，我介绍了Apache SOAP实现所提供的脚本语言支持。为什么说它很重要呢？只要分析一下为何Web开发如此流行。在我看来，一个关键的原因在于Web开发已经成熟，几乎任何人都能够用HTML和JavaScript之类的简单脚本语言构造出复杂的Web页面。类似地，在Web开发的服务器端，人们可以使用JSP这类易学但强大的脚本语言。我认为这种推理同样适用于SOAP开发。如果SOAP想要挺进主流，获得绝大部分人的支持，那么它应该尽量地简化。Apache SOAP加入对脚本的支持正是为了这个目标；它显著地扩展了创建SOAP服务的开发者的范围。 <br />
<br />
不过，不要忘了还有另一个因素需要考虑：客户端开发者，即调用SOAP服务的开发者。如前所述，Apache SOAP的客户端开发者比较&#8220;吃亏&#8221;，反而增加了一些原本不必做的工作。因此，在本系列文章的下一篇也即最后一篇中，我将介绍一个框架，它以Java 2平台1.3版本新引入的动态代理类为基础，使得创建客户程序就象创建SOAP服务一样简单直观。
<img src ="http://www.blogjava.net/libin2722/aggbug/159458.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/libin2722/" target="_blank">礼物</a> 2007-11-09 22:05 <a href="http://www.blogjava.net/libin2722/articles/159458.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SOAP净化有线协议（二）：Apache SOAP介绍</title><link>http://www.blogjava.net/libin2722/articles/159455.html</link><dc:creator>礼物</dc:creator><author>礼物</author><pubDate>Fri, 09 Nov 2007 14:03:00 GMT</pubDate><guid>http://www.blogjava.net/libin2722/articles/159455.html</guid><wfw:comment>http://www.blogjava.net/libin2722/comments/159455.html</wfw:comment><comments>http://www.blogjava.net/libin2722/articles/159455.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/libin2722/comments/commentRss/159455.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/libin2722/services/trackbacks/159455.html</trackback:ping><description><![CDATA[SOAP（简单对象访问协议）是一种利用XML编码数据的有线协议。它是同类协议中要求最低的一个规范，只定义了有线协议所要求的最关键的部分，有意地忽略了垃圾收集、对象激活等方面的细节。 <br />
<br />
SOAP对于Java开发者来说尤其重要，因为它让平台无关和可移植的Java程序更容易协同操作，使得Java的宝贵特性进一步增值。事实上，如果Java 2平台企业版（J2EE）的下一个版本让SOAP成为一种必须遵循的有线协议，规定所有遵从J2EE规范的应用服务器都必须支持SOAP协议，我也不会感到奇怪。不过就现在来说，我想我的猜想应该暂停了。 <br />
<br />
这个系列的文章总共四篇，这是第二篇。在这里，我要介绍的是Apache SOAP实现。 <br />
<br />
<strong>一、Apache SOAP概述</strong> <br />
Apache SOAP，即Apache Software Foundation对SOAP规范的实现，建立在IBM的SOAP4J的基础上。和所有其他Apache工程类似，Apache SOAP源代码开放，按照Apache许可协议发行。我觉得这是当前最好的SOAP实现之一。然而，虽然Apache SOAP遵从SOAP规范1.1版本，但它缺乏SOAP 1.1所包含的某些功能（参见本文最后的&#8220;参考资源&#8221;，了解Apache SOAP支持功能的清单）。 <br />
<br />
<strong>1.1、下载和安装Apache SOAP</strong> <br />
如前所述，Apache SOAP可以免费下载（参见&#8220;参考资源&#8221;中提供的下载链接）。我为我的Windows NT便携机下载了soap-bin-2.0.zip文件，该文件包含Apache SOAP 2.0，这是写作本文时的最新版本。安装Apache SOAP可谓轻而易举，共包含如下三个简单的步骤： <br />
<br />
<ol>
    <li>解开下载所得文件的ZIP压缩：解开压缩之后就得到了一个soap-2_0子目录。我把ZIP文件的内容解压缩到E盘的根目录下，因此有了一个包含Apache SOAP的E:\soap-2_0目录。
    <li>配置Web环境：要有一个支持Servlet和JSP的Web服务器。至此，你可能遇到下面两种情况之一：
    <ul>
        <li>情况1：已经有一个支持Servlet和JSP的Web服务器，而且你觉得配置这个服务器没有问题。在这种情况下，请配置服务器，使得浏览器可以访问http://localhost:8080/apache-soap/，打开soap-2_0 \webapps\soap\目录下面的index.html文件。
        <li>情况2：没有支持Servlet和JSP的Web服务器，或者虽然有这样一个服务器，却不想拿它做试验。在这种情况下，我建议你下载Tomcat的最新版本（写作本文时，最新版本是3.1）（参见&#8220;参考资源&#8221;中的链接）。Tomcat是Apache创建和免费提供给软件开发者的又一个优秀软件。下载合适的ZIP文件之后（jakarta-tomcat-3.1.1.zip），解开压缩时创建一个jakarta-tomcat子目录。和前面相似，我把解压缩得到的文件放入E盘的根目录之下。在jakarta-tomcat\conf\server.xml配置文件中增加一个新的&lt;Context&gt;标记，如下所示：
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="400" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">&lt;Context path="/apache-soap" docBase="E:/soap-2_0/webapps/soap"<br />
                    debug="1" reloadable="true"&gt;<br />
                    &lt;/Context&gt;</td>
                </tr>
            </tbody>
        </table>
        在Context元素的docBase属性中，你应该在指定soap-2_0目录时把E:替换成合适的盘符。要启动Tomcat，执行startup.bat（对于Unix，执行startup.sh）。要关闭Tomcat，执行shutdown.bat（对于Unix，执行shutdown.sh）。但请稍等——现在请不要启动Tomcat。 </li>
    </ul>
    <li>设置Web服务器classpath：Apache SOAP要求有1.1.2版本或更高的Apache Xerces（Java），它支持DOM（文档对象模型）Level 2规范，支持名称空间。我使用1.2版本的Xerces，即Apache网站的Xerces-J-bin.1.2.0.zip。解开这个压缩文件，得到xerces-1_2_0子目录。和前面一样，我把解压缩得到的文件放到了E:盘的根目录之下。你应该配置Web服务器，使它能够用xerces.jar（它在xerces-1_2_0子目录下）进行所有XML解析——而不是用服务器附带的库或jar解析XML。例如，Tomcat附带了一个XML解析器（jakarta-tomcat\lib\xml.jar），支持DOM Level 1接口。即使你把xerces.jar放入了classpath，Tomcat下运行的Java代码也可能找错接口，因为在用来启动Tomcat的Shell脚本/批命令文件中，xerces.jar被放到了classpath的最后。因此，必须编辑jakarta-tomcat\bin目录下的tomcat.bat（对于Unix，则是tomcat.sh），把xerces.jar放到classpath的前面。下面是我在jakarta-tomcat\bin\tomcat.bat文件中所作的修改：
    <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="450" bordercolorlight="black" border="1">
        <tbody>
            <tr>
                <td class="code" bgcolor="#e6e6e6">set CLASSPATH=E:\xerces-1_2_0\xerces.jar;%CLASSPATH%;%cp%</td>
            </tr>
        </tbody>
    </table>
    </li>
</ol>
</descript>如果你在第二个步骤中属于情况2，也必须配置服务器，使它能够使用xerces.jar。 <br />
<br />
不管你属于哪一种情况，除了配置xerces.jar之外，你还必须配置Web服务器的classpath使它能够使用soap-2_0\lib\目录下的soap.jar。 <br />
<br />
<strong>1.2、检查安装是否成功</strong> <br />
现在，启动Web服务器，用浏览器打开http://localhost:8080/apache-soap/admin，验证安装是否成功。这时，你应该看到下图所示的管理屏幕。 <br />
<br />
<div align="center"><a href="http://www0.ccidnet.com/tech/app/2001/09/21/image/JavaAndSOAP2_1.gif" target="_blank"><img src="http://www0.ccidnet.com/tech/app/2001/09/21/image/JavaAndSOAP2_1-s.gif" border="0"  alt="" /></a><br />
<strong>图一：Web界面的Apache SOAP管理工具</strong></div>
<br />
<strong>二、实例：HelloWorld</strong> <br />
现在你已经设置好了Apache SOAP，我们来实际使用一下，构造一个简单的HelloWorld应用。在SOAP术语中，应用称为服务。一般地，创建服务分两个步骤，这两个步骤可能由同一个人或组织实施，也可能不是。第一个步骤是在选定的语言中定义和实现服务本身，这里我们选择Java语言。第二个步骤是创建实际调用服务的客户程序。首先我们来看HelloWorld服务。 <br />
<br />
<strong>2.1、HelloWorld服务</strong> <br />
我在第一篇文章中讨论了一个用SOAP实现的HelloWorld服务实例。这个服务要求输入一个用户名字，返回一个定制的Hello消息给调用者。下面的代码显示了HelloWorld服务的完整Java代码。 <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">
            <p>package hello;<br />
            public class HelloServer<br />
            {<br />
            public String sayHelloTo(String name)<br />
            {<br />
            System.out.println("sayHelloTo(String name)");<br />
            return "Hello " + name + ", How are you doing?";<br />
            }<br />
            }</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
这就是全部的代码吗？如果这是真的话，实在太简单了！ <br />
<br />
Apache SOAP使创建服务变得极其简单。服务主要由业务逻辑构成，不管服务以哪种方式提供给外界使用，业务逻辑代码都是必须编写的。换句话说，服务不会和任何SOAP相关的代码混合，因为Apache SOAP底层体系——它由rpcrouter Servlet和soap.jar构成——帮助我们完成了所有错综复杂的工作。我们来简要地探讨一下这些错综复杂的工作究竟包含些什么，例如，Apache SOAP如何处理HTTP上的远程过程调用（RPC）请求？理解这一点将给创建客户程序带来方便（不错，是客户程序）。 <br />
<br />
在Apache SOAP中，org.apache.soap.rpc包支持在SOAP上进行RPC调用。Apache RPC支持的关键在于对象ID。所有的Apache SOAP服务必须有一个唯一的ID，它被视为服务的对象ID。众所周知，唯一性是一个相对的概念；在Apache SOAP中，对象ID的唯一性相对于服务所部署的Apache SOAP服务器而言。也就是说，部署在不同Apache SOAP服务器上的两个服务可能有同样的对象ID。 <br />
<br />
想要使用服务的客户程序设置一个org.apache.soap.rpc.Call对象，指定目标服务的对象ID、待调用方法的名字以及提供给方法的参数（如果有的话）。设置好Call对象之后，客户程序调用它的invoke()方法。invoke()方法需要两个参数，第一个参数是一个执行rpcrouter Servlet的URL，如http://localhost:8080/apache-soap/servlet/rpcrouter；第二个参数是SOAPAction头（请参考本系列的第一篇文章，了解SOAPAction头的重要性和可能的取值）。 <br />
<br />
invoke()方法把Call对象转换成XML SOAP请求（类似第一篇文章所提供的示例），把请求发送给第一个参数中的URL所指定的rpcrouter Servlet。当Servlet返回应答，invoke()方法返回一个org.apache.soap.rpc.Response对象，这个对象包含了服务的应答（如果有的话）或错误信息（如果出现了错误）。HTTP规定每一个请求都必须有一个应答；因此，即使服务本身不返回任何东西，rpcrouter Servlet总是会返回一些内容。因此，invoke()方法总是返回一个Response对象。 <br />
<br />
在服务端，Apache SOAP服务器（也就是rpcrouter Servlet）接收客户程序发送的SOAP请求，重新构造出Call对象。Servlet使用重新构造得到的Call对象中的对象ID在服务管理器中确定具体的对象。 <br />
<br />
接下来，Servlet在已经确定的对象上检验被调用方法的名字并调用方法。完成后，Servlet串行化该调用的返回值，在HTTP应答中把它发送给客户程序。 <br />
<br />
从上述讨论中，我们可以发现一个有趣的问题：Apache SOAP如何知道串行化某种给定数据类型的方法？Apache SOAP通过一个类型注册器（org.apache.soap.encoding.SOAPMappingRegistry），以及通过所有装配器（marshaller）和反装配器（marshaller）分别必须实现的串行化（org.apache.soap.util.xml.Serializer）和反串行化（org.apache.soap.util.xml.Deserialization）接口，实现Java数据类型和XML之间的装配和反装配。Apache SOAP提供了许多实现这些接口的内建的装配器和反装配器。例如，我们可以用org.apache.soap.encoding.soapenc.BeanSerializer类装配和反装配JavaBean。本文后面我将介绍如何使用这个类。 <br />
<br />
对于Java基本数据类型（int，long，double，等等）及其对应的对象化形式（Integer，Long，Double，等等）来说，它们的串行化器和反串行化器已经预先在类型映射注册器中注册。因此，对于客户程序来说，用Java基本数据类型及其对象形式作为方法参数是无缝的。然而，如果服务所要求的参数是一个JavaBean，则必须手工在类型映射注册器中注册BeanSerializer。服务永远不会做任何额外的工作，最后客户程序的负担总是较多。在这个系列的第四篇文章中，我将介绍用动态代理构造的框架，它将使创建客户程序和创建服务程序一样简单。 <br />
<br />
<strong>2.2、部署HelloWorld服务</strong> <br />
部署Apache SOAP服务有两种方法：使用Web界面的管理工具，或通过命令行进行部署。所有这两种方法都可以部署服务，使服务可被客户程序访问。 <br />
<br />
■ 使用管理工具部署服务 <br />
<br />
要使用管理工具，用浏览器打开http://localhost:8080/apache-soap/admin。浏览器将显示出图一所示的界面。点击窗口左边的Deploy按钮，一个带有许多输入框的窗口就会出现。并非所有的输入框现在都要用到。我将在用到这些输入框的时候介绍它们的含义。由于本文无需用到所有的输入框，所以我们将忽略部分输入框的含义。但不用担心，到第三篇文章结束时，我将介绍完所有的输入框。 <br />
<br />
ID输入框用来设置对象ID；如前所述，SOAP基础设施利用对象ID把RPC请求绑定到SOAP服务。我在前面已经提到，所有Apache SOAP服务必须有一个对象ID，这个对象ID在该服务器上部署的所有服务之间唯一。我通常使用&#8220;urn:&amp;lt;UniqueServiceID&amp;gt;&#8221;格式，其中UniqueServiceID是服务的唯一对象ID。在本例中，把ID设置成&#8220;urn:hello&#8221;。 <br />
<br />
Scope输入框用来定义响应调用请求的服务实例的生存范围和时间。Scope可以是下列值之一： <br />
<br />
<li>page：服务实例一直有效，直至应答发送完毕或把请求传递给了另一个页面——如果使用标准的部署机制，向前传递请求不太可能发生。
<li>request：服务实例在请求处理期间一直有效，不管是否出现请求传递。
<li>session：服务实例对于整个会话都有效。
<li>application：服务实例被用于所有对服务的调用请求。
<ul></ul>
    </descript>Scope的值对安全有着重要的影响，记住这一点很重要。page和request值确保了连续调用之间的隔离。在另一个极端，application值意味着所有SOAP的用户共享服务实例。细心的读者可能已经注意到，JSP的&lt;jsp:useBean&gt;标记同样要用到这些值。事实上，rpcrouter Servlet曾经就是一个JSP页面，这也许是这些值被选用的原因。在本例中，我们把Scope的值设置成application。 <br />
    <br />
    在Methods输入框中，输入用空白字符分隔的方法名字，这些方法名字指示出当前部署的服务上允许调用的方法。我们的服务示例只支持一个方法，即sayHelloTo()。 <br />
    <br />
    把Provider Type设置成Java。它意味着服务用Java实现，而且你必须为Apache SOAP提供服务完整的类名。这个任务在Provider Class输入框完成，我们把它设置成hello.HelloServer。由于sayHelloTo()方法不是静态的，保持Static输入框原来的值，即no。 <br />
    <br />
    现在滚动到页面的下方，点击表单下面的Deploy按钮（不是左边的Deploy按钮）。要验证服务已经部署完毕，点击左边的List按钮，这时列表所显示的服务中应该包含一个urn:Hello服务。 <br />
    <br />
    ■ 从命令行部署服务 <br />
    <br />
    部署服务除了可以用Web界面的管理工具，还可以用命令行Java工具org.apache.soap.server.ServiceManagerClient，它是一个Apache SOAP附带的类。这个类要求有两个必不可少的参数，一个指向Apache SOAP路由Servlet（即rpcrouter）的URL，以及一个动作。这个动作可以是以下四者之一：deploy，undeploy，list，或query。根据指定动作的不同，有时候还要提供额外的参数。例如，如果动作是deploy，则必须提供XML部署描述器文件的名字。部署描述器文件应该包含Apache SOAP服务器成功部署服务所需要的全部信息。例如，描述HelloWorld部署细节的部署XML文件可以如下： <br />
    <br />
    <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
        <tbody>
            <tr>
                <td class="code" bgcolor="#e6e6e6">
                <p>&lt;isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment"<br />
                id="urn:Hello"&gt;<br />
                &lt;isd:provider type="java" scope="Application" methods="sayHelloTo"&gt;<br />
                &lt;isd:java class="hello.HelloServer" static="false"/&gt;<br />
                &lt;/isd:provider&gt;<br />
                &lt;/isd:service&gt;</p>
                </td>
            </tr>
        </tbody>
    </table>
    <br />
    上述XML代码所包含的信息和我们在Web界面的管理工具中所输入的信息一样。接下来，输入下面的命令，从命令行部署HelloWorld服务： <br />
    <br />
    <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
        <tbody>
            <tr>
                <td class="code" bgcolor="#e6e6e6">
                <p>java org.apache.soap.server.ServiceManagerClient<br />
                http://localhost:8080/apache-soap/servlet/rpcrouter<br />
                deploy DeploymentDescriptor.xml</p>
                </td>
            </tr>
        </tbody>
    </table>
    <br />
    DeploymentDescriptor.xml是上面显示的描述部署信息的XML文件名字。要验证服务是否部署成功，输入以下命令： <br />
    <br />
    <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
        <tbody>
            <tr>
                <td class="code" bgcolor="#e6e6e6">
                <p>java org.apache.soap.server.ServiceManagerClient<br />
                http://localhost:8080/apache-soap/servlet/rpcrouter query urn:Hello</p>
                </td>
            </tr>
        </tbody>
    </table>
    <br />
    这时，我们应该看到和DeploymentDescriptor.xml文件内一样的XML。 <br />
    <br />
    <strong>2.3、HelloWorld客户程序</strong> <br />
    编写客户程序要比编写HelloWorld服务复杂得多。不过，你应该不会对此感到奇怪，因为前面已经提到，客户程序（至少）必须负责设置Call对象，这需要不少工作。顺便说一下，本系列文章的第四篇将介绍一个框架，这个框架以Java 2 1.3版新引入的动态代理类为基础，使创建客户程序和创建服务一样简单。 <br />
    <br />
    Listing 1显示了完整的客户程序。接下来我们一步一步地仔细看看这个程序。这个程序需要一个必不可少的参数：程序要向他说Hello信息的用户名字。 <br />
    <br />
    <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
        <tbody>
            <tr>
                <td class="code" bgcolor="#e6e6e6">
                <p>Listing 1: Client.java</p>
                <p>package hello;</p>
                <p>import java.net.URL;<br />
                import java.util.Vector;<br />
                import org.apache.soap.SOAPException;<br />
                import org.apache.soap.Constants;<br />
                import org.apache.soap.Fault;<br />
                import org.apache.soap.rpc.Call;<br />
                import org.apache.soap.rpc.Parameter;<br />
                import org.apache.soap.rpc.Response;<br />
                <br />
                public class Client<br />
                {<br />
                public static void main(String[] args) throws Exception<br />
                {<br />
                if(args.length == 0)<br />
                {<br />
                System.err.println("Usage: java hello.Client [SOAP-router-URL] ");<br />
                System.exit (1);<br />
                }</p>
                <p>try<br />
                {<br />
                URL url = null;<br />
                String name = null;<br />
                if(args.length == 2)<br />
                {<br />
                url = new URL(args[0]);<br />
                name = args[1];<br />
                }<br />
                else<br />
                {<br />
                url = new URL("http://localhost:8080/apache-soap/servlet/rpcrouter");<br />
                name = args[0];<br />
                }</p>
                <p>// 构造Call对象<br />
                Call call = new Call();<br />
                call.setTargetObjectURI("urn:Hello");<br />
                call.setMethodName("sayHelloTo");<br />
                call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);<br />
                Vector params = new Vector();<br />
                params.addElement(new Parameter("name", String.class, name, null));<br />
                call.setParams(params);</p>
                <p>// 发出调用<br />
                Response resp = null;<br />
                try<br />
                {<br />
                resp = call.invoke(url, "");<br />
                }<br />
                catch( SOAPException e )<br />
                {<br />
                System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " +<br />
                e.getMessage());<br />
                System.exit(-1);<br />
                }<br />
                <br />
                // 检查应答<br />
                if( !resp.generatedFault() )<br />
                {<br />
                Parameter ret = resp.getReturnValue();<br />
                Object value = ret.getValue();<br />
                System.out.println(value);<br />
                }<br />
                else<br />
                {<br />
                Fault fault = resp.getFault();<br />
                System.err.println("Generated fault: ");<br />
                System.out.println (" Fault Code = " + fault.getFaultCode());<br />
                System.out.println (" Fault String = " + fault.getFaultString());<br />
                }<br />
                }<br />
                catch(Exception e)<br />
                {<br />
                e.printStackTrace();<br />
                }<br />
                }<br />
                }</p>
                </td>
            </tr>
        </tbody>
    </table>
    <br />
    客户程序首先设置Call对象，它需要如下信息： <br />
    <br />
    <li>被调用服务的对象ID，它通过Call对象的setTargetObjectURI()方法设置。本例的对象ID是urn:Hello。
    <li>待调用方法的名字，它通过Call对象的setMethodName()方法设置。本例的方法名字是sayHelloTo()。
    <li>参数的编码方式，它通过Call对象的setEncodingStyleURI()方法设置。本例我们使用标准的SOAP编码方式，这种编码方式由名称空间http://schemas.xmlsoap.org/soap/encoding/定义。
    <li>方法调用的参数通过Call对象的setParams()方法设置。setParams()方法的参数是一个Java Vector（向量）。这个向量包含所有的参数，向量中索引为0的参数是被调用方法从左边数起的第一个参数，索引为1的参数是被调用方法从左边数起的第二个参数，依此类推。向量中的每一个元素都是一个org.apache.soap.rpc.Parameter的实例。Parameter构造函数要求指定参数的名字、Java类型和值，还有一个可选的编码方式。如果指定了null编码方式（正如本例所做的那样），则默认使用Call对象的编码方式。虽然每一个参数对应着一个名字，但这个名字可以设置成任何内容，Apache SOAP服务器调用方法时不会用到这个名字。因此，绝对有必要让向量中参数的次序和被调用方法的参数次序一致。
    <ul></ul>
        </descript>下面的代码片断显示了客户程序创建Call对象的过程： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">
                    <p>// 构造Call对象<br />
                    Call call = new Call();<br />
                    call.setTargetObjectURI("urn:Hello");<br />
                    call.setMethodName("sayHelloTo");<br />
                    call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);<br />
                    Vector params = new Vector();<br />
                    params.addElement(new Parameter("name", String.class, name, null));<br />
                    call.setParams(params);</p>
                    </td>
                </tr>
            </tbody>
        </table>
        <br />
        现在，该是实际调用HelloWorld远程服务所提供方法的时候了。为此，客户程序调用了Call对象的invoke()方法，这个方法返回一个org.apache.soap.rpc.Response对象，如下所示： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">
                    <p>// 发出调用<br />
                    Response resp = null;<br />
                    try<br />
                    {<br />
                    resp = call.invoke(url, "");<br />
                    }<br />
                    catch( SOAPException e )<br />
                    {<br />
                    System.err.println("Caught SOAPException (" + e.getFaultCode() + "): " +<br />
                    e.getMessage());<br />
                    System.exit(-1);<br />
                    }</p>
                    </td>
                </tr>
            </tbody>
        </table>
        <br />
        接下来，客户程序检查Response对象。如果方法调用过程中出现了错误，generateFault()方法返回一个true值，客户程序提取并显示实际的错误信息： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">
                    <p>Fault fault = resp.getFault();<br />
                    System.err.println("Generated fault: ");<br />
                    System.out.println (" Fault Code = " + fault.getFaultCode());<br />
                    System.out.println (" Fault String = " + fault.getFaultString());</p>
                    </td>
                </tr>
            </tbody>
        </table>
        <br />
        如果方法调用成功，则客户程序提取并显示Hello信息： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">
                    <p>// 检查应答<br />
                    if( !resp.generatedFault() )<br />
                    {<br />
                    Parameter ret = resp.getReturnValue();<br />
                    Object value = ret.getValue();<br />
                    System.out.println(value);<br />
                    }</p>
                    </td>
                </tr>
            </tbody>
        </table>
        <br />
        <strong>三、带有JavaBean的HelloWorld实例</strong> <br />
        如前所述，Apache SOAP提供了许多预先构造的串行化和反串行化方法，其中包括为利用Java Vector、Enumeration、数组、JavaBean作为参数和返回值而提供的串行化器和反串行化器。在这一部分，我将修改HelloWorld服务，通过一个JavaBean传入接收Hello信息的用户名。 <br />
        <br />
        <strong>3.1、HelloWorld服务 </strong><br />
        改写后的HelloWorld服务完整代码如下： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">
                    <p>package hello;<br />
                    public class HelloServer<br />
                    {<br />
                    public String sayHelloTo(String name)<br />
                    {<br />
                    System.out.println("sayHelloTo(String name)");<br />
                    return "Hello " + name + ", How are you doing?";<br />
                    }</p>
                    <p>public <strong>String sayHelloTo(Name theName)</strong><br />
                    {<br />
                    System.out.println("sayHelloTo(Name theName)");<br />
                    return "Hello " + theName.getName() + ", How are you doing?";<br />
                    }<br />
                    }</p>
                    </td>
                </tr>
            </tbody>
        </table>
        <br />
        服务的代码仍旧很简单，仍旧类似于不用JavaBean时的HelloWorld服务。不过，这意味着最复杂的工作都转移到了客户端。事实上，这个版本的服务与以前版本的唯一差别在于，现在出现了一个重载的sayHelloTo()方法。上面的代码中重载后的方法用粗体字显示。 <br />
        <br />
        重载的方法需要一个对Name JavaBean的引用。Name JavaBean的定义如下： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">
                    <p>package hello;</p>
                    <p>public class Name<br />
                    {<br />
                    private String name;<br />
                    public String getName()<br />
                    {<br />
                    return name;<br />
                    }<br />
                    public void setName(String name)<br />
                    {<br />
                    this.name = name;<br />
                    }<br />
                    }</p>
                    </td>
                </tr>
            </tbody>
        </table>
        <br />
        <strong>3.2、部署服务</strong> <br />
        部署一个使用了JavaBean的服务时，需要为Apache SOAP服务器提供一些额外的信息。因此，现在部署服务的过程稍微复杂一点。 <br />
        <br />
        ■ 使用管理工具部署服务 <br />
        <br />
        要使用管理工具部署这个新版的HelloWorld服务，首先按照前面所介绍的步骤进行，但这一次不要点击Deploy按钮。现在，在Number of Mappings输入框输入1，它表示我们将给出一个映射（即Name JavaBean）的信息。紧接Mappings之下有一个表格，我们要用到这个表格的第一行。保留Encoding Style的值为SOAP，把NameSpace URI设置成对象的ID：在本例中，它是urn:Hello。接下来，把Local Part和Java Type输入框设置成Name JavaBean的完整名字，即hello.Name。最后，把Java to XML Serializer和XML to Java Deserializer输入框设置成org.apache.soap.encoding.soapenc.BeanSerializer，这是一个实现了Serializer和Deserializer接口的类，用来串行化和反串行化Name JavaBean。如果你用到了更多的JavaBean（比如还有一个Address Bean），则应该在这个表格中输入其他Bean的信息，同时还应该更新Number of Mappings输入框的值，使之反映出表格中实际被使用的行数。 <br />
        <br />
        ■ 从命令行部署服务 <br />
        <br />
        要从命令行进行部署，我们只需修改作为命令行参数传入的XML部署描述器文件。修改后的XML文件如下所示： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">
                    <p>&lt;isd:service xmlns:isd="http://xml.apache.org/xml-soap/deployment" id="urn:Hello"&gt;<br />
                    &lt;isd:provider type="java" scope="Application" methods="sayHelloTo"&gt;<br />
                    &lt;isd:java class="hello.HelloServer" static="false"/&gt;<br />
                    &lt;/isd:provider&gt;<br />
                    &lt;isd:mappings&gt;<br />
                    &lt;isd:map encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"<br />
                    xmlns:x="urn:Hello" qname="x:hello.Name"<br />
                    javaType="hello.Name"<br />
                    java2XMLClassName="org.apache.soap.encoding.soapenc.BeanSerializer"<br />
                    xml2JavaClassName="org.apache.soap.encoding.soapenc.BeanSerializer"/&gt;<br />
                    &lt;/isd:mappings&gt;<br />
                    &lt;/isd:service&gt;</p>
                    </td>
                </tr>
            </tbody>
        </table>
        <br />
        正如在前一个例子中，这些XML代码所包含的信息和通过Web界面的管理工具所提供的信息一样。 <br />
        <br />
        <strong>3.3、HelloWorld客户程序</strong> <br />
        和第一个例子一样，客户程序更复杂，也更令人感兴趣。这里我不再仔细分析整个客户程序，而是介绍两个客户程序版本的不同之处。由于调用方法的一个参数（在本例中，它是唯一的参数）是一个JavaBean，所以必须手工设置一个类型映射注册项。这个任务通过如下步骤完成：先创建org.apache.soap.encoding.SOAPMappingRegistry类的一个实例，然后调用它的mapTypes()方法。正如mapTypes()方法名字所预示的，它用来注册一个以前未知的类型，比如定制的JavaBean。mapTypes()方法的参数包括要使用的编码方式、限定的JavaBean名字、类型的完整类名、串行化器和反串行化器。在本例中，执行串行化任务的是标准的Bean串行化器。限定的JavaBean名字包含一个元素的名字，包括它所属的名称空间。在本例中，Name JavaBean的限定名字由名称空间URI（urn:Hello）和本地名字（hello.Name）结合构成。请看下面的代码片断： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">
                    <p>// 创建类型映射注册器<br />
                    SOAPMappingRegistry smr = new SOAPMappingRegistry();<br />
                    BeanSerializer beanSer = new BeanSerializer();<br />
                    // 映射类型<br />
                    smr.mapTypes(Constants.NS_URI_SOAP_ENC,<br />
                    new QName("urn:Hello", "hello.Name"),hello.Name.class, beanSer, beanSer);</p>
                    </td>
                </tr>
            </tbody>
        </table>
        <br />
        接下来，客户程序必须告诉Call对象使用新的注册器而不是默认的注册器。为此，我们要调用Call对象的setSOAPMappingRegistry()方法，如下所示： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">call.setSOAPMappingRegistry(smr);</td>
                </tr>
            </tbody>
        </table>
        <br />
        手工设置好类型映射注册器之后，接下来还必须为Call对象设置参数。这一步骤可以按前面介绍的方法完成，不同之处在于，现在我们不再用字符串类型的名字作为参数，而是用JavaBean作为参数，如下所示： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">
                    <p>// 设置调用参数<br />
                    Vector params = new Vector();<br />
                    Name theName = new Name();<br />
                    theName.setName(name);<br />
                    params.addElement(new Parameter("name", hello.Name.class, theName, null));<br />
                    call.setParams(params);</p>
                    </td>
                </tr>
            </tbody>
        </table>
        <br />
        客户程序剩下的部分和原来的版本一样。Listing 3显示了完整的客户程序代码： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">
                    <p>Listing 3: Client2.java</p>
                    <p>package hello;</p>
                    <p>import java.net.URL;<br />
                    import java.util.Vector;<br />
                    import org.apache.soap.SOAPException;<br />
                    import org.apache.soap.Constants;<br />
                    import org.apache.soap.Fault;<br />
                    import org.apache.soap.rpc.Call;<br />
                    import org.apache.soap.rpc.Parameter;<br />
                    import org.apache.soap.rpc.Response;<br />
                    import org.apache.soap.encoding.SOAPMappingRegistry;<br />
                    import org.apache.soap.encoding.soapenc.BeanSerializer;<br />
                    import org.apache.soap.util.xml.QName;<br />
                    <br />
                    public class Client2<br />
                    {<br />
                    public static void main(String[] args) throws Exception<br />
                    {<br />
                    if(args.length == 0)<br />
                    {<br />
                    System.err.println("Usage: java hello.Client [SOAP-router-URL] ");<br />
                    System.exit (1);<br />
                    }</p>
                    <p>try<br />
                    {<br />
                    URL url = null;<br />
                    String name = null;<br />
                    if(args.length == 2)<br />
                    {<br />
                    url = new URL(args[0]);<br />
                    name = args[1];<br />
                    }<br />
                    else<br />
                    {<br />
                    url = new URL("http://localhost:8080/apache-soap/servlet/rpcrouter");<br />
                    name = args[0];<br />
                    }</p>
                    <p>// 构造调用对象<br />
                    Call call = new Call();<br />
                    call.setTargetObjectURI("urn:Hello");<br />
                    call.setMethodName("sayHelloTo");<br />
                    call.setEncodingStyleURI(Constants.NS_URI_SOAP_ENC);</p>
                    <p>// 创建类型映射注册器<br />
                    SOAPMappingRegistry smr = new SOAPMappingRegistry();<br />
                    BeanSerializer beanSer = new BeanSerializer();<br />
                    // 映射类型<br />
                    smr.mapTypes(Constants.NS_URI_SOAP_ENC,<br />
                    new QName("urn:Hello", "hello.Name"),<br />
                    hello.Name.class, beanSer, beanSer);<br />
                    call.setSOAPMappingRegistry(smr);</p>
                    <p>// 设置参数<br />
                    Vector params = new Vector();<br />
                    Name theName = new Name();<br />
                    theName.setName(name);<br />
                    params.addElement(new Parameter("name", hello.Name.class,<br />
                    theName, null));<br />
                    call.setParams(params);</p>
                    <p>// 发出调用<br />
                    Response resp = null;<br />
                    try<br />
                    {<br />
                    resp = call.invoke(url, "");<br />
                    }<br />
                    catch( SOAPException e )<br />
                    {<br />
                    System.err.println("Caught SOAPException (" +<br />
                    e.getFaultCode() + "): " + e.getMessage());<br />
                    System.exit(-1);<br />
                    }<br />
                    <br />
                    // 检查应答<br />
                    if( !resp.generatedFault() )<br />
                    {<br />
                    Parameter ret = resp.getReturnValue();<br />
                    Object value = ret.getValue();<br />
                    System.out.println(value);<br />
                    }<br />
                    else<br />
                    {<br />
                    Fault fault = resp.getFault();<br />
                    System.err.println("Generated fault: ");<br />
                    System.out.println (" Fault Code = " + fault.getFaultCode());<br />
                    System.out.println (" Fault String = " + fault.getFaultString());<br />
                    }<br />
                    }<br />
                    catch(Exception e)<br />
                    {<br />
                    e.printStackTrace();<br />
                    }<br />
                    }<br />
                    }</p>
                    </td>
                </tr>
            </tbody>
        </table>
        <br />
        <strong>四、编译和运行程序</strong> <br />
        现在整个程序的开发工作已经完成，该是运行它的时候了。不过，我们首先要编译服务程序和客户程序。 <br />
        <br />
        创建一个hello目录，把Client1.java、Client2.java和HelloServer.java复制到这个目录。我把hello目录放到了Apache SOAP的示例目录（即E:\soap-2_0\samples）之下。编译程序时，classpath中只需包含hello目录的父目录（即E:\soap-2_0\samples）、soap.jar和xerces.jar。我用下面的批命令编译程序： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">
                    <p>set CLASSPATH=E:\soap-2_0\samples\;E:\soap-2_0\lib\soap.jar;E:\xerces-1_2_0\xerces.jar<br />
                    javac -d .. HelloServer.java Client.java Client2.java</p>
                    </td>
                </tr>
            </tbody>
        </table>
        <br />
        注意：从hello目录执行这个批命令文件。 <br />
        <br />
        要使用这个服务，除了部署它之外，还需要修改Web服务器的classpath，确保Web服务能够找到hello.HelloServer类——对于本例，这是指把E:\soap-2_0\samples加入到Web服务器的classpath。对classpath进行必要的修改之后，重新启动Web服务器。接下来就可以运行客户程序了。下面是我运行hello.Client的批命令文件： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">set CLASSPATH=E:\soap-2_0\samples\;E:\soap-2_0\lib\soap.jar;E:\xerces-1_2_0\xerces.jar<br />
                    java hello.Client Tarak</td>
                </tr>
            </tbody>
        </table>
        <br />
        这里的classpath和编译程序时用的classpath相同。 <br />
        <br />
        最后，运行hello.Client2的批命令文件可以如下： <br />
        <br />
        <table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
            <tbody>
                <tr>
                    <td class="code" bgcolor="#e6e6e6">set CLASSPATH=E:\soap-2_0\samples\;E:\soap-2_0\lib\soap.jar;E:\xerces-1_2_0\xerces.jar<br />
                    java hello.Client2 Tarak</td>
                </tr>
            </tbody>
        </table>
        <br />
        观察Web服务器的控制台窗口，看看在运行两个不同的客户程序时，HelloWorld服务的哪些方法正在被调用。 <br />
        <br />
        <strong>■ 结束语</strong> <br />
        在这篇文章中，我介绍了如何用Apache SOAP实现来创建简单的基于SOAP的服务。在SOAP实现方面，另一个重要的竞争者是Microsoft。遗憾的是，&#8220;纯&#8221;Java开发者在使用Microsoft实现的时候会有一段艰苦的时光，因为它的实现包含了COM对象。 <br />
        <br />
        在下一篇文章中，我将介绍Apache SOAP支持的另一种创建服务的方式：使用JavaScript之类的脚本语言，而不是Java。另外，我还要介绍一个很不错的JavaScript引擎，即Rhino。</li>
<img src ="http://www.blogjava.net/libin2722/aggbug/159455.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/libin2722/" target="_blank">礼物</a> 2007-11-09 22:03 <a href="http://www.blogjava.net/libin2722/articles/159455.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SOAP净化有线协议（一）：SOAP基础知识</title><link>http://www.blogjava.net/libin2722/articles/159454.html</link><dc:creator>礼物</dc:creator><author>礼物</author><pubDate>Fri, 09 Nov 2007 13:59:00 GMT</pubDate><guid>http://www.blogjava.net/libin2722/articles/159454.html</guid><wfw:comment>http://www.blogjava.net/libin2722/comments/159454.html</wfw:comment><comments>http://www.blogjava.net/libin2722/articles/159454.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/libin2722/comments/commentRss/159454.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/libin2722/services/trackbacks/159454.html</trackback:ping><description><![CDATA[许多开发者都遇到过这样的情形：一个CORBA客户程序需要获得分布式组件对象模型（DCOM）客户程序的服务，或者相反。常见的解决方案是使用一个COM/CORBA桥。然而，这种解决方案存在许多问题。假设在两个已经很复杂的系统之间（CORBA ORB和COM之间）引入了一个新的软件，在CORBA的Internet Inter-ORB Protocol（IIOP）到DCOM的Object Remote Procedure Call（ORPC）之间，繁杂的双向转换将使得中间起桥接作用的软件变得很复杂。任何对IIOP协议和ORPC协议的修改都导致修改桥接软件。如果我说SOAP能够缓解这个问题，你会怎么想呢？感兴趣吗？ <br />
<br />
SOAP的全称是Simple Object Access Protocol，即简单对象访问协议。简单地说，SOAP是一种有线协议，类似于CORBA的IIOP、DCOM的ORPC或Java远程方法调用的Java远程方法协议（Java Remote Method Protocol，JRMP）。你也许会怀疑，既然已经有了那么多有线协议，为什么我们还需要另外一种？事实上，这不正好导致前面所讨论的问题吗？这些问题都有道理，但是，SOAP和其他有线协议有所不同。 <br />
<br />
我们来分析一下： <br />
<br />
<ul>
    <li>IIOP、ORPC和JRMP都是二进制协议，而SOAP则是一种使用XML的以文本为基础的协议。利用XML进行数据编码为SOAP带来一些独一无二的功能。例如，调试以SOAP为基础的应用程序更容易，因为阅读XML要比阅读二进制数据容易得多。另外，由于所有的SOAP消息都是文本格式，和IIOP、ORPC或者JRMP相比，SOAP更容易和<a href="http://www.ccidnet.com/school/dict/explain/F_809.html">防火墙</a>协作。
    <li>SOAP协议以非供应商私有的协议为基础，即XML、HTTP和Simple Mail Transfer Protocol（SMTP），所有供应商都可以使用SOAP协议。例如，Microsoft和各个CORBA ORB供应商（例如Iona）一样，已经承诺支持SOAP。IBM在创建SOAP协议的过程中起到了重要的作用，它也为Java程序员创建了一个优秀的SOAP工具包。该公司已经把工具包捐赠给Apache Software Foundation的XML Project，后者以该软件包为基础，构造出了Apache-SOAP实现。这个实现在Apache许可之下免费提供给用户。再返回来看本文开头提出的问题，如果DCOM使用SOAP，ORB供应商也使用了SOAP，那么，COM/CORBA协同操作中出现的问题将变得不值一提。</li>
</ul>
<br />
SOAP决不只是一个漂亮的口号，它是一种即将深入渗透到未来分布式计算的技术。人们希望，SOAP结合其他技术，比如UDDI（Universal Discovery Description, and Integration）和WSDL（Web Services Description Language），在Web服务这一概念的支持下，改变未来商业应用跨越Web进行通信的方法。我甚至无法充分地表达出在开发者的工具包中加上SOAP知识的重要程度。这是一个关于SOAP的系列文章，总共四篇。这是第一篇，介绍一些基础知识。我们将从SOAP这一思想的构思说起。 <br />
<br />
<strong>一、SOAP简介</strong> <br />
如前所述，SOAP用XML作为数据编码格式。用XML作为数据编码格式并非SOAP的原创，实际上这是一种相当自然的选择。XML-RPC和ebXML也同样使用XML。要了解这方面的更多信息，请参见本文最后的&#8220;参考资源&#8221;。 <br />
<br />
请考虑下面的Java接口： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 1<br />
            <br />
            public interface Hello<br />
            {<br />
            public String sayHelloTo(String name);<br />
            }</td>
        </tr>
    </tbody>
</table>
<br />
客户程序在调用sayHelloTo()方法时提供了一个名字，它希望从服务器接收到一则个性化的&#8220;Hello&#8221;信息。现在，假定RMI、CORBA和DCOM都不存在，开发者必须负责串行化方法调用，并把消息发送给远程机器。几乎所有的人都会说&#8220;这该使用XML&#8221;，我同意。因此，让我们先从对服务器的请求格式开始。假设要模拟sayHelloTo("John")调用，我打算发送的请求是： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 2<br />
            <br />
            &lt;?xml version="1.0"?&gt;<br />
            &lt;Hello&gt;<br />
            &lt;sayHelloTo&gt;<br />
            &lt;name&gt;John&lt;/name&gt;<br />
            &lt;/sayHelloTo&gt;<br />
            &lt;/Hello&gt;</td>
        </tr>
    </tbody>
</table>
<br />
在这里，我把接口的名字作为根结点。另外，我还把方法名字和参数名字都当作节点。接下来，我们要把这个请求发送给服务器。我们不创建自己的TCP/IP消息，而是使用HTTP。因此，下面的步骤应该是把请求封装成HTTP POST请求格式，然后把它发送给服务器。实际创建该HTTP POST请求的详细过程在本文后面介绍，现在，我们先假定它已经创建完毕。服务器接收到了这个请求，解码XML，然后再以XML格式向客户程序发送应答。假设应答内容如下： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 3<br />
            <br />
            &lt;?xml version="1.0"?&gt;<br />
            &lt;Hello&gt;<br />
            &lt;sayHelloToResponse&gt;<br />
            &lt;message&gt;Hello John, How are you?&lt;/message&gt;<br />
            &lt;/sayHelloToResponse&gt;<br />
            &lt;/Hello&gt;</td>
        </tr>
    </tbody>
</table>
<br />
根节点仍然是接口的名字Hello。但这一次，原来对应着方法的节点名字不再是sayHelloTo，而是方法的名字加上&#8220;Response&#8221;字符串。客户程序知道自己调用了哪一个方法，要找出被调用方法的返回值，它只需查看名字为方法名字加上&#8220;Response&#8221;字符串的元素。 <br />
<br />
以上就是SOAP的根本思路。Listing 4显示了同一请求用SOAP XML编码之后的结果： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 4<br />
            <br />
            &lt;SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org/1999/XMLSchema"&gt;<br />
            &lt;SOAP-ENV:Header&gt;<br />
            &lt;/SOAP-ENV:Header&gt;<br />
            &lt;SOAP-ENV:Body&gt;<br />
            &lt;ns1:sayHelloTo<br />
            xmlns:ns1="Hello"<br />
            SOAP-ENV:encodingStyle="<br />
            http://schemas.xmlsoap.org/soap/encoding/"&gt;<br />
            &lt;name xsi:type="xsd:string"&gt;John&lt;/name&gt;<br />
            &lt;/ns1:sayHelloTo&gt;<br />
            &lt;/SOAP-ENV:Body&gt;<br />
            &lt;/SOAP-ENV:Envelope&gt;</td>
        </tr>
    </tbody>
</table>
<br />
看起来稍微复杂了一点，对吧？实际上，它和我们前面编写的请求类似，只是略微扩展了一些东西。首先，注意SOAP文档通过一个Envelope（根节点）、一个Header区、一个Body区，整洁地组织到一起。Header区用来封装那些与方法本身无直接关系的数据，提供环境方面的信息，比如事务ID和安全信息。Body区包含面向方法本身的信息。在Listing 2中，我们自己编写的XML只包含一个Body区。 <br />
<br />
第二，注意Listing 4大量地应用了XML名称空间。SOAP-ENV映射到名称空间http://schemas.xmlsoap.org/soap/envelope/，xsi映射到http://www.w3.org/1999/XMLSchema-instance，而xsd映射到http://www.w3.org/1999/XMLSchema。这三者是所有SOAP文档都拥有的标准名称空间。 <br />
<br />
最后，在Listing 4中，接口名称（即Hello）不再象在Listing 2中那样成为节点的名字；相反，它引用了一个名称空间nsl。另外，参数的类型信息也随同参数的值一起发送给了服务器。注意信封（Envelope）encodingStyle属性的值。这个属性值设置成了http://schemas.xmlsoap.org/soap/encoding/。这个值告诉服务器用来编码（即串行化）方法的编码方式；服务器需要这个信息，以便正确地解除方法的串行化。对于服务器来说，SOAP文档的自我描述能力是相当完善的。 <br />
<br />
对于上面的SOAP请求，服务器的应答如下： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 5<br />
            <br />
            &lt;SOAP-ENV:Envelope<br />
            xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"<br />
            xmlns:xsi="http://www.w3.org/1999/XMLSchema-nstance"<br />
            xmlns:xsd="http://www.w3.org/1999/XMLSchema"&gt;<br />
            &lt;SOAP-ENV:Body&gt;<br />
            &lt;ns1:sayHelloToResponse<br />
            xmlns:ns1="Hello"<br />
            SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"&gt;<br />
            &lt;return xsi:type="xsd:string"&gt;Hello John, How are you doing?&lt;/return&gt;<br />
            &lt;/ns1:sayHelloToResponse&gt;<br />
            &lt;/SOAP-ENV:Body&gt;<br />
            &lt;/SOAP-ENV:Envelope&gt;</td>
        </tr>
    </tbody>
</table>
<br />
Listing 5与Listing 4的请求消息类似。在上面的代码中，返回值（即个性化的&#8220;Hello&#8221;消息）包含在Body区。SOAP消息文档的格式非常灵活。例如，编码方式不固定，而是由客户程序指定。只要是客户程序和服务器都认可的编码方式，可以是任何合法的XML文档。 <br />
<br />
另外，分离调用环境信息意味着方法本身并不关心这类信息。在当前的市场上，主流应用服务器都遵从这一理念。早先，我曾经指出环境信息可以包含事务和安全方面的信息。事实上，环境可以涵盖几乎所有的东西。下面是一个SOAP消息头的例子，它带有一些事务方面的信息： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 6<br />
            <br />
            &lt;SOAP-ENV:Header&gt;<br />
            &lt;t:Transaction xmlns:t="some-URI" SOAP-ENV:mustUnderstand="1"&gt;<br />
            5<br />
            &lt;/t:Transaction&gt;<br />
            &lt;/SOAP-ENV:Header&gt;</td>
        </tr>
    </tbody>
</table>
<br />
名称空间t映射到了与特定应用有关的URI。这里的5表示的是该方法从属于其中的事务ID。注意SOAP信封mustUnderstand属性的应用。这个属性被设置成了1，它表示服务器要么理解并按照要求处理该事务请求，要么表示无法处理该请求；这是SOAP规范所要求的。 <br />
<br />
<strong>二、错误处理</strong> <br />
使用SOAP并不意味着任何时候所有的请求都会获得成功。许多地方可能会出现差错。例如，服务器可能无法访问某个关键性的资源（比如数据库），因而无法顺利地处理请求。 <br />
<br />
让我们返回&#8220;Hello&#8221;实例，为它加上一个假想的约束，即&#8220;在星期二向别人说Hello不合法。&#8221;因此，星期二的时候，即使发送给服务器的请求是合法的，服务器也会把一个错误信息返回给客户端。应答内容将如下所示： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 7<br />
            <br />
            &lt;SOAP-ENV:Envelope xmlns:SOAP-ENV="<br />
            http://schemas.xmlsoap.org/soap/envelope/"&gt;<br />
            &lt;SOAP-ENV:Body&gt;<br />
            &lt;SOAP-ENV:Fault&gt;<br />
            &lt;faultcode&gt;SOAP-ENV:Server&lt;/faultcode&gt;<br />
            &lt;faultstring&gt;Server Error&lt;/faultstring&gt;<br />
            &lt;detail&gt;<br />
            &lt;e:myfaultdetails xmlns:e="Hello"&gt;<br />
            &lt;message&gt;<br />
            Sorry, my silly constraint says that I cannot say hello on Tuesday.<br />
            &lt;/message&gt;<br />
            &lt;errorcode&gt;<br />
            1001<br />
            &lt;/errorcode&gt;<br />
            &lt;/e:myfaultdetails&gt;<br />
            &lt;/detail&gt;<br />
            &lt;/SOAP-ENV:Fault&gt;<br />
            &lt;/SOAP-ENV:Body&gt;<br />
            &lt;/SOAP-ENV:Envelope&gt;</td>
        </tr>
    </tbody>
</table>
<br />
我们来分析一下http://schemas.xmlsoap.org/soap/envelope/名称空间定义的Fault元素。Fault元素总是Body元素的直接子元素，所有的SOAP服务器必须始终通过该元素报告所有错误情况。Fault元素必须包含faultcode和faultstring元素，不能有例外。faultcode是一个能够标识问题的代码；客户程序按照SOAP规范的要求利用faultcode进行算法处理。SOAP规范定义了一小组错误代码供用户使用。另一方面，faultstring是供人类阅读的错误信息。 <br />
<br />
Listing 7的代码还包含了一个detail元素。由于错误在处理SOAP消息的Body区时出现，detail元素必须出现。正如你将在本文后面看到的，如果错误在处理Header区时出现，detail元素不应出现。在Listing 7中，应用利用detail元素提供当前错误更详细、更自然的解释，即星期二不允许说Hello。SOAP还提供另外一个面向具体应用的错误代码，即半可选的faultfactor元素，但上面的错误信息中没有显示这个元素。之所以称这个元素是半可选的，是因为如果错误消息不是由请求最终处理点的服务器发送，即由一个中间服务器发送，则错误消息必须包含该元素。SOAP对faultcode元素不应出现的情形没有作任何规定。 <br />
<br />
在Listing 7中，错误起源于方法调用本身，处理该方法的应用导致了这个错误。现在，我们来看一下另一种类型的错误，这种错误由于服务器不能处理请求头信息而导致。举例来说，假设所有的Hello消息必须在一个事务环境之内生成，则请求类似于： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 8
            <p>&lt;SOAP-ENV:Envelope<br />
            xmlns:SOAP-ENV="<br />
            http://schemas.xmlsoap.org/soap/envelope/"<br />
            xmlns:xsi="<br />
            http://www.w3.org/1999/XMLSchema-instance"<br />
            xmlns:xsd="http://www.w3.org/1999/XMLSchema"&gt;<br />
            &lt;SOAP-ENV:Header&gt;<br />
            &lt;t:Transaction xmlns:t="some-URI"<br />
            SOAP-ENV:mustUnderstand="1"&gt;<br />
            5<br />
            &lt;/t:Transaction&gt;<br />
            &lt;/SOAP-ENV:Header&gt;<br />
            &lt;SOAP-ENV:Body&gt;<br />
            &lt;ns1:sayHelloTo<br />
            xmlns:ns1="Hello"<br />
            SOAP-ENV:encodingStyle="<br />
            http://schemas.xmlsoap.org/soap/encoding/"&gt;<br />
            &lt;name xsi:type="xsd:string"&gt;Tarak&lt;/name&gt;<br />
            &lt;/ns1:sayHelloTo&gt;<br />
            &lt;/SOAP-ENV:Body&gt;<br />
            &lt;/SOAP-ENV:Envelope&gt;</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
上面消息的Header区包含一个transaction元素，它指定了方法调用必须从属于其中的事务编号。这里我说&#8220;必须&#8221;是因为transaction元素使用了mustUnderstand属性。如前所述，SOAP服务器要么遵照属性的指示处理请求，要么声明不能处理请求。假定SOAP服务器不能处理，它必须返回一个错误信息。这时的应答应该类似于： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 9
            <p>&lt;SOAP-ENV:Envelope xmlns:SOAP-ENV="<br />
            http://schemas.xmlsoap.org/soap/envelope/"&gt;<br />
            &lt;SOAP-ENV:Body&gt;<br />
            &lt;SOAP-ENV:Fault&gt;<br />
            &lt;faultcode&gt;SOAP-ENV:MustUnderstand&lt;/faultcode&gt;<br />
            &lt;faultstring&gt;SOAP Must Understand<br />
            Error&lt;/faultstring&gt;<br />
            &lt;/SOAP-ENV:Fault&gt;<br />
            &lt;/SOAP-ENV:Body&gt;<br />
            &lt;/SOAP-ENV:Envelope&gt;</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
上面的代码类似Listing 7显示的错误信息。但应该注意的是，detail元素不再出现。正如我在前面指出的：SOAP规范规定，如果错误在处理Header区的时候出现，则错误消息中不应包含detail元素。事实上，我们可以根据detail元素是否出现，迅速判定错误是在处理Body区还是在处理Header区时出现。 <br />
<br />
<strong>三、SOAP与HTTP</strong> <br />
在第一个例子中，我通过HTTP把定制的XML请求发送给服务器，但没有详细介绍这么做涉及到了哪些操作。现在我们回过头来看那个问题。怎样才能把一个SOAP请求（而不是定制的XML）通过HTTP发送给服务器？SOAP很自然地遵循了HTTP的请求/应答消息模型。这个模型在HTTP请求中提供SOAP请求参数，在HTTP应答中提供SOAP应答参数。实际上，SOAP 1.0特别指明HTTP作为它的传输协议。SOAP 1.1略有放松。虽然SOAP 1.1仍旧可以使用HTTP，但它也可以使用其他协议，比如SMTP。在这个系列的文章中，我只讨论通过HTTP使用SOAP的情形。 <br />
<br />
让我们返回Hello示例。如果我们通过HTTP把SOAP请求发送给服务器，则代码应该类似于： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 10
            <p>POST http://www.SmartHello.com/HelloApplication HTTP/1.0<br />
            Content-Type: text/xml; charset="utf-8"<br />
            Content-Length: 587<br />
            SOAPAction: "http://www.SmartHello.com/HelloApplication#sayHelloTo"<br />
            &lt;SOAP-ENV:Envelope<br />
            xmlns:SOAP-ENV="<br />
            http://schemas.xmlsoap.org/soap/envelope/"<br />
            xmlns:xsi="<br />
            http://www.w3.org/1999/XMLSchema-instance"<br />
            xmlns:xsd="http://www.w3.org/1999/XMLSchema"&gt;<br />
            &lt;SOAP-ENV:Header&gt;<br />
            &lt;/SOAP-ENV:Header&gt;<br />
            &lt;SOAP-ENV:Body&gt;<br />
            &lt;ns1:sayHelloTo<br />
            xmlns:ns1="Hello"<br />
            SOAP-ENV:encodingStyle="<br />
            http://schemas.xmlsoap.org/soap/encoding/"&gt;<br />
            &lt;name xsi:type="xsd:string"&gt;Tarak&lt;/name&gt;<br />
            &lt;/ns1:sayHelloTo&gt;<br />
            &lt;/SOAP-ENV:Body&gt;<br />
            &lt;/SOAP-ENV:Envelope&gt;</p>
            <p>&nbsp;</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
Listing 10代表的SOAP请求与Listing 4的请求基本相同，但Listing 10的开头加入了一些HTTP特有的代码。第一行代码表明这是一个遵循HTTP 1.1规范的POST请求，POST的目标是http://www.SmartHello.com/HelloApplication。下一行指示内容的类型，在HTTP消息中包含SOAP实体时，内容类型必须是text/xml。Content-Length指明了POST请求有效载荷的长度。 <br />
<br />
第四行是SOAP特有的，而且它是必不可少的。SOAPAction HTTP请求头指明了SOAP HTTP请求的目标，它的值是一个标识目标的URI。SOAP不对该URI的格式作任何限制，实际上，这个URI甚至不必对应某个实际的位置。 <br />
<br />
SOAPAction的一个可能的应用是，<a href="http://www.ccidnet.com/school/dict/explain/F_809.html">防火墙</a>检查该请求头的值，决定是否允许请求通过<a href="http://www.ccidnet.com/school/dict/explain/F_809.html">防火墙</a>。 <br />
<br />
一旦服务器处理完请求，它将向客户返回一个应答。应答的内容如Listing 11所示（假设没有出现错误）： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 11
            <p>HTTP/1.0 200 OK<br />
            Content-Type: text/xml; charset="utf-8"<br />
            Content-Length: 615<br />
            &lt;SOAP-ENV:Envelope<br />
            xmlns:SOAP-ENV="<br />
            http://schemas.xmlsoap.org/soap/envelope/"<br />
            xmlns:xsi="<br />
            http://www.w3.org/1999/XMLSchema-instance"<br />
            xmlns:xsd="http://www.w3.org/1999/XMLSchema"&gt;<br />
            &lt;SOAP-ENV:Body&gt;<br />
            &lt;ns1:sayHelloToResponse<br />
            xmlns:ns1="Hello"<br />
            SOAP-ENV:encodingStyle="<br />
            http://schemas.xmlsoap.org/soap/encoding/"&gt;<br />
            &lt;return xsi:type="xsd:string"&gt;Hello John, How are<br />
            you doing?&lt;/return&gt;<br />
            &lt;/ns1:sayHelloToResponse&gt;<br />
            &lt;/SOAP-ENV:Body&gt;<br />
            &lt;/SOAP-ENV:Envelope&gt;</p>
            <p>&nbsp;</p>
            <p>&nbsp;</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
这个SOAP应答与Listing 5所显示的一样，但前面加上了一些HTTP特有的代码。由于没有出现错误，第一行代码显示应答状态是200。在HTTP协议中，200应答状态代码表示&#8220;一切正常&#8221;。如果在处理SOAP消息（Header区或者Body区）的时候出现了任何错误，则返回的状态代码将是500。在HTTP中，500状态代码表示&#8220;internal server error&#8221;。此时，上述SOAP应答的第一行代码将是： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">HTTP 500 Internal Server Error</td>
        </tr>
    </tbody>
</table>
<br />
<strong>四、HTTP扩充框架</strong> <br />
许多应用对服务的需求超过了传统HTTP提供的服务。其结果就是，这类应用扩充了传统的HTTP协议。然而，这种扩充是应用本身私有的。HTTP扩充框架试图确立一个通用的HTTP扩充机制，从而解决这个问题。HTTP扩充框架的扩充之一是增加了一个M-POST方法，其中的M表示Mandatory（必须遵循的，强制的）。如果一个HTTP请求包含至少一个强制的扩充声明，那么这个请求就称为强制的请求。引入强制的扩充声明通过Man或者C-Man头进行。强制请求的请求方法名字必须带有&#8220;M-&#8221;前缀，例如，强制的POST方法称为M-POST。 <br />
<br />
SOAP 1.0要求客户程序首先发送一个HTTP POST请求，只有当服务器返回错误510时才发送M-POST请求。SOAP 1.1不再对客户作这种限制，也就是说，SOAP 1.1允许客户从发送任何一种类型的请求开始。下面的请求就是迄今为止我们一直在讨论的那个请求，但它现在是M-POST格式： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 12
            <p>M-POST http://www.SmartHello.com/HelloApplication HTTP/1.1<br />
            Content-Type: text/xml; charset="utf-8"<br />
            Content-Length: 587<br />
            Man: "http://schemas.xmlsoap.org/soap/envelope/"; ns=01<br />
            01-SOAPAction: "http://www.SmartHello.com/HelloApplication#sayHelloTo"<br />
            &lt;SOAP-ENV:Envelope<br />
            xmlns:SOAP-ENV="<br />
            http://schemas.xmlsoap.org/soap/envelope/"<br />
            xmlns:xsi="<br />
            http://www.w3.org/1999/XMLSchema-instance"<br />
            xmlns:xsd="http://www.w3.org/1999/XMLSchema"&gt;<br />
            &lt;SOAP-ENV:Header&gt;<br />
            &lt;/SOAP-ENV:Header&gt;<br />
            &lt;SOAP-ENV:Body&gt;<br />
            &lt;ns1:sayHelloTo<br />
            xmlns:ns1="Hello"<br />
            SOAP-ENV:encodingStyle="<br />
            http://schemas.xmlsoap.org/soap/encoding/"&gt;<br />
            &lt;name xsi:type="xsd:string"&gt;Tarak&lt;/name&gt;<br />
            &lt;/ns1:sayHelloTo&gt;<br />
            &lt;/SOAP-ENV:Body&gt;<br />
            &lt;/SOAP-ENV:Envelope&gt;</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
对于实际的SOAP消息来说，Listing 12和Listing 10没有什么不同。但请求头中有一些不同的地方，例如，现在我们发出的不是POST请求，而是一个M-POST请求。正如前面所介绍的，象M-POST这样的强制请求至少有一个强制扩充声明。这里我们就有一个：Man域描述了一个强制性的端到端扩充声明，把头前缀01映射到了名称空间http://schemas.xmlsoap.org/soap/envelope/。请注意这个前缀关联到SOAPAction域的方式。 <br />
<br />
一旦服务器处理完该请求，它将返回一个应答给客户。应答内容类如（假设没有出现错误）： <br />
<br />
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="0" width="580" bordercolorlight="black" border="1">
    <tbody>
        <tr>
            <td class="code" bgcolor="#e6e6e6">Listing 13
            <p>HTTP/1.0 200 OK<br />
            Ext:<br />
            Content-Type: text/xml; charset="utf-8"<br />
            Content-Length: 615<br />
            &lt;SOAP-ENV:Envelope<br />
            xmlns:SOAP-ENV="<br />
            http://schemas.xmlsoap.org/soap/envelope/"<br />
            xmlns:xsi="<br />
            http://www.w3.org/1999/XMLSchema-instance"<br />
            xmlns:xsd="http://www.w3.org/1999/XMLSchema"&gt;<br />
            &lt;SOAP-ENV:Body&gt;<br />
            &lt;ns1:sayHelloToResponse<br />
            xmlns:ns1="Hello"<br />
            SOAP-ENV:encodingStyle="<br />
            http://schemas.xmlsoap.org/soap/encoding/"&gt;<br />
            &lt;return xsi:type="xsd:string"&gt;Hello John, How are<br />
            you doing?&lt;/return&gt;<br />
            &lt;/ns1:sayHelloToResponse&gt;<br />
            &lt;/SOAP-ENV:Body&gt;<br />
            &lt;/SOAP-ENV:Envelope&gt;</p>
            </td>
        </tr>
    </tbody>
</table>
<br />
同样地，Listing 13显示的应答类似于对普通POST请求的应答（如Listing 11所示），两者的不同之处在于Ext域。 <br />
<br />
在通过HTTP使用SOAP的过程中，我们欣喜地看到，实际的SOAP消息（SOAP信封和它里面的所有内容）总是保持不变，就如消息尚未加载HTTP协议时一样。根据这一事实可以推断出，HTTP不是能够与SOAP协作的唯一协议。例如，SOAP可以方便地和SMTP协议或者其他定制的私有协议一起运行。唯一的要求是两者——客户端和服务器端——都理解该协议。 <br />
<br />
<strong>五、SOAP的特点：简单</strong> <br />
至此为止，我们讨论了SOAP定义的方方面面，但有许多领域的问题SOAP没有定义。SOAP规范的创立者明确地排除了一些关系密切的领域，比如构造对象模型，还有其他许多已经确立的标准。 <br />
<br />
造成这种现象的原因可以从分析SOAP的目标理解。SOAP的目标除了扩展性之外，另一个主要的设计目标是简单。为了保持SOAP简单，SOAP规范的创立者决定，只定义那些对于创建一个轻型协议来说绝对必须的东西。例如，SOAP没有定义/指定任何有关分布式垃圾收集、类型安全或版本控制、双向HTTP通信、消息盒（Message-box）运输或管道处理、对象激活等方面的内容。SOAP的目标就是成为一种简单的协议——一种在任何操作系统上，单个开发者能够用任何语言化数天时间实现的协议。考虑到这一点，SOAP在许多方面没有作出明确定义实际上是一件好事，因为在构造分布式系统时，所有现有的技术都可以方便地采用SOAP，即使不同技术之间的差异象CORBA和DCOM的差异那样明显。 <br />
<br />
<strong>■ 结束语</strong> <br />
在这篇文章中，我介绍了SOAP的一些基本概念，以及它之所以如此设计的一些原因。然而，相对于SOAP这座冰山来说，这只是它的一角。要查看有关SOAP的更多信息，请查阅参考资源部分给出的SOAP规范链接。随着本系列文章的展开，我将在这里介绍有关SOAP规范所有你必须了解的知识。 <br />
<br />
在第二部分中，我将介绍Apache的SOAP实现。我们将使用该实现，创建一个利用SOAP作为有线协议的简单分布式应用
<img src ="http://www.blogjava.net/libin2722/aggbug/159454.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/libin2722/" target="_blank">礼物</a> 2007-11-09 21:59 <a href="http://www.blogjava.net/libin2722/articles/159454.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>