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方面的专家,文章难免存在错误之处,敬请读者点评。 |
|