先实现一个POJO类,代码如下:
package service;
public class MyService
{
public String getGreeting(String name)
{
return "您好 " + name;
}
public void update(String data)
{
System.out.println("<" + data + ">已经更新");
}
}
这个类有两个方法,这两个方法都需要发布成Web Service方法。这种方式和直接放在pojo目录中的POJO类不同。要想将MyService类发布成Web Service,需要一个services.xml文件,这个文件需要放在meta-inf目录中,该文件的内容如下:
<service name="myService">
<description>
Web Service例子
</description>
<parameter name="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>元素用于发布Web Service,一个<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 .
最后将ws.aar文件复制到<Tomcat安装目录>\webapps\axis2\WEB-INF\services目录中,启动Tomcat后,就可以调用这个WebService了。调用的方法和《WebService大讲堂之Axis2(1):用POJO实现0配置的WebService》所讲的方法类似。
另外services.xml文件中也可以直接指定WebService类的方法,如可以用下面的配置代码来发布WebService:
<service name="myService">
<description>
Web Service例子
</description>
<parameter name="ServiceClass">
service.MyService
</parameter>
<operation name="getGreeting">
<messageReceiver class="org.apache.axis2.rpc.receivers.RPcmessageReceiver" />
</operation>
<operation name="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类,代码如下:
package service
public class MyService1
{
public String getName()
{
return "bill";
}
}
在services.xml文件中可以使用如下的配置代码来配置MyService和MyService1类:
<serviceGroup>
<service name="myService">
<description>
Web Service例子
</description>
<parameter name="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>
<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>
</serviceGroup>
在《WebService大讲堂之Axis2(2):复合类型数据的传递》中讲过,如果要传递二进制文件(如图像、音频文件等),可以使用byte[]作为数据类型进行传递,然后客户端使用RPC方式进行调用。这样做只是其中的一种方法,除此之外,在客户端还可以使用wsdl2java命令生成相应的stub类来调用WebService,wsdl2java命令的用法详见《WebService大讲堂之Axis2(1):用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类的代码如下:
package service;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import javax.activation.DataHandler;
public class FileService
{
// 使用byte[]类型参数上传二进制文件
public boolean uploadWithByte(byte[] file, String filename)
{
FileOutputStream fos = null;
try
{
fos = new FileOutputStream(filename);
fos.write(file);
fos.close();
}
catch (Exception e)
{
return false;
}
finally
{
if (fos != null)
{
try
{
fos.close();
}
catch (Exception e)
{
}
}
}
return true;
}
private void writeInputStreamToFile(InputStream is, OutputStream os) throws Exception
{
int n = 0;
byte[] buffer = new byte[8192];
while((n = is.read(buffer)) > 0)
{
os.write(buffer, 0, n);
}
}
// 使用DataHandler类型参数上传文件
public boolean uploadWithDataHandler(DataHandler file, String filename)
{
FileOutputStream fos = null;
try
{
fos = new FileOutputStream(filename);
// 可通过DataHandler类的getInputStream方法读取上传数据
writeInputStreamToFile(file.getInputStream(), fos);
fos.close();
}
catch (Exception e)
{
return false;
}
finally
{
if (fos != null)
{
try
{
fos.close();
}
catch (Exception e)
{
}
}
}
return true;
}
}
<service name="fileService">
<description>
文件服务
</description>
<parameter name="ServiceClass">
service.FileService
</parameter>
<messageReceivers>
<messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"
class="org.apache.axis2.rpc.receivers.RPcmessageReceiver" />
</messageReceivers>
</service>
如果使用wsdl2java命令生成调用Java客户端代码,则需要创建DataHandler类的对象实例,代码如下:
DataHandler dh = new DataHandler(new FileDataSource(imagePath));
wsdl2java命令会为每一个方法生成一个封装方法参数的类,类名为方法名(第一个字符大写),如uploadWithByte方法生成的类名为UploadWithByte。如果要设置file参数的值,可以使用UploadWithByte类的setFile方法,代码如下:
UploadWithByte uwb = new UPloadWithByte();
uwb.setFile(dh);
最后是调用uploadWithByte方法,代码如下(FileServiceStub为wsdl2java生成的stub类名):
FileServiceStub fss = new FileServiceStub();
fss.uploadWithByte(uwb);
如果使用C#调用FileService,则file参数类型均为byte[],代码如下:
MemoryStream ms = new MemoryStream();
Bitmap bitmap = new Bitmap(picUpdateImage.Image);
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
service.fileService fs = new WSC.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类,代码如下:
package service;
import org.apache.axis2.context.ServiceContext;
import org.apache.axis2.context.MessageContext;
public class LoginService
{
public boolean login(String username, String password)
{
if("bill".equals(username) && "1234".equals(password))
{
// 第1步:设置key-value对
MessageContext mc = MessageContext.getCurrentMessageContext();
ServiceContext sc = mc.getServiceContext();
sc.setProperty("login", "成功登录");
return true;
}
else
{
return false;
}
}
public String getLoginMsg()
{
// 第1步:获得key-value对中的value
MessageContext mc = MessageContext.getCurrentMessageContext();
ServiceContext sc = mc.getServiceContext();
return (String)sc.getProperty("login");
}
}
在LoginService类中有两个方法:login和getLoginMsg,如果login方法登录成功,会将“成功登录”字符串保存在ServiceContext对象中。如果在login方法返回true后调用getLoginMsg方法,就会返回“成功登录”。
下面是LoginService类的配置代码(services.xml):
<!-- 第2步:添加scope属性 -->
<service name="loginService" scope="transportsession">
<description>
登录服务
</description>
<parameter name="ServiceClass">
service.LoginService
</parameter>
<messageReceivers>
<messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"
class="org.apache.axis2.rpc.receivers.RPcmessageReceiver" />
</messageReceivers>
</service>
使用如下的命令生成客户端使用的stub类:
%AXIS2_HOME%\bin\wsdl2java -uri http://localhost:8080/axis2/services/loginService?wsdl -p client -s -o stub
在stub\src\client目录中生成了一个LoginServiceStub.java类,在该类中找到如下的构造句方法:
public LoginServiceStub(org.apache.axis2.context.ConfigurationContext configurationContext,
java.lang.String targetEndpoint, boolean useSeparateListener)
throws org.apache.axis2.AxisFault
{
_serviceClient.getoptions().setSoapVersionURI(
org.apache.axiom.soap.soAP12Constants.soAP_ENVELOPE_NAMESPACE_URI);
}
// 第3步:打开客户端的Session管理功能
_serviceClient.getoptions().setManageSession(true);
下面的客户端代码使用LoginServiceStub对象访问了刚才建立的WebService:
LoginServiceStub stub = new LoginServiceStub();
LoginServiceStub.Login login = new LoginServiceStub.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
package service;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.ServiceGroupContext;
public class LoginService
{
public boolean login(String username, String password)
{
if("bill".equals(username) && "1234".equals(password))
{
// 第1步:设置key-value对
MessageContext mc = MessageContext.getCurrentMessageContext();
ServiceGroupContext sgc = mc.getServiceGroupContext();
sgc.setProperty("login", "成功登录");
return true;
}
else
{
return false;
}
}
public String getLoginMsg()
{
// 第1步:获得key-value对中的value
MessageContext mc = MessageContext.getCurrentMessageContext();
ServiceGroupContext sgc = mc.getServiceGroupContext();
return (String)sgc.getProperty("login");
}
}
SearchService.java
package service;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.ServiceGroupContext;
public class SearchService
{
public String findByName(String name)
{
// 第1步:获得key-value对中的value
MessageContext mc = MessageContext.getCurrentMessageContext();
ServiceGroupContext sgc = mc.getServiceGroupContext();
if (sgc.getProperty("login") != null)
return "找到的数据<" + name + ">";
else
return "用户未登录";
}
}
<serviceGroup>
<!-- 第2步:添加scope属性,并设置属性值为application -->
<service name="loginService" scope="application">
<description>
登录服务
</description>
<parameter name="ServiceClass">
service.LoginService
</parameter>
<messageReceivers>
<messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"
class="org.apache.axis2.rpc.receivers.RPcmessageReceiver" />
</messageReceivers>
</service>
<!-- 第2步:添加scope属性,并设置属性值为application -->
<service name="searchService" scope="application">
<description>
搜索服务
</description>
<parameter name="ServiceClass">
service.SearchService
</parameter>
<messageReceivers>
<messageReceiver mep="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的客户端代码:
LoginServiceStub stub = new LoginServiceStub();
LoginServiceStub.Login login = new LoginServiceStub.Login();
login.setUsername("bill");
login.setPassword("1234");
if(stub.login(login).local_return)
{
System.out.println(stub.getLoginMsg().local_return);
SearchServiceStub searchStub = new SearchServiceStub();
SearchServiceStub.FindByName fbn = new SearchServiceStub.FindByName();
fbn.setName("abc");
System.out.println(searchStub.findByName(fbn).local_return);
}
成功登录
找到的数据<abc>
读者可以将scope属性值改成transportsession,看看会输出什么!
实际上,Axis2的会话管理也是通过Cookie实现的,与Web应用中的Session管理类似。如果读者使用C#访问支持会话(在同一个服务中的会话管理)的WebService,需要指定一个CookieContainer对象,代码如下:
service.loginService ls = new service.loginService();
System.Net.CookieContainer cc = new System.Net.CookieContainer();
ls.CookieContainer = cc;
bool r, rs;
ls.login("bill", "1234", out @r, out rs);
if (r)
{
MessageBox.Show(ls.getLoginMsg().@return);
}
如果是访问跨服务的支持会话的WebService,则不需要指定CookieContainer对象,代码如下:
service.loginService ls = new service.loginService();
bool r, out rs);
if (r)
{
service1.searchService ss = new service1.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),代码如下:
package service;
import entity.Person;
public class SpringService
{
private String name;
private String job;
public void setName(String name)
{
this.name = name;
}
public void setJob(String job)
{
this.job = job;
}
public Person getPerson()
{
Person person = new Person();
person.setName(name);
person.setJob(job);
return person;
}
public String getGreeting(String name)
{
return "hello " + name;
}
}
package entity;
public class Person
{
private String name;
private String job;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getJob()
{
return job;
}
public void setJob(String job)
{
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的配置文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="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/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="springService" class="service.SpringService">
<property name="name" value="姚明" />
<property name="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文件,内容如下:
<service name="springService">
<description>
Spring aware
</description>
<parameter name="ServiceObjectsupplier">
org.apache.axis2.extensions.spring.receivers.SpringServletContextObjectsupplier
</parameter>
<parameter name="SpringBeanName">
springService
</parameter>
<messageReceivers>
<messageReceiver mep="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类,代码如下:
package service;
public class MyService
{
public String getName()
{
try
{
System.out.println("getName方法正在执行 ");
// 延迟5秒
Thread.sleep(5000);
}
catch (Exception e)
{
}
return "火星";
}
}
为了模拟需要一定时间才返回的WebService方法,在getName方法中使用了sleep方法来延迟5秒。
下面是MyService类的配置代码:
<!-- services.xml -->
<service name="myService">
<description>
异步调用演示
</description>
<parameter name="ServiceClass">
service.MyService
</parameter>
<messageReceivers>
<messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"
class="org.apache.axis2.rpc.receivers.RPcmessageReceiver" />
</messageReceivers>
</service>
从上面的配置代码可以看出,MyService的配置方式与前几章的WebService的配置方式完全一样,也就是说,MyService只是一个普通的WebService。
下面是异步调用MyService的Java客户端代码:
package client;
import javax.xml.namespace.QName;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.rpc.client.RPCServiceClient;
public class RPCAsyncclient
{
public static void main(String[] args) throws Exception
{
RPCServiceClient serviceClient = new RPCServiceClient();
Options options = serviceClient.getoptions();
EndpointReference targetEPR = new EndpointReference(
"http://localhost:8080/axis2/services/myService");
options.setTo(targetEPR);
Object[] opAddEntryArgs = new Object[]{};
QName opAddEntry = new QName("http://service", "getName");
serviceClient.invokeNonBlocking(opAddEntry, opAddEntryArgs,
new org.apache.axis2.client.async.AxisCallback()
{
@Override
public void onComplete()
{
}
@Override
public void onError(Exception arg0)
{
} }
@Override
public void onFault(MessageContext arg0)
{
}
@Override
public void onMessage(MessageContext mc)
{
// 输出返回值
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 -uri http://localhost:8080/axis2/services/myService?wsdl -p client -s -a -o stub
在执行上面的命令后,将生成两个类:MyServiceStub和MyServiceCallbackHandler类,其中MyServiceStub类负责同步和异步调用WebService,MyServiceCallbackHandler类是一个抽象类,也是一个回调类,当使用异步方式调用WebService方法时,如果方法返回,则MyServiceCallbackHandler类的receiveResultgetName方法被调用。下面是使用MyServiceStub类异步访问WebService的代码:
package client;
import client.MyServiceStub.GetNameResponse;
class MyCallback extends MyServiceCallbackHandler
{
@Override
public void receiveResultgetName(GetNameResponse result)
{
// 输出getName方法的返回结果
System.out.println(result.get_return());
}
}
public class StubClient
{
public static void main(String[] args) throws Exception
{
MyServiceStub stub = new MyServiceStub();
// 异步调用WebService
stub.startgetName(new MyCallback());
System.out.println("异步调用!");
system.in.read();
}
}
执行上面的程序后,将输出如下的信息:
异步调用!
火星
在.net中也可以使用异步的方式来调用WebService,如在C#中可使用如下的代码来异步调用getName方法:
// 回调方法
private void getNameCompletedEvent(object sender, WSC.asyn.getNameCompletedEventArgs e)
{
listBox1.Items.Add( e.Result.@return);
}
private void button1_Click(object sender, EventArgs e)
{
async.myService my = new WSC.async.myService();
my.getNameCompleted += new WSC.async.getNameCompletedEventHandler(getNameCompletedEvent);
my.getNameAsync();
MessageBox.Show("完成调用");
}
其中async是引用MyService的服务名。要注意的是,在C#中不能在同一个WebService实例的getName方法未返回之前,再次调用该实例的getName方法,否则将抛出异常。如下面的代码会抛出一个异常:
async.myService my = new WSC.async.myService();
my.getNameCompleted += new WSC.async.getNameCompletedEventHandler(getNameCompletedEvent);
my.getNameAsync();
// 将抛出异常
my.getNameAsync();
但不同的WebService实例的方法可以在方法未返回时调用,如下面的代码是可以正常工作的:
asyn.myService my = new WSC.asyn.myService();
my.getNameAsync();
my.getNameCompleted += new WSC.asyn.getNameCompletedEventHandler(getNameCompletedEvent);
asyn.myService my1 = new WSC.asyn.myService();
my1.getNameCompleted += new WSC.asyn.getNameCompletedEventHandler(getNameCompletedEvent);
my1.getNameAsync();
下一篇:WebService大讲堂之Axis2(9):编写Axis2模块(Module)
国内最棒的Google Android技术社区(eoeandroid),欢迎访问!
《银河系列原创教程》发布
《Java Web开发速学宝典》出版,欢迎定购
posted on 2009-02-13 14:23 银河使者 阅读(3407) 评论(3) 编辑 收藏 所属分类:java 、 原创 、webservice
# re: WebService大讲堂之Axis2(8):异步调用WebService[未登录] 2009-12-04 18:06 su
采用JAVA第2种方式执行主函数后,客户端和服务端没任何反应,好奇怪 回复 更多评论
# re: WebService大讲堂之Axis2(8):异步调用WebService 2010-01-09 16:08 Mr.Blue
@su
看下你的客户端代码,楼主最后的system.in.read()代码不能少的,否则你的客户端代码提前结束了,当然什么都看不到 ,试试看 回复 更多评论
# re: WebService大讲堂之Axis2(8):异步调用WebService[未登录] 2010-01-14 17:32 lee
我想返回结果能获取到,怎么做
// 输出返回值
String dd=mc.getEnvelope().getFirstElement()
.getFirstElement().getFirstElement().getText()这个dd这个值如何在其他类的方法调用到
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类,代码如下:
package service;
public class MyService
{
public String getGreeting(String name)
{
return "您好 " + name;
}
}
下面我们来编写一个记录请求和响应SOAP消息的Axis2模块。当客户端调用WebService方法时,该Axis2模块会将请求和响应SOAP消息输出到Tomcat控制台上。
第1步:编写LoggingModule类
LoggingModule类实现了Module接口,代码如下:
package module;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.description.AxisDescription;
import org.apache.axis2.description.AxisModule;
import org.apache.axis2.modules.Module;
import org.apache.neethi.Assertion;
import org.apache.neethi.Policy;
public class LoggingModule implements Module
{
// initialize the module
public void init(ConfigurationContext configContext, AxisModule module)
throws AxisFault
{
System.out.println("init");
}
public void engageNotify(AxisDescription axisDescription) throws AxisFault
{
}
// shutdown the module
public void shutdown(ConfigurationContext configurationContext)
throws AxisFault
{
System.out.println("shutdown");
}
public String[] getPolicyNamespaces()
{
return null;
}
public void applyPolicy(Policy policy, AxisDescription axisDescription)
throws AxisFault
{
}
public boolean canSupportAssertion(Assertion assertion)
{
return true;
}
}
在本例中LoggingModule类并没实现实际的功能,但该类必须存在。当Tomcat启动时会装载该Axis2模块,同时会调用LoggingModule类的init方法,并在Tomcat控制台中输出“init”。
第2步:编写LogHandler类
LogHandler类实现了Handler接口,代码如下:
package module;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.engine.Handler;
import org.apache.axis2.handlers.AbstractHandler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class LogHandler extends AbstractHandler implements Handler
{
private static final Log log = LogFactory.getLog(LogHandler.class);
private String name;
public String getName()
{
return name;
}
public InvocationResponse invoke(MessageContext msgContext)
throws AxisFault
{
// 向Tomcat控制台输出请求和响应SOAP消息
log.info(msgContext.getEnvelope().toString());
return InvocationResponse.CONTINUE;
}
public void revoke(MessageContext msgContext)
{
log.info(msgContext.getEnvelope().toString());
}
public void setName(String name)
{
this.name = name;
}
}
LogHandler类的核心方法是invoke,当使用该Axis2模块的WebService的方法被调用时,LogHandler类的invoke方法被调用。
第3步:编写module.xml文件
在meta-inf目录中建立一个module.xml文件,内容如下:
<module name="logging" class="module.LoggingModule">
<InFlow>
<handler name="InFlowLogHandler" class="module.LogHandler">
<order phase="loggingPhase"/>
</handler>
</InFlow>
<OutFlow>
<handler name="OutFlowLogHandler" class="module.LogHandler">
<order phase="loggingPhase"/>
</handler>
</OutFlow>
<OutFaultFlow>
<handler name="FaultOutFlowLogHandler" class="module.LogHandler">
<order phase="loggingPhase"/>
</handler>
</OutFaultFlow>
<InFaultFlow>
<handler name="FaultInFlowLogHandler" class="module.LogHandler">
<order phase="loggingPhase"/>
</handler>
</InFaultFlow>
</module>
第4步:在axis2.xml文件中配置Axis2模块
打开axis2.xml文件,分别在如下四个<phaSEOrder>元素中加入<phase name="loggingPhase"/>:
<phaSEOrder type="InFlow">
<phase name="soapmonitorPhase"/>
<phase name="loggingPhase"/>
</phaSEOrder>
<phaSEOrder type="OutFlow">
<phase name="Security"/>
<phase name="loggingPhase"/>
</phaSEOrder>
<phaSEOrder type="InFaultFlow">
<phase name="soapmonitorPhase"/>
<phase name="loggingPhase"/>
</phaSEOrder>
<phaSEOrder type="OutFaultFlow">
<phase name="Security"/>
<phase name="loggingPhase"/>
</phaSEOrder>
第5步:在services.xml文件中引用logging模块
<service name="myService">
<description>
使用logging模块
</description>
<!-- 引用logging模块 -->
<module ref="logging"/>
<parameter name="ServiceClass">
service.MyService
</parameter>
<messageReceivers>
<messageReceiver mep="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.myService my = new WSC.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文件的内容如下:
<service name="myService">
<description>
使用logging和soapmonitor模块
</description>
<!-- 引用logging模块 -->
<module ref="logging"/>
<!-- 引用soapmonitor模块 -->
<module ref="soapmonitor"/>
<parameter name="ServiceClass">
service.MyService
</parameter>
<messageReceivers>
<messageReceiver mep="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模块 --> <module ref="logging"/> <!-- 引用soapmonitor模块 --> <module ref="soapmonitor"/>
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。