【Java EE 学习 80 上】【WebService】
一、WebService概述
什么是WebService,顾名思义,就是基于Web的服务,它使用Http方式接收和响应外部系统的某种请求,从而实现远程调用。WebService实际上就是依据某些标准,不关心对方使用的是何种语言,实现对外部其它应用程序提供跨平台、跨语言服务的一种技术。
使用WebService,我们可以调用互联网上查询天气信息的Web服务,然后将其嵌入到我们的程序中,当用户从我们的网点看到天气信息的时候,他会以为我们为他提供了很多的信息服务,但其实我们什么也没有做,只是简单调用了一下服务器上的一端代码而已。
之前曾经说过Spring的远程调用,实际上WebService的功能和Spring的远程调用完全相同,WebService的本质也就是远程调用,只不过Spring和WebService的实现原理不相同而已。比较WebService和Spring的远程调用:
1.Spring远程调用在网络上传输的是“串行化对象”,而WebService在网络上传输的是XML字符串。
2.Spring远程调用效率更高,因为它传输的是真正的“数据”,而为了满足传输的XML的规范,WebService需要将数据进行一层层的封装,比如服务端需要传递给客户端一个大小为100的List<Student>list,使用Spring的远程调用直接传递该对象到网络流即可,但是使用WebService却必须得将数据进行严格的规整以使其满足XML的规范(示例,实际上的传输规范并不是这样):
<List> <Student> <id>1</id> <name>小张</name> <sex>男</sex> <age>13</age> <phone>111111111</phone> <email>11111@123.com</email> </Student> <Student> <id>2</id> <name>小李</name> <sex>女</sex> <age>15</age> <phone>2222222</phone> <email>22222@123.com</email> </Student> ...此处省略98个Student... </List>
这样导致的问题就是传输了大量的“无用字符”,也就是大量的标签了,实际上在上述的例子中标签所占据的字符数量几乎和要传递的数据量一样多,其效率之低是显而易见的。
3.spring远程调用要求两个程序都使用spring环境(java环境),但是WebService可以实现跨平台通信,也就是说即使两个程序之间使用的不是同一种语言,比如一个PHP的程序和Java的程序,他们也能够相互通信,这是WebService的最大卖点。
4.在不考虑通用性的情况下,如果程序两端都使用了Spring,那么就使用Spring的远程调用;如果平台语言不相同,则使用WebService;如果考虑到通用性,即需要向大众提供服务,那么一定得使用Webservice。
二、名词解释
1.WSDL
全名为WebService Description Language ,翻译过来就是Web服务描述语言。它使用XML描述服务的地址,服务提供的方法以及参数列表、返回值类型等,实际上就是一个Web服务的说明书,比如webxml.com.cn网站提供了天气预报查询的服务,对应的WSDL就是:http://webservice.webxml.com.cn/WebServices/WeatherWS.asmx?wsdl
2.SOAP
全名为Simple Object Access Protocol,翻译过来就是简单对象访问协议,SOAP是一种XML语言的协议,用于在网络上传输数据。
SOAP=XML+HTTP
SOAP是基于Http协议的,SOAP的组成:Envelope,这是必须的部分,以XML根元素的形式出现;Headers,可选;Body,必须的部分,包含要执行的服务器上的方法和发送到服务器上的数据。
三、在Java项目中发布第一个WebService服务
1 package com.kdyzm.ws; 2 3 import javax.jws.WebService; 4 import javax.xml.ws.Endpoint; 5 6 @WebService 7 public class MyWsServer { 8 public String calculate(int input){ 9 return input*input+""; 10 } 11 public static void main(String[] args) { 12 Endpoint.publish("http://localhost:9090/ws", new MyWsServer()); 13 System.out.println("server ready ......"); 14 } 15 }
OK,一个最简单的WebService服务就发布了。
对以上的代码的说明:在Java中,使用EndPoint类发布服务,使用该类发布服务的方法是publish方法,该方法由两个参数,一个参数是发布服务的地址,另外一个参数是提供服务的类;当执行到publish方法的时候,程序会新开一个线程专门用于提供服务,主线程会继续执行下去,所以下面的打印的代码会不受影响的执行。
另外,提供服务的类至少应当提供一个非静态方法否则服务启动不起来。
如何查看wsdl?在浏览器上输入http://localhost:9090/ws?wsdl,回车即可查看wsdl内容:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!-- Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is 3 JAX-WS RI 2.1.6 in JDK 6. --> 4 <!-- Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is 5 JAX-WS RI 2.1.6 in JDK 6. --> 6 <definitions 7 xmlns="http://schemas.xmlsoap.org/wsdl/" 8 xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 9 xmlns:tns="http://ws.kdyzm.com/" 10 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 11 targetNamespace="http://ws.kdyzm.com/" 12 name="MyWsServerService"> 13 <types> 14 <xsd:schema> 15 <xsd:import namespace="http://ws.kdyzm.com/" 16 schemaLocation="http://localhost:9090/ws?xsd=1"></xsd:import> 17 </xsd:schema> 18 </types> 19 <message name="calculate"> 20 <part name="parameters" element="tns:calculate"></part> 21 </message> 22 <message name="calculateResponse"> 23 <part name="parameters" element="tns:calculateResponse"></part> 24 </message> 25 <portType name="MyWsServer"> 26 <operation name="calculate"> 27 <input message="tns:calculate"></input> 28 <output message="tns:calculateResponse"></output> 29 </operation> 30 </portType> 31 <binding name="MyWsServerPortBinding" type="tns:MyWsServer"> 32 <soap:binding transport="http://schemas.xmlsoap.org/soap/http" 33 style="document"></soap:binding> 34 <operation name="calculate"> 35 <soap:operation soapAction=""></soap:operation> 36 <input> 37 <soap:body use="literal"></soap:body> 38 </input> 39 <output> 40 <soap:body use="literal"></soap:body> 41 </output> 42 </operation> 43 </binding> 44 <service name="MyWsServerService"> 45 <port name="MyWsServerPort" binding="tns:MyWsServerPortBinding"> 46 <soap:address location="http://localhost:9090/ws"></soap:address> 47 </port> 48 </service> 49 </definitions>
四、使用wsimport命令生成本地调用代码。
如果能力足够高的话,直接阅读wsdl文件就能够直接向服务器请求并获取服务了,但是一般来说这么做非常的复杂,为了提高开发效率,在java环境下我们使用wsimport命令生成本地调用代码,然后直接使用这些生成的代码就可以了。
wsimport命令是jdk提供的命令,但是应当注意的是,该命令只是在jdk1.6.0_24及以上的版本才提供,所以如果使用了jdk1.5的版本肯定不能支持。
wsimport命令能够解析指定的wsdl文件并将根据wsdl文件中的配置生成对应的java代码并将其编译成class文件。
wsimport命令的常用参数:
-d <目录名>:将会生成.class文件,默认参数,就算不使用该参数也会自动加上。
-s <目录名>:将会生成java文件
-p <生成的包名>:将生成的类放到指定的目录下。
步骤:使用命令生成本地调用代码:wsimport -s . http://localhost:9090/ws?wsdl
任务执行完毕之后,会在当前目录下生成一个文件夹,文件夹中存放着所有将要使用到的java类和编译之后的.class文件
将所有的class文件删除掉,之后将文件夹直接拷贝到项目中即可使用。
注意事项:如果是我们自己发布的服务,我们使用wsimport命令肯定能够解析成功,但是如果我们使用网络上其它站点提供的服务,那么在生成本地调用代码的时候很有可能会生成失败。比如我们经常回到这个网站上找相关的免费服务:http://www.webxml.com.cn/zh_cn/index.aspx,在这个网站上有一个“天气预报查询”的服务,它也提供了一个wsdl文档,但是我们在解析该文档生成本地调用代码的时候会生成失败。解决方法参考:http://blog.sina.com.cn/s/blog_924d6a570102w203.html
五、使用本地调用代码的方法
以之前发布的服务为例。
新建一个普通类型的java工程,将文件夹中的所有.class文件删除掉之后,将包括文件夹在内的所有文件都拷贝到项目中。
将生成的代码拷贝到项目中之后很有可能会报错,提示Service类中没有对应的构造方法,解决方法参考 http://blog.sina.com.cn/s/blog_924d6a570102w21v.html
接下来只需要使用这些类就可以了,如何使用这些类,应当参考wsdl文档。
客户端的测试代码:
1 package com.kdyzm.test; 2 3 import com.kdyzm.ws.MyWsServer; 4 import com.kdyzm.ws.MyWsServerService; 5 6 public class Main { 7 public static void main(String[] args) { 8 MyWsServerService myWsServerService=new MyWsServerService(); 9 MyWsServer myWsServer=myWsServerService.getMyWsServerPort(); 10 String result=myWsServer.calculate(2); 11 System.out.println(result); 12 } 13 }
控制台输出结果是4(客户端)。
六、使用Web Service Exployer工具查看请求信息和相应信息。
java ee版本的eclipse中(java ee视图)提供了一种专门用于WebService的浏览器,名字为Web Service Exployer
直接进入到该视图中,然后切换到WSDL Page视图,并点击clear按钮,最终显示的界面如下:
在地址栏中输入wsdl的地址,这里是http://localhost:9090/ws?wsdl,并回车
接下来会显示出所有的服务(类)和服务下的方法,图略。
下一步就是单击方法名,然后给方法添加实参发出请求之后查看请求的时候的请求信息和响应信息,这里还是以2为例,最终结果是这样的:
显示返回值是4,然后单击source
这也不是我想要的,直接对着框框右键“查看源代码”,可以看到SOAP的请求信息和SOAP的响应信息:
请求信息:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:q0="http://ws.kdyzm.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <soapenv:Body> <q0:calculate> <arg0>2</arg0> </q0:calculate> </soapenv:Body> </soapenv:Envelope>
响应信息:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"> <S:Body> <ns2:calculateResponse xmlns:ns2="http://ws.kdyzm.com/"> <return>4</return> </ns2:calculateResponse> </S:Body> </S:Envelope>
可以看到无论是请求信息还是响应信息都是以Envelope为根节点,而且接下来都是Body节点,这点是符合SOAP协议的规范的。