WebService实战
WebService实战一、?????什么是WebService? 1.?????官方解释 Web service是一个平台独立的,松耦合的,自包含的、基于可编程的web的应用程序,可使用开放的XML标准来描述、发布、发现、协调和配置这些应用程序,用于开发分布式的互操作的应用程序。???????????? ---摘自百度百科 2.?????对概念的理解 Web service翻译出来叫做Web服务,也就是说把具备某种功能的web服务开发出来,供大家使用,这样的web服务就是一个Web service。从这个角度讲,其实Web service是一个很泛的概念,只要是用于给其他系统提供服务的web系统,都可以称作为Web service,它并没有一种固定的标准。 要提供一个web服务,最重要的就是输入和输出的统一,比如一个web系统提供"根据ip地址查找具体位置"的服务,那它首先需要规定查询的接口,从java的角度看就是设计一个暴露给外界的public方法,规定好其中的入参、出参。但是它不是一个RMI,不能像RMI那样暴露接口给对方,让调用方像调用本地类一样调用远程的web系统的某个类的方法,所以,光设计一套java的public方法是不行,还要给出一套规定,用于指明一个请求是调用哪个类的哪个方法,这就是实现web service的标准,这些标准通常由国际组织制定。 3.?????Web service与RMI的区别 Web service提供的是远程服务,是通过通信的手段完成数据传递的,客户端准备入参等数据并发起请求,服务端负责处理这些并返回结果给客户端。 RMI是java的远程调用系统,它的核心思想是远程服务在我本地执行,而不是通过通信传输执行结果。客户端本地有服务端提供的API的jar包,可以直接在本地执行远程服务的某个方法,具体执行哪个方法,可以直接写在java代码里,当然其底层的实现也是依赖通信的,核心是java的串行化。 二、?????WebService的实现标准有哪些? Web service既然只是一种概念,那肯定有具体的实现标准,这些标准的核心就是要解决这样一个问题:以何种格式告诉服务端我要执行哪个类的哪个方法,以及给定的入参,以何种格式从服务端接收执行结果。 常见的标准有:PRC、SOAP、REST。RPC相对来说功能较简单,传递的数据类型也较少。SOAP具备比较完整的功能,但显得稍微有点重量级。REST的特点是简单高效,单规范性不如SOAP好。 基于上面三种标准,分别有自己对应的实现,RPC有XML-RPC实现,SOAP有Apache SOAP、Axis、xfire,REST有restlet等。 三、?????基于Axis2实现的SOAP标准的WebService 1.?????概述 一个WebService包括客户端和服务端,服务端通过具体的SOAP实现方案(如Axis2),将需要提供服务的类和方法发布成为services,并且生成对应的访问地址,客户端则通过RPC请求访问这个访问地址,并在请求中携带具体访问的方法名和参数,服务端在接收到这个客户端的请求后就执行业务逻辑,并返回运行结果。 2.?????基本术语 WebService三要素:SOAP、WSDL (Web Services Description Language)、UDDI(Universal Description Discovery and Integration )之一, soap用来传递信息的格式,WSDL用来描述如何访问具体的接口,uddi用来管理、分发、查询webService,具体实现可以搜索 Web Services简单实例 ?---摘自百度百科 3.?????基于eclipse实现一个基本的Web Service/Client 本小节将介绍如何通过eclipse搭建一个web服务,实现客户端和服务端的通信 1)???????准备工作: 1. 下载Axis2包 从http://axis.apache.org/axis2/java/core/download.cgi下载,本文用的是1.6.2版本 Binary Distribution zip。 2. 配置eclipse Eclipse本身就提供对webservice的支持,但由于存在版本差异,故需要更新eclipse,使其支持1.6.2版本,操作如下:Window --->Perferences--->Web service--->Axis2 Preferences 将最新下载的axis包路径配置到Axis2 Runtime location中。 ? 2)???????第一步:创建一个普通的Web服务,并设计用于提供服务的接口 在支持webservice之前,首先需要创建一个普通的web服务,并设计一些用于提供web服务的类和方法,然后将其发布到tomcat中并确保能成功运行。这样做的目的是,后面的步骤需要基于该web服务,增加webservice的特性,将我们设计用于提供web服务的类和方法通过webservice发布出去,所以首先要做的就是为POJO设计这些API,API要具备一些简单的业务逻辑,方便测试。此处选用一个service类作为POJO,其API是2个get方法 WebServiceProject就是一个普通的web工程,IssueSoapService是准备发布出去的服务类,其中的2个方法是发布的具体方法。这一步要确保整个服务能够在tomcat容器中成功启动和运行。 ? 3)???????第二步:为创建的Web服务增加webservice的支持 这一步是在上一步创建的普通的web工程的基础上,增加webservice的支持,这里我叫做"增加",但在eclipse中具体操作的时候,其实是通过新建来完成的,但效果还是"增加"的效果。增加的过程可以 新建--->webservice--->在service implementation中指定需要发布的类,并选择 web service runtime:Apache Axis2--->然后next直到finish。 单击Finish后,刚才的web工程,多了一些文件夹和文件,这些是由eclipse帮忙生成的Axis2支持webservice的文件,主要是webcontent下多了个axis2-web的文件夹,WEB-INF下多了modules和services文件夹。此时,如果不出错的话,那tomcat应该能成功启动该web服务 ? 4)???????第三步:测试该服务 浏览器中输入如下地址:http://localhost:8080/WebServiceProject/, 页面显示axis2的首页并列出提供的web服务:点击Services,页面显示刚才配置的POJO及发布的方法,红色部分表示我们发布的类的服务信息,包括发布地址,发布接口列表点击打开IssueSoapService,页面显示wsdl描述文件:这个页面的url是这个soap接口的wsdl描述文件,targetNamespace表示的是命名空间,在客户 端访问是需要使用到,getIssue和getIssueByExt这是2个接口,wsdl文件描述了这个服务的所有信息。此时,可以通过在浏览器端输入如下地址来测试getIssue接口是否功能正常:http://localhost:8080/WebServiceProject/services/IssueSoapService/getIssue?issueKey=789根据getIssue的实现,要求给如一个入参issueKey,执行时会在控制台答应出issueKey的值,并返回该值。到这里,表示该webservice已经发布成功并可以被调用,下一步将介绍然后编写客户端 ? 5)???????3.3.5 第四步:创建客户端 本步骤通过一个普通的java工程的main函数,发起对webservice的getIssue的调用。创建工程,并将axis2的jar包放入buildpath,然后在main函数中编写客户端访问服务端的代码,暂时使用同步访问,代码如下: import javax.xml.namespace.QName; import org.apache.axis2.AxisFault; importorg.apache.axis2.addressing.EndpointReference; import org.apache.axis2.client.Options; import org.apache.axis2.rpc.client.RPCServiceClient; public class ServiceClient { ?public static void main(String[] args) { ? //TODO Auto-generated method stub ?String target = "http://localhost:8080/WebServiceProject/services/IssueSoapService"; ? try{ ??RPCServiceClient client = new RPCServiceClient(); ??EndpointReference epr = new EndpointReference(target); ??Options options = client.getOptions(); ??options.setTo(epr); ??QName qname = new QName("http://soap.xxx.com","getIssue"); ?? ?? //同步调用 ??Object[] result = client.invokeBlocking(qname,new Object[]{"abc"}, new Class[] { String.class }); ??System.out.println((String)result[0]);?? ? }catch (AxisFault e) { ?? //TODO Auto-generated catch block ??e.printStackTrace(); ? } ?} } 6)???????第五步:客户端访问服务器 将该文件(或工程)以java application的形式运行,发起对服务端的访问,并且成功接收到返回值。 ? 7)???????第六步:更新服务端提供的services 如果IssueSoapService.java类需要再发布一个方法,或者说有一个新的类需要发布成webservice服务,则需要重新执行"第二步"。也就是说更新发布结果,也是通过第二步的new操作来实现的。 4.?????扩展Web Service/Client的功能 1)???????入参和返回值都使用自定义的POJO 1. 通常是服务端定义一个POJO,然后使用这个POJO作为返回值类型或者入参类型。同时,服务端将该POJO类(一系列POJO类)打成jar,供客户端使用 2. 客户端引入服务端提供的jar包,引入该POJO并使用其接收返回值 3. POJO定义时,必须实现串行化接口,服务端和客户端的POJO允许有不同的包路径 服务端: issueBean.setNumber(number+" . Sessionnot exist,first access"); issueBean.setExternalKey(externalKey); return issueBean; ? 客户端: String target ="http://localhost:8080/WebServiceProject/services/IssueSoapService"; ? try{ ??RPCServiceClient client = new RPCServiceClient(); ??EndpointReference epr = new EndpointReference(target); ??Options options = client.getOptions(); ?? options.setTo(epr); ??QName qname = new QName("http://soap.com","getIssueBean"); ?? ?? //同步调用 ??Object[] result = client.invokeBlocking(qname,new Object[]{"abc","externalkey"},new Class[] { IssueBean.class }); ??IssueBean issueBean = (IssueBean)result[0]; ??System.out.println("SyncServiceClient"+issueBean.getNumber()+" "+issueBean.getExternalKey());?? ? }catch (AxisFault e) { ?? //TODO Auto-generated catch block ??e.printStackTrace(); ? } 2)???????异步调用 Webservice通常情况下都是用同步调用的,此处只是介绍下异步调用 //异步访问 ?? client.invokeNonBlocking(qname,new Object[] {"abc",new AxisCallback() { ???@Override ???public void onError(Exception ex) { ??? } ???@Override ???public void onComplete() { ??? } ???@Override ???public void onFault(MessageContext arg0) { ??? } ???@Override ???public void onMessage(MessageContext ctx) { ????// TODO Auto-generated method stub ????System.out.println("Message:" +ctx.getEnvelope().getFirstElement().getFirstElement().getFirstElement().getText()); ??? } ??}); ??System.out.println("AsyncServiceClient over!"); ?? ?? //阻止程序退出 ??System.in.read(); 注意,System.in.read();起到阻止程序退出的效果,一旦程序退出,那回调就不会起作用了的,所以异步其实意义不大。 3)???????手动发布和合并类 手动发布(参考自网络):手动发布WebService,必须打包成.aar文件,.aar文件实际上就是改变了扩展名的.jar文件。在现在建立了两个文件:MyService.java和services.xml。将MyService.java编译,生成MyService.class。services.xml和MyService.class文件的位置如下:D:wsserviceMyService.class D:wsMETA-INFservices.xml ??? 在windows控制台中进入ws目录,并输入如下的命令生成.aar文件(实际上,.jar文件也可以发布webservice,但axis2官方文档中建议使用.aar文件发布webservice):jar cvf ws.aar 最后将ws.aar文件复制到<Tomcat安装目>webappsaxis2WEB-INFservices目录中,启动Tomcat后,就可以调用这个WebService了 合并多个发布类,通过serviceGroup标签搞定,同时要求class文件要在一个目录下,或者打成同一个aar包: <serviceGroup> ??? <servicename="myService"> ??????? <description> ??????????? Web Service例子 ??????? </description> ??????? <parametername="ServiceClass"> ???????????service.MyService? ??????? </parameter> ???????<messageReceivers> ???????????<messageReceiver mep=http://www.w3.org/2004/08/wsdl/in-out class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/> ???????????<messageReceiver mep=http://www.w3.org/2004/08/wsdl/in-only class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/> ???????</messageReceivers> ??? </service> <service name="myService1"> ??????<description> ??????????Web Service例子 ?????</description> ??????<parameter name="ServiceClass"> ??????????service.MyService1 ??????</parameter> ???????<messageReceivers> ?????????<messageReceivermep="http://www.w3.org/2004/08/wsdl/in-out" ??????????????class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/> ?????????<messageReceivermep=http://www.w3.org/2004/08/wsdl/in-only class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/> ??????</messageReceivers> ??</service> </serviceGroup> ? ? ? 手动增加一个service的方法(自己实践的):如果通过eclipse自动生成新的服务,曾通常是会在ebContentWEB-INFservices目录下新增一个以新服务类名为名称的文件夹,然后里面有两个文件夹,一个是class文件的包路径的第一层,另一个是META-INF。其实DtsSoapServeice和IssueSoapService这2个目录根本没用,它只eclipse在逻辑上帮我们划分的,而每个目录下面其实是对应服务的完整包路径和全部class文件,且都有独立的services.xml(只包含一个服务的service配置)。基于上面的发现,其实可以合并这些服务,合并后services/SoapService/com/xxx/soap/DtsSopaService.class、IssueSoapService.class:SoapService这个目录其实通常没有意义,只是逻辑上的划分,但是现在它已经不再和具体某个服务一一对应了。com.xxx.soap目录下具备2个服务的class文件。META-INF/Services.xml也做了合并,如下图: <serviceGroup> ?<service name="IssueSoapService"scope="application" > ?<Description> ??Please Type your service description here ?</Description> ?<messageReceivers> ??<messageReceivermep="http://www.w3.org/2004/08/wsdl/in-only"class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver" /> ??<messageReceiver?mep="http://www.w3.org/2004/08/wsdl/in-out"?class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/> ?</messageReceivers> ?<parameter name="ServiceClass"locked="false">com.huawei.jira.soap.IssueSoapService</parameter> ?</service> ?<servicename="DtsSoapService" scope="application" > ?<Description> ??Please Type your service description here ?</Description> ?<messageReceivers> ??<messageReceivermep="http://www.w3.org/2004/08/wsdl/in-only"class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver" /> ??<messageReceiver?mep="http://www.w3.org/2004/08/wsdl/in-out"?class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/> ?</messageReceivers> ?<parameter name="ServiceClass"locked="false">com.huawei.jira.soap.DtsSoapService</parameter> ?</service> </serviceGroup> 这种合并有什么用呢?个人感觉使用场景就是把服务逻辑分组,一组服务内部可以共享session,具体如何共享会在3.4.5中讲到。 按照上面的说法,如果我要新增一个服务,是不是就可以手动将class及包路径拷贝到WebContentWEB-INFservices目录下,同时自己添加META-INF/services.xml文件即可,再也不用依赖eclipse自动生成了 ? 4)???????3.4.4 webservice的session管理 Webservice为什么需要管理session?按理说,webservice只是提供无状态的服务,每次soap调用,都是彼此独立的,不需要记录任何状态,但是,对于同一客户端而言,有时候需要服务器记录它调用的一些结果,供下次调用时使用,最典型的就是第一次调用时需要鉴权(也就是登陆),一旦鉴权通过,随后的调用都不需要再鉴权,这就是记录状态,这种能力通常通过session来实现。因此,webservice需要提供对session的管理能力,从而能够提供有状态的web服务。 由于webservice本质上是通过servlet实现的,所以对状态的记录,其实可以通过容器提供的context来实现,事实上,webservice也的确是基于context实现的。补充知识:servlet本身其实是单例多线程的,即只有一个实例化对象,但是通过多线程来并发执行service方法,故servlet本身是无状态的,一个请求的状态,是由容器负责维护的(参见:http://www.cnblogs.com/yjhrem/articles/3160864.html)。 客户端代码: try { ??RPCServiceClient client = new RPCServiceClient(); ??EndpointReference epr = new EndpointReference(target); ??Options options = client.getOptions(); ??options.setTo(epr); ??options.setManageSession(true); ??QName qname = new QName("http://soap.jira.huawei.com","getIssueBean"); ??for(int i = 0; i < 3; i++) ?? { ???//同步调用 ???Object[] result = client.invokeBlocking(qname,new Object[]{"abc"+i,"456"},new Class[] { IssueBean.class }); ???IssueBean issueBean = (IssueBean)result[0]; ???System.out.println("SyncServiceSessionClient 第 "+i+"次调用,返回值:number="+issueBean.getNumber()+",externalkey="+issueBean.getExternalKey()); ?? } ? } ? 服务端代码: public IssueBean getIssueBean(Stringnumber,String externalKey) ?{ ?System.out.println("getIssueBean call,number = "+number); ?IssueBean issueBean = new IssueBean(); ?MessageContext context = MessageContext.getCurrentMessageContext(); ?ServiceGroupContext ctx = context.getServiceGroupContext(); ?String numberValue = (String) ctx.getProperty("number"); ? //第一次访问,session中为空,就返回 ?if(StringUtils.isEmpty(numberValue)) ? { ??issueBean.setNumber(number+" . Session not exist,firstaccess"); ??issueBean.setExternalKey(externalKey); ??ctx.setProperty("number",number); ??System.out.println("已保存number:"+number); ? } ?else ? { ??issueBean.setNumber(number+" . Session exist,last number is:"+numberValue); ??issueBean.setExternalKey(externalKey); ? } ?return issueBean; ?} ? 从上面的例子可以得出如下几点: 1、 服务端通过MessageContext、ServiceContext、getProperty、setProperty共同完成session的更新和获取 2、 客户端需要打开session管理功能:options.setManageSession(true); 3、 客户端是通过java application运行的,无法保存sessionId,一旦运行结束再次启动时,就会让服务器重新创建session,故3次调用代码是在for循环中执行的。如果客户端是一个http请求,则可以通过cookie保存sessionId,可以用下面的地址进行测试,连续执行2次,结果不一样:http://localhost:8080/WebServiceProject/services/IssueSoapService/getIssueBean?number=789&externalKey=ddd ? 5)???????跨service的session管理 一个webservice可以有多个服务,3.4.4中讲到了如何确保一个服务在多次调用时,可以保存session,这节将会讲到,如何确保多个服务之间也能共享session。首先,需要共享session,就必须确保这些服务在同一分组中,也就是要把需要共享session的服务分到同一分组,通过services.xml的<serviceGroup>标签实现。这里把IssueSoapService和DtsSoapService这2个服务合并,并且把scop改为application即可。服务端读写session也有一些相应的变化,使用GroupContext:ServiceGroupContextctx = context.getServiceGroupContext();客户端不需要做任何改动,这样ctx中保存的属性,就可以被group中所有的服务共享。 ? 6)???????3.4.6 Axis2与spring集成 四、?????基于Restlet实现的REST标准的WebService 1.?????4.1 五、?????Q&A 1.?????Axis2不会生出wsdl文件,那如何指定哪些类或者函数是被发布的?目前都是通过eclipse增加,实际是产生什么效果? 答:webservice自动从WEB-INFservices目录下寻找需要发布的类,通过eclipse发布的类,会在该目录下生出这个类对应的com和META-INF文件,META-INF中会有services.xml用来指明该类的发布属性,同时系统也会读取services.list中指明的aar包作为发布的类。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- WebService返回DataTable问题
- bash处理一条命令的步骤
- scala – 如何避免Slick的模型声明详细程度和重复性
- nyoj.305 表达式求值【数据结构】 2015/03/21
- 下拉菜单 – Bootstrap4下拉列表仅在第二次单击时有效
- shell – 查找并删除主文件夹中的所有符号链接,无法使其正常
- 使用bash或python或其他一些linux命令行工具创建一个doveco
- tensorflow 升级到1.9-rc0,tensorboard 报错:TypeError: G
- angularJS $injector:modulerr 有效解决方案
- 《AngularJS权威教程》笔记(8-10)——指令简介、内置指令