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:\ws\service\MyService.class
D:\ws\meta-inf\services.xml
在windows控制台中进入ws目录,并输入如下的命令生成.aar文件(实际上,.jar文件也可以发布webservice,但axis2官方文档中建议使用.aar文件发布webservice):jar cvf ws.aar
最后将ws.aar文件复制到<Tomcat安装目>\webapps\axis2\WEB-INF\services目录中,启动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自动生成新的服务,曾通常是会在ebContent\WEB-INF\services目录下新增一个以新服务类名为名称的文件夹,然后里面有两个文件夹,一个是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及包路径拷贝到WebContent\WEB-INF\services目录下,同时自己添加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-INF\services目录下寻找需要发布的类,通过eclipse发布的类,会在该目录下生出这个类对应的com和meta-inf文件,meta-inf中会有services.xml用来指明该类的发布属性,同时系统也会读取services.list中指明的aar包作为发布的类。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。