Axis2是目前比较流行的WebService引擎

Axis2是目前比较流行的WebService引擎。WebService被应用在很多不同的场景。例如,可以使用WebService来发布服务端 Java类的方法,以便使用不同的客户端进行调用。这样可以有效地集成多种不同的技术来完成应用系统。WebService还经常被使用在SOA中,用于 SOA各个部分交换数据。本课程重点讲解了Axis2的核心功能,并提供了三个项目以使学员理解并掌握如何将Axis2应用于不同的场景。


本课程站在初学者角度介绍了Axis2的大多数常用的功能,例如,以多种方式编写和发布WebService、JAX-RPC、JAX-WS、复杂类型传输、WebService会话管理、Axis2模块等。本课程共分为两部分,其中第一部分为Axis2基础知识讲解。在这部分介绍了前面所述的Axis2 知识。第二部分介绍了三个小的项目。这三个项目的功能类似。所不同的是,它们使用了不同的技术来实现。第一个项目使用Java Swing技术实现了个人信息管理(PIM)系统,这个系统可以对个人信息(如姓名、职位、电话等)进行增、删、改、查。在这些操作中,涉及到了如何使用 WebService来传输和下载图像文件、传输查询结果(Resultset对象)等技术难点。第二个项目使用Struts 2.1.6(目前Struts 2的最新版,2009年1月发布)来实现PIM。功能与第一个PIM类似。只是这个系统为Web版。在这个项目中学员可以学习到如果将 WebService应用在Web程序中。该系统的主要思想是如果Web程序和WebService部署在同一台机器上,Web程序可以直接调用 WebService所对应的Java类。当然,也可以象第一个项目一样,直接通过HTTP SOAP协议调用WebService。在第二个项目中同时使用了这两种方式来实现。这两种调用方式分别封装在两组Model类中。可以通过配置 struts.xml文件来提定使用哪组模型类(调用方式)。第三个项目使用了C#来实现PIM。这个项目的目的是为了演示如何将.net技术与Java 技术集成来实现一个完整的应用。一个团队要想获得成功,需要每个成员通力合作,各尽所长。而在一个项目中,也和一个团队是一样的,没有哪一种技术适合做所有的事情,因此,需要将各个领域的杰出代表组成一个技术团队。例如,Java适合做企业应用,也能跨平台,比较适合做服务端程序,但它在客户端(C/S)的实现上略显不足,而.net(以C#为代表)和visual studio是windows上语言和开发工具的王者,在GUI上有着无法超越的优势。因此,将Java和.net结合是最完美的联姻。学员从这个项目中就会体会到这个优势所在。


本课程通过一套完备的WebService电子书教程+20多讲教学视频+ 中老师辅导的教学服务方式的教学方式, 手把手辅导学员掌握Axis2的核心技术,并一步步带领学员完成这三个项目。

WebService大讲堂之Axis2系列教程

1.用POJO实现0配置的WebService

2.复合类型数据的传递

3.使用services.xml文件发布WebService

4.二进制文件传输

5.会话(Session)管理

6.跨服务会话(Session)管理

7.将Spring的装配JavaBean发布成WebService

8.异步调用WebService

9.编写Axis2模块(Module

10.使用soapmonitor模块监视soap请求与响应消息

Axis2是一套崭新的WebService引擎,该版本是对Axis1.x重新设计的产物。Axis2不仅支持SOAP1.1和SOAP1.2,还集成了非常流行的RESTWebService,同时还支持Spring、JSON等技术。这些都将在后面的系列教程中讲解。在本文中主要介绍了如何使用Axis2开发一个不需要任何配置文件的WebService,并在客户端使用Java和C#调用这个WebService。

一、Axis2的下载和安装

读者可以从如下的网址下载Axis2的最新版本:

http://ws.apache.org/axis2/

在本文使用了目前Axis2的最新版本1.4.1。读者可以下载如下两个zip包:

 axis2-1.4.1-bin.zip f3d04032c142898c206a1312d8385d9a 
 axis2-1.4.1-war.zip 51a77ec0a47483cdfd8166797bdc977e 

其中axis2-1.4.1-bin.zip文件中包含了Axis2中所有的jar文件,axis2-1.4.1-war.zip文件用于将WebService发布到Web容器中。

将axis2-1.4.1-war.zip文件解压到相应的目录,将目录中的axis2.war文件放到<Tomcat安装目录>\webapps目录中(本文使用的Tomcat的版本是6.x),并启动Tomcat。

在浏览器地址栏中输入如下的URL:

http://localhost:8080/axis2/

如果在浏览器中显示出如图1所示的页面,则表示Axis2安装成功。



图1

二、编写和发布WebService

对于用Java实现的服务程序给人的印象就是需要进行大量的配置,不过这一点在Axis2中将被终结。在Axis2中不需要进行任何的配置,就可以直接将一个简单的POJO发布成WebService。其中POJO中所有的public方法将被发布成WebService方法。

下面我们来实现一个简单的POJO,代码如下:

publicclassSimpleService
{
publicStringgetGreeting(Stringname)
{
return"你好"+name;
}
publicintgetPrice()
{
returnnewjava.util.Random().nextInt(1000);
}
}

在SimpleService类中有两个方法,由于这两个方法都是public方法,因此,它们都将作为WebService方法被发布。

编译SimpleService类后,将SimpleService.class文件放到<Tomcat安装目录>\webapps\axis2\WEB-INF\pojo目录中(如果没有pojo目录,则建立该目录)。现在我们已经成功将SimpleService类发布成了WebService。在浏览器地址栏中输入如下的URL:

http://localhost:8080/axis2/services/listServices

这时当前页面将显示所有在Axis2中发布的WebService,如图2所示。



图2

在浏览器地址栏中输入如下的两个URL来分别测试getGreeting和getPrice方法:

http://localhost:8080/axis2/services/SimpleService/getGreeting?name=bill

http://localhost:8080/axis2/services/SimpleService/getPrice

图3和图4分别显示了getGreeting和getPrice方法的测试结果。

图3getGreeting方法的测试结果

图4getPrice方法的测试结果

在编写、发布和测试0配置的WebService时应注意如下几点:

1.POJO类不能使用package关键字声明包。

2.Axis2在默认情况下可以热发布WebService,也就是说,将WebService的.class文件复制到pojo目录中时,Tomcat不需要重新启动就可以自动发布WebService。如果想取消Axis2的热发布功能,可以打开<Tomcat安装目录>\webapps\axis2\WEB-INF\conf\axis2.xml,找到如下的配置代码:

<parametername="hotdeployment">true</parameter>

将true改为false即可。要注意的是,Axis2在默认情况下虽然是热发布,但并不是热更新,也就是说,一旦成功发布了WebService,再想更新该WebService,就必须重启Tomcat。这对于开发人员调试WebService非常不方便,因此,在开发WebService时,可以将Axis2设为热更新。在axis2.xml文件中找到<parametername="hotupdate">false</parameter>,将false改为true即可。

3.在浏览器中测试WebService时,如果WebService方法有参数,需要使用URL的请求参数来指定该WebService方法参数的值,请求参数名与方法参数名要一致,例如,要测试getGreeting方法,请求参数名应为name,如上面的URL所示。

4.发布WebService的pojo目录只是默认的,如果读者想在其他的目录发布WebService,可以打开axis2.xml文件,并在<axisconfig>元素中添加如下的子元素:

<deployerextension=".class"directory="my"class="org.apache.axis2.deployment.POJODeployer"/>

上面的配置允许在<Tomcat安装目录>\webapps\axis2\WEB-INF\my目录中发布WebService。例如,将本例中的SimpleService.class复制到my目录中也可以成功发布(但要删除pojo目录中的SimpleService.class,否则WebService会重名)。

三、用Java实现调用WebService的客户端程序

WebService是为程序服务的,只在浏览器中访问WebService是没有意义的。因此,在本节使用Java实现了一个控制台程序来调用上一节发布的WebService。调用WebService的客户端代码如下:

packageclient;

importjavax.xml.namespace.QName;
importorg.apache.axis2.addressing.EndpointReference;
importorg.apache.axis2.client.Options;
importorg.apache.axis2.rpc.client.RPCServiceClient;

publicclassRPCClient
{
publicstaticvoidmain(String[]args)throwsException
{
//使用RPC方式调用WebService
RPCServiceClientserviceClient=newRPCServiceClient();
Optionsoptions=serviceClient.getOptions();
//指定调用WebServiceURL
EndpointReferencetargetEPR=newEndpointReference(
"http://localhost:8080/axis2/services/SimpleService");
options.setTo(targetEPR);
//指定getGreeting方法的参数值
Object[]opAddEntryArgs=newObject[]{"超人"};
//指定getGreeting方法返回值的数据类型的Class对象
Class[]classes=newClass[]{String.class};
//指定要调用的getGreeting方法及WSDL文件的命名空间
QNameopAddEntry=newQName("http://ws.apache.org/axis2","getGreeting");
//调用getGreeting方法并输出该方法的返回值
System.out.println(serviceClient.invokeBlocking(opAddEntry,opAddEntryArgs,classes)[0]);
//下面是调用getPrice方法的代码,这些代码与调用getGreeting方法的代码类似
classes=newClass[]{int.class};
opAddEntry=newQName("http://ws.apache.org/axis2","getPrice");
System.out.println(serviceClient.invokeBlocking(opAddEntry,newObject[]{},classes)[0]);
}
}

运行上面的程序后,将在控制台输出如下的信息:

你好超人
443

在编写客户端代码时应注意如下几点:

1.客户端代码需要引用很多Axis2的jar包,如果读者不太清楚要引用哪个jar包,可以在Eclipse的工程中引用Axis2发行包的lib目录中的所有jar包。

2.在本例中使用了RPCServiceClient类的invokeBlocking方法调用了WebService中的方法。invokeBlocking方法有三个参数,其中第一个参数的类型是QName对象,表示要调用的方法名;第二个参数表示要调用的WebService方法的参数值,参数类型为Object[];第三个参数表示WebService方法的返回值类型的Class对象,参数类型为Class[]。当方法没有参数时,invokeBlocking方法的第二个参数值不能是null,而要使用new Object[]{}。

3.如果被调用的WebService方法没有返回值,应使用RPCServiceClient类的invokeRobust方法,该方法只有两个参数,它们的含义与invokeBlocking方法的前两个参数的含义相同。

4.在创建QName对象时,QName类的构造方法的第一个参数表示WSDL文件的命名空间名,也就是<wsdl:definitions>元素的targetNamespace属性值,下面是SimpleService类生成的WSDL文件的代码片段:

<?xmlversion="1.0"encoding="UTF-8"?>
<wsdl:definitionsxmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"xmlns:ns1="http://org.apache.axis2/xsd"
xmlns:ns
="http://ws.apache.org/axis2"xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl"
xmlns:http
="http://schemas.xmlsoap.org/wsdl/http/"xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mime
="http://schemas.xmlsoap.org/wsdl/mime/"xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soap12
="http://schemas.xmlsoap.org/wsdl/soap12/"
targetNamespace="http://ws.apache.org/axis2"
>
<wsdl:types>

</wsdl:types>

</wsdl:definitions>

四、用wsdl2java简化客户端的编写

也许有很多读者会说“有没有搞错啊,只调用两个WebService方法用要写这么多代码,太麻烦了”。

不过幸好Axis2提供了一个wsdl2java.bat命令可以根据WSDL文件自动产生调用WebService的代码。wsdl2java.bat命令可以在<Axis2安装目录>"bin目录中找到。在使用wsdl2java.bat命令之前需要设置AXIS2_HOME环境变量,该变量值是<Axis2安装目录>。

在Windows控制台输出如下的命令行来生成调用WebService的代码:

%AXIS2_HOME%\bin\wsdl2java-url http://localhost:8080/axis2/services/SimpleService?wsdl -p client -s -ostub

%AXIS2_HOME%\bin\wsdl2java–uri http://localhost:8080/axis2/services/SayHello.aar?wsdl -p D:\Work\NPU\Develop\eclipse-workspace\SayHello-s -o test.stub

%AXIS2_HOME%\bin\wsdl2java–uri D:\Work\NPU\Develop\eclipse-workspace\SayHello\src\SayHello.wsdl -p D:\Work\NPU\Develop\eclipse-workspace\SayHello-s -o test.stub

%AXIS2_HOME%\bin\wsdl2java-uri http://localhost:8080/axis2/services/SayHello?wsdl -p org.example.www.sayhello-s -o D:\Work\NPU\Develop\eclipse-workspace\SayHello\src

C:\axis2\bin>wsdl2java.bat-uri http://localhost:8080/axis2/services/HelloWorldService.aar?wsdl -oC:\JAVA\eclipse\workspace\HelloWorldClient -p test.joeyta

其中-url参数指定了wsdl文件的路径,可以是本地路径,也可以是网络路径。-p参数指定了生成的Java类的包名,-o参数指定了生成的一系列文件保存的根目录。在执行完上面的命令后,读者就会发现在当前目录下多了个stub目录,在."stub"src"client目录可以找到一个SimpleServiceStub.java文件,该文件复杂调用WebService,读者可以在程序中直接使用这个类,代码如下:

packageclient;

importjavax.xml.namespace.QName;
importorg.apache.axis2.addressing.EndpointReference;
importorg.apache.axis2.client.Options;
importorg.apache.axis2.rpc.client.RPCServiceClient;

publicclassStubClient
{
publicstaticvoidmain(String[]args)throwsException
{
SimpleServiceStubstub=newSimpleServiceStub();
SimpleServiceStub.GetGreetinggg=newSimpleServiceStub.GetGreeting();
gg.setName("比尔");
System.out.println(stub.getGreeting(gg).get_return());
System.out.println(stub.getPrice().get_return());
}
}

上面的代码大大简化了调用WebService的步骤,并使代码更加简洁。但要注意的是,wsdl2java.bat命令生成的Stub类将WebService方法的参数都封装在了相应的类中,类名为方法名,例如,getGreeting方法的参数都封装在了GetGreeting类中,要想调用getGreeting方法,必须先创建GetGreeting类的对象实例。

五、使用C#调用WebService

从理论上说,WebService可以被任何支持SOAP协议的语言调用。在VisualStudio中使用C#调用WebService是在所有语言中最容易实现的(VB.net的调用方法类似,也同样很简单)。

新建一个VisualStudio工程,并在引用Web服务的对话框中输入如下的URL,并输入Web引用名为“WebService”:

http://localhost:8080/axis2/services/SimpleService?wsdl

然后引用Web服务的对话框就会显示该WebService中的所有的方法,如图5所示。


图5

在完成上面的工作后,只需要如下三行C#代码就可以调用getGreeting和getPrice方法,并显示这两个方法的返回值:

WebService.SimpleServicesimpleService=newWSC.WebService.SimpleService();
MessageBox.Show(simpleService.getGreeting("比尔"));
MessageBox.Show(simpleService.getPrice()[email protected]());

在.net解析WSDL文件时直接将getGreeting方法的参数映射为String类型,因此,可以直接进行传值。
从上面的调用过程可以看出,添加Web引用的过程就相当于在Java中调用wsdl2java.bat自动生成stub类的过程。只是在调用stub类时与C#有一定的区别,但从总体上来说,都大大简化了调用WebService的过程。

在实际的应用中,不仅需要使用WebService来传递简单类型的数据,有时也需要传递更复杂的数据,这些数据可以被称为复合类型的数据。数组与类(接口)是比较常用的复合类型。在Axis2中可以直接使用将WebService方法的参数或返回值类型声明成数组或类(接口)。但要注意,在定义数组类型时只能使用一维数组,如果想传递多维数组,可以使用分隔符进行分隔,如下面的代码所示:

String[]strArray = new String[]{ "自行车,飞机,火箭","中国,美国,德国","超人,蜘蛛侠,钢铁侠" } ;

上面的代码可以看作是一个3*3的二维数组。

在传递类的对象实例时,除了直接将数组类型声明成相应的类或接口,也可以将对象实例进行序列化,也就是说,将一个对象实例转换成字节数组进行传递,然后接收方再进行反序列化,还原这个对象实例。

下面的示例代码演示了如何传递数组与类(接口)类型的数据,并演示如何使用字节数组上传图像。本示例的客户端代码使用Java和C#编写。要完成这个例子需要如下几步:

一、实现服务端代码

ComplexTypeService是一个WebService类,该类的代码如下:

importjava.io.FileOutputStream;
importdata.DataForm;

publicclassComplexTypeService
{
//上传图像,imageByte参数表示上传图像文件的字节,
//length
参数表示图像文件的字节长度(该参数值可能小于imageByte的数组长度)
publicbooleanuploadImageWithByte(byte[]imageByte,intlength)
{
FileOutputStreamfos=null;
try
{
//将上传的图像保存在D盘的test1.jpg文件中
fos=newFileOutputStream("d:\\test1.jpg");
//开始写入图像文件的字节
fos.write(imageByte,0,length);
fos.close();
}
catch(Exceptione)
{
returnfalse;
}
finally
{
if(fos!=null)
{
try
{
fos.close();
}
catch(Exceptione)
{

}
}
}
returntrue;
}
//返回一维字符串数组
publicString[]getArray()
{
String[]strArray=newString[]{"自行车","飞机","火箭"};
returnstrArray;
}
//返回二维字符串数组
publicString[]getMDArray()
{
String[]strArray=newString[]{"自行车,飞机,火箭","中国,美国,德国","超人,蜘蛛侠,钢铁侠"};
returnstrArray;
}
//返回DataForm类的对象实例
publicDataFormgetDataForm()
{
returnnewDataForm();
}
//DataForm类的对象实例序列化,并返回序列化后的字节数组
publicbyte[]getDataFormBytes()throwsException
{
java.io.ByteArrayOutputStreambaos=newjava.io.ByteArrayOutputStream();
java.io.ObjectOutputStreamoos=newjava.io.ObjectOutputStream(baos);
oos.writeObject(newDataForm());
returnbaos.toByteArray();
}
}

二、实现DataForm类

DataForm是要返回的对象实例所对应的类,该类的实现代码如下:

packagedata;

publicclassDataFormimplementsjava.io.Serializable
{
privateStringname="bill";
privateintage=20;

publicStringgetName()
{
returnname;
}
publicvoidsetName(Stringname)
{
this.name=name;
}
publicintgetAge()
{
returnage;
}
publicvoidsetAge(intage)
{
this.age=age;
}
}

三、发布WebService

由于本示例的WebService类使用了一个Java类(DataForm类),因此,在发布WebService之前,需要先将DataForm.class文件复制到<Tomcat安装目录>\webapps\axis2\WEB-INF\classes\data目录中,然后将ComplexTypeService.class文件复制到<Tomcat安装目录>\webapps\axis2\WEB-INF\pojo目录中,最后启动Tomcat(如果Tomcat已经启动,由于增加了一个DataForm类,因此,需要重新启动Tomcat)。

四、使用Java编写调用WebService的客户端代码

在客户端仍然使用了RPC的调用方式,代码如下:

packageclient;

importjavax.xml.namespace.QName;
importorg.apache.axis2.addressing.EndpointReference;
importorg.apache.axis2.client.Options;
importorg.apache.axis2.rpc.client.RPCServiceClient;

publicclassComplexTypeRPCClient
{

publicstaticvoidmain(String[]args)throwsException
{
RPCServiceClientserviceClient=newRPCServiceClient();
Optionsoptions=serviceClient.getOptions();
EndpointReferencetargetEPR=newEndpointReference(
"http://localhost:8080/axis2/services/ComplexTypeService");
options.setTo(targetEPR);
//下面的代码调用uploadImageWithByte方法上传图像文件
/////////////////////////////////////////
//
打开图像文件,确定图像文件的大小
java.io.Filefile=newjava.io.File("f:\\images.jpg");
java.io.FileInputStreamfis=newjava.io.FileInputStream("f:\\images.jpg");
//创建保存要上传的图像文件内容的字节数组
byte[]buffer=newbyte[(int)file.length()];
//将图像文件的内容读取buffer数组中
intn=fis.read(buffer);
System.out.println("文件长度:"+file.length());
Object[]opAddEntryArgs=newObject[]{buffer,n};
Class[]classes=newClass[]{Boolean.class};
QNameopAddEntry=newQName("http://ws.apache.org/axis2","uploadImageWithByte");
fis.close();
//开始上传图像文件,并输出uploadImageWithByte方法的返回传
System.out.println(serviceClient.invokeBlocking(opAddEntry,opAddEntryArgs,classes)[0]);
/////////////////////////////////////////

//
下面的代码调用了getArray方法,并返回一维String数组
/////////////////////////////////////////
opAddEntry=newQName("http://ws.apache.org/axis2","getArray");
String[]strArray=(String[])serviceClient.invokeBlocking(opAddEntry,
new
Object[]{},newClass[]{String[].class})[0];
for(Strings:strArray)
System.out.print(s+"");
System.out.println();
/////////////////////////////////////////


//
下面的代码调用了getMDArray方法,并返回一维String数组
/////////////////////////////////////////
opAddEntry=newQName("http://ws.apache.org/axis2","getMDArray");
strArray=(String[])serviceClient.invokeBlocking(opAddEntry,newObject[]{},
new
Class[]{String[].class})[0];
for(Strings:strArray)
{
String[]array=s.split(",");
for(Stringss:array)
System.out.print("<"+ss+">");
System.out.println();
}
System.out.println();
/////////////////////////////////////////

//
下面的代码调用了getDataForm方法,并返回DataForm对象实例
/////////////////////////////////////////
opAddEntry=newQName("http://ws.apache.org/axis2","getDataForm");
data.DataFormdf=(data.DataForm)serviceClient.invokeBlocking(opAddEntry,newObject[]{},
newClass[]{data.DataForm.class})[0];
System.out.println(df.getAge());
/////////////////////////////////////////

//
下面的代码调用了getDataFormBytes方法,并返回字节数组,最后将返回的字节数组反序列化后,转换成DataForm对象实例
/////////////////////////////////////////
opAddEntry=newQName("http://ws.apache.org/axis2","getDataFormBytes");
buffer=(byte[])serviceClient.invokeBlocking(opAddEntry,newObject[]{},newClass[]{byte[].class})[0];
java.io.ObjectInputStreamois=newjava.io.ObjectInputStream(
newjava.io.ByteArrayInputStream(buffer));
df=(data.DataForm)ois.readObject();
System.out.println(df.getName());
//////////////////////////////////////////
}
}

运行上面的程序,将输出如下的内容:

文件长度:3617

true

自行车飞机火箭

<自行车><飞机><火箭>

<中国><美国><德国>

<超人><蜘蛛侠><钢铁侠>

20

bill

五、使用C#编写调用WebService的客户端代码

在VisualStudio中使用WebService就简单得多。假设引用WebService时的引用名为complexType,则下面的代码调用了uploadImageWithByte方法来上传图像文件。在VisualStudio引用WebService时,uploadImageWithByte方法多了两个out参数,在使用时要注意。

complexType.ComplexTypeServicects=newWSC.complexType.ComplexTypeService();
System.IO.FileStreamfs=newSystem.IO.FileStream(@"f:\images.jpg",System.IO.FileMode.Open);
byte[]buffer=newbyte[fs.Length];
fs.Read(buffer,0,(int)fs.Length);
boolr;
boolrs;
cts.uploadImageWithByte(buffer,(int)fs.Length,true,outr,outrs);

在获得二维数组时,可以将数据加载到DataGridView或其他类似的控件中,代码如下:

String[]strArray=cts.getMDArray();
for(inti=0;i<strArray.Length;i++)
{
//用正则表达式将带分隔符的字符串转换成String数组
String[]columns=strArray[i].Split(',');
//如果DataGridView的表头不存在,向DataGridView控件添加三个带表头的列
if(dataGridView1.Columns.Count==0)
for(intj=0;j<columns.Length;j++)
dataGridView1.Columns.Add("column"+(j+1).ToString(),"列"+(j+1).ToString());
//添加行
dataGridView1.Rows.Add(1);
for(intj=0;j<columns.Length;j++)
{
dataGridView1.Rows[i].Cells[j].Value=columns[j];
}
}

向DataGridView控件添加数据后的效果如图1所示。



图1

对于其他的WebService方法的调用都非常简单,读者可以自己做这个实验。

要注意的是,由于.net和java序列化和反序列化的差异,通过序列化的方式传递对象实例只使用于客户端与服务端为同一种语言或技术的情况,如客户端和服务端都使用Java来编写。

如果读者要上传大文件,应尽量使用FTP的方式来传递,而只通过WebService方法来传递文件名等信息。这样有助于提高传输效率。

用Axis2实现Web Service,虽然可以将POJO类放在axis2\WEB-INF\pojo目录中直接发布成WebService,这样做不需要进行任何配置,但这些POJO类不能在任何包中。这似乎有些不方便,为此,Axis2也允许将带包的POJO类发布成Web Service。
先实现一个POJO类,代码如下:

packageservice;

publicclassMyService
{
publicStringgetGreeting(Stringname)
{
return"您好"+name;
}
publicvoidupdate(Stringdata)
{
System.out.println("<"+data+">已经更新");
}
}

这个类有两个方法,这两个方法都需要发布成Web Service方法。这种方式和直接放在pojo目录中的POJO类不同。要想将MyService类发布成Web Service,需要一个services.xml文件,这个文件需要放在META-INF目录中,该文件的内容如下:

<servicename="myService">
<description>
WebService例子
</description>
<parametername="ServiceClass">
service.MyService
</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>

其中<service>元素用于发布WebService,一个<service>元素只能发布一个WebService类,name属性表示WebService名,如下面的URL可以获得这个WebService的WSDL内容:

http://localhost:8080/axis2/services/myService?wsdl

其中name属性名就是上面URL中"?"和"/"之间的部分。

<description>元素表示当前Web Service的描述,<parameter>元素用于设置WebService的参数,在这里用于设置WebService对应的类名。在这里最值得注意的是<messageReceivers>元素,该元素用于设置处理WebService方法的处理器。例如,getGreeting方法有一个返回值,因此,需要使用可处理输入输出的RPCMessageReceiver类,而update方法没有返回值,因此,需要使用只能处理输入的RPCInOnlyMessageReceiver类。

使用这种方式发布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 .jar cvf AxisTest.aar .

最后将ws.aar文件复制到<Tomcat安装目录>\webapps\axis2\WEB-INF\services目录中,启动Tomcat后,就可以调用这个WebService了。调用的方法和WebService大讲堂之Axis21):用POJO实现0配置的WebService所讲的方法类似。

另外services.xml文件中也可以直接指定WebService类的方法,如可以用下面的配置代码来发布WebService:

<servicename="myService">
<description>
WebService例子
</description>
<parametername="ServiceClass">
service.MyService
</parameter>
<operationname="getGreeting">
<messageReceiverclass="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</operation>
<operationname="update">
<messageReceiver
class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>
</operation>
</service>

上面的配置代码前面的部分和以前的services.xml文件的内容相同,但后面使用了<operation>元素来指定每一个WebService方法,并单独指定了处理每一个方法的处理器。对于客户端来说,调用使用这两个services.xml文件发布的WebService并没有太大我区别,只是使用第二个services.xml文件发布WebServices后,在使用wsdl2java命令或使用C#、delphi等生成客户端的stub时,update方法的String类型被封装在了update类中,在传递update方法的参数时需要建立update类的对象实例。而使用第一个services.xml文件发布的WebService在生成stub时直接可以为update方法传递String类型的参数。从这一点可以看出,这两种方法生成的WSDL有一定的区别。但实际上,如果客户端程序使用第一个services.xml文件发布的WebService生成stub类时(这时update方法的参数是String),在服务端又改为第二个services.xml文件来发布WebService,这时客户端并不需要再重新生成stub类,而可以直接调用update方法。也就是说,服务端使用什么样的方式发布WebService,对客户端并没有影响。

如果想发布多个WebService,可以使用<serviceGroup>元素,如再建立一个MyService1类,代码如下:

packageservice
publicclassMyService1
{
publicStringgetName()
{
return"bill";
}
}

在services.xml文件中可以使用如下的配置代码来配置MyService和MyService1类:

<serviceGroup>
<servicename="myService">
<description>
WebService例子
</description>
<parametername="ServiceClass">
service.MyService
</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>
<servicename="myService1">
<description>
WebService例子
</description>
<parametername="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>




在《WebService大讲堂之Axis22):复合类型数据的传递》中讲过,如果要传递二进制文件(如图像、音频文件等),可以使用byte[]作为数据类型进行传递,然后客户端使用RPC方式进行调用。这样做只是其中的一种方法,除此之外,在客户端还可以使用wsdl2java命令生成相应的stub类来调用WebService,wsdl2java命令的用法详见《WebService大讲堂之Axis21):用POJO实现0配置的WebService》。
WebService类中包含byte[]类型参数的方法在wsdl2java生成的stub类中对应的数据类型不再是byte[]类型,而是javax.activation.DataHandler。DataHandler类是专门用来映射WebService二进制类型的。
在WebService类中除了可以使用byte[]作为传输二进制的数据类型外,也可以使用javax.activation.DataHandler作为数据类型。不管是使用byte[],还是使用javax.activation.DataHandler作为WebService方法的数据类型,使用wsdl2java命令生成的stub类中相应方法的类型都是javax.activation.DataHandler。而象使用.net、delphi生成的stub类的相应方法类型都是byte[]。这是由于javax.activation.DataHandler类是Java特有的,对于其他语言和技术来说,并不认识javax.activation.DataHandler类,因此,也只有使用最原始的byte[]了。
下面是一个上传二进制文件的例子,WebService类的代码如下:

packageservice;

importjava.io.InputStream;
importjava.io.OutputStream;
importjava.io.FileOutputStream;
importjavax.activation.DataHandler;

publicclassFileService
{
//使用byte[]类型参数上传二进制文件
publicbooleanuploadWithByte(byte[]file,Stringfilename)
{
FileOutputStreamfos=null;
try
{
fos=newFileOutputStream(filename);
fos.write(file);
fos.close();
}
catch(Exceptione)
{
returnfalse;
}
finally
{
if(fos!=null)
{
try
{
fos.close();
}
catch(Exceptione)
{
}
}
}
returntrue;
}
privatevoidwriteInputStreamToFile(InputStreamis,OutputStreamos)throwsException
{
intn=0;
byte[]buffer=newbyte[8192];
while((n=is.read(buffer))>0)
{
os.write(buffer,0,n);
}
}
//使用DataHandler类型参数上传文件
publicbooleanuploadWithDataHandler(DataHandlerfile,Stringfilename)
{

FileOutputStreamfos=null;
try
{
fos=newFileOutputStream(filename);
//可通过DataHandler类的getInputStream方法读取上传数据
writeInputStreamToFile(file.getInputStream(),fos);
fos.close();
}
catch(Exceptione)
{
returnfalse;
}
finally
{
if(fos!=null)
{
try
{
fos.close();
}
catch(Exceptione)
{
}
}
}
returntrue;
}
}

上面代码在services.xml文件的配置代码如下:

<servicename="fileService">
<description>
文件服务
</description>
<parametername="ServiceClass">
service.FileService
</parameter>
<messageReceivers>
<messageReceivermep="http://www.w3.org/2004/08/wsdl/in-out"
class
="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</messageReceivers>
</service>


如果使用wsdl2java命令生成调用Java客户端代码,则需要创建DataHandler类的对象实例,代码如下:

DataHandlerdh=newDataHandler(newFileDataSource(imagePath));

wsdl2java命令会为每一个方法生成一个封装方法参数的类,类名为方法名(第一个字符大写),如uploadWithByte方法生成的类名为UploadWithByte。如果要设置file参数的值,可以使用UploadWithByte类的setFile方法,代码如下:

UploadWithByteuwb=newUPloadWithByte();
uwb.setFile(dh);

最后是调用uploadWithByte方法,代码如下(FileServiceStub为wsdl2java生成的stub类名):

FileServiceStubfss=newFileServiceStub();
fss.uploadWithByte(uwb);

如果使用C#调用FileService,则file参数类型均为byte[],代码如下:

MemoryStreamms=newMemoryStream();
Bitmapbitmap=newBitmap(picUpdateImage.Image);
bitmap.Save(ms,System.Drawing.Imaging.ImageFormat.Jpeg);
service.fileServicefs=newWSC.service.fileService();
fs.uploadWithDataHandler(ms.ToArray());
fs.uploadWithByte(ms.ToArray());

其中picUpdateImage为c#中加载图像文件的picturebox控件。

WebService给人最直观的感觉就是由一个个方法组成,并在客户端通过SOAP协议调用这些方法。这些方法可能有返回值,也可能没有返回值。虽然这样可以完成一些工具,但这些被调用的方法是孤立的,当一个方法被调用后,在其他的方法中无法获得这个方法调用后的状态,也就是说无法保留状态。

读者可以想象,这对于一个完整的应用程序,无法保留状态,就意味着只依靠WebService很难完成全部的工作。例如,一个完整的应用系统都需要进行登录,这在Web应用中使用Session来保存用户登录状态,而如果用WebService的方法来进行登录处理,无法保存登录状态是非常令人尴尬的。当然,这也可以通过其他的方法来解决,如在服务端使用static变量来保存用户状态,并发送一个id到客户端,通过在服务端和客户端传递这个id来取得相应的用户状态。这非常类似于Web应用中通过Session和Cookie来管理用户状态。但这就需要由开发人员做很多工作,不过幸好Axis2为我们提供了WebService状态管理的功能。

使用Axis2来管理WebService的状态基本上对于开发人员是透明的。在WebService类需要使用org.apache.axis2.context.MessageContext和org.apache.axis2.context.ServiceContext类来保存与获得保存在服务端的状态信息,这有些象使用HttpSession接口的getAttribute和setAttribute方法获得与设置Session域属性。

除此之外,还需要修改services.xml文件的内容,为<service>元素加一个scope属性,该属性有四个可取的值:Application, SOAPSession, TransportSession, Request,不过要注意一下,虽然Axis2的官方文档将这四个值的单词首字母和缩写字母都写成了大写,但经笔者测试,必须全部小写才有效,也就是这四个值应为:application、soapsession、transportsession、request,其中request为scope属性的默认值。读者可以选择使用transportsession和application分别实现同一个WebService类和跨WebService类的会话管理。

在客户端需要使用setManageSession(true)打开Session管理功能。

综上所述,实现同一个WebService的Session管理需要如下三步:

1.使用MessageContext和ServiceContext获得与设置key-value对。

2.为要进行Session管理的WebService类所对应的<service>元素添加一个scope属性,并将该属性值设为transportsession

3.在客户端使用setManageSession(true)打开Session管理功能。

下面是一个在同一个WebService类中管理Session的例子。

先建立一个WebService类,代码如下:

packageservice;
importorg.apache.axis2.context.ServiceContext;
importorg.apache.axis2.context.MessageContext;
publicclassLoginService
{
publicbooleanlogin(Stringusername,Stringpassword)
{
if("bill".equals(username)&&"1234".equals(password))
{
//1步:设置key-value
MessageContextmc=MessageContext.getCurrentMessageContext();
ServiceContextsc=mc.getServiceContext();
sc.setProperty("login","成功登录");
returntrue;
}
else
{
returnfalse;
}
}
publicStringgetLoginMsg()
{
//1步:获得key-value对中的value
MessageContextmc=MessageContext.getCurrentMessageContext();
ServiceContextsc=mc.getServiceContext();
return(String)sc.getProperty("login");
}
}

在LoginService类中有两个方法:login和getLoginMsg,如果login方法登录成功,会将“成功登录”字符串保存在ServiceContext对象中。如果在login方法返回true后调用getLoginMsg方法,就会返回“成功登录”。

下面是LoginService类的配置代码(services.xml):

<!--2步:添加scope属性-->
<servicename="loginService"scope="transportsession">
<description>
登录服务
</description>
<parametername="ServiceClass">
service.LoginService
</parameter>
<messageReceivers>
<messageReceivermep="http://www.w3.org/2004/08/wsdl/in-out"
class
="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</messageReceivers>
</service>

使用如下的命令生成客户端使用的stub类:

%AXIS2_HOME%\bin\wsdl2java-urihttp://localhost:8080/axis2/services/loginService?wsdl-pclient-s-ostub

%AXIS2_HOME%\bin\wsdl2java-urihttp://localhost:8080/axis2/services/loginService?wsdl-pclient-s-oD:/Work/NPU/Develop/eclipse-workspace/SayHello/src

在stub\src\client目录中生成了一个LoginServiceStub.java类,在该类中找到如下的构造句方法:

publicLoginServiceStub(org.apache.axis2.context.ConfigurationContextconfigurationContext,
java.lang.StringtargetEndpoint,booleanuseSeparateListener)
throwsorg.apache.axis2.AxisFault
{

_serviceClient.getOptions().setSoapVersionURI(
org.apache.axiom.soap.SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI);
}

在该方法中最后添加如下的代码:

//3步:打开客户端的Session管理功能
_serviceClient.getOptions().setManageSession(true);

下面的客户端代码使用LoginServiceStub对象访问了刚才建立的WebService:

LoginServiceStubstub=newLoginServiceStub();
LoginServiceStub.Loginlogin=newLoginServiceStub.Login();
login.setUsername("bill");
login.setPassword("1234");
if(stub.login(login).local_return)
{
System.out.println(stub.getLoginMsg().local_return);
}

运行上面的代码后,会输出“成功登录”信息。

WebService大讲堂之Axis2(5):会话(Session)管理》一文中介绍了如何使用Axis2来管理同一个服务的会话,但对于一个复杂的系统,不可能只有一个WebService服务,例如,至少会有一个管理用户的WebService(用户登录和注册)以及处理业务的WebService。象这种情况,就必须在多个WebService服务之间共享会话状态,也称为跨服务会话(Session)管理。实现跨服务会话管理与实现同一个服务的会话管理的步骤类似,但仍然有一些差别,实现跨服务会话管理的步骤如下:

实现跨服务的Session管理需要如下三步:

1.使用MessageContext和ServiceGroupContext获得与设置key-value对。

2.为要进行Session管理的WebService类所对应的<service>元素添加一个scope属性,并将该属性值设为application

3.在客户端使用setManageSession(true)打开Session管理功能。

从上面的步骤可以看出,实现跨服务会话管理与实现同一个服务的会话管理在前两步上存在着差异,而第3步是完全一样的。下面是一个跨服务的会话管理的实例。在这个例子中有两个WebService类:LoginService和SearchService,代码如下:

LoginService.java

packageservice;
importorg.apache.axis2.context.MessageContext;
importorg.apache.axis2.context.ServiceGroupContext;
publicclassLoginService
{
publicbooleanlogin(Stringusername,Stringpassword)
{
if("bill".equals(username)&&"1234".equals(password))
{
//1步:设置key-value
MessageContextmc=MessageContext.getCurrentMessageContext();
ServiceGroupContextsgc=mc.getServiceGroupContext();
sgc.setProperty("login","成功登录");
returntrue;
}
else
{
returnfalse;
}
}
publicStringgetLoginMsg()
{
//1步:获得key-value对中的value
MessageContextmc=MessageContext.getCurrentMessageContext();
ServiceGroupContextsgc=mc.getServiceGroupContext();
return(String)sgc.getProperty("login");
}
}


SearchService.java

packageservice;
importorg.apache.axis2.context.MessageContext;
importorg.apache.axis2.context.ServiceGroupContext;
publicclassSearchService
{
publicStringfindByName(Stringname)
{
//1步:获得key-value对中的value
MessageContextmc=MessageContext.getCurrentMessageContext();
ServiceGroupContextsgc=mc.getServiceGroupContext();
if(sgc.getProperty("login")!=null)
return"找到的数据<"+name+">";
else
return"用户未登录";
}
}

services.xml文件中的配置代码如下:

<serviceGroup>
<!--2步:添加scope属性,并设置属性值为application-->
<servicename="loginService"scope="application">
<description>
登录服务
</description>
<parametername="ServiceClass">
service.LoginService
</parameter>
<messageReceivers>
<messageReceivermep="http://www.w3.org/2004/08/wsdl/in-out"
class
="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</messageReceivers>
</service>
<!--2步:添加scope属性,并设置属性值为application-->
<servicename="searchService"scope="application">
<description>
搜索服务
</description>
<parametername="ServiceClass">
service.SearchService
</parameter>
<messageReceivers>
<messageReceivermep="http://www.w3.org/2004/08/wsdl/in-out"
class
="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</messageReceivers>
</service>
</serviceGroup>

第3步与WebService大讲堂之Axis2(5):会话(Session)管理》一文中介绍的方法类似。

下面是使用两个stub类的对象实例访问上面实现的两个WebService的客户端代码:

LoginServiceStubstub=newLoginServiceStub();
LoginServiceStub.Loginlogin=newLoginServiceStub.Login();
login.setUsername("bill");
login.setPassword("1234");
if(stub.login(login).local_return)
{
System.out.println(stub.getLoginMsg().local_return);
SearchServiceStubsearchStub=newSearchServiceStub();
SearchServiceStub.FindByNamefbn=newSearchServiceStub.FindByName();
fbn.setName("abc");
System.out.println(searchStub.findByName(fbn).local_return);
}

在执行上面的代码后,将输出如下的信息:

成功登录

找到的数据<abc>

读者可以将scope属性值改成transportsession,看看会输出什么!

实际上,Axis2的会话管理也是通过Cookie实现的,与Web应用中的Session管理类似。如果读者使用C#访问支持会话(在同一个服务中的会话管理)的WebService,需要指定一个CookieContainer对象,代码如下:

service.loginServicels=newservice.loginService();
System.Net.CookieContainercc=newSystem.Net.CookieContainer();
ls.CookieContainer=cc;
boolr,rs;
ls.login("bill","1234",out@r,outrs);
if(r)
{
MessageBox.Show(ls.getLoginMsg().@return);
}

如果是访问跨服务的支持会话的WebService,则不需要指定CookieContainer对象,代码如下:

service.loginServicels=newservice.loginService();
boolr,rs;
ls.login("bill","1234",out@r,outrs);
if(r)
{
service1.searchServicess=newservice1.searchService();
MessageBox.Show(ss.findByName("abc"));
}

如果读者使用delphi(本文使用的是delphi2009,其他的delphi版本请读者自行测试)调用支持会话的WebService时有一些差别。经笔者测试,使用delphi调用WebService,将scope属性值设为transportsession和application都可以实现跨服务的会话管理,这一点和Java与C#不同,Java和C#必须将scope属性值设为application才支持跨服务会话管理。在delphi中不需要象C#指定一个CookieContainer或其他类似的对象,而只需要象访问普通的WebService一样访问支持会话的WebService即可。

在现今的Web应用中经常使用Spring框架来装载JavaBean。如果要想将某些在Spring中装配的JavaBean发布成WebService,使用Axis2的Spring感知功能是非常容易做到的。
在本文的例子中,除了<Tomcat安装目录>\webapps\axis2目录及该目录中的相关库外,还需要Spring框架中的spring.jar文件,将该文件复制到<Tomcat安装目录>\webapps\axis2\WEB-INF\lib目录中。
下面先建立一个JavaBean(该JavaBean最终要被发布成WebService),代码如下:

packageservice;
importentity.Person;
publicclassSpringService
{
privateStringname;
privateStringjob;
publicvoidsetName(Stringname)
{
this.name=name;
}
publicvoidsetJob(Stringjob)
{
this.job=job;
}
publicPersongetPerson()
{
Personperson=newPerson();
person.setName(name);
person.setJob(job);
returnperson;
}
publicStringgetGreeting(Stringname)
{
return"hello"+name;
}
}

其中Person也是一个JavaBean,代码如下:

packageentity;
publicclassPerson
{
privateStringname;
privateStringjob;
publicStringgetName()
{
returnname;
}
publicvoidsetName(Stringname)
{
this.name=name;
}
publicStringgetJob()
{
returnjob;
}
publicvoidsetJob(Stringjob)
{
this.job=job;
}
}

将上面两个Java源文件编译后,放到<Tomcat安装目录>\webapps\axis2\WEB-INF\classes目录中。

在<Tomcat安装目录>\webapps\axis2\WEB-INF\web.xml文件中加入下面的内容:

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

在<Tomcat安装目录>\webapps\axis2\WEB-INF目录中建立一个applicationContext.xml文件,该文件是Spring框架用于装配JavaBean的配置文件,内容如下:

<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop
="http://www.springframework.org/schema/aop"
xmlns:tx
="http://www.springframework.org/schema/tx"
xsi:schemaLocation
="
http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

<beanid="springService"class="service.SpringService">
<propertyname="name"value="姚明"/>
<propertyname="job"value="职业男篮"/>
</bean>
</beans>

在applicationContext.xml文件中装配了service.SpringService类,并被始化了name和job属性。在配置完SpringService类后,就可以直接在程序中FileSystemXmlApplicationContext类或其他类似功能的类读取applicationContext.xml文件中的内容,并获得SpringService类的对象实例。但现在我们并不这样做,而是将SpringService类发布成WebService。

在<Tomcat安装目录>\webapps\axis2\WEB-INF\lib目录中有一个axis2-spring-1.4.1.jar文件,该文件用于将被装配JavaBean的发布成WebService。在D盘建立一个axi2-spring-ws目录,并在该目录中建立一个META-INF子目录。在META-INF目录中建立一个services.xml文件,内容如下:

<servicename="springService">
<description>
Springaware
</description>
<parametername="ServiceObjectSupplier">
org.apache.axis2.extensions.spring.receivers.SpringServletContextObjectSupplier
</parameter>
<parametername="SpringBeanName">
springService
</parameter>
<messageReceivers>
<messageReceivermep="http://www.w3.org/2004/08/wsdl/in-out"
class
="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</messageReceivers>
</service>

在Windows控制台进入axi2-spring-ws目录,并使用jar命令将axi2-spring-ws目录中的内容打包成axi2-spring-ws.aar,然后将该文件复制到<Tomcat安装目录>\webapps\axis2\WEB-INF\services目录中,启动Tomcat后,就可以访问该WebService了,访问方式与前面几篇文章的访问方式相同。获得wsdl内容的URL如下:

http://localhost:8080/axis2/services/springService?wsdl

在将Spring中的装配JavaBean发布成WebService需要注意以下几点:

1.由JavaBean编译生成的.class文件需要放在WEB-INF\classes目录中,或打成.jar包后放在WEB-INF\lib目录中,而WEB-INF\services目录中的.aar包中不需要包含.class文件,而只需要包含一个META-INF目录,并在该目录中包含一个services.xml文件即可。

2.services.xml的配置方法与前几篇文章的配置方法类似,只是并不需要使用ServiceClass参数指定要发布成WebService的Java类,而是要指定在applicationContext.xml文件中的装配JavaBean的名称(SpringBeanName参数)。

3.在services.xml文件中需要通过ServiceObjectSupplier参数指定SpringServletContextObjectSupplier类来获得Spring的ApplicationContext对象。

在前面几篇文章中都是使用同步方式来调用WebService。也就是说,如果被调用的WebService方法长时间不返回,客户端将一直被阻塞,直到该方法返回为止。使用同步方法来调用WebService虽然很直观,但当WebService方法由于各种原因需要很长时间才能返回的话,就会使客户端程序一直处于等待状态,这样用户是无法忍受的。
当然,我们很容易就可以想到解决问题的方法,这就是多线程。解决问题的基本方法是将访问WebService的任务交由一个或多个线程来完成,而主线程并不负责访问WebService。这样即使被访问的WebService方法长时间不返回,客户端仍然可以做其他的工作。我们可以管这种通过多线程访问WebService的方式称为异步访问。
虽然直接使用多线程可以很好地解决这个问题,但比较麻烦。幸好Axis2的客户端提供了异步访问WebService的功能。
RPCServiceClient类提供了一个invokeNonBlocking方法可以通过异步的方式来访问WebService。下面先来建立一个WebService。
MyService是一个WebService类,代码如下:

packageservice;
publicclassMyService
{
publicStringgetName()
{
try
{
System.out.println("getName方法正在执行");
//延迟5
Thread.sleep(5000);
}
catch(Exceptione)
{
}
return"火星";
}
}

为了模拟需要一定时间才返回的WebService方法,在getName方法中使用了sleep方法来延迟5秒。
下面是MyService类的配置代码:

<!--services.xml-->
<servicename="myService">
<description>
异步调用演示
</description>
<parametername="ServiceClass">
service.MyService
</parameter>
<messageReceivers>
<messageReceivermep="http://www.w3.org/2004/08/wsdl/in-out"
class
="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</messageReceivers>
</service>

从上面的配置代码可以看出,MyService的配置方式与前几章的WebService的配置方式完全一样,也就是说,MyService只是一个普通的WebService。
下面是异步调用MyService的Java客户端代码:

packageclient;

importjavax.xml.namespace.QName;
importorg.apache.axis2.addressing.EndpointReference;
importorg.apache.axis2.client.Options;
importorg.apache.axis2.context.MessageContext;
importorg.apache.axis2.rpc.client.RPCServiceClient;

publicclassRPCAsyncClient
{
publicstaticvoidmain(String[]args)throwsException
{
RPCServiceClientserviceClient=newRPCServiceClient();
Optionsoptions=serviceClient.getOptions();
EndpointReferencetargetEPR=newEndpointReference(
"http://localhost:8080/axis2/services/myService");
options.setTo(targetEPR);
Object[]opAddEntryArgs=newObject[]{};
QNameopAddEntry=newQName("http://service","getName");
serviceClient.invokeNonBlocking(opAddEntry,opAddEntryArgs,
neworg.apache.axis2.client.async.AxisCallback()
{
@Override
publicvoidonComplete()
{
}
@Override
publicvoidonError(Exceptionarg0)
{
}}
@Override
publicvoidonFault(MessageContextarg0)
{
}
@Override
publicvoidonMessage(MessageContextmc)
{
//输出返回值
System.out.println(mc.getEnvelope().getFirstElement()
.getFirstElement().getFirstElement().getText());
}
});
System.out.println("异步调用!");
//阻止程序退出
System.in.read();
}
}

从上面的代码可以看出,invokeNonBlocking方法有三个参数,前两个参数分别指定了要调用的方法及方法参数的相关信息,而最后一个参数并不是方法返回值的类型信息,而是一个实现org.apache.axis2.client.async.AxisCallback接口的类的对象实例。在本例中隐式实现了AxisCallback接口。在AxisCallback接口中有四个方法需要实现,其中当被异步调用的方法返回时onMessage方法被调用。当运行上面的程序后,将输出如下的信息:

异步调用!
火星

虽然上面的例子可以实现异步调用,但比较麻烦。为了更方便地实现异步调用,可以使用wsdl2java命令的-a参数生成可异步调用的Stub类。下面的命令可生成同步和异步调用的客户端代码(两个类),其中-s表示生成同步调用代码,-a表示生成异步调用代码。

%AXIS2_HOME%\bin\wsdl2java-urihttp://localhost:8080/axis2/services/myService?wsdl-pclient-s-a-ostub

在执行上面的命令后,将生成两个类:MyServiceStub和MyServiceCallbackHandler类,其中MyServiceStub类负责同步和异步调用WebService,MyServiceCallbackHandler类是一个抽象类,也是一个回调类,当使用异步方式调用WebService方法时,如果方法返回,则MyServiceCallbackHandler类的receiveResultgetName方法被调用。下面是使用MyServiceStub类异步访问WebService的代码:

packageclient;

importclient.MyServiceStub.GetNameResponse;

classMyCallbackextendsMyServiceCallbackHandler
{
@Override
publicvoidreceiveResultgetName(GetNameResponseresult)
{
//输出getName方法的返回结果
System.out.println(result.get_return());
}
}
publicclassStubClient
{
publicstaticvoidmain(String[]args)throwsException
{
MyServiceStubstub=newMyServiceStub();
//异步调用WebService
stub.startgetName(newMyCallback());
System.out.println("异步调用!");
System.in.read();
}
}

执行上面的程序后,将输出如下的信息:

异步调用!
火星


在.net中也可以使用异步的方式来调用WebService,如在C#中可使用如下的代码来异步调用getName方法:

//回调方法
privatevoidgetNameCompletedEvent(objectsender,WSC.asyn.getNameCompletedEventArgse)
{
listBox1.Items.Add(e.Result.@return);
}
privatevoidbutton1_Click(objectsender,EventArgse)
{
async.myServicemy=newWSC.async.myService();
my.getNameCompleted+=newWSC.async.getNameCompletedEventHandler(getNameCompletedEvent);
my.getNameAsync();
MessageBox.Show("完成调用");
}

其中async是引用MyService的服务名。要注意的是,在C#中不能在同一个WebService实例的getName方法未返回之前,再次调用该实例的getName方法,否则将抛出异常。如下面的代码会抛出一个异常:

async.myServicemy=newWSC.async.myService();
my.getNameCompleted+=newWSC.async.getNameCompletedEventHandler(getNameCompletedEvent);
my.getNameAsync();
//将抛出异常
my.getNameAsync();

但不同的WebService实例的方法可以在方法未返回时调用,如下面的代码是可以正常工作的:

asyn.myServicemy=newWSC.asyn.myService();
my.getNameAsync();
my.getNameCompleted+=newWSC.asyn.getNameCompletedEventHandler(getNameCompletedEvent);
asyn.myServicemy1=newWSC.asyn.myService();
my1.getNameCompleted+=newWSC.asyn.getNameCompletedEventHandler(getNameCompletedEvent);
my1.getNameAsync();



Axis2可以通过模块(Module)进行扩展。Axis2模块至少需要有两个类,这两个类分别实现了Module和Handler接口。开发和使用一个Axis2模块的步骤如下:

1.编写实现Module接口的类。Axis2模块在进行初始化、销毁等动作时会调用该类中相应的方法)。

2.编写实现Handler接口的类。该类是Axis2模块的业务处理类。

3.编写module.xml文件。该文件放在META-INF目录中,用于配置Axis2模块。

4.在axis2.xml文件中配置Axis2模块。

5.在services.xml文件中配置Axis2模块。每一个Axis2模块都需要使用<module>元素引用才能使用。

6.发布Axis2模块。需要使用jar命令将Axis2模块压缩成.mar包(文件扩展名必须是.mar),然后将.mar文件放在

<Tomcat安装目录>\webapps\axis2\WEB-INF\modules目录中。
先来编写一个WebService类,代码如下:

packageservice;

publicclassMyService
{
publicStringgetGreeting(Stringname)
{
return"您好"+name;
}
}

下面我们来编写一个记录请求和响应SOAP消息的Axis2模块。当客户端调用WebService方法时,该Axis2模块会将请求和响应SOAP消息输出到Tomcat控制台上。

第1步:编写LoggingModule类

LoggingModule类实现了Module接口,代码如下:

packagemodule;

importorg.apache.axis2.AxisFault;
importorg.apache.axis2.context.ConfigurationContext;
importorg.apache.axis2.description.AxisDescription;
importorg.apache.axis2.description.AxisModule;
importorg.apache.axis2.modules.Module;
importorg.apache.neethi.Assertion;
importorg.apache.neethi.Policy;

publicclassLoggingModuleimplementsModule
{
//initializethemodule
publicvoidinit(ConfigurationContextconfigContext,AxisModulemodule)
throwsAxisFault
{
System.out.println("init");
}
publicvoidengageNotify(AxisDescriptionaxisDescription)throwsAxisFault
{
}
//shutdownthemodule
publicvoidshutdown(ConfigurationContextconfigurationContext)
throwsAxisFault
{
System.out.println("shutdown");
}
publicString[]getPolicyNamespaces()
{
returnnull;
}
publicvoidapplyPolicy(Policypolicy,AxisDescriptionaxisDescription)
throwsAxisFault
{
}
publicbooleancanSupportAssertion(Assertionassertion)
{
returntrue;
}
}

在本例中LoggingModule类并没实现实际的功能,但该类必须存在。当Tomcat启动时会装载该Axis2模块,同时会调用LoggingModule类的init方法,并在Tomcat控制台中输出“init”。

第2步:编写LogHandler类

LogHandler类实现了Handler接口,代码如下:

packagemodule;

importorg.apache.axis2.AxisFault;
importorg.apache.axis2.context.MessageContext;
importorg.apache.axis2.engine.Handler;
importorg.apache.axis2.handlers.AbstractHandler;
importorg.apache.commons.logging.Log;
importorg.apache.commons.logging.LogFactory;

publicclassLogHandlerextendsAbstractHandlerimplementsHandler
{
privatestaticfinalLoglog=LogFactory.getLog(LogHandler.class);
privateStringname;
publicStringgetName()
{
returnname;
}
publicInvocationResponseinvoke(MessageContextmsgContext)
throwsAxisFault
{
//Tomcat控制台输出请求和响应SOAP消息
log.info(msgContext.getEnvelope().toString());
returnInvocationResponse.CONTINUE;
}
publicvoidrevoke(MessageContextmsgContext)
{
log.info(msgContext.getEnvelope().toString());
}
publicvoidsetName(Stringname)
{
this.name=name;
}
}

LogHandler类的核心方法是invoke,当使用该Axis2模块的WebService的方法被调用时,LogHandler类的invoke方法被调用。

第3步:编写module.xml文件

在META-INF目录中建立一个module.xml文件,内容如下:

<modulename="logging"class="module.LoggingModule">
<InFlow>
<handlername="InFlowLogHandler"class="module.LogHandler">
<orderphase="loggingPhase"/>
</handler>
</InFlow>
<OutFlow>
<handlername="OutFlowLogHandler"class="module.LogHandler">
<orderphase="loggingPhase"/>
</handler>
</OutFlow>

<OutFaultFlow>
<handlername="FaultOutFlowLogHandler"class="module.LogHandler">
<orderphase="loggingPhase"/>
</handler>
</OutFaultFlow>
<InFaultFlow>
<handlername="FaultInFlowLogHandler"class="module.LogHandler">
<orderphase="loggingPhase"/>
</handler>
</InFaultFlow>
</module>

第4步:在axis2.xml文件中配置Axis2模块

打开axis2.xml文件,分别在如下四个<phaseOrder>元素中加入<phasename="loggingPhase"/>:

<phaseOrdertype="InFlow">

<phasename="soapmonitorPhase"/>
<phasename="loggingPhase"/>
</phaseOrder>
<phaseOrdertype="OutFlow">

<phasename="Security"/>
<phasename="loggingPhase"/>
</phaseOrder>
<phaseOrdertype="InFaultFlow">

<phasename="soapmonitorPhase"/>
<phasename="loggingPhase"/>
</phaseOrder>
<phaseOrdertype="OutFaultFlow">

<phasename="Security"/>
<phasename="loggingPhase"/>
</phaseOrder>

第5步:在services.xml文件中引用logging模块

services.xml文件的内容如下:

<servicename="myService">
<description>
使用logging模块
</description>
<!--引用logging模块-->
<moduleref="logging"/>
<parametername="ServiceClass">
service.MyService
</parameter>
<messageReceivers>
<messageReceivermep="http://www.w3.org/2004/08/wsdl/in-out"
class
="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</messageReceivers>
</service>

第6步:发布logging模块

到现在为止,我们应用可以建立两个发行包:logging.mar和service.aar。其中logging.mar文件是Axis2模块的发行包,该包的目录结构如下:

logging.mar

module\LoggingModule.class

module\LogHandler.class

META-INF\module.xml

service.aar文件是本例编写的WebService发行包,该包的目录结构如下:

service.aar

service\MyService.class

META-INF\services.xml

将logging.mar文件放在<Tomcat安装目录>\webapps\axis2\WEB-INF\modules目录中,将service.aar文件放在<Tomcat安装目录>\webapps\axis2\WEB-INF\services目录中。要注意的是,如果modules目录中包含了modules.list文件,Axis2会只装载在该文件中引用的Axis2模块,因此,必须在该文件中引用logging模块,该文件的内容如下:

addressing-1.4.1.mar

soapmonitor-1.4.1.mar

ping-1.4.1.mar

mex-1.4.1.mar

axis2-scripting-1.4.1.mar

logging.mar

如果modules目录中不包含modules.list文件,则Axis2会装载modules文件中的所有Axis2模块。

现在启动Tomcat,使用如下的C#代码调用MyService的getGreeting方法则会在Tomcat控制台中输出相应的请求和响应SOAP消息。

//async是引用MyService的服务名
async.myServicemy=newWSC.asyn.myService();
MessageBox.Show(my.getGreeting("中国"));
MessageBox.Show("完成调用");

在执行上面的代码后,在Tomcat控制台中输出的信息如下图所示。

在Axis2中提供了一个Axis2模块(soapmonitor),该模块实现了与WebService大讲堂之Axis2(9):编写Axis2模块(Module)》中实现的logging模块相同的功能,所不同的是,logging模块直接将SOAP请求与响应消息输出到Tomcat控制台中,而soapmonitor模块利用applet直接在页面中输出SOAP请求和响应消息。

下面是配置和使用soapmonitor模块的步骤:

第1步:部署Applet和Servlet

由于axis2默认情况下已经自带了soapmonitor模块,因此,soapmonitor模块并不需要单独安装。但applet所涉及到的相应的.class文件需要安装一下。在<Tomcat安装目录>\webapps\axis2\WEB-INF\lib目录中找到soapmonitor-1.4.1.jar文件,将该文件解压。虽然applet并不需要soapmonitor-1.4.1.jar文件中所有的.class文件,但为了方便,读者也可以直接将解压目录中的org目录复制到<Tomcat安装目录>\webapps\axis2目录中,Applet所需的.class文件需要放在这个目录。然后再将org目录复制到<Tomcat安装目录>\webapps\axis2\WEB-INF\classes目录中,soapmonitor模块中的Servlet所对应的.class文件需要放在这个目录。

第2步:配置Servlet

打开<Tomcat安装目录>\webapps\axis2\WEB-INF\web.xml文件,在其中加入如下的内容:

<servlet>
<servlet-name>SOAPMonitorService</servlet-name>
<servlet-class>
org.apache.axis2.soapmonitor.servlet.SOAPMonitorService
</servlet-class>
<init-param>
<param-name>SOAPMonitorPort</param-name>
<param-value>5001</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SOAPMonitorService</servlet-name>
<url-pattern>/SOAPMonitor</url-pattern>
</servlet-mapping>

第3步:在services.xml文件中引用soapmonitor模块

与引用logging模块一样,引用soapmonitor模块也需要使用<module>元素,引用soapmonitor模块的services.xml文件的内容如下:

<servicename="myService">
<description>
使用logging和soapmonitor模块
</description>
<!--引用logging模块-->
<moduleref="logging"/>
<!--引用soapmonitor模块-->
<moduleref="soapmonitor"/>
<parametername="ServiceClass">
service.MyService
</parameter>
<messageReceivers>
<messageReceivermep="http://www.w3.org/2004/08/wsdl/in-out"
class
="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</messageReceivers>
</service>

由于soapmonitor模块已经在axis2.xml进行配置了,因此,在本例中不需要再对axis2.xml文件进行配置了。

第4步:使用soapmonitor模块

启动Tomcat后,在浏览器中输入如下的URL:

http://localhost:8080/axis2/SOAPMonitor

在浏览器中将出现soapmonitor所带的Applet的界面,当访问MyService的getGreeting方法时,在Tomcat控制台与Applet中都显示了相应的SOAP请求和响应消息。如图1和图2分别是调用了两次getGreeting方法后输出的SOAP请求和响应消息。



图1



图2

如果读者想让logging和soapmonitor模块监视部署在Axis2中的所有WebService,可以在axis2.xml文件中使用<module>元素来引用这两个模块,代码如下:

<!--引用logging模块-->
<moduleref="logging"/>
<!--引用soapmonitor模块-->
<moduleref="soapmonitor"/>



你可能感兴趣的:(webservice)