Web服务和SOA(五)

基于SOAP协议的Web服务风格之比较

 前面我们已经看到,SOAP可以在后台替我们完成那些比较困难的工作。但我们并没有看到服务器端和客户端交互的XML文档,实际上,我们可以利用一些TCP/IP监测工具,比如ApacheTCPMon工具来查看SOAP中传输的XML文档,其结果如代码清单16所示:

代码清单16 – SOAP中的XML请求文档

<?xml version="1.0" ?>

<soapenv:Envelope

xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:ns1="http://item.service.soap.soajava.packt.com/">

<soapenv:Body>

<ns1:insert>

<arg0>

<code>XY</code>

<description>xy desc</description>

<id>26</id>

</arg0>

</ns1:insert>

</soapenv:Body>

</soapenv:Envelope>

 

代码清单17 – SOAP中的XML应答文档

<?xml version="1.0" ?>

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">

<S:Body>

<ns2:insertResponse

xmlns:ns2="http://item.service.soap.soajava.packt.com/">

<return>

<retCode>OK</retCode>

<retMessage>Item was inserted successfully</retMessage>

</return>

</ns2:insertResponse>

</S:Body>

</S:Envelope>

您可以看出,SOAPXML消息以Envelope元素开始,包含一个必须的Body元素,但SOAP消息中的header元素是可选的。Body元素中包含的内容就是要传输消息的有效负载。然而,我们在使用SOAP时会有多种选择,就前面的例子而言,我们为了简明起见,只采用了SOAP的缺省设置。您有可能听说过SOAP的绑定风格(Binding style,其值为RPCDocument风格)、使用(use,其值为EncodedLiteral)或参数风格(parameter style,其值为BareWrapped)。在本节中,我们将探讨一下这些概念,重点介绍一下SOAP的绑定风格。

在解释这些概念之前,我们先花点时间解释一下WS-I这个概念,它是Web Service-Interoperability(Web服务的可互操作性)的缩写,它代表了一整套的标准,这些标准组合起来可以让我们在异种环境中(比如.NetJava)进行数据交换。绑定风格、使用和参数风格可以任意组合,但只有以下的组合和WS-I兼容,所以它们是我们讨论的重点:

(1)    RPC/Literal

(2)    Document/Literal(bareunwrapped)

(3)    Document/Literal wrapped

WS-I规定,use属性值不能等于”encoded(已编码的)”。如果use属性值等于encoded,数据将按照SOAP编码规范进行编码,但要验证经过SOAP编码的消息是否违反WSDL描述将非常困难,而验证工作又是可互操作性的基础,所以WS-I不对数据进行编码,它只传输literal(不经编码的)的数据。

SOAP服务的可选架构一 : RPC/Literal

SOAP服务的第一个可选架构为RPC/Literal,它决定我们是否要在Web服务中使用RPC还是要使用Document作为SOAP的绑定风格。RPC(远程过程调用)是远程调用的一种通用机制,驻留在一台计算机(或虚拟机)上的程序可被运行在远程机器(或虚拟机)上的程序调用,这种方法已经使用了几十年。RPC可由多种技术实现,其中比较流行的有CORBADCOMRMI。实现技术虽然不同,但RPC调用都具有以下特点:

(1)    需要有远程地址

(2)    需要调用的方法(或操作)

(3)    需要一系列参数

(4)    需要同步应答

请注意,除了上面的第一个特点,RPC调用与本机调用几乎相同。那么,古老的RPC方法和SOAP服务又有什么关系呢?其实它们之间关系非同一般,SOAP在其早期定义(尚未发布之前)中,其设计只支持RPC。从某种意义上说,当时的SOAP是当时各种分布式编程技术的集大成者,它对这些技术进行了标准化。

我们可以对前面的Web服务稍加修改,加些注解(annotation),我们就可以将它转化为RPC风格(JAX-WS的缺省风格为Document而非RPC),其实现代码如下:

代码清单18 – SOAPRPC风格

@WebService

@SOAPBinding(style=SOAPBinding.Style.RPC)

public class ItemWs {

@WebMethod

public Outcome insert(@WebParam(name="itemParam") Item item,

@WebParam(name="categoryParam") String category)

{

//Insert item ...

您会看到,我们在方法中加了另一个参数category,这样我们就可以把某个商品插入到某一指定的商品集中,并且我们使用了@WebParam注解给方法中的参数命名。

现在我们来发布这个服务,我们只需要运行带有EndPoint.publish的那个主类即可,而不需要运行wsgen来生成服务器端的类。然后,请使用wsimport工具根据发布的WSDL生成客户端类,并导入到客户端项目中,这样,我们就可以通过下面的方式在客户端调用该RPC风格的SOAP服务。

Outcome outcome = itemWs.insert(item1, "A");

如果我们监测XML请求文档,我们将会看到如下结构:

代码清单19 –RPC风格的SOAP请求文档

<soapenv:Body>

<ans:insert xmlns:ans="http:// ... ">

<itemParam>

<code>XY</code>

<description>xy desc</description>

<id>26</id>

</itemParam>

<categoryParam>A</categoryParam>

</ans:insert>

</soapenv:Body>

我们可以从上面的请求文档中看到RPC的典型风格,即方法名加上一系列参数。其相应的WSDL(您可以使用TCP监测工具查看)文档如下所示:

代码清单20 –RPC风格的WSDL文档

<types>

<xsd:schema>

<xsd:import schemaLocation="http://127.0.0.1:8002/SoaBookSOAP_RPC_server/itemWs?xsd=1"

namespace="http://item.service.soap.soajava.packt.com/"></xsd:import>

</xsd:schema>

</types>

<message name="insert">

<part name="itemParam" type="tns:item"></part>

<part name="categoryParam" type="xsd:string"></part>

</message>

<message name="insertResponse">

<part name="return" type="tns:outcome"></part>

</message>

<portType name="ItemWs">

<operation name="insert" parameterOrder="itemParam categoryParam">

<input message="tns:insert"></input>

<output message="tns:insertResponse"></output>

</operation>

</portType>

<binding name="ItemWsPortBinding" type="tns:ItemWs">

<soap:binding style="rpc"

transport="http://schemas.xmlsoap.org/soap/http">

</soap:binding>

<operation name="insert">

<soap:operation soapAction=""></soap:operation>

<input>

<soap:body use="literal" namespace=

"http://item.service.soap.soajava.packt.com/">

</soap:body>

</input>

<output>

<soap:body use="literal" namespace=

"http://item.service.soap.soajava.packt.com/">

</soap:body>

</output>

</operation>

</binding>

<service name="ItemWsService">

<port name="ItemWsPort" binding="tns:ItemWsPortBinding">

<soap:address location=

"http://127.0.0.1:8002/SoaBookSOAP_RPC_server/itemWs">

</soap:address>

</port>

</service>

</definitions>

上面WSDLXML Schema文档可以通过下面的URL地址来访问:

http://127.0.0.1:8001/SoaBookSOAP_RPC_server/itemWs?xsd=1

WSDLXML Schema文档内容如下所示:

代码清单21 –RPC风格WSDL文档的XML Schema(XSD)文档

<?xml version="1.0" encoding="UTF-8"?>

<xs:schema xmlns:tns="http://item.service.soap.soajava.packt.com/" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://item.service.soap.soajava.packt.com/" version="1.0">

<xs:element name="Item" type="tns:item"></xs:element>

<xs:element name="Outcome" type="tns:outcome"></xs:element>

<xs:complexType name="item">

<xs:sequence>

<xs:element name="code" type="xs:string"

minOccurs="0"></xs:element>

<xs:element name="description" type="xs:string"

minOccurs="0"></xs:element>

<xs:element name="id" type="xs:int"></xs:element>

</xs:sequence>

</xs:complexType>

<xs:complexType name="outcome">

<xs:sequence>

<xs:element name="retCode" type="xs:string"

minOccurs="0"></xs:element>

<xs:element name="retMessage" type="xs:string"

minOccurs="0"></xs:element>

</xs:sequence>

</xs:complexType>

</xs:schema>

这里,我们需要注意的是,上面的XSD只定义了复杂类型元素,如本例中的ItemOutcome类型,但它没有办法提供有效的信息来验证简单数据类型的参数,也不能验证SOAP消息的其它部分。因此,采用RPC架构的主要问题是,如何才能验证要交换消息的XML文档符合XSD规范?

接下来我们看看Document风格的架构能否客服这一缺点。

SOAP服务的可选架构二 : Document/Literal

这种SOAP风格的实现也称之为”bare””unwrapped”(未组装的)Document风格,为什么叫未组装的Document,我们马上会解释其中的含义。但现在请您注意,JAX-WS中的参数类型的缺省值是wrapped(已组装的),因此,为了使用bare类型的document风格,我们需要在源代码中显式声明:

代码清单22 –Document风格SOAP服务实现

@SOAPBinding(style=SOAPBinding.Style.DOCUMENT,

parameterStyle=SOAPBinding.ParameterStyle.BARE)

public class ItemWs {

@WebMethod

public Outcome insert(@WebParam(name="itemParam") Item item,

@WebParam(name="categoryParam") String category) {

...

如果您现在按正常的步骤在客户端调用上面的服务,您会在导入由WSDL产生的客户端类时发现下面的错误:

error: operation "insert": more than one part bound to body

这个错误有助于我们理解RPCDocument风格的SOAP实现方法之间的区别,我们已经了解,WS-I只允许SOAP消息体中只能包含一个XML根元素,这是为什么呢?

Document风格是SOAP实现的一种新方法,它不同于以往的方法。其表现在,服务要求的输入是一个文档,而不是一个带方法名及其相应参数的一个请求。这意味着在Document风格中,我们应该传入一个对象,这个对象将是Web服务的唯一输入。Document中会包含服务调用的所有有用信息,但不是方法名及其相应参数值的组合。这就是WS-I为什么只允许Document只能包含一个根元素的原因。

因此,为了调用上面的Document风格的服务,把商品插入到商品目录中,并保持和WS-I兼容,我们应重构服务的源代码。我们应该新建一个对新,比如ItemInsertRequest对象,这个对象将包装所有需要的信息(商品及其目录)。这就是为什么这种风格又称之为”bare””unwrapped”Document风格的由来,它没有对XML消息的组成部分进行包装,我们必须手动的显式包装它们。

代码清单23 –Document风格的SOAP服务对请求进行组装

@XmlRootElement(name = "ItemInsertRequest")

public class ItemInsertRequest {

private Item item;

private String category;

...

而且,为了使Web服务只需要一个参数,我们还需要对Web服务的实现进行重构,其实现代码如下:

代码清单24 –Document风格的SOAP服务实现代码(使用了上面定义的封装类)

@WebMethod

public Outcome insert(@WebParam(name="itemInsertRequestParam")

ItemInsertRequest itemInsertRequest) {

...

此时,但客户端使用wsimport工具生成客户端需要的类时,就不会产生上面的错误,客户端最终调用服务的代码如下:

代码清单25 –Document风格的客户端调用代码

ItemInsertRequest req = new ItemInsertRequest();

req.setItem(item1);

req.setCategory("A");

Outcome outcome = itemWs.insert(req);

 

上面的代码执行后,它会想服务器发出SOAP请求,其请求消息体的内容如下:

代码清单26 –Document风格XML请求

<ns1:itemInsertRequestParam>

<category>A</category>

<item>

<code>XY</code>

<description>xy desc</description>

<id>26</id>

</item>

</ns1:itemInsertRequestParam>

请注意,上面Document风格的XML请求在结构上虽然和RPC风格相似(请参展代码清单19),但我们现在处理的是XML文档(Document),而不是对远程方法的调用。在Document文档中,商品及其目录只是文档的属性;但在RPC风格的方法调用中,它们是作为方法的参数传入的。

代码清单27–Document风格WSDL

<message name="insert">

<part element="tns:itemInsertRequestParam"

name="itemInsertRequestParam"></part>

</message>

...

<binding name="ItemWsPortBinding" type="tns:ItemWs">

<soap:binding style="document"

这时,上面WSDL附属的XSD就可以用来对整个WSDL文档进行验证。

代码清单28–Document风格WSDL附属的XSD文档

<xs:element name="Item" type="tns:item"></xs:element>

<xs:element name="ItemInsertRequest"

type="tns:itemInsertRequest"></xs:element>

<xs:element name="Outcome" type="tns:outcome"></xs:element>

<xs:element nillable="true" name="insertResponse"

type="tns:outcome"></xs:element>

<xs:element nillable="true" name="itemInsertRequestParam"

type="tns:itemInsertRequest"></xs:element>

<xs:complexType name="itemInsertRequest">

<xs:sequence>

<xs:element name="category" type="xs:string"

minOccurs="0"></xs:element>

<xs:element name="item" type="tns:item"

minOccurs="0"></xs:element>

</xs:sequence>

</xs:complexType>

<xs:complexType name="item">

<xs:sequence>

<xs:element name="code" type="xs:string"

minOccurs="0"></xs:element>

<xs:element name="description" type="xs:string"

minOccurs="0"></xs:element>

<xs:element name="id" type="xs:int"></xs:element>

</xs:sequence>

</xs:complexType>

Document/Literal风格的SOAP服务的主要优势在于,它能对整个XML交换消息进行验证。

但是,使用这种方法,我们会丢掉一些信息,如我们在SOAP消息中没有发现操作的名称;在某些情况下,比如在使用SMTP等异步TCP/IP协议来传输消息时,这可能会有所不足。因为此时消息的派发将变得非常困难,有时甚至不可能做到。

另外,这种方法的另一个缺点是,对那些已经开发完成的应用,我们得考虑花力气对它们进行改造,即对服务器端和客户端的代码进行重构。

SOAP服务的可选架构三 : Document/Literal Wrapped

Document/Literal Wrapped风格是JAX-WS的缺省风格,在我们第一个SOAP示例中,我们用的就是这种风格(请参考程序清单14)。因此,下面的注解其实是没用的。

代码清单29–Document  Wrapped风格的服务实现

@SOAPBinding(style=SOAPBinding.Style.DOCUMENT,

parameterStyle=SOAPBinding.ParameterStyle.WRAPPED)

现在,我们在Document bare风格代码的基础上,初始化Web服务的结构。

代码清单30–Document  Wrapped风格的服务实现

@WebMethod

public Outcome insert(@WebParam(name="itemParam") Item item,

@WebParam(name="categoryParam") String category) {

要在客户端调用该Web服务,按照正常的步骤进行即可(此时需要调用wsgen工具生成服务器端需要的类)

现在,即使Web方法中带有类似于RPC风格的多个参数,在服务的调用过程中也不会产生错误。这是如何做到的呢?原来,它只是对方法名和参数进行简单的封装,将方法名和参数当成一个新的对象,新对象名和Web方法名正好一致。当然,这个封装过程是自动完成的。此时,SOAP消息体的内容如下:

代码清单31–Document  Wrapped风格的XML请求

<ns1:insert>

<itemParam>

<code>XY</code>

<description>xy desc</description>

<id>26</id>

</itemParam>

<categoryParam>A</categoryParam>

</ns1:insert>

总之,Document/literal wrapped风格集成DocumentRPC方法的优势,它具有以下优点:

(1)    SOAP消息可以用XML Schema进行验证

(2)    SOAP消息体只有一个子元素,因此它和WS-I兼容

(3)    如果Web方法带有多个参数,不需要修改代码就可发布这种类型的服务

(4)    操作的方法名包含在SOAP消息中

 

你可能感兴趣的:(String,文档,insert,SOAP,web服务,SOA)