深入浅出WebService

leceam 贡献于2012-04-07

作者 Rain Ye  创建于2009-12-24 06:23:00   修改者Rain Ye  修改于2010-01-03 09:53:00字数9129

文档摘要:最近几年WebService技术正在逐渐成熟,并在项目中开始广泛使用。现在我们简单了解一下什么是WebService,如何使用它。
关键词:

深入浅出WebService 引言 最近几年WebService技术正在逐渐成熟,并在项目中开始广泛使用。现在我们简单了解一下什么是WebService,如何使用它。 什么是WebService WebService,顾名思义就是基于Web的服务。它使用Web方式,接收和响应外部系统的某种请求。比如,我们可以提供一个用于查询天气信息的WebService,然后可以用任何支持WebService的客户端连接到这个Service进行查询。 很多企业用户经过多年的积累,已经部署了很多应用系统。这些应用系统在企业运营中分担着不同的功能或任务。随着企业的发展壮大,由于种种原因,这些企业用户逐渐开始考虑如何对原有的这些旧系统进行整合。使用WebService方式将这些旧的应用系统整合起来,对外部提供一致的接口,不仅可以达到整合已有旧系统的目的,还可以避开因为完全构建一个新系统而产生的风险。这样就大大降低了项目的成本和风险。这就是SOA得以被客户广泛采纳的原因。 从WebService的工作模式上理解的话,它跟普通的Web程序(比如ASP、JSP等)并没有本质的区别,都是基于HTTP传输协议的程序。WebService与普通Web程序的区别主要在于:1) WebService只采用HTTP POST方式传输数据,不使用GET方式; 2) WebService从数据传输格式上作了限定。WebService所使用的数据均是基于XML格式的。目前标准的WebService在数据格式上主要采用SOAP协议。SOAP协议实际上就是一种基于XML编码规范的文本协议。 所以我们可以用下面这个图来简单地描述WebService: Client WebService SOAP (XML) 通迅协议:HTTP POST WebService和Web服务器的区别 有人曾疑问WebService和Web服务器有什么区别呢?我们可以把WebService看作是Web服务器上应用;反过来说,Web服务器是WebService运行时所必需的容器。这就是它们的区别和联系。 WebService的特点 通过上面对WebService的简要介绍,我们了解到WebService的主要特性: 1) WebService通过HTTP POST方式接受客户的请求 2) WebService与客户端之间一般使用SOAP协议传输数据 必须注意到,WebService标准本身并没有限制服务端或客户端的操作系统环境或编程语言环境。因为它本身就是为了跨平台或跨应用而设计的。所以我们会看到很多声称支持WebService的平台。比如:Microsoft Visual Studio开发平台,Sping,Axis,XFire,Spring等等。 我现在主要讨论Java平台上的WebService框架,实际上其中的原理也适用于其它平台。 基于Java技术的WebService 对于Java平台,常见的WebService框架有Spring+XFire,Axis,CXF。 这些框架各有千秋,本次讨论以理解WebService的原理为目标,所以我选择了CXF这个相对简单的框架。通过使用CXF,我们可以了解WebService服务端的工作机制,实际上它跟其它WebService框架在这方面没有区别。我会介绍如何创建一个通用的WebService服务端;然后我还会介绍如何自己编写一个通用的客户端,直接通过HTTP POST方式调用WebService服务端。 CXF是Apache基金会组织下的一个项目,简化了WebService服务端的创建过程。CXF实现了JAX-WS2.0规范,并通过了JAX-WS2.0 TCK; CXF可以和Spring无缝集成;CXF支持多种传输协议(HTTP, JMS, Corba等), 支持多种Binding数据格式(SOAP,XML,JSON等), 支持多种DataBinding数据类型(JAXB, Aegis) 。CXF基于Interceptor的架构,使得整个框架非常易于扩展。本次我主要讨论CXF在HTTP/SOAP模式下的处理机制。 可以从http://cxf.apache.org/download.html下载CXF,目前最新版本是2.2.5。下载最新的版本后,解压压缩包,从lib目录下至少要拿出下面几个jar放入你的工程: cxf-2.2.5.jar antlr-2.7.7.jar commons-codec-1.3.jar commons-collections-3.2.1.jar commons-lang-2.4.jar commons-logging-1.1.1.jar commons-pool-1.5.2.jar geronimo-annotation_1.0_spec-1.1.1.jar geronimo-jaxws_2.1_spec-1.0.jar geronimo-ws-metadata_2.0_spec-1.1.2.jar jaxb-api-2.1.jar xalan-2.7.1.jar wsdl4j-1.6.2.jar XmlSchema-1.4.5.jar 假设我们的Web工程目录结构是如下所示: myapp |-------- src |-------- WebContent | |---------WEB-INF | |---------classes | |---------lib (在这里放入上面所列的jar包) | |---------web.xml 上述的jar包应该放入myapp/WebContent/WEB-INF/lib目录下。 如何编写WebService服务端 CXF是一个嵌入式的WebService框架,它没有直接的WEB接口。也就是说如果我们要在WEB模块中使用CXF,必须自己在WEB模块中实现一个调用CXF的WEB接口。现在我使用WEB Servlet方式来调用CXF。首先我在myapp/src目录下创建一个自己的Servlet程序(所有使用CXF的Web Servlet必须继承servlet.CXFNonSpringServlet)。 下面是Web Servlet的主要代码: public class WebServiceServlet extends CXFNonSpringServlet { public void loadBus(ServletConfig servletConfig) throws ServletException { ... super.loadBus(servletConfig); Bus bus = getBus(); ServerFactoryBean factroy = new ServerFactoryBean(); factroy.setBus(bus); factroy.setServiceClass(IWebService.class); factroy.setAddress("/SampleWebService"); factroy.setServiceBean(new SampleWebService()); factroy.create(); ... } } 下面这两行代码主要为了获得CXF的内部上下文对象Bus: super.loadBus(servletConfig); Bus bus = getBus(); 然后我再使用CXF提供的ServerFactoryBean工具类把自己写的一个普通的JavaBean发布成WebService。ServerFactoryBean需要以下参数: 1) Bus。也就是上面我们所得到的Bus对象。 2) WebService的接口定义。也就是指自己的WebService实现了哪个Java Interface。 3) WebService的发布地址。也就是访问WebService的URL地址。 4) WebService的实现类 我们执行下面的这几行代码就能发布一个WebService: ServerFactoryBean factroy = new ServerFactoryBean(); factroy.setBus(bus); factroy.setServiceClass(IWebService.class); factroy.setAddress("/SampleWebService"); factroy.setServiceBean(new SampleWebService()); factroy.create(); 如果我们要发布多个WebService,仅仅需要更改少量代码(修改WebService的接口定义、发布地址、实现类),然后不断重复上面的代码即可。 在此之前,我还要定义一个WebService接口和一个WebService实现类。 下面的代码是WebService接口: public interface IWebService { String execute(String params); } 它就是一个普通的Java Interface而已。 下面的代码是一个WebService的简单实现类: public class SampleWebService implements IWebService { public String execute(String params) { System.out.println(params); return params; } } 接下来,我们在web.xml中配置WebServiceServlet: WebServiceServlet WebServiceServlet 1 WebServiceServlet /services/* 完成上述的操作后,我们启动应用服务器(这里我用TOMCAT作测试,假设TOMCAT的端口为8080,我的应用名称为vo)。 然后,我们可以从http://localhost:8080/vo/services/?wsdl 访问WebService主画面,页面上显示出当前Web应用中可用的所有WebService。 我们可以点击某个WebService的链接查看WebService的详细信息: 如果你能看到上面两个画面,说明WebService已经正常发布了。 这时,我们的SampleWebService的访问地址为: http://localhost:8080/vo/services/SampleWebService 请注意,我们绝不能直接在浏览器上输入这个地址,虽然它看起来就像一个普通的Web应用地址。因为通过浏览器直接访问这个地址,浏览器是以POST方式向Web服务器发出请求,这不符合WebService的运行要求。 如果你强行在浏览器上用http://localhost:8080/vo/services/SampleWebService地址访问WebService,你会看到浏览器上显示一个HTTP 5XX错误。 当你在用浏览器直接访问WebService时看到类似的错误,不要以为是WebService没有发布成功。事实上,你不能用这种方式来测试WebService是否正常发布。 如何编写WebService客户端 CXF框架提供了调用WebService的客户端API,使用起来也比较方便。下面是CXF客户端的写法: ClientProxyFactoryBean factory = new ClientProxyFactoryBean(); factory.setServiceClass(IWebService.class); factory.setAddress( "http://localhost:8080/vo/services/SampleWebService"); IWebService client = (IWebService)factory.create(); System.out.println("Invoke execute()...."); System.out.println(client.execute("Hello World")); 在启动服务端后,运行这段代码,客户端终端窗口上会显示“Hello World”,服务端的信息窗口也会显示一个“Hello World”。这表示WebService能成功运行了。 CXF框架提供的客户端API封装了WebService的内幕,虽然它很好用,但使用者却不能理解WebService的运行机制。 破解WebService的秘密 接下来,我们对WebService作进一步拆解,来了解它是怎么运作的。 之前,我提到WebService一般是基于HTTP POST方式工作的。刚刚作为范例的CXF框架的WebService就是基于HTTP POST方式工作的。现在,我用一种称为通迅协议解析工具的软件来帮我们了解它的工作过程。我现在所使用的这个软件叫Ethereal,是一个开源的通迅协议解析工具。(可以从http://www.ethereal.com 下载最新版本的Ethereal) 首先,我们启动WebService所在的Web服务器。 然后,我们启动Ethereal。点击工具条上的第二个图标按钮,如下图所示: 在弹出的画面中,Interface栏位选中正确的网卡;Capture Filter中填入port 8080(Tomcat的默认端口是8080,如果你修改了这个端口,必须在这里填入相同的端口);Display options中的三个选项都勾上;最后,点 “Start”按钮开始监视WebService的运作。 这三个都选上 这里改为对应的网卡 我们还是运行刚刚使用CXF客户端API编写的客户端程序测试一下WebService。(此时要特别注意,你的WebService客户端和WebService服务端不要位于一台机器上。这是源于Ethereal软件本身的限制,它没有办法监控同一台机器上的网卡数据。) 如果按照上面那样做,我们会在Ethereal看到类似于这样的内容: 消息详细信息区域(二进制方式) 消息详细信息区域 消息区域 上面的画面显示了几个区域。 我们注意到,在画面上部第一个区域中,Protocol中显示的协议类型为HTTP,Info中显示的是POST方式,POST后紧跟的是WebService的调用地址。Source中显示的是客户端的IP,Destination中显示的是服务端的IP。 画面中间的Hypertext Transfer Protocol展开,显示以下内容: POST /vo/services/SampleWebService HTTP/1.1\r\n Content-Type: text/xml; charset=UTF-8\r\n SOAPAction: ""\r\n Accept: */*\r\n User-Agent: Apache CXF 2.2.5\r\n Cache-Control: no-cache\r\n Pragma: no-cache\r\n Host: 192.168.2.6:8080\r\n Connection: keep-alive\r\n Content-Length: 593\r\n \r\n 其中Content-Type指明数据类型为XML,使用UTF-8字符编码。SOAPAction表明这是一个WebService调用。User-Agent说明当前客户端是使用CXF API制作的。Host表明WebService服务器的IP和端口。Content-Length说明后面将使用POST方式传送的数据的长度。 选中消息列表区域中的第三条,会显示如下信息: 特别地,选中“Data ( 593 bytes )”,在消息详细内容二进制区域会显示出这593字节的详细内容。根据WebService中传递的数据的长度不同,这里的字节数也会不尽相同。 我们仔细观察使用CXF API编写的WebService客户端所发送出去的POST数据是类似下面这样的内容: Hello World 其中,指明要调用的方法是execute(),arg0说明调用时的参数值为“Hello World”。这些内容都包括在标签之中。 所以SOAP协议规定的WebService数据一般有如下格式: |_________ |____________ <方法名> |___________<参数> 上面消息列表的最后是WebService服务端向客户端返回的消息: 返回的消息内容为: Hello World 我们注意到,返回的消息中内的内容换成了与所调用的方法名对应的Response标签。Response标签内的Return标签说明了方法的返回值。 我们回过头来再仔细想想,所谓WebService,似乎只是将方法调用的过程用XML形式来重新定义其规则,然后再用一种类似CXF这样的符合这类WebService标准的框架来支持它,就可以达到这种远程调用方法的效果。 现在我们知道了WebService所使用XML数据的一般形式,可以绕开CXF这样的API,直接采用HTTP协议编写客户端,来访问WebService服务端。 在Java中,我直接使用Socket类来实现WebService的客户端。实际上,你也可以参考这个思路,用C++或.Net等程序来实现同样的功能。 //建立与WebService服务端的连接 Socket sock = new Socket("192.168.2.6", 8080); //建立向WebService服务端的输出流,编码格式为UTF-8 BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream(), "UTF-8")); //参照用Ethereal截获的信息一样向输出流写入数据 wr.write("POST "+path+" HTTP/1.0\r\n"); wr.write("Host: "+host+"\r\n"); wr.write("Content-Length: 593\r\n"); wr.write("Content-Type: text/xml; charset=\"UTF-8\"\r\n"); wr.write("SOAPAction: \"\"\r\n"); wr.write("Accept: */*\r\n"); wr.write("Cache-Control: no-cache\r\n"); wr.write("Pragma: no-cache\r\n"); wr.write("\r\n"); wr.write(xmldata); wr.flush(); ... wr.flush(); //建立来自WebService服务端的输入流 BufferedReader rd = new BufferedReader(new InputStreamReader(sock.getInputStream())); String line; StringBuffer xmlReturn=new StringBuffer(); //循环读入从服务端发送回来的数据 while((line = rd.readLine()) != null) { xmlReturn.append(line); } sock.close(); 按照上面这个思路,我们可以直接使用Socket实现WebService客户端,同样可以调用标准的WebService服务端。唯一的缺点就是,我们需要对WebService返回的信息进行解析。由于WebService返回的内容是XML格式的。所以我们可以使用DOM或SAX对XML进行解析。只是还是稍显麻烦。不过即便如此,我们用Socket直接调用WebService,这已经很大程度上解决了如果编写一个通用WebService客户端的问题。所以用这种方式编写的客户端,不仅可以调用不同Java WebService框架发布的WebService,甚至也可以调用不同语言开发平台上发布的WebService。 至于返回信息的解析问题,我们可以采用一些其它的方法来弥补。比如:XML序列化技术。其实现在已经有不少这方面的技术。比如从JDK 1.5开始提供的JAXB技术、Apache的XMLBean等等。读者有兴趣的话,可以自行研究一下。很多Java WebService框架就是采用这些技术来实现的。 写在最后 本文对WebService的核心通讯协议方面作了探讨。事实上WebService标准还包括很多其它内容,比如最重要的安全问题等等。虽然如此,我还是希望这篇文章能帮助你开启迈向WebService的大门。(我将在后文中为大家继续讨论关于XML序列化的问题)。另外,我也建议读者去了解一下WSDL文法标准,虽然很多WEBService框架隐藏了它的细节,因为我认为它是我们了解WebService的基础。就像我们要写Web界面程序时,必须先了解HTML文法一样。

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ] 2 人已下载

下载文档