WebService和编程语言中的类相似,它们都实现了一些功能,并且通过方法供给外部调用,不同的是WebService是部署在网络服务器上,而类在同一个进程空间里面。WebService部署在网络上,通过网络进行服务调用,只要遵循了访问协议,任何编程语言都可以访问服务,所以它是编程语言不相关的。
我们可以通过类文件知道一个类提供了哪些方法,那怎么知道WebService提供了哪些服务供外部调用呢?每一个WebService都有一个WSDL文件,WSDL全称Web Services Description Language(网络服务描述语言),WSDL有两个作用:
- 描述了WebService提供的服务,就像类里面的方法,包含名字、参数、返回值。
- 描述了访问服务的方法。
下面是一个WSDL的示例文件:
<?xml version="1.0" encoding="utf-8"?> <!-- Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.2.7-b01 svn-revision#${svn.Last.Changed.Rev}. --> <!-- Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.2.7-b01 svn-revision#${svn.Last.Changed.Rev}. --> <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis--wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://www.w3.org/ns/ws-policy" xmlns:wsp1_2="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://example/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://example/" name="HelloWorldService"> <types> <xsd:schema> <xsd:import namespace="http://example/" schemaLocation="http://localhost:9090/HelloWorld?xsd=1"/> </xsd:schema> </types> <message name="sayHelloWorld"> <part name="parameters" element="tns:sayHelloWorld"/> </message> <message name="sayHelloWorldResponse"> <part name="parameters" element="tns:sayHelloWorldResponse"/> </message> <portType name="HelloWorld"> <operation name="sayHelloWorld"> <input wsam:Action="http://example/HelloWorld/sayHelloWorldRequest" message="tns:sayHelloWorld"/> <output wsam:Action="http://example/HelloWorld/sayHelloWorldResponse" message="tns:sayHelloWorldResponse"/> </operation> </portType> <binding name="HelloWorldPortBinding" type="tns:HelloWorld"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/> <operation name="sayHelloWorld"> <soap:operation soapAction=""/> <input> <soap:body use="literal"/> </input> <output> <soap:body use="literal"/> </output> </operation> </binding> <service name="HelloWorldService"> <port name="HelloWorldPort" binding="tns:HelloWorldPortBinding"> <soap:address location="http://localhost:9090/HelloWorld"/> </port> </service> </definitions>
只听到从知秋君办公室传来知秋君的声音: 湛湛长空黑。有谁来对上联或下联?
WSDL包含了五部分内容:
- types
- message
- portType
- binding
- service
其中portType里面的内容描述了WebService提供的服务,上例中的portType内容可以翻译为Java类,如下:
此代码由一叶知秋网-知秋君整理<portType name="HelloWorld"> ==> 对应类名 <operation name="sayHelloWorld"> ==> 对应方法名 ...... </operation> </portType>
//翻译后的portType内容 public class HelloWorld { public ... sayHelloWorld(...) { // ...... } }
可以看到sayHelloWorld方法中的参数和返回值使用了"...",这是因为通过portType无法直接得到参数和返回值,所以用“...”代替。至于具体的参数和返回值怎么获取,我们在后面讲解,姑且先当做void看待。现在知道了WebService提供的方法,要怎么调用它呢?还是得看WSDL,我们知道WebService是部署在网络上的,访问WebService就需要进行网络数据交互,网络数据交互需要通信协议。 WSDL中的binding部分描述了通信协议,WebService通常是HTTP + SOAP(Simple Object Access Protocol 简单对象访问协议)协议来进行数据交互,SOAP基于XML它被设计成在WEB上交换结构化的和固化的信息。本例中WebService也是采用HTTP+SOAP协议进行数据交互,本文中WSDL的binding内容如下所示:
此代码由一叶知秋网-知秋君整理<!-- 为HelloWorld绑定通信协议 --> <binding name="HelloWorldPortBinding" type="tns:HelloWorld"> <!-- HelloWorld 使用 http + soap 协议进行数据交互--> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/> <operation name="sayHelloWorld"> <soap:operation soapAction=""/> <input> <soap:body use="literal"/> </input> <output> <soap:body use="literal"/> </output> </operation> </binding>
下面是一个 HTTP+SOAP 示例:
POST URI HTTP/1.1 Accept: text/xml, multipart/related Content-Type: text/xml; charset=utf-8 SOAPAction: "Action" User-Agent: JAX-WS RI 2.2.7-b01 svn-revision#${svn.Last.Changed.Rev} Host: localhost:9090 Connection: keep-alive Content-Length: xxx <!-- SOAP协议 --> <?xml version="1.0" encoding="utf-8"?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Body> <!-- 内容由使用者定义 --> <ns2:自定义元素 xmlns:ns2="http://example/"> 自定义元素内容 </ns2:自定义元素> </S:Body> </S:Envelope>
可以看到HTTP+SOAP就是把SOAP放入HTTP的body部分使用POST发送给WebService服务器。SOAP协议的Body内容由使用者定义,WSDL为每个WebService方法的调用和返回定义了SOAP body格式也就是定义了一个XML元素,只要找到 WebService 方法对应的自定义元素,就能封装SOAP协议了。通过WSDL的portType 、message两部分内容可以找到方法对应的自定义元素,本例中这两部分内容如下所示:
<message name="sayHelloWorld"> <!-- 关联了一个sayHelloWorld元素 --> <part name="parameters" element="tns:sayHelloWorld"/> </message> <message name="sayHelloWorldResponse"> <!-- 关联了一个sayHelloWorldResponse元素 --> <part name="parameters" element="tns:sayHelloWorldResponse"/> </message> <portType name="HelloWorld"> <operation name="sayHelloWorld"> <!-- input关联了一个sayHelloWorld message--> <input wsam:Action="http://example/HelloWorld/sayHelloWorldRequest" message="tns:sayHelloWorld"/> <!-- output关联了一个sayHelloWorldResponse message--> <output wsam:Action="http://example/HelloWorld/sayHelloWorldResponse" message="tns:sayHelloWorldResponse"/> </operation> </portType>
portType中的sayHelloWorld方法有一个input和output,input表示调用,output表示返回,input元素关联了一个message,本例中为“sayHelloWorld”,这个message关联了一个元素,本例中为sayHelloWorld,message关联的这个元素就是我们用来填充到SOAP body的元素了,同理output关联的元素为 “ sayHelloWorldResponse ” 。现在我们知道了调用 sayHelloWorld方法时使用
sayHelloWorld 元素填充Soap body,返回值使用 sayHelloWorldResponse 元素填充Soap body。但是我们还不知道这两个元素的具体定义,通过types部分我们可以知道这两个元素的具体定义,本例中的types定义如下:
<types> <xsd:schema> <!--从http://localhost:9090/HelloWorld?xsd=1引入元素定义 --> <xsd:import namespace="http://example/" schemaLocation="http://localhost:9090/HelloWorld?xsd=1"/> </xsd:schema> </types>
types从http://localhost:9090/HelloWorld?xsd=1引入了元素定义,引入的内容如下:
<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://example/" version="1.0" targetNamespace="http://example/"> <xs:element name="sayHelloWorld" type="tns:sayHelloWorld"/> <xs:element name="sayHelloWorldResponse" type="tns:sayHelloWorldResponse"/> <xs:complexType name="sayHelloWorld"> <xs:sequence> <!-- sayHelloWorld方法的参数 --> <xs:element name="arg0" type="xs:string" minOccurs="0"/> </xs:sequence> </xs:complexType> <xs:complexType name="sayHelloWorldResponse"> <xs:sequence> <!-- sayHelloWorld方法的返回值 --> <xs:element name="return" type="xs:string" minOccurs="0"/> </xs:sequence> </xs:complexType> </xs:schema>
可以看到sayHelloWorld元素包含一个类型为string的arg0元素,表示 sayHelloWorld 方法有一个string类型的参数。saysayHelloWorldResponse包含一个类型为string的return元素, 表示 sayHelloWorld 返回一个string类型的值。知道了参数和方法Webservice描述的方法就可以用Java完整的翻译出来,如下:
//WebService提供的服务 public class HelloWorld { public String sayHelloWorld(String arg0) { // ...... } }
知道了 sayHelloWorld 元素和 saysayHelloWorldResponse 元素的定义以后我们就知道怎么封装Soap了,调用 sayHelloWorld 并且传参数jack的Soap封装为:
<?xml version="1.0" encoding="utf-8"?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Body> <!-- 元素sayHelloWorld包含一个string类型的arg0元素表示参数 --> <ns2:sayHelloWorld xmlns:ns2="http://example/"> <!-- 传参jack --> <arg0>jack</arg0> </ns2:sayHelloWorld> </S:Body> </S:Envelope>
返回时的Soap封装如下:
<?xml version="1.0" encoding="utf-8"?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Body> <!-- 元素 sayHelloWorldResponse 包含一个string类型的return元素表示返回值 --> <ns2:sayHelloWorldResponse xmlns:ns2="http://example/"> <return>Hello, world, from jack</return> </ns2:sayHelloWorldResponse> </S:Body> </S:Envelope>
可以看到sayHelloWorld返回值是“Hello, world, from jack”。封装好Soap协议以后就可以往Webservice服务器发送协议了,但是我们目前还不知道Webservice的地址,通过WSDL的service部分可以获取Webservice的地址,本例中的service内容如下:
<!-- WebService的地址从service部分获取 --> <service name="HelloWorldService"> <port name="HelloWorldPort" binding="tns:HelloWorldPortBinding"> <!-- HelloWorld 的URI --> <soap:address location="http://localhost:9090/HelloWorld"/> </port> </service>
地址http://localhost:9090/HelloWorld即为Webservice的服务器地址。另外HTTP头部有
SOAPAction需要填充,它来自 WSDL的portType的input和output,示例如下:
<input wsam:Action="http://example/HelloWorld/sayHelloWorldRequest" message="tns:sayHelloWorld"/> <output wsam:Action="http://example/HelloWorld/sayHelloWorldResponse" message="tns:sayHelloWorldResponse"/>
wsam:Action内容即是HTTP头部的SOAPAction。至此我没可以组装出完整的HTTP+SOAP,完整的调用协议内容如下:
POST /HelloWorld HTTP/1.1 Accept: text/xml, multipart/related Content-Type: text/xml; charset=utf-8 SOAPAction: "http://example/HelloWorld/sayHelloWorldRequest" User-Agent: JAX-WS RI 2.2.7-b01 svn-revision#${svn.Last.Changed.Rev} Host: localhost:9090 Connection: keep-alive Content-Length: xxx <?xml version="1.0" encoding="utf-8"?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Body> <!-- 元素sayHelloWorld包含一个string类型的arg0元素表示参数 --> <ns2:sayHelloWorld xmlns:ns2="http://example/"> <!-- 传参jack --> <arg0>jack</arg0> </ns2:sayHelloWorld> </S:Body> </S:Envelope>
完整的返回协议如下:
HTTP/1.1 200 OK Date: Fri, 17 May 2019 08:34:13 GMT Transfer-encoding: chunked Content-type: text/xml; charset=utf-8 <?xml version="1.0" encoding="utf-8"?> <S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Body> <!-- 元素 sayHelloWorldResponse 包含一个string类型的return元素表示返回值 --> <ns2:sayHelloWorldResponse xmlns:ns2="http://example/"> <return>Hello, world, from jack</return> </ns2:sayHelloWorldResponse> </S:Body> </S:Envelope>
虽然WebService的底层很复杂,但是大多数编程语言都有现成的框架帮助我们完成底层的细节,具体的我们在编写WebService时通常只用创建一个类并且实现类的一些方法, 框架会根据类自动生成WSDL 。WebService的调用也很简单,框架可以根据WSDL生成一个类,我们只需实例化对象然后调用方法获取返回值,和使用普通的类没什么两样。
WSDL是Webservice中最重要的内容,理解WSDL对使用Webservice和调试Webservice非常有用,
它包含五部分内容,通过上文的学习我们可以总结出每个部分的功能:
- service,提供了WebService的访问地址 。
- portType,定义了WebService提供的服务。
- binding,定义了WebService通信协议 。
- message,定义了每个服务调用的和返回的格式,常通过引用一个自定义元素,由自定义的元素 (element) 描述格式。
- types,定义了message引用的各种自定义元素(element)。
本文描述的WSDL使用Document literal wapped 格式的协议,它还支持RPC literal/encode 协议,关于两种协议的区别和各自的应用场景会在后面的文章中讲解。