配色: 字号:
Java远程通信技术——Axis实战
2016-09-22 | 阅:  转:  |  分享 
  
Java远程通信技术——Axis实战

前言

在Internet网络覆盖全球的今天,网络通信已经是当今软件开发过程中离不开的话题。在常用的Windows、Linux、Unix系统当中,大部分的网络数据传输都是使用TCP/IP、UDP/IP作为底层传输协议的,而HTTP协议就是基于TCP/IP协议而运行的超文本传送协议。
在JAVA高级开发语言中,陆续出现RMI、CORBA、JAX-RPC、JAX-WS、Axis、XFire、HTTPInvoker、Hessian、Burlap、JMX等远程通信架构去实现系统之间数据传送。在“远程通信技术”的一系列文章中,本人将对上述复杂的JAVA远程通信技术作出归纳。
首先,在本篇文章中先对有着10多年历史的Axis进行介绍。


一、Axis简介

1.1Web服务的起源

Web服务是现今实现网络服务概念的趋势,它把基础架构建立于标准化的XML语言之上,能够使用一种与平台无关的方式对数据进行编码,其中SOAP与WSDL都遵从此标准化的XML编码规则。
SOAP(SimpleObjectAccessProtocol,简单对象访问协议)是一种轻量的、简单的、基于XML的协议,用于描述在服务过程中服务器端与客户端之间所交换的消息。SOAP可以和现存的许多因特网协议和格式结合使用,包括超文本传输协议(HTTP),简单邮件传输协议(SMTP),多用途网际邮件扩充协议(MIME)。
WSDL(WebServiceDefinitionLanguage,Web服务描述语言)是一种基于XML的协议,用于定义服务端与客户端之间的契约,描述Web服务的公共接口,列出Web服务进行交互时需要绑定的协议和信息格式。
Web服务采用WSDL语言描述该服务支持的操作和信息,运行时再将实际的数据以SOAP方式在服务端与客户端进行信息传递。
由于软件开发平台众多,当中存在不同的开发风格,当服务器端与客户端使用不同的开发工具时,数据转换成为复杂且关键的问题。而SOAP与WSDL的主要特性之一在于它们都是可扩展的,且与开发平台无关。为了建立统一的XML协议,微软、IBM、Sun、Oracle、BEA等多家软件开发商联合起来,组成了一个名为WS-I(WebServiceInteroperability)组织,由该组织制定WS-ReliableMessage、WS-Discovery、WS-Federation、WS-Coordination、WS-AtomicTransaction、WS-BusinessActivity、WS-Enumeration、WS-Eventing、WS-Management等一系列用于数据交换的规范。


1.2JAX-RPC、JAX-WS简介

JAX-RPC(JavaAPIforXML-basedRPC)是Java库中基于XML远程服务的一组标准API,它通过WSDL方式对所提供的服务进行描述,并以RPC的风格把SOAP信息进行公开,是Java库中最早对Web服务提供支持的一组API。
JAX-RPC1.0从其名称可以看出,最初的目的只是为了支持使用(RemoteProcedureCall,RPC)的XML远程过程调用操作,它以BP1.0(WS-I’sBasicProfile1.0)为基础,依赖于SAAJ1.2(SOAPwithAttachmentsAPIforJava)为规范,虽然支持SOAP协议,但对Web服务功能有一定的局限性。于是在2003年底,开发团队对JAX-RPC1.0进行大幅修订,由Sun公司组织了一个专家组开始进行JAX-RPC2.0规范的开发。
JAX-RPC2.0是基于JAVA5而开发的,它依赖于Annotation等新特性,在JAX-RPC的基础上提供还增加了如异步回调,面向消息等新增技术。JAX-RPC2.0以BP1.1(WS-I’sBasicProfile1.1)为基础,依赖于SAAJ1.3(SOAPwithAttachmentsAPIforJava)为规范,能使用SOAP1.1、SOAP1.2进行信息公开。它是JAX-RPC1.1架构发展的成果,在开发完成后,JAX-RPC2.0被正式改名成为JAX-WS(JavaAPIforXML-WebServices)。



1.3Axis概述

Axis全称ApacheEXtensibleInteractionSystem(阿帕奇可扩展交互系统),它是一个SOAP引擎,提供创建Web服务的基本框架。Axis1.x是基于JAX-RPC而实现一个工具包,它可以使用HTTP、JMS、SMTP等多种传输方式支持SOAP。
Axis2.x是新一代的Axis引擎,它支持JAX-WS、JAX-PRC等API,并且在Axis1.x的基础上增加了灵活数据绑定、异步调用等新增功能,可使用SOAP1.1、SOAP1.2协议。在服务请求上,Axis2.x支持三种请求-响应模式:In-Only、Robust-In和In-Out,也可支持使用REST风格的开发方式。
基本的AxisWeb服务由四部分组成:AxisServlet、Axis部署描述、远程服务接口、服务实现类。
AxisServlet是Axis的核心,它负责WSDL基础服务信息的公开,并把SOAP请求转化为Java方法的调用,最后把返回值转化为SOAP。AxisServlet隐藏了构建Web服务的大量代码,使用开发人员不用直接与SOAP打交道便可轻松完成Web服务的开发。
Axis部署描述是一个XML文档,它用于管理Web服务的发布,决定哪些服务类需要通过SOAP对外公开。
远程服务接口并非必要的,但在很多的Web服务开发过程中都会使用远程服务接口用于对外暴露服务类的方法,在服务器端通过服务实现类去继承实现服务接口。
由于Axis1.x与Axis2.x有各自的特色,下面将分开来介绍。


回到目录

二、Axis1.x实例

2.1Axis1.x的下载与安装

Axis1.x可于官网http://axis.apache.org/axis/下载,完成下载后建立WebProject作为测试项目,把lib文件夹下的jar文件拷贝,引入到测试项目当中。
在web.xml文件下加入AxisServlet配置后,系统就会对以后缀为.jws及路径为/services/的请求进行监听,遇到此类请求时将把信息交由org.apache.axis.transport.http.AxisServlet进行处理。

复制代码
1
2Apache-Axis
3
4org.apache.axis.transport.http.AxisHTTPSessionListener
5

6
7Apache-AxisServlet
8AxisServlet
9
10org.apache.axis.transport.http.AxisServlet
11

12

13
14
15AxisAdminServlet
16AdminServlet
17
18org.apache.axis.transport.http.AdminServlet
19

20100
21

22
23
24SOAPMonitorService
25SOAPMonitorService
26
27org.apache.axis.monitor.SOAPMonitorService
28

29
30SOAPMonitorPort
315001
32

33100
34

35
36AxisServlet
37/servlet/AxisServlet
38

39
40AxisServlet
41.jws
42

43
44AxisServlet
45/services/
46

47
48SOAPMonitorService
49/SOAPMonitor
50

51

复制代码
而SOAPMonitorService并非必要配置,但加入SOAPMonitorService配置,可以便于对服务运行时所传输的SOAP信息进行监听,在服务的requestFlow或responseFlow中加入SOAPMonitorHandler,系统就可显示服务请求和回发时的SOAP信息。

复制代码
12xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
3
4
56value="http://tempuri.org/wsdl/2001/12/SOAPMonitorService-impl.wsdl"/>
7
8
9
10
11
12
13
14
15
16
17
18
19

20
21
22

23
24
25
26
复制代码


2.2调用服务的三种方式

下面从最简单的HelloWorld开始,介绍Axis的使用方法。首先在WEB-INF文件夹下建立server-config.wsdd文件,在Axis1.x当中,此文件正是用于管理服务发布的默认配置文件。首先service用于定义对外暴露的服务,其中name属性用于定义服务的名称。像下面例子,当name为PersonService时,对外暴露的服务路径则对应为http://localhost:8080/axis.server/services/PersonService。
而parameter用于定义服务的相关属性,className表示此服务的实现类,而allowedMethods表示所公开的服务方法,“"则默认为公开此类中的所有public公共方法。而scope则是用于定义服务对象生成的方式,它包括三个选项:request、session、application。
request是默认选择,表示为每个请求生成一个服务对象;
session表示对同一个客户代理对象所发送的请求使用同一个服务对象,并把服务信息放在同一个上下文当中。当使用有状态服务时,使用此session更为合适,在下节将再作进一步介绍;
application类似于使用单体模式,表示所示的请求均使用同一个服务对象,当使用无状态服务时使用application能有效提高运行效率。
最后在transport中定义一个requestFlow处理类org.apache.axis.handlers.http.URLMapper,表示在系统接受到http请求时,将会调用URLMapper类来处理路径映射等问题。

复制代码
12xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
3
4
5
6
7
8
9
10
11
12
13
14

15
16
复制代码
PersonService服务代码如下,此时运行服务,输入路径http://localhost:8080/axis.server/services/PersonService?wsdl,浏览器上将显示此服务的wsdl信息。

复制代码
1publicinterfaceIPersonService{
2StringHelloWorld(Stringname);
3}
4
5publicclassPersonServiceImplimplementsIPersonService{
6@Override
7publicStringHelloWorld(stringname){
8return"Hello"+name;
9}
10}
复制代码
客户端的生成工具有多种,其中一种是使用Axis1.x中的自带生成器WSDL2Java,此生成器可以根据wsdl文件生成客户端。
首先在环境变量中把Axis_Home绑定到Axis1.x的根目录,在path加入设置".;%Axis_Home%\lib",然后输入
Javaorg.apache.axis.wsdl.WSDL2Javahttp://localhost:8080/axis.server/services/PersonService?wsdl-paxis.client.person
此时系统将在axis.client.person包内生成客户端代码类PersonServiceImpl、PersonServiceImplService、PersonServiceImplServiceLocator、PersonServiceSoapBindingStub。
PersonServiceImplServiceLocator用于实现PersonServiceImplService接口,它绑定了服务的名称,地址,端口等详细资料。
PersonServiceImpl用于定义服务的接口,而PersonServiceSoapBindingStub则是此服务代理,它通过SOAP协议把操作请求发送到服务器,并把返回信息转化为Java对象。

复制代码
1publicstaticvoidmain(String[]args)throws
2RemoteException,MalformedURLException,ServiceException{
3//TODOAuto-generatedmethodstub
4HelloWorld();
5}
6
7privatestaticvoidHelloWorld()throws
8RemoteException,MalformedURLException{
9PersonServiceImplpersonService=newPersonServiceSoapBindingStub(
10newURL("http://localhost:8080/axis.server/services/PersonService"),
11newPersonServiceImplServiceLocator());
12System.out.println(personService.helloWorld("Leslie"));
13}
复制代码
通过系统自动生成的代理类就能简单地调用远程服务,这是因为在代理类中已经完成了大量关于PersonService服务的配置,但本人觉得想要深入地了解Axis的开发,就应该了解其内部的结构。所以在下面例子当中将介绍如何使用Axis的内部机制直接调用Web服务。
在org.apache.axis.client中存在着服务的基础类Service,通过Service可用于管理服务的绑定地址,端点,获取服务的WSDL等详细信息。另外Call类用于管理每个服务请求动作,它可以设置每个请求的方法,最后通过call.invoke(Object[])调用服务,并获取完成后的返回值。

复制代码
1publicstaticvoidmain(String[]args)throws
2RemoteException,MalformedURLException,ServiceException{
3HelloWorld();
4}
5
6privatestaticvoidHelloWorld()throws
7ServiceException,RemoteException{
8//生成服务对象Service
9Serviceservice=newService();
10Callcall=(Call)service.createCall();
11//设置Endpoint地址
12call.setTargetEndpointAddress(
13"http://localhost:8080/axis.server/services/PersonService");
14//绑定请求方法名称
15call.setOperationName("HelloWorld");
16//通过call.invoke调用服务,获取返回值
17Stringdata=(String)call.invoke(newObject[]{"Leslie"});
18System.out.println(data);
19}
复制代码
如果觉得使用Call实现请求较为麻烦,Service中还提供一个getPort方法,通过此方法还可直接实现服务接口PersonServiceImpl。另外,Axis还为准备了一个ServiceFactory工厂,通过ServiceFactory可以直接获取Service对象。

复制代码
1publicstaticvoidmain(String[]args)throws
2RemoteException,MalformedURLException,ServiceException{
3//TODOAuto-generatedmethodstub
4HelloWorld();
5}
6
7privatestaticvoidHelloWorld()throws
8ServiceException,RemoteException,MalformedURLException{
9Stringwsdl="http://localhost:8080/axis.server/services/PersonService?wsdl";
10Stringuri="http://localhost:8080/axis.server/services/PersonService";
11StringserviceName="PersonServiceImplService";
12
13//使用serviceFacotry直接生成服务
14ServiceFactoryfactory=ServiceFactory.newInstance();
15Serviceservice=(Service)factory.createService(
16newURL(wsdl),newQName(uri,serviceName));
17
18//使用service.getPort方法实现服务接口
19PersonServiceImplpersonService=(PersonServiceImpl)service
20.getPort(PersonServiceImpl.class);
21Stringdata=personService.helloWorld("Leslie");
22System.out.println(data);
23}
复制代码


2.3以自定义对象传输数据

若需要以自定义对象作为数据传输的载体,则需要为自定义对象继承Serializable接口。另外可以留意一下服务的wsdl,因为Axis并没有默认使用List,Map等类型,在List,Map等作为参数时,wsdl都会把返回类型设置为ArrayOf_xsd_anyType,所以建议使用简单数组作为返回值。

复制代码
1publicclassPersonEntityimplementsSerializable{
2privateIntegerid;
3privateStringname;
4privateIntegerage;
5privateStringaddress;
6
7publicPersonEntity(Integerid,Stringname,Integerage,Stringaddress){
8this.id=id;
9this.name=name;
10this.age=age;
11this.address=address;
12}
13
14publicIntegergetId(){
15returnid;
16}
17
18publicvoidsetId(Integerid){
19this.id=id;
20}
21..........
22}
23
24publicinterfaceIPersonService{
25PersonEntityGetPerson(intid);
26PersonEntity[]GetList();
27ListGetList(Stringname);
28}
29
30publicclassPersonServiceImplimplementsIPersonService{
31@Override
32publicPersonEntity[]GetList(){
33PersonEntity[]list=newPersonEntity[2];
34PersonEntityperson1=newPersonEntity(1,"Leslie",32,"tianhe");
35PersonEntityperson2=newPersonEntity(2,"Elva",31,"henan");
36list[0]=person1;
37list[1]=person2;
38returnlist;
39}
40
41@Override
42publicListGetList(Stringname){
43Listlist=newArrayList();
44PersonEntityperson1=newPersonEntity(1,name+"Lee",32,"tianhe");
45PersonEntityperson2=newPersonEntity(2,name+"Chen",31,"henan");
46list.add(person1);
47list.add(person2);
48returnlist;
49}
50
51@Override
52publicPersonEntityGetPerson(intid){
53returnnewPersonEntity(id,"Leslie",32,"tianhe");
54}
55}
复制代码
在server-config.wsdd中使用beanMapping加入自定义对象绑定,以languageSpecificType绑定类名,qname可由用户设置,但必须与xmln特性相对应。

复制代码
1.........
2
3
4
5
67languageSpecificType="java:axis.entity.PersonEntity"/>
8
9.........
复制代码
以WSDL2Java生成客户端代码后,可以留意PersonEntity对象已经自动实现了Serializable接口,增加了getDeserializer、getSerializer等序列化与反序列化方法。此时直接使用代理类,会自动地完成对象序列化的过程,可以节省了不少时间。

复制代码
1publicstaticvoidmain(String[]args)throws
2RemoteException,MalformedURLException,ServiceException{
3GetList();
4GetPerson();
5}
6
7privatestaticvoidGetPerson()throws
8RemoteException,MalformedURLException{
9PersonServiceImplpersonService=newPersonServiceSoapBindingStub(
10newURL("http://localhost:8080/axis.server/services/PersonService"),
11newPersonServiceImplServiceLocator());
12PersonEntityperson=personService.getPerson(1);
13DisplayPersonProperty(person);
14}
15
16privatestaticvoidGetList()throws
17ServiceException,RemoteException,MalformedURLException{
18PersonServiceImplpersonService=newPersonServiceSoapBindingStub(
19newURL("http://localhost:8080/axis.server/services/PersonService"),
20newPersonServiceImplServiceLocator());
21Object[]objs=personService.getList("Leslie");
22for(Objectperson:objs)
23DisplayPersonProperty((PersonEntity)person);
24}
25
26//显示对象属性
27privatestaticvoidDisplayPersonProperty(PersonEntityperson){
28System.out.println("Id:"+person.getId()+"Name:"+person.getName()+"Age:"+
29person.getAge()+"Address:"+person.getAddress());
30}
复制代码
但需要注意,如果使用Service类去调用服务的时候,需要使用Call.registerTypeMapping注册一个类型,把接收到的信息转换为PersonEntity类型。在注册类型时namespaceURI参数值需要与服务端server-config.wsdd中的值保持一致。

复制代码
1publicstaticvoidmain(String[]args)throws
2RemoteException,MalformedURLException,ServiceException{
3//TODOAuto-generatedmethodstub
4GetArray();
5GetList();
6}
7
8privatestaticvoidGetArray()throws
9ServiceException,RemoteException{
10Serviceservice=newService();
11Callcall=(Call)service.createCall();
12call.setTargetEndpointAddress(
13"http://localhost:8080/axis.server/services/PersonService");
14
15//注册返回类型,namespaceURI必须与服务端注册值一致
16QNameqName2=newQName("urn:PersonEntity","PersonEntity");
17call.registerTypeMapping(PersonEntity.class,qName2,
18newBeanSerializerFactory(PersonEntity.class,qName2),
19newBeanDeserializerFactory(PersonEntity.class,qName2));
20
21//绑定请求方法
22call.setOperation("GetList");
23
24//设置返回类型
25call.setReturnClass(PersonEntity[].class);
26PersonEntity[]list=(PersonEntity[])call.invoke(newObject[]{});
27for(PersonEntityperson:list)
28DisplayPersonProperty(person);
29}
30
31privatestaticvoidGetList()throws
32ServiceException,RemoteException{
33Serviceservice=newService();
34Callcall=(Call)service.createCall();
35call.setTargetEndpointAddress(
36"http://localhost:8080/axis.server/services/PersonService");
37
38//注册返回类型,namespaceURI必须与服务端注册值一致
39QNameqName2=newQName("urn:PersonEntity","PersonEntity");
40call.registerTypeMapping(PersonEntity.class,qName2,
41newBeanSerializerFactory(PersonEntity.class,qName2),
42newBeanDeserializerFactory(PersonEntity.class,qName2));
43
44//绑定请求方法
45call.setOperationName(newjavax.xml.namespace.QName(
46"http://serviceImpls.axis","GetList"));
47//输入参数
48Object[]list=(Object[])call.invoke(newObject[]{"Leslie"});
49for(Objectperson:list)
50DisplayPersonProperty((PersonEntity)person);
51}
52
53privatestaticvoidDisplayPersonProperty(PersonEntityperson){
54System.out.println("Id:"+person.getId()+"Name:"+person.getName()+"Age:"+
55person.getAge()+"Address:"+person.getAddress());
56}
复制代码

回到目录

三、Web服务会话管理

记得在第二节曾经为大家介绍服务对象的生成方式,当scope设置session时,系统会对同一个客户代理对象所发送的请求使用同一个服务对象,并把服务信息放在同一个上下文当中。利用session可以把用户名、用户密码、订单号此类信息在方法中传播,也可以确保不同的客户信息分别保存在不同的上下文之上。session数据的保存时间可以通过session.setTimeout方法设置。

复制代码
1publicinterfaceILoginService{
2publicBooleanLogin(Stringname,Stringpassword);
3publicStringGetUserName();
4}
5
6publicclassLoginServiceImplimplementsILoginService{
7
8@Override
9publicBooleanLogin(Stringname,Stringpassword){
10//TODOAuto-generatedmethodstub
11MessageContextcontext=MessageContext.getCurrentContext();
12Sessionsession=context.getSession();
13if(session!=null)
14context.getSession().set("User",name);
15returntrue;
16}
17
18@Override
19publicStringGetUserName(){
20MessageContextcontext=MessageContext.getCurrentContext();
21Sessionsession=context.getSession();
22returnsession.get("User").toString();
23}
24}
复制代码
在server-config.wsdd中,把scope设置为session模式

复制代码
1.......
2
3
4
5
6
7.......
复制代码
在客户端调用时,需要把maintainSession设置为true,此时可以把userName,password等信息存到上下文当中。

复制代码
1publicstaticvoidmain(String[]args)throws
2RemoteException,MalformedURLException,ServiceException{
3//TODOAuto-generatedmethodstub
4Login();
5}
6
7privatestaticvoidLogin()throws
8MalformedURLException,RemoteException{
9LoginServiceImplservice1=getService();
10LoginServiceImplservice2=getService();
11
12service1.login("Leslie","12345678");
13service2.login("Jack","12345678");
14
15System.out.println("UserName:"+service1.getUserName());
16System.out.println("UserName:"+service2.getUserName());
17}
18
19privatestaticLoginServiceImplgetService()throws
20AxisFault,MalformedURLException{
21LoginServiceImplServiceLocatorlocator=newLoginServiceImplServiceLocator();
22locator.www.hunanwang.netsetMaintainSession(true);
23LoginServiceImplloginService=newLoginServiceSoapBindingStub(
24newURL("http://localhost:8080/axis.server/services/LoginService"),
25locator);
26returnloginService;
27}
复制代码
四、自定义Handler

Axis的Handler与Servlet中的Filter有点相似,用于过滤服务,检测管理Web服务信息的接收发送过程。开发Handler需要实现org.apache.axis.Handler接口,接口包含了下面多个方法:



为了简化Handler的开发,Axis在org.apache.axis.handlers命名空间内就为客户提供了BasicHandler、ErrorHandler、LogHandler、SimpleSessionHandler、SimpleAuthenticationHandler、SimpleAuthorizationHandler等Handler用于管理Axis的错误处理,日志记录,身份认证,权限管理等工作。BasicHandler是实现Handler接口的基础类,用户可以继承BasicHandler类,开发自定义的Handler对特定的服务分别在服务请求request、信息回送response时进行处理。init、invoke是BasicHandler最常用的方法,init方法会在对象初始化时执行,而对服务的管理操作可以在invoke方法中定义。invoke方法包括了messageContext参数,利用messageContext,可以获取到服务的SOAP,HttpServletRequest、HttpServletResponse、URL等相关信息。
下面的例子就是利用自定义的LoginHandler对LoginService服务请求进行监听。
首先修改server-config.wsdd文件,在LoginService服务的requestFlow中加入LoginHandler。

复制代码
1.......
2
3
4
5
6
7
8

9
10......
复制代码
建立LoginService,在对LoginService服务接收到SOAP请求时,系统可以通过LoginHandler监听请求信息,获取相关的方法名,输入参数等信息。通过messageContext.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST)方法,可以获取到HttpServletRequest对象。

复制代码
1publicinterfaceILoginService{
2publicBooleanLogin(Stringname,Stringpassword);
3}
4
5publicclassLoginServiceImplimplementsILoginService{
6
7@Override
8publicBooleanLogin(Stringname,Stringpassword){
9//TODOAuto-generatedmethodstub
10UserServiceservice=newUserService();
11Useruser=service.getUser(name);
12if(user!=null)
13returnuser.password==password;
14else
15returnfalse;
16}
17}
18
19publicclassLoginHandlerextendsBasicHandler{
20privateMessageContextcontext;
21
22publicvoidinvoke(MessageContextcontext){
23this.context=context;
24GetServletRequest();
25GetSOAP();
26}
27
28//获取HtppServletRequest对象
29privatevoidGetServletRequest(){
30HttpServletRequestrequest=(HttpServletRequest)context
31.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST);
32StringremoteAddress=request.getRemoteAddr();
33Stringmethod=request.getMethod();
34StringBufferURL=request.getRequestURL();
35Stringmessage=String.format("ClientAddress:%s\nMethod:%s\n"+
36"RequestURL:%s\n",remoteAddress,method,URL);
37System.out.println(message);
38}
39
40//获取SOAPBody信息
41privatevoidGetSOAP(){
42Stringsoap=null;
43try{
44soap=context.getCurrentMessage().getSOAPBody().toString();
45}catch(SOAPExceptione){
46//TODOAuto-generatedcatchblock
47e.printStackTrace();
48}
49if(soap!=null){
50String[]data=soap.split(">");
51for(Stringline:data)
52System.out.println(line+">");
53}
54}
55}


复制代码
1publicstaticvoidmain(String[]args)throws
2RemoteException,MalformedURLException,ServiceException{
3LoginServiceImplservice1=getService();
4service1.login("Leslie","12345678");
5}
6
7privatestaticLoginServiceImplgetService()throws
8AxisFault,MalformedURLException{
9LoginServiceImplServiceLocatorlocator=newLoginServiceImplServiceLocator();
10locator.setMaintainSession(true);
11LoginServiceImplloginService=newLoginServiceSoapBindingStub(
12newURL("http://localhost:8080/axis.server/services/LoginService"),
13locator);
14returnloginService;
15}
复制代码
只要在service的requestFlow对Handler进行绑定,在客户端发送请求后,Handler就能对服务请求进行监听。





同样地,在service的responseFlow输入流中对Handler进行绑定,就可以对回发的SOAP信息进行监听。
下面例子是利用自定义LoginHandler对用户的登录回发信息进行监察,计算成功登录的在线人数。
首先修改server-config.wsdd文件,在LoginService服务的responseFlow中加入LoginHandler。

复制代码
1
2
3
4
5
6
7

8
复制代码
在自定义Handler中可以通过Message.getSOAPEnvelope,Message.getSOAPHead,Message.getSOAPBody等多个方法对Web服务的回发信息进行监测。若登录成功,系统检测到LoginReturn值为true时,则修改成功登录人数数量。
除此以外,开发人员还可在SOAP的head、body等多处地方额外添加回发信息。下面的例子将记录成功登陆的人数,并把此信息记录在头文件中返还到客户端。

复制代码
1publicinterfaceILoginService{
2publicBooleanlogin(Stringname,Stringpassword);
3}
4
5publicclassLoginServiceImplimplementsILoginService{
6
7@Override
8publicBooleanlogin(Stringname,Stringpassword){
9UserServiceuserService=newUserService();
10Useruser=userSerivice.getUser(name);
11if(user!=null)
12returnuser.password==password;
13else
14returnfalse;
15}
16}
17
18publicclassLoginHandlerextendsBasicHandler{
19privateMessageContextcontext;
20
21publicvoidinvoke(MessageContextcontext){
22this.context=context;
23try{
24if(success())
25logged();
26addElement();
27getSOAP();
28}catch(AxisFaulte){
29e.printStackTrace();
30}catch(SOAPExceptione){
31e.printStackTrace();
32}
33}
34
35//记录登录人数
36privatevoidlogged(){
37Handlerhandler=context.getService();
38//判断服务是否为LoginService
39if(handler.getName().equals("LoginService")){
40if(this.getOption("loggedCount")==null)
41this.setOption("loggedCount",0);
42
43Integercount=Integer.parseInt(
44this.getOption("loggedCount").toString());
45count++;
46this.setOption("loggedCount",count);
47
48System.out.println("loggedcount:"+count+""+
49newDate().toString()+"\n");
50}
51}
52
53//获取LoginService服务返回值
54//若登录成功则返回true,失败则返回false
55privateBooleansuccess()throwsSOAPException{
56SOAPBodysoap=context.getCurrentMessage().getSOAPBody();
57Nodenode=soap.getElementsByTagName("LoginReturn").item(0);
58returnnode.toString().contains("true");
59}
60
61//在回发的SOAP中加入已登录人数的信息
62privatevoidaddElement()throwsSOAPException{
63SOAPEnvelopesoap=context.getCurrentMessage().getSOAPEnvelope();
64SOAPElementelement=soap.getHeader().addChildElement("loggedOnline");
65element.addTextNode(this.getOption("loggedCount").toString());
66}
67
68//显示SOAP信息
69privatevoidgetSOAP()throwsAxisFault{
70SOAPEnvelopesoap=context.getCurrentMessage().getSOAPEnvelope();
71if(soap!=null){
72String[]data=soap.toString().split(">");
73for(Stringline:data)
74System.out.println(line+">");
75}
76}
77}
复制代码
客户端

复制代码
1publicstaticvoidmain(String[]args)throws
2RemoteException,MalformedURLException,ServiceException{
3LoginServiceImplservice1=getService();
4service1.login("Leslie","12345678");
5}
6
7privatestaticLoginServiceImplgetService()throws
8AxisFault,MalformedURLException{
9LoginServiceImplServiceLocatorlocator=newLoginServiceImplServiceLocator();
10locator.setMaintainSession(true);
11LoginServiceImplloginService=newLoginServiceSoapBindingStub(
12newURL("http://localhost:8080/axis.server/services/LoginService"),
13locator);
14returnloginService;
15}
复制代码

五、新一代SOAP引擎Axis2.x

5.1Axis2.x核心结构

Axis1.x建立在JAX-RPC基础之上的,但事实证明这并非一个好方法,因为JAX-RPC限制了Axis代码的功能,而且造成了性能问题使系统缺乏灵活性。Axis2.x在设计时已经考虑到灵活性操作的问题,它同时实现了对JAXB2.x、JavaXML数据绑定标准,并以JAX-WS技术替代了JAVA-PRC作为JavaWeb服务标准。
Axis2.x是纯SOAP处理引擎,它的核心功能是处理传输消息,并将其交付给目标应用程序。像JAX-WS此类Web服务标准不会进入Axis2.x核心部分当中,而只作为Axis2.x服务传递组件。AXIOM(Axis2ObjectModel,Axis2对象模型)才是Axis2的基础,任何SOAP消息在Axis2中都可看作为AXIOM。它把延迟构建和轻型的可定制对象模型结合了起来,尽可能地减轻对系统资源特别是CPU和内存的压力。
关于Axis2.x的消息处理过程与AXIOM对象模型将在下节再作进一步介绍。



5.2Axis2.x安装部署
Axis2.x可以在http://axis.apache.org/axis2/java/core/index.html下载,当中包括BinaryDistribution、WAR等多个版本,使用WAR版本更方便把Web服务部署到Tomcat、WebLogic等服务管理器上,在开发阶段,使用BinaryDirstribution等版本更便于服务的调试。
完成下载后在环境变量中把Axis2_Home绑定到Axis2.x的根目录,在path加入设置".;%Axis2_Home%\bin"。
Axis1.x当中只是包括了几个工具包,而Axis2.x更像是一个框架,在Axis2.x项目的“\WebRoot\WEB-INF\”文件夹内包含了Axis2.x多个储存库,在“conf”文件夹内的“axis2.xml”文件是Axis2.x全局描述符,所有的系统级配置都是通过“axis2.xml”文件完成的。在“services”文件夹是用于存放后缀名为“.aar”的服务模块的,在“modules”文件夹内用于存放后缀名为“.mar”的自定义模块的,在“pojo”文件夹内用于存放传统的POJO对象服务文件。而服务描述符“services.xml”文件与模块描述符“module.xml”文件则存放于“\WebRoot\META-INF”文件夹当中。



5.3将传统的POJO对象作为服务对象部署

Axis2.x为用户提供了最简约的服务部署方式,能把简单的一个POJO对象作为服务发布。
首先在一个缺省包里建立一个POJO对象,再把被编译后的Example.class文件加入到“\WebRoot\WEB-INF\pojo\”文件夹内,此时Example即会被默认为POJO服务。

1publicclassExample{
2publicStringHelloWorld(Stringname){
3return"Hello"+name;
4}
5}
运行程序后,你就可以http://leslie-laptop:8080/axis2-1.6.2/services/Example?wsdl上看到Example服务的wsdl信息。



5.4以存档文件部署服务

使用POJO对象部署服务固然简单,但由于在安全性事务、消息监听等方面缺乏支持,所以Axis2.x更多时候是使用存档文件方式部署服务的。首先在项目内建立服务接口PersonService和服务类PersonServiceImpl。

复制代码
1publicclassPersonEntityimplementsSerializable{
2privateIntegerid;
3privateStringname;
4privateIntegerage;
5privateStringaddress;
6
7publicPersonEntity(Integerid,Stringname,Integerage,Stringaddress){
8this.id=id;
9this.name=name;
10this.age=age;
11this.address=address;
12}
13
14publicIntegergetId(){
15returnid;
16}
17
18publicvoidsetId(Integerid){
19this.id=id;
20}
21........
22}
23
24publicinterfacePersonService{
25PersonEntitygetPerson(intid);
26PersonEntity[]getList();
27ListgetListByName(Stringname);
28}
29
30publicclassPersonServiceImplimplementsPersonService{
31
32@Override
33publicPersonEntity[]getList(){
34PersonEntity[]list=newPersonEntity[2];
35PersonEntityperson1=newPersonEntity(1,"Leslie",32,"tianhe");
36PersonEntityperson2=newPersonEntity(2,"Elva",31,"henan");
37list[0]=person1;
38list[1]=person2;
39returnlist;
40}
41
42@Override
43publicListgetListByName(Stringname){
44Listlist=newLinkedList();
45PersonEntityperson1=newPersonEntity(1,name+"Lee",32,"tianhe");
46PersonEntityperson2=newPersonEntity(2,name+"Chen",31,"henan");
47list.add(person1);
48list.add(person2);
49returnlist;
50}
51
52@Override
53publicPersonEntitygetPerson(intid){
54returnnewPersonEntity(id,"Leslie",32,"tianhe");
55}
56}
复制代码
在“\WebRoot\META-INF”文件夹内加入配置文件services.xml。当中ServiceClass的parameter用于绑定服务实现类,而operation用于绑定要暴露的服务方法。
Axis2.x支持三种信息交换模式,包括In-Only,Robust-In,In-Out。In-Only消息交换模式只接收SOAP请求,而无需返还信息;Robust-In消息交换模式发送SOAP请求,只有在出错的情况下才返回应答;In-Out消息交换模式总是对SOAP请求返还信息。在服务的messageReceive设置中有RPCMessageReceiver、RawXMLINOutMessageReceiver、RawXMLINOnlyMessageReceiver等多个选项可以针对不同Web服务方法设置不同的信息交换模式。

复制代码
1
2ThisisasampleWebService.
3
4
5axis2.serviceImpl.PersonServiceImpl
6
7
8
9
10
11
12
13
14
15
16
17
复制代码
完成配置后,把“META-INF\services.xml”文件(包含文件夹META-INF)和服务类PersonEntity.class、PersonService.class、PersonServiceImpl复制到自定义文件夹“axis2serivces”当中。打开命令提示符,进入axix2services文件夹输入命令“jarcvfaxisService.aar.”(注意:“.”代表生成包含文件夹所有文件)。最后把“axisService.aar”文件加入到“\WebRoot\WEB-INF\services”文件夹当中,启动Axis2.x项目,打开http://leslie-laptop:8080/axis2-1.6.2/services/PersonService?wsdl就可看到PersonService服务的wsdl信息。在wsdl中可以看到axis2支持SOAP1.1、SOAP1.2等多种传输格式。



Axis2.x支持多种客户端生成工具,包括原有WSDL2Java工具
WSDL2Java-urihttp://localhost/axis2-1.6.2/services/PersonSerivce.wsdl-p包名-o文件夹
还有支持JAX-WS的WsImport工具
Wsimport-p包名-keep-extensionhttp://localhost/axis2-1.6.2/services/PersonSerivce.wsdl
也可使用MyEclipse自带的JAX-WS客户端生成工具完成。
生成客户端后可以进行测试

复制代码
1publicstaticvoidmain(String[]args)throwsMalformedURLException{
2//TODOAuto-generatedmethodstub
3getList();
4}
5
6privatestaticvoidgetList()throwsMalformedURLException{
7PersonServicepersonService=newPersonService();
8PersonServicePortTypepersonServicePortType=personService
9.getPersonServiceHttpSoap12Endpoint();
10ListpersonList=personServicePortType.getList();
11for(PersonEntityperson:personList)
12displayPersonProperty(person);
13}
14
15privatestaticvoiddisplayPersonProperty(PersonEntityperson){
16System.out.println("Id:"+person.getId().getValue()+"Name:"
17+person.getName().getValue()+"Age:"+person.getAge().getValue()
18+"Address:"+person.getAddress().getValue());
19}
复制代码

六、AXIOM对象模型

6.1AXIOM的特点

AXIOM(AxisObjectModel,Axis对象模型)是Axis2.x对XML信息处理的核心部分,它把延迟构建和可定制对象模型技术结合起来,极大地提高了SOAP信息构建的灵活度。对应Axis1.x的SAX(SimpleAPIforXML)推式(Push)解析器,Axis2.x使用更具灵活性的StAX(StreamingAPIforXML)拉式(Pull)解析器,可尽量减轻对系统资源的压力。在使用推方式(Push)的情况下,系统会先定义数据的处理程序,然后在数据录入时对处理程序进行回调。然而回调操作只能对录入的数据进行如读取、修改等某些操作,除非引发异常,否则无法左右XML文档的录入。而AXIOM所使用的拉式(Pull)解析器,实际上是一个高效的迭代器,它使用XML树形结构方式,支持延时构建,可以根据需要对文档中的不同部分进行遍历。在大型的XML文件中,使用拉式解析器更具吸引力,它可以仅对部分XML数据进行处理,剩下的留给解析器完成操作。
在Web服务开发过程中,大部分的开发人员都是以对象的形式进行信息传递,并使用WSDL2Java等工具构建服务代理以实现XML数据与对象之间的转换。然而使用此方式限制了Web服务框架中的数据绑定的灵活程度,利用AXIOM的特性更便于XML与Java对象之间转换,大部分的JAVA对象都可以利用AXIOM转换成SOAP信息。



6.2AXIOM的使用方式

AXIOM建立于StAX拉式解析器的基础上,它为开发人员准备了完善的API,其中最常用到的是OMFactory工厂,它提供了createOMNamespace、createOMElement、createOMAttribute、createOMDocument、createOMText等多个方法用于建立XML文档信息。在BeanUtil类中还包括了getPullParser、getOMElement、processObject等多个静态方法用于处理自定义对象与XML之间的转换。
下面以POCO服务作为一个例子,演示一下AXIOM对象绑定方式。在客户端Web服务分为SOAP请求数据绑定与返回信息处理两个阶段,在服务请求阶段系统会把设定的服务地址、传输方式绑定到ServiceClient请求对象当中,然后利用serviceClient.sendReceive的方法把已定义的OMElement对象信息加入到SOAP当中发送到服务端。当接收到返回信息后,再把OMElement信息转换为对象显示。

复制代码
1publicstaticvoidmain(String[]args)throwsAxisFault{
2excute();
3}
4
5publicstaticvoidexcute()throwsAxisFault{
6//设置endpoint地址
7EndpointReferencetargetEndpoint=newEndpointReference(
8"http://leslie-laptop:8080/axis2-1.6.2/services/PersonService");
9Optionsoptions=newOptions();
10options.setTo(targetEndpoint);
11
12//设置传输方式
13//可使用TRANSPORT_JMS;TRANSPORT_HTTP;TRANSPORT_MAIL;TRANSPORT_TCP;
14options.setTransportInProtocol(Constants.TRANSPORT_HTTP);
15
16//把设置的地址、传输方式绑定到Service请求当中
17ServiceClientsender=newServiceClient();
18sender.setOptions(options);
19
20//设置请求的SOAP信息
21OMElementrequestOMElement=getPersonRequest(1);
22OMElementresponseOMElement=sender.sendReceive(requestOMElement);
23
24//对返回的SOAP进行处理,显示返回值
25PersonEntityperson=convertToPerson(responseOMElement);
26displayPersonProperty(person);
27}
28
29privatestaticOMElementgetPersonRequest(Integerid){
30//新建OMFactory工厂
31OMFactoryfactory=OMAbstractFactory.getOMFactory();
32
33//加入OMNamespace、OMElement、OMText等数据
34OMNamespaceomNs=factory.createOMNamespace(
35"http://ws.apache.org/axis2","myNS");
36OMElementvalue=factory.createOMElement("id",omNs);
37value.addChild(factory.createOMText(value,id.toString()));
38
39//加入请求方法名
40OMElementmethod=factory.createOMElement("getPerson",omNs);
41method.addChild(value);
42returnmethod;
43}
44
45//把返回OMElement对象转换成person对象
46privatestaticPersonEntityconvertToPerson(OMElementelement)
47throwsAxisFault{
48PersonEntityperson=null;
49OMElementomElement=element.getFirstElement().getFirstElement();
50StringlocalName=omElement.getLocalName().toLowerCase();
51if(localName.equals("personentity")){
52person=(PersonEntity)BeanUtil.processObject(omElement,
53PersonEntity.class,null,true,newDefaultObjectSupplier(),null);
54}
55returnperson;
56}
57
58privatestaticvoiddisplayPersonProperty(PersonEntityperson){
59System.out.println("Id:"+person.getId()+"Name:"+person.getName()
60+"Age:"+person.getAge()+"Address:"+person.getAddress());
61}
复制代码
发送的SOAP请求



在服务端,系统会从发送的SOAP信息中获取请求的id值,并通过BeanUtil.getPullParser等方法把personEntity对象转换成SOAP信息返还到客户端。

复制代码
1publicclassPersonService{
2
3publicOMElementgetPerson(OMElementelement){
4//获取请求条件Id
5Integerid=Integer.valueOf(element.getText());
6//模拟返回数据
7PersonEntityperson=newPersonEntity(id,"Leslie",32,"tianhe");
8//把person对象转换为OMElement
9javax.xml.stream.XMLStreamReaderreader=BeanUtil.getPullParser(person);
10StreamWrapperparser=newwww.zycaihui.comStreamWrapper(reader);
11OMXMLParserWrapperstAXOMBuilder=OMXMLBuilderFactory
12.createStAXOMBuilder(OMAbstractFactory.getOMFactory(),parser);
13returnstAXOMBuilder.getDocumentElement();
14}
15}
复制代码

6.3突显AXIOM的优势

在面向对象的Web服务开发模式下,系统都会利用集成工具进行XML信息与对象的自动化转换。数据的搜索与查找都会在JAVA对象中进行,在信息交换密度频繁的系统当中,这将占用大量内存空间,对系统造成压力。所以,在返回信息中直接对XML数据进行拦截、分类、筛选是常用的方法。Axis2.x使用更具灵活性的StAX拉式解析器,它可以使用虚拟文档的方式构建XML树。每个节点都可被视为一个容器OMContainer,它可以使用OMContainer.getChildren方法获取子节点,再以Iterator迭代器的方式对节点进行遍历。此外系统还提供了OMNode.getNextOMSibling、OMNode.getPreviousOMSibling等多个方法,以进行节点之间的跳转。这意味着它可以跳过其他的子节点,直接找到需要的节点再进行遍历。在数据量较大的系统当中使用此种遍历方式更能突显出StAX拉式(Pull)解析器的优势。
以下的例子主要为了展示使用StAX拉式解析器进行遍历的方式,BookService服务主要是根据客户所输入的出版社信息进行查找,然后把该出版社的书本进行按类分配,返还到客户端。下面是服务端的代码:

复制代码
1publicclassBookEntity{
2privateIntegerid;
3privateStringtitle;
4privateStringauthor;
5privateStringpublishing;
6privateStringintroduction;
7privateStringtype;
8
9publicBookEntity(Integerid,Stringtitle,Stringauthor
10,Stringtype,Stringpublishing,Stringintroduction){
11this.id=id;
12this.title=title;
13this.author=author;
14this.type=type;
15this.publishing=publishing;
16this.introduction=introduction;
17}
18
19publicIntegergetId(){
20returnid;
21}
22
23publicvoidsetId(Integerid){
24this.id=id;
25}
26........
27}
28
29publicinterfaceBookService{
30OMElementgetList(OMElementelement);
31}
32
33publicclassBookServiceImplimplementsBookService{
34
35@Override
36publicOMElementgetList(OMElementelement){
37//获取请求信息publishing出版社名称
38OMElementchild=(OMElement)element.getChildren().next();
39Stringpublishing=child.getText();
40
41//构建OMFactory工厂
42OMFactoryfactory=OMAbstractFactory.getOMFactory();
43OMNamespaceomNamespace=factory.createOMNamespace(
44"http://serviceImpl.axis2","ns");
45
46//获取计算机类书本子节computerElement
47ListcomputerTypeList=getBooks(
48publishing,"computer");
49OMElementcomputerElement=convertToOMElement(
50computerTypeList,"computer",omNamespace);
51
52//获取文学类书本子节literatureElement
53ListliteratureTypeList=getBooks(
54publishing,"literature");
55OMElementliteratureElement=convertToOMElement(
56literatureTypeList,"literature",omNamespace);
57//加入多个类型的书本
58...........
59//构建XML树
60OMElementresponse=factory.createOMElement(
61"getListResponse",omNamespace);
62OMElementreturnValue=factory.createOMElement
63("return",omNamespace);
64OMElementbooks=factory.createOMElement(
65"publishing",omNamespace);
66
67books.addAttribute("name",publishing,omNamespace);
68books.addChild(computerElement);
69books.addChild(literatureElement);
70.........
71returnValue.addChild(books);
72response.addChild(returnValue);
73returnresponse;
74}
75
76//把计算机类书本对象转换为XML信息
77privateOMElementconvertToOMElement(Listlist
78,StringtypeName,OMNamespaceomNamespace){
79OMElementomElement=BeanUtil.getOMElement(newQName("theme")
80,list.toArray(),newQName("book"),false,null);
81omElement.addAttribute("name",typeName,omNamespace);
82returnomElement;
83}
84
85//根据书本类型查找数据
86privateListgetBooks(Stringpublishing,Stringtype){
87Listlist=newArrayList();
88for(BookEntitybook:virtualDatabase(publishing))
89if(book.getType()=="computer")
90list.add(book);
91returnlist;
92}
93
94//虚拟数据
95privateListvirtualDatabase(Stringpublishing){
96Listlist=newArrayList();
97BookEntitybook1=newBookEntity(
981,"CoreJAVAAdvancedFeatures","GaryCornell",
99"computer",publishing,
100"CoreJavabyCayS.HorstmannandGaryCornellisabook\n"+
101"intheJavaseriesofSunMicrosystemsPress,published\n"+
102"byPrentice-Hall.Thebookisaimedatexperienced\n"+
103"programmerswhowanttolearnhowtowriteusefulJava\n"+
104"applicationsandapplets.Nohype,notoycode,nolanguage\n"+
105"lawyering,justsolidfactsandin-depthresearchtohelpyou\n"+
106"writerealprograms."
107);
108list.add(book1);
109........
110returnlist;
111}
112}
复制代码
在配置services.xml文件时,需要把messageReceiver设置为org.apache.axis2.receivers.RawXMLINOutMessageReceiver

复制代码
1
2ThisisasampleWebService.
3
4axis2.serviceImpl.BookServiceImpl
5
6
7
8
9
复制代码
返还的SOAP信息



在客户端利用StAX就可以轻松地对返还数据进行分类处理,例如要在返回数据当中显示计算机类型的Book信息,可以使用Iterator方式进行遍历,利用StaX推时分析的特点跳过其他类型的节点,直至遇到ns:name=computer的节点时,才把该节点的XML树放入容器进行处理。使用此种遍历方式,所占用的内存空间更小,在返回数据量较大的系统当中更能突显其优势。

复制代码
1publicstaticvoidmain(String[]args)throwsAxisFault{
2//设置请求的endpoint地址
3EndpointReferencetargetEndpoint=newEndpointReference(
4"http://leslie-laptop:8080/axis2-1.6.2/services/BookService");
5Optionsoptions=newOptions();
6options.setTo(targetEndpoint);
7
8//设置传输方式
9//可使用TRANSPORT_JMS;TRANSPORT_HTTP;TRANSPORT_MAIL;TRANSPORT_TCP;
10options.setTransportInProtocol(Constants.TRANSPORT_HTTP);
11
12//把设置的地址、传输方式绑定到Service请求当中
13ServiceClientsender=newServiceClient();
14sender.setOptions(options);
15
16//设置请求的SOAP信息,输入出版社名称
17OMElementrequestOMElement=getBookRequest("ChinaMachinePress");
18//发送请求
19OMElementresponseOMElement=sender.sendReceive(requestOMElement);
20//遍历返回值,在返回信息中获取computer类的Element
21OMElementcomputerBookElement=getComputerElement(responseOMElement);
22//把XML转换为BookEntity对象集
23Listlist=convertToBooks(computerBookElement);
24for(BookEntitybook:list)
25DisplayBook(book);
26}
27
28//把XML数据转换为BookEntity对象集
29privatestaticListconvertToBooks(OMElementelement)
30throwsAxisFault{
31Listlist=newArrayList();
32Iteratorbooks=element.getChildElements();
33while(books.hasNext()){
34OMElementbookElement=(OMElement)books.next();
35BookEntitybook=(BookEntity)BeanUtil.processObject(bookElement,
36BookEntity.class,null,true,newDefaultObjectSupplier(),null);
37list.add(book);
38}
39returnlist;
40}
41
42//对OMElement元素进行遍历,找到computer类型的节点
43privatestaticOMElementgetComputerElement(OMElementelement){
44OMElementreturnOMElement=(OMElement)element
45.getChildElements().next();
46OMElementpublishingOMElement=(OMElement)returnOMElement
47.getChildElements().next();
48Iteratorthemes=publishingOMElement.getChildElements();
49OMElementtheme=null;
50while(themes.hasNext()){
51theme=(OMElement)themes.next();
52OMAttributethemeName=(OMAttribute)theme.getAllAttributes().next();
53if(themeName.getAttributeValue().equals("computer"))
54break;
55}
56returntheme;
57}
58
59//构建请求文档
60privatestaticOMElementgetBookRequest(Stringpublishing){
61//新建OMFactory工厂
62OMFactoryfactory=OMAbstractFactory.getOMFactory();
63
64//绑定OMNamespace,请求数据
65OMNamespaceomNamespace=factory.createOMNamespace(
66"http://serviceImpl.axis2","ns");
67OMElementvalue=factory.createOMElement("publishing",omNamespace);
68value.addChild(factory.createOMText(value,publishing.toString()));
69
70//绑定请求方法名
71OMElementmethod=factory.createOMElement("getList",omNamespace);
72method.addChild(value);
73returnmethod;
74}
75
76privatestaticvoidDisplayBook(BookEntitybook){
77System.out.println("##Id##:"+book.getId()+"##Title##:"+book.getTitle()
78+"\n##Author##:"+book.getAuthor()+"##Type##:"+book.getType()
79+"##Publishing##:"+book.getPublishing()+"\n##Introduction##:\n"
80+book.getIntroduction()+"\n");
81}
复制代码
发送的SOAP请求

七、Module模块独立化处理方式

7.1Axis2.x信息处理流程

Axis2.x对信息处理流程作出了较大幅的修改,在介绍模块处理结构前,需要简单介绍一下Axis2.x的信息处理机制。Axis1.x只接受请求-响应的信息处理模式,而Axis2.x支持In-Only、In-Out和Robust-In三种消息交换模式。而这三种消息交换机制是建立在TransportListener和TransportSender之上的,在SOAP信息进站时,系统会通过TransportListener进行监听。通过一系列处理后,最后会由TransportSender进行信息的回送。
系统定义了InFlow、OutFlow两种流用于处理服务器端的请求消息和响应消息。而InFaultFlow、OutFaultFlow只会在请求或者响应出现错误时才会被调用。当TransportListener监听到入站信息时会把信息送到InFlow流当中,系统可通过修改“\WEB-INF\conf\axis2.xml”配置文件,把多个Phase绑定到InFlow当中。每个Phase相当于一个阶段,同一个阶段可以绑定多个Handler进行处理。数据通过InFlow流处理后就会被发送到MessageReceiver,在services.xml中会绑定对应的MessageReceiver服务方法。最后通过OutFlow流对返回信息进行处理后,由TransportSender把SOAP响应回发到客户端。

Module模块结构

Axis2.x把Handler放入Module模块当中,进行了独立化处理。每个Module模块是一个容器,当中可以包含多个Handler处理程序、第三方库、模块相关资源和模块配置文件。系统把Module模块定义为后缀名为“.mar”的文件,系统可通过“jarcvfmyModule.mar.”命令可以生成.mar的模板文件。当中必须包含module.xml文件对Handler进行部署,否则系统会无法识别此模块。想要在某个Web服务的InFlow流或OutFlow流中调用Module模块中的Handler,还需要修改axis2.xml文件,在phaseOrder中加入自定义的phase,并绑定Handler的处理类。最后修改“services.xml”文件,在对应此服务配置中加入字节。
下面先以一个简单的例子,说明Module模块的使用方式。在例子中将建立一个MyModule模块,在模块中加入一个InputHandler处理文件对InFlow流进行检测。首先建立org.apache.axis2.modules.Module的子类MyModule,此类是用于对模块init、engageNotify等事件进行监测的。然后建立InputHandler类,此类必须继承org.apache.axis2.engine.Handler且实现org.apache.axis2.engine.Handler.AbstractHandler接口的publicInvocationResponseinvoke(MessageContextcontext)方法,此方法的返回值InvocationResponse包括CONTINUE,SUSPEND,ABORT三个选项,可以"继续"或者"停止"流的执行。
在流输入时此方法将会被自动执行,它所带的MessageContext参数对象中将包括此服务的上下文信息。
此例子的主要目的是为开发人员显示一下在InFlow流可以获取到的数据信息,所以在此先介绍一下MessageContext常用方法:


方法 说明
getCurrentMessageContext() 获取当前上下文对象
getExecutedPhases() 获取包含在此流中的Phase集合
getAxisService() 获取被调用的AxisService服务对象
getEnvelope() 获取SOAP,在InFlow中,此方法将显示该请求的SOAP信息
getFrom() 获取客户端地址
getTo() 获取服务地址
MessageContext常用方法


当中getAxisService方法所返回的AxisService对象正是当前被调用的Web服务对象。


方法 说明
getOperationContext() 返回当前被调用的OperationContext上下文对象
getOperations() 返回此服务所包含的所有Operation对象
getParameters() 返回此服务的所包含的所有Parameters对象
getFileName() 返回此.aar服务文件路径
getTargetNamespace() 返回此服务的targetNamespace名称
getEndpointName() 返回此服务的Endpoint名称
AxisService常用方法


复制代码
1publicclassMyModuleimplementsModule{
2
3@Override
4publicvoidapplyPolicy(Policyarg0,AxisDescriptionarg1)
5throwsAxisFault{
6//TODOAuto-generatedmethodstub
7}
8
9@Override
10publicbooleancanSupportAssertion(Assertionarg0){
11//TODOAuto-generatedmethodstub
12returnfalse;
13}
14
15@Override
16publicvoidengageNotify(AxisDescriptionarg0)throwsAxisFault{
17//TODOAuto-generatedmethodstub
18}
19
20@Override
21publicvoidinit(ConfigurationContextarg0,AxisModulearg1)
22throwsAxisFault{
23//TODOAuto-generatedmethodstub
24}
25
26@Override
27publicvoidshutdown(ConfigurationContextarg0)throwsAxisFault{
28//TODOAuto-generatedmethodstub
29}
30}
31
32publicclassInputHandler
33extendsAbstractHandlerimplementsHandler{
34
35publicInvocationResponseinvoke(MessageContextcontext){
36//显示当前上下文信息
37System.out.println("##from:##\n"+context.getFrom()
38+"\n##to:##\n"+context.getTo());
39//显示所执行的phase信息
40phasesMessageDisplay(context.getExecutedPhases());
41//显示服务对象信息
42AxisServiceaxisService=context.getAxisService();
43serviceMessageDisplay(axisService);
44//显示当前operation信息
45AxisOperationoperation=context.getOperationContext().getAxisOperation();
46currentOperationDisplay(operation);
47//显示服务的所有operation信息
48operationsMessageDisplay(axisService.getOperations());
49//显示服务参数parameters信息
50parametersMessageDisplay(axisService.getParameters());
51returnInvocationResponse.CONTINUE;
52}
53
54//显示所执行的phases
55privatevoidphasesMessageDisplay(Iteratoriterator){
56Stringdata="##phaseList:##\n";
57while(iterator.hasNext()){
58Handlerhandler=(Handler)iterator.next();
59data+=""+handler.getName();
60}
61System.out.println(data);
62}
63
64//显示服务信息
65privatevoidserviceMessageDisplay(AxisServiceaxisService){
66System.out.println("\n##serviceFile:##\n"+axisService.getFileName()+
67"\n##targetNamespace:##\n"+axisService.getTargetNamespace()+
68"\n##endpointName##:\n"+axisService.getEndpointName()+"\n");
69}
70
71//显示当前Operation信息
72privatevoidcurrentOperationDisplay(AxisOperationoperation){
73System.out.println("##currentOperation:##\nname:"
74+operation.getName().getLocalPart()+"\nmessageReceive:"
75+operation.getMessageReceiver().toString()+"\n");
76}
77
78//显示服务的所有Operation信息
79privatevoidoperationsMessageDisplay(Iteratoroperations){
80while(operations.hasNext()){
81AxisOperationoperation=(AxisOperation)operations.next();
82System.out.println("##operation:##\nname:"
83+operation.getName().getLocalPart()+"\nmessageReceive:"
84+operation.getMessageReceiver().toString());
85}
86System.out.println();
87}
88
89//显示parameter信息
90privatevoidparametersMessageDisplay(Listparameters){
91for(Parameterparameter:parameters){
92System.out.println("##parameter:##\nname:"+parameter.getName()
93+"\nvalue:"+parameter.getValue().toString());
94}
95}
96}
复制代码
在“\WebRoot\META-INF\”内加入“module.xml”配置文件

复制代码
1
2
3
4
5

6
7
复制代码
把“module.xml”配置文件(包含“META-INF”文件夹)和编译后的MyModule.class、InputHandler.class加入到自定义文件夹,使用命令提示符进行入自定义文件夹内输入“jarcvfmyModule.mar.”命令生成“myModule.mar”包,把生成的包加入“\WEB-INF\modules”文件夹内。
最后修改“\WEB-INF\conf\axis2.xml”配置文件,加入自定义的phase配置。此时重启系统,即完成了myModule.mar自定义模块的配置。

注意:自定义handler可以在axis2.xml或者module.xml中进行绑定。

axis2.xml中所配置的是全局变量,如果直接在axis2.xml文件中加入对handler进行绑定,那所有的服务在InFlow和OutFlow中都执行此handler。
如果只在axis2.xml中建立自定义phase,然后在module.xml中绑定handler,那只有在服务绑定此module时,该handler才会被执行。配置应该按需要而定,一般只有登录、日志记录、系统信息监视等handler才会在axis2.xml中直接绑定。

1
2
3
4
5
想要在某Web服务中调用此模块,只需要修改对应的services.xml配置文件,在服务配置内加入“”字节即可。

复制代码
1
2ThisisasampleWebService.
3
4
5
6axis2.serviceImpl.PersonServiceImpl
7
8
9
10
11
12
13
14
15
16
17
复制代码
在启动此Web服务,调用GetPerson服务方法时,InputHandler将会对服务流进行处理,显示测试结果:





7.3Module实用方式

以上对Module的使用方式进行了简单的介绍,下面想以一个更为实际的订单管理例子介绍一下Module的用途。在Web服务项目当中,很多的服务在操作前都需要先进行登录验证,所以下面例子当中会把用户登录功能独立开来,作为一个自定义Handler置于模块当中,这样可以使登录服务的功能更为独立,易于管理。用户在调用OrderService服务时,会先在头文件中加入用户信息,在服务端接收到SOAP请求时,在InFlow流中加入OrderInputHandler进行处理,获取头文件信息进行登录,登录成功就会在Session中记录User对象信息。
像Order管理这类服务中,订单费用的计算往往是一个长期不变的规则,但销售商很多时候会进行产品的抽奖,优惠等销售策略,这些策略都是短期的,具有多变性。如果直接把这些业务规则写入OrderService服务中,OderService就会经常需要修改。此时,可以尝试利用OutFlow流,建立OrderOutputHandler对完成操作的Order进行检测。若通过得奖规则,则在SOAP返还信息的头文件中加入奖励信息。
由于本节的目的主要是为了展示Module的独立性与灵活性,所以省略了OrderManager、UserManager、FavourableManager等操作对象。
首先建立OrderInputHandler在SOAP信息进入InFlow流时进行登录处理。
然后建立OrderOutputHandler在SOAP信息进行OutFlow时对已处理Order对象进行检测。

复制代码
1publicclassMyModuleimplementsModule{
2
3@Override
4publicvoidapplyPolicy(Policyarg0,AxisDescriptionarg1)
5throwsAxisFault{
6//TODOAuto-generatedmethodstub
7}
8
9@Override
10publicbooleancanSupportAssertion(Assertionarg0){
11//TODOAuto-generatedmethodstub
12returnfalse;
13}
14
15@Override
16publicvoidengageNotify(AxisDescriptionarg0)throwsAxisFault{
17//TODOAuto-generatedmethodstub
18}
19
20@Override
21publicvoidinit(ConfigurationContextarg0,AxisModulearg1)
22throwsAxisFault{
23//TODOAuto-generatedmethodstub
24}
25
26@Override
27publicvoidshutdown(ConfigurationContextarg0)throwsAxisFault{
28//TODOAuto-generatedmethodstub
29}
30}
31
32publicclassOrderInputHandler
33extendsAbstractHandlerimplementsHandler{
34
35publicInvocationResponseinvoke(MessageContextcontext){
36//判断被调用的是否OrderSerivce的addOrder方法
37if(context.getOperationContext().getAxisOperation()
38.getName().getLocalPart().equals("addOrder")){
39//获取头文件信息
40SOAPHeaderhead=context.getEnvelope().getHeader();
41//把头文件信息转换成User对象进行登录
42String[]userMes=head.getFirstElement().getText().split(",");
43//用户登录
44UserEntityuser=UserManager.Login(userMes[0],userMes[1]);
45.......
46if(user!=null){
47//登录成功记录User
48SessionContextsession=context.getSessionContext();
49if(session.getProperty("User")==null)
50session.setProperty("User",user);
51}
52}
53returnInvocationResponse.CONTINUE;
54}
55}
56
57publicclassOrderOutputHandler
58extendsAbstractHandlerimplementsHandler{
59
60publicInvocationResponseinvoke(MessageContextcontext){
61//判断被调用的是否OrderSerivce的addOrder方法
62if(context.getOperationContext().getAxisOperation()
63.getName().getLocalPart().equals("addOrder")){
64//获取返回信息的SOAPBody,判断订单总体价格是否高于200dollar
65SOAPBodybody=context.getEnvelope().getBody();
66OMElementelement=body.getFirstElement();
67try{
68OrderEntityorder=(OrderEntity)BeanUtil.processObject(element,
69OrderEntity.class,null,true,newDefaultObjectSupplier(),null);
70
71//总体价格高于200dollar的订单在头文件中输入获奖信息
72if(order.getTotalPrice()>200){
73//在数据库中记录获奖订单
74Favourablefavourable=FavourableManager.addOrder(order);
75.......
76//修改头文件,加入获奖信息
77setHead(context.getEnvelope().getHeader(),favourable);
78}catch(AxisFaulte){
79e.printStackTrace();
80}
81}
82returnInvocationResponse.CONTINUE;
83}
84
85//修改头文件,加入获奖信息
86privatevoidsetHead(SOAPHeaderhead,favourable){
87OMFactoryfactory=OMAbstractFactory.getOMFactory();
88OMNamespaceomNamespace=factory.createOMNamespace(
89"http://serviceImpl.axis2","ns");
90OMElementelement=factory.createOMElement("favourable",omNamespace);
91element.setText("Congratulations!Priceishigherthan200dollar......");
92//加入favourable信息
93.........
94head.addChild(element);
95}
96}
97
98publicclassOrderEntityimplementsSerializable{
99privateIntegerid;
100privateStringorderCode;
101privateDoubletotalPrice;
102.........
103
104publicOrderEntity(Integerid,StringorderCode,DoubletotalPrice,........){
105this.id=id;
106this.orderCode=orderCode;
107this.totalPrice=totalPrice;
108........
109}
110..........
111}
复制代码
完成自定义Handler后,进行module.xml配置,分别在InFlow流OutFlow绑定OrderInputHandler和OrderOutputHandler

复制代码
1
2
3
4
5
6

7
8
9
10
11

12
复制代码
修改axis2.xml文件,建立自定义的phase,在InFlow流和OutFlow流中加入orderInPhase和orderOutPhase。

复制代码
1
2
3
4
5
6
7
复制代码
建立Web服务OrderService,当调用addOrder前先检测用户是否登录成功,若用户未登录则释放出异常。若登录成功,把输入的Order对象加入数据库,然后把处理后的Order转换为OMElement返还到客户端。

复制代码
1publicinterfaceOrderService{
2OMElementaddOrder(OMElementelement)throwsException;
3}
4
5publicclassOrderServiceImplimplementsOrderService{
6
7@Override
8publicOMElementaddOrder(OMElementelement)throwsException{
9//验证是否登录成功
10if(isLogged()){
11//把订单加入数据库,把更新后的订单返回客户端
12OrderEntityorder=(OrderEntity)BeanUtil.processObject(
13element.getFirstElement(),OrderEntity.class,
14null,true,newDefaultObjectSupplier(),null);
15//加入订单,计算总体价格,Code号码等信息
16OrderEntityorderRefresh=OrderManager.addOrder(order);
17.......
18//把修改后order对象转换为OMElement
19XMLStreamReaderreader=BeanUtil.getPullParser(orderRefresh);
20StreamWrapperparser=newStreamWrapper(reader);
21OMXMLParserWrapperstAXOMBuilder=OMXMLBuilderFactory
22.createStAXOMBuilder(OMAbstractFactory.getOMFactory(),parser);
23returnstAXOMBuilder.getDocumentElement();
24}
25else
26thrownewException();
27}
28
29//验证用户是否已经登录
30privatebooleanisLogged(){
31MessageContextcontext=MessageContext.getCurrentMessageContext();
32SessionContextsession=context.getSessionContext();
33returnsession.getProperty("User")!=null;
34}
35}
复制代码
在services.xml文件加入module节点

复制代码
1
2ThisisasampleWebService.
3
4
5axis2.serviceImpl.OrderServiceImpl
6
7
8
9
10
复制代码
当调用OrderService服务后,数据进入OutFlow流后系统将检测返还数据,若Order符合得奖条件,OrderOutputHandler将在SOAP头部加入得奖信息。下面是totalPrice超过200dollor(符合得奖条件)所返回的SOAP信息。





在客户端发送OrderService.addOrder请求时,在头文件加入用户userName,password等资料。为了在使用Session存储User对象,需要在客户端使用options.setManageSession(bool)方法打开sessionContext。由于使用方式与Axis1.x较为相像,在此不再详细说明了。

复制代码
1publicstaticvoidmain(String[]args)throwsRemoteException,OrderServiceExceptionException{
2//TODOAuto-generatedmethodstub
3excute();
4}
5
6publicstaticvoidexcute()throwsAxisFault{
7EndpointReferencetargetEndpoint=newEndpointReference(
8"http://leslie-laptop:8080/axis2-1.6.2/services/OrderService");
9Optionsoptions=newOptions();
10options.setManageSession(true);
11options.setTo(targetEndpoint);
12
13//设置传输方式
14//可使用TRANSPORT_JMS;TRANSPORT_HTTP;TRANSPORT_MAIL;TRANSPORT_TCP;
15options.setTransportInProtocol(Constants.TRANSPORT_HTTP);
16
17//把设置的地址、传输方式绑定到Service请求当中
18ServiceClientsender=newServiceClient();
19sender.setOptions(options);
20
21//设置请求的SOAP信息
22OMElementrequestOMElement=getOrderRequest(newOrderEntity(-1,null,200.5,......));
23//在头文件中加入用户资料
24sender.addHeader(setHead());
25OMElementresponseOMElement=sender.sendReceive(requestOMElement);
26
27//对返回的SOAP进行处理,显示返回值
28OrderEntityorder=convertToOrder(responseOMElement);
29displayOrderProperty(order);
30}
31
32//在头文件中加入用户资料
33privatestaticOMElementsetHead(){
34//新建OMFactory工厂
35OMFactoryfactory=OMAbstractFactory.getOMFactory();
36//绑定OMNamespace,请求数据
37OMNamespaceomNamespace=factory.createOMNamespace(
38"http://serviceImpl.axis2","ns");
39//加入用户资料
40OMElementuser=factory.createOMElement("User",omNamespace);
41user.setText("Leslie,12345678");
42returnuser;
43}
44
45privatestaticOMElementgetOrderRequest(OrderEntityorder){
46//新建OMFactory工厂
47OMFactoryfactory=OMAbstractFactory.getOMFactory();
48//绑定OMNamespace
49OMNamespaceomNamespace=factory.createOMNamespace(
50"http://serviceImpl.axis2","ns");
51//绑定请求方法名
52OMElementmethod=factory.createOMElement("addOrder",omNamespace);
53
54//把order对象转换为OMElement
55javax.xml.stream.XMLStreamReaderreader=BeanUtil.getPullParser(order);
56StreamWrapperparser=www.wang027.comnewStreamWrapper(reader);
57OMXMLParserWrapperstAXOMBuilder=OMXMLBuilderFactory
58.createStAXOMBuilder(OMAbstractFactory.getOMFactory(),parser);
59OMElementparam=stAXOMBuilder.getDocumentElement();
60
61method.addChild(param);
62returnmethod;
63}
64
65//把返回OMElement对象转换成person对象
66privatestaticOrderEntityconvertToOrder(OMElementelement)
67throwsAxisFault{
68OrderEntityorder=null;
69OMElementomElement=element.getFirstElement().getFirstElement();
70StringlocalName=element.getLocalName().toLowerCase();
71if(localName.equals("orderentity")){
72order=(OrderEntity)BeanUtil.processObject(element,
73OrderEntity.class,null,true,newDefaultObjectSupplier(),null);
74}
75returnorder;
76}
77//显示Order数据
78privatestaticvoiddisplayOrderProperty(OrderEntityorder){
79System.out.println("Id:"+order.getId()+"Code:"+order.getOrderCode()
80+"TotalPrice:"+order.getTotalPrice());
81}
复制代码
发送的SOAP请求





回到目录

八、异步调用Web服务

在Axis1.x中服务只支持“请求-回复”的操作方式,客户端在发送请求后将处于等待的状态,在Web服务操作时间较长的情况下,这种操作方式将影响了系统的效率。从Axis2.x开始客户端支持异步操作的方式,在发送请求后,系统将使用异步线程绑定回调操作。在发出请求后,客户端无需再处理于长期等待的状态。生成异步操作的方法有很多,比较简单的是使用WSDL2Java的“-a”命令:
WSDL2Java-urihttp://localhost/axis2-1.6.2/services/PersonSerivce.wsdl-p包名-o文件夹-a
所生成的客户端会包含一个ServiceCallbackHandler类,下面的例子当中,只要实现了PersonServiceCallbackHandler类的receiveResultgetList方法,在调用personServiceStub.startgetList(PersonServiceStub.GetList,PersonServiceCallbackHandler)方法后,系统将释放主线程。当接收到服务端的返还信息后,信息将交由PersonServiceCallbackHandler所绑定的方法进行处理。

服务端

复制代码
1publicclassPersonService{
2
3publicListgetList(){
4Listlist=newArrayList();
5list.add(newPersonEntity(1,"Leslie",32,"tianhe"));
6list.add(newPersonEntity(2,"Elva",28,"henan"));
7returnlist;
8}
9}
复制代码
客户端

复制代码
1publicstaticvoidmain(String[]args)
2throwsInterruptedException,RemoteException{
3//TODOAuto-generatedmethodstub
4threadMessage("start");
5//建立服务对象
6PersonServiceStubpersonService=newPersonServiceStub();
7PersonServiceStub.GetListgetList=newPersonServiceStub.GetList();
8//建立回调函数
9PersonServiceCallbackHandlercallback=newPersonServiceCallbackHandler(){
10publicvoidreceiveResultgetList(GetListResponseresponse){
11threadMessage("callback");
12PersonEntity[]list=response.get_return();
13for(Integern=0;n14displayPersonProperty(list[n]);
15}
16};
17//启动异步服务
18personService.startgetList(getList,callback);
19Thread.sleep(500);
20}
21
22//显示线程id
23privatestaticvoidthreadMessage(Stringdata){
24Threadthread=Thread.currentThread();
25System.out.println(data+"threadId:"+thread.getId());
26}
27
28//显示对象信息
29privatestaticvoiddisplayPersonProperty(PersonEntityperson){
30System.out.println("Id:"+person.getId()+"Name:"
31+person.getName()+"Age:"+person.getAge()
32+"Address:"+person.getAddress());
33}
复制代码

本章小结

Aixs的主要特点在于其操作的灵活性,它能对请求和回发的SOAP信息直接进行处理,在头文件或者自定义节点中加入数据。特别在Axis2.x引入AXIOM后,其优点更为突出。它使用StAX(StreamingAPIforXML)拉式(Pull)解析器,可尽量减轻对系统资源的压力。Axis2.x使用Module模块化的部署方式,使系统功能分割更为简单,Module可以独立于服务进行开发。为了解决客户端为等待返回数据而长时间处于柱塞状态,Axis2.x还加入异步操作的方法,提高了客户端的运行效率。
由于本人并非JAVA方面的专家,文章难免存在错误之处,敬请读者点评。
献花(0)
+1
(本文系thedust79首藏)