在Spring中使用XFire构建Web Service应用

qingjiuquan 2011-08-31

概述 

XFire是与Axis2并列的新一代WebService框架,通过提供简单的API支持WebService各项标准协议,帮助你方便快速地开发WebService应用。XFixe内建在STAX的基础上之上,STAX是基于流的XML解析引擎,这使得XFire拥有很高的性能。

相对Axis来说,目前它的人气指数相当高,加上其提供了和String集成的支持,在目前的WebService开源社区拥有众多的追随者,XFire被多个开源项目所使用。XFire为Spring提供了支持,这使得我们可以很容易在Spring中使用XFire构建WebService应用。本文讲述了如何使用XFire在Spring中开发WebService的具体过程。

XFire特性

XFire是WebService框架的后起之秀,它从现有的框架中借鉴了许多优秀的理念,力争将WebService的应用开发难度降到最低。此外,还提供了各种绑定技术、支持多种传输协议,对WebService体系中许多新的规范提供了支持。简单来说,它具有以下一些特性:

支持重要的WebService规范,如SOAP、WSDL、WS-IBasicProfile,、WS-Addressing、WS-Security等;

高性能的SOAP栈设计;

可插拔的绑定,支持POJO、XMLBeans、JAXB1.1、JAXB2以及Castor;

支持JSR181规范,该规范通过JDK5.0注解将POJO导出为WebService;

支持多种传输协议:HTTP、JMS、XMPP、In-JVM等,其中In-JVM允许我们在不启动Web应用器的情况下,象一般的程序一样测试WebService应用;

易用的API,XFireAPI简洁明了,便于使用;

支持Spring、Pico、Plexus、Loom等容器;

支持JBI(JavaBusinessIntegration:Java业务整合)规范,JBI是JSR208的实现;

能够根据WSDL创建客户端和服务器端的存根代码;

率先对JAX-WS提供了支持,JAX-WS是JAX-RPC的替代者。

XFire体系及重要API

ServiceFactory

ServiceFactory是XFire的核心类,它可以将一个POJO生成为一个WebService。让我们通过一个最简单的例子了解ServiceFactory的用途,假设我们在一个POJO中定义了一个业务,现在希望将其导出为WebService,通过ServiceFactory可以轻而易举地达到目的:

XFirexfire=XFireFactory.newInstance().getXFire();

ServiceFactoryfactory=newObjectServiceFactory(xfire.getTransportManager(),null);

Serviceservice=factory.create(YourService.class);

这样我们就为YourService类创建基于SOAP1.1封装的WebService。Service的输入输出参数如果为简单类型的对象(由String、int、long等基本类型组成),无须进行额外的映射设置,对于复杂类型的输入输出Service,XFire将会自动尝试将其序列化(通过Aegis绑定)。紧接着,你就可以注册这个Service。

xfire.getServiceRegistry().register(service);

XFire推荐通过一个接口开放服务,此时,你可以指定一个具体的实现类:

service.setProperty(ObjectInvoker.SERVICE_IMPL_CLASS,YourServiceImpl.class);

Handler

一个Handler可以看成是XFire的一个加工套件,XFire通过它们定义SOAP发送和接收之前的各种加工处理逻辑。如Handler可以对SOAP体的内容进行加工处理,或者SOAP头进行处理。可以简单地通过扩展AbstractHandler定义一个自己的Handler类:

importorg.codehaus.xfire.MessageContext;

importorg.codehaus.xfire.handler.AbstractHandler;

publicclassYourHandlerextendsAbstractHandler

{

publicvoidinvoke(MessageContextcontext)

{

//DoSth...

}

}

Handler可以注册到Service或Transport(代表SOAP输入输出的的传输对象)中,在服务请求和响应管道里,Service和Transport注册的Handler将执行额外的处理操作。你可以按如下方式注册Handler:

Servics=factory.create(YourService.class);

s.addInHandler(newYourHandler1());①添加一个Handler,对输入SOAP进行处理

s.addOutHandler(newYourHandler2());②添加一个Handler,对输出SOAP进行处理

s.addFaultHandler(newYourHandler3());③添加一个Handler,对错误SOAP进行处理

下面的Handler对SOAP头进行处理,添加一些特定的信息:

importorg.codehaus.xfire.MessageContext;

importorg.codehaus.xfire.handler.AbstractHandler;

publicclassYourHandlerextendsAbstractHandler

{

publicQName[]getUnderstoodHeaders()

{

returnnewQName[]{newQName("YourHeader","urn:your:header:ns")};

}

}

在管道中以流方式处理SOAP

XFire是完全基于流数据处理进行工作的系统,这意味着XFire不是将整个SOAP文档缓存在内存中,而是以管道的方式接收SOAP流数据。这种工作方式的转变带来了可观的性能回报,同时节省了内存的占用。

对于习惯了Axis、GLUE等这些基于DOM处理模型WebService框架的开发者来说,需要一些时间来适应这种转变。

XFire从管道中接收一个SOAP请求到返回一个SOAP响应,会经历一系列的阶段。在管道调用的任何一个阶段,XFire都可以添加一些额外的Handler,在对消息进行加工处理后再传入到下一个阶段中。图1展示了XFire管道从接收SOAP请求到返回SOAP响应所经历的所有阶段:

图1XFireWebService请求和响应的过程

在SOAP请求消息对WebService发起真正调用之前,分别会经过传输(Transport)、预转发(PreDispatch)、转发(Dispatch)、策略实施(Policy)、用户信息处理(User)、预调用(PreInvoke)、服务调用(ServiceInvocation)等阶段。当,WebService调用后,XFire生成响应SOAP消息并通过管道发送给客户端请求者,这一过程会先后经历调用后(PostInvoke)、用户信息处理(User)、策略实施(Policy)、传输(Transport)这四个阶段。每一个阶段都是一个可控点,通过编写并注册一些相应的Handler就可以实施一些额外处理逻辑,如审计、SOAP消息加密、签名、压缩等。

将POJOBean导出为WebService

通过XFire为Spring提供的服务导出器可以轻松地将POJO导出为标准的WebService,此外,XFire还允许我们使用JSR181注解对POJO进行标注,无需使用XML配置就可以导出为WebService,各种复杂的转换细节被巧妙地隐藏在XFire之中。

使用导出器导出WebService

XFire为Spring提供了方便易用的导出器XFireExporter,借助XFireExporter的支持,我们可以在Spring容器中将一个POJO导出为WebService。BbtForum是Baobaotao论坛业务服务类,它拥有众多的业务方法,我们现在希望将其提供查询最近几天精华帖子数的业务方法开放为WebService。为了避免过多地开放不必要的接口方法,需要定义了一个BbtForumService窄接口,它定义那些需要开放为WebService的业务方法:

packagecom.baobaotao.xfire.server;

publicinterfaceBbtForumService{

intgetRefinedTopicCount(intlastDay);①查询最近几天论坛精华帖子数的服务接口

}

将一个业务类所有需要开放为WebService的方法通过一个窄接口来描述是值得推荐的作法,这让WebService的接口显得很“干净”。其次,XFire的导出器也需要服务接口的支持,因为它采用基于接口的动态代理技术。真实的业务类当然需要实现WebService窄接口:

packagecom.baobaotao.service;

importjavax.jws.WebService;

importcom.baobaotao.xfire.server.BbtForumService;

publicclassBbtForumimplementsBbtForumService{①实现WebService窄接口

publicintgetRefinedTopicCount(intlastDay){②该方法在窄接口中定义,导出为WebService服务

if(lastDay<=2)return10;

elseif(lastDay<=5)return20;

elsereturn32;

}

}

BbtForum中的方法在真实的系统中应该引用其它的业务类或DAO获取数据库中的真实数据,为了简化实例,我们通过一段简单的代码进行模拟,如②所示。

在拥有了窄接口之后中,剩余的工作就是在Spring配置文件中通过XFireExporter将BbtForum#getRefinedTopicCount()方法导出为WebService,具体配置如代码清单1所示:

代码清单1applicationContext.xml:将POJO导出WebService

<beans>

①引入XFire预配置信息

<importresource="classpath:org/codehaus/xfire/spring/xfire.xml"/>

②使用XFire导出器

<beanid="BbtForumService"class="org.codehaus.xfire.spring.remoting.XFireExporter">

<propertyname="serviceFactory"ref="xfire.serviceFactory"/>②-1:引用xfire.xml中定义工厂

<propertyname="xfire"ref="xfire"/>②-2:引用xfire.xml中定义的xfire实例

<propertyname="serviceBean"ref="bbtForum"/>②-3:业务服务Bean

<propertyname="serviceClass"②-4:业务服务Bean的窄接口类

value="com.baobaotao.xfire.server.BbtForumService"/>

<propertyname="name"value="BbtForumServiceUT"/>②-5:WebService名称

</bean>

<beanid="bbtForum"class="com.baobaotao.service.BbtForum"/>

</beans>

上面的配置将BbtForum所有定义在BbtForumService窄接口中的方法导出为WebService。在XFire核心JAR包中拥有一个预定义的Spring配置文件,它定义了XFire在Spring中必须用到的一些Bean和资源,需要引入这个预定义的配置文件,如①所示。紧接着,就可以使用XFireExporter将业务类导出为WebService了。②-1、②-2为导出器引入XFire环境,对于任何导出器,这都是标准的配置,所以如果有多个导出器,可以将这两个属性通过一个父<bean>标签进行抽象。而②-3、②-4分别定义了业务服务类及需要导出为WebService方法的窄接口。WebService的默认名称是窄接口的类名,即BbtForumService,你可以通过name属性显式指定WebService的名称,如②-5所示。

通过这个简单的配置,就完成了将业务服务类开放为WebService的工作,接下来,我们就可以通过配置Web服务器的web.xml,将其通过HTTP传输协议开放出去。

配置web.xml

一般情况下,我们通过HTTP作为WebService的传输协议,这样你只需启动一个Web服务器(如Tomcat),客户端就可以通过HTTP访问到WebService服务了。为了集成Spring容器,XFire专门提供一个XFireSpringServlet,我们可以在web.xml中配置该Servlet,将Spring容器中定义的WebService在某个URI下发布,如代码清单2所示:

代码清单2web.xml配置

<web-app>

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>classpath:applicationContext.xml</param-value>①刚才配置的Spring文件

</context-param>

<listener>

<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

</listener>

<servlet>②配合Spring容器中XFire一起工作的Servlet

<servlet-name>xfireServlet</servlet-name>

<servlet-class>org.codehaus.xfire.spring.XFireSpringServlet</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>xfireServlet</servlet-name>

<url-pattern>/service/*</url-pattern>③在这个URI下开放WebService服务

</servlet-mapping>

</web-app>

首先,我们指定在代码清单1中所配置的Spring配置文件,如①所示。然后定义一个XFireSpringServlet,让其截取所有/serviceURI下的请求,如②和③所示。

创建一个Tomcat配置文件baobaotao.xml,并编写一行映射配置(这里使用Tomcat5.5),这种方式无须将Web应用打包成WAR,方便开发测试:

<Contextpath="/baobaotao"docBase="D:/masterSpring/chapter16/webapp"/>

将baobaotao.xml放置到<TOMCAT_HOME>/conf/Catalina/localhost目录下,启动Tomcat服务,键入http://localhost:8080/baobaotao/service/BbtForumService?wsdl,你将可以看到BbtForumService对应的WSDL,如图2所示:

图2BbtForumService的WSDL

阅读这个WSDL文档,我们可以知道BbtForum的getRefinedTopicCount已经被成功地发布为WebService了。只要拿到这个WSDL就可以开发相应的客户端调用程序了。

使用JSR181注解导出WebService

前面两小节中,我们领教了XFireExporter导出器的威力。在需要导出为WebService的业务类数目不大时,XFireExporter的配置方式非常优雅。但是,如果有很多需要导出为WebService的业务类,你必须分别为它们配置一个XFireExporter,这让我们回忆起了TransactionProxyFactoryBean(每一个需要事务功能的业务类需要分别配置)。在学习过@Transaction注解后,我们自然而然地希望使用类似注解技术完成WebService导出的工作。

JSR181就是为此目的而提出的,它是BEA领导的一个WebService规范。XFire已经支持JSR1812.0,你既可以使用JDK5.0的注解,也可以在JDK5.0之前的版本中使用commons-attributes注解。

使用JSR181的明显好处是,你仅需在业务类和窄接口标注JSR181注解,不管你有多少需要导出为WebService的业务类,仅须在Spring中配置一个XFire提供的JSR181注解增强Bean就可以了。

注解增强处理器会对Spring容器中所有标注JSR181注解的业务类进行处理,并分别将它们导出为WebService。使用JSR181时,必须将XFire的依赖类库xfire-jsr181-api-1.0-M1.jar添加到类路径中。

如果输入、输出的对象类型仅包括基本类型的属性,仅需要在业务类和窄接口中分别使用@WebService注解进行简单的配置就可以了,XFire将根据默认约定导出WebService。

窄接口仅需要定义一个@WebService注解,并指定SOAP的命名空间就可以了:

packagecom.baobaotao.xfire.server;

importjavax.jws.WebService;

@WebService(targetNamespace="http://www.baobaotao.com")①指定SOAP的命名空间

publicinterfaceBbtForumService{

intgetRefinedTopicCount(intlastDay);

}

XFire应用JSR181比较怪的一点是,除了需要在窄接口中提供注解外,在实现业务类中也需要提供相应的注解:

packagecom.baobaotao.service;

importjavax.jws.WebService;

importcom.baobaotao.xfire.server.BbtForumService;

@WebService(servicename="BbtForumService",①指定导出的WebService名称

endpointInterface="com.baobaotao.xfire.server.BbtForumService")②对应的窄接口

publicclassBbtForumimplementsBbtForumService{

}

如果碰到以下应用场景:输入、输出对象是复杂的对象(如未使用泛型的集合类),当返回类型是一个对象但不希望输出所有的结果,或者不希望使用默认的属性名。

这时可以在业务方法中通过@WebMethod、@WebResult等注解提供额外的信息来达到目的。更多关于JSR181的信息请参考:http://dev2dev.bea.com/webservices/jwsm.html。

按照相似的方式,可以为应用中其它的业务类进行WebService标注,在完成标注后,需要在Spring配置中启用XFireJSR181处理器,对Spring容器中所有标注@WebService的Bean进行统一的处理,以便执行真正WebService的导出工作。XFire在Spring中对应的配置如下所示:

代码清单3applicationContext.xml:使用JSR181导出WebService

<beans>

<importresource="classpath:org/codehaus/xfire/spring/xfire.xml"/>

<beanid="webAnnotations"①该Bean获取Spring容器中所有标注@WebService的Bean

class="org.codehaus.xfire.annotations.jsr181.Jsr181WebAnnotations"/>

<beanid="jsr181HandlerMapping"②对标注@WebService的Bean进行处理,完成导出工作

class="org.codehaus.xfire.spring.remoting.Jsr181HandlerMapping">

<propertyname="xfire"ref="xfire"/>

<propertyname="webAnnotations"ref="webAnnotations"/>

</bean>

③该Bean标注了@WebService注解

<beanid="bbtForum"class="com.baobaotao.service.BbtForum"/>

</beans>

重启Tomcat,查看http://localhost:8080/baobaotao/service/BbtForumService?wsdl,你依旧可以看到如图2BbtForumService的WSDL所示的WSDL。

各种客户端调用方式

XFire为访问服务端WebService提供了各种方便的方式:在可以获取服务端窄接口类的情况下,可以根据服务地址和窄接口类创建客户调用程序。

如果不能获得服务窄接口类,XFire允许你通过WSDL文件生成客户端调用程序,通过指定服务接口的方式调用服务。鉴于这种调用方式不够面向对象,XFire提供了一个根据WSDL生成客户端存根代码的工具,这样你就可以方便以面向对象的方式编写客户端程序了。

使用服务端的窄接口类

如果客户端可以获取服务端的WebService的窄接口类,这时可以使用XFire的ObjectServiceFactory将对应地址的WebService转换为窄接口实例进行调用,如代码清单4所示:

代码清单4使用窄接口调用WebService应用

packagecom.baobaotao.xfire.client;

importorg.codehaus.xfire.client.XFireProxyFactory;

importorg.codehaus.xfire.service.Service;

importorg.codehaus.xfire.service.binding.ObjectServiceFactory;

importcom.baobaotao.xfire.server.BbtForumService;

publicclassWithClassClient{

publicintgetRefinedTopicCount(){

①根据窄接口创建Service模型

ServiceserviceModel=newObjectServiceFactory().create(BbtForumService.class);

②服务对应URL地址

StringserviceURL="http://localhost:8080/baobaotao/service/BbtForumService";

BbtForumServiceservice=null;

try{

③将WebService转换为窄接口实例

service=(BbtForumService)newXFireProxyFactory().

create(serviceModel,serviceURL);

}catch(Exceptione){

thrownewRuntimeException(e);

}

returnservice.getRefinedTopicCount(20);④调用WebService方法

}

publicstaticvoidmain(String[]args){

WithClassClientclient=newWithClassClient();

System.out.println("topiccountis:"+client.getRefinedTopicCount());

}

}

XFire根据Service模型对象及WebService的URL地址就可以构造出WebService的调用实例。在服务端Tomcat启动的情况下,运行以上的客户端代码,将可以获得正确的输出。

使用WSDL文件构造客户端程序

并不是任何时候都可以获得WebService服务端的窄接口类,但我们必然可以获取WebService对应的WSDL文档。XFire允许我们仅通过WebService对应的WSDL文件构造客户端访问程序。

这无疑给创建客户端程序带来了极大的便利性,你可以直接通过URL指定WSDL,也可以将WSDL保存在本地系统中,通过InputStream的方式获取WSDL内容。下面,我们使用InputStream的方式提供WSDL:

代码清单5通过WSDL创建客户端程序

packagecom.baobaotao.xfire.client;

importjava.net.URL;

importorg.codehaus.xfire.client.Client;

publicclassOnlyWsdlClient{

publicintgetRefinedTopicCount(){

try{

Stringwsdl="com/baobaotao/xfire/client/BbtForumService.wsdl";①对应的WSDL文件

Resourceresource=newClassPathResource(wsdl);

Clientclient=newClient(resource.getInputStream(),null);②根据WSDL创建客户实例

③调用特定的WebService方法

Object[]results=client.invoke("getRefinedTopicCount",newObject[]);

return(Integer)results[0];

}catch(Exceptione){

thrownewRuntimeException(e);

}

}

publicstaticvoidmain(String[]args){

OnlyWsdlClientclient=newOnlyWsdlClient();

System.out.println("topiccountis:"+client.getRefinedTopicCount());

}

}

你可以通过http://localhost:8080/baobaotao/service/BbtForumService?wsdl地址获取BbtForumService对应的WSDL,并将其保存在工程项目的src对应的类包目录:com/baobaotao/xfire/client/BbtForumService.wsdl。

我们通过Spring的ClassPathResource读取BbtForumService.wsdl,XFire从Resource中获取WSDL的输入流并生成一个客户端实例。接着,我们就可以通过这个客户端实例,指定Service服务名和输入参数调用WebService的服务方法了,如③所示。

可能会有读者认为这种完全根据WSDL创建客户端程序的方式会带来低劣的运行性能,笔者通过测试发现,确实会造成一定的性能降低,但也不象想象中那样低效。

使用基于窄接口的客户端程序和使用基于WSDL的客户端程序访问一次BbtForumService的时间依次是1300ms和1450ms。如果WSDL文档很复杂,由于需要解析整个WSDL文档,这种客户端程序的性能会受到更多的挑战。不过,如果只要在程序中缓存Client实例,由于创建Client的代价是一次性的,性能问题就可以忽略了。

根据WSDL生成客户端代码

XFire允许通过运行Ant任务,根据WSDL文件生成访问WebService的客户端代码存根,同时XFire还提供了一个Eclipse插件完成相同的任务。本节里,我们将学习通过XFireEclipse插件生成BbtForumService客户端存根代码的知识。

安装EclipseXFire插件

1.Help->SoftwareUpdates->FindandInstall...

2.选择“Searchfornewfeaturestoinstall”,并点击Next;

3.选择“NewRemoteSite...”,创建一个Name为XFire,URL为

http://dist.codehaus.org/xfire/update/的网站;

4.点击Finish安装XFire插件。

使用插件创建客户端代码存根

File->New->Other...->XFire->CodegenerationfromWSDLdocument;

弹出一个对话框,如图3所示:

图3创建客户端代码存根

指定WSDL文件的位置,存根代码的输出地址及对应的类包,点击Finish。

XFire插件将在生成客户端代码存根的同时生成服务端代码的存根,如下图所示:

图4生成的代码

BbtForumServiceClient是BbtForumServicePortType的工厂类,它提供了若干个获取BbtForumServicePortType实例的重载方法。BbtForumServicePortType对应服务端的窄接口BbtForumService类。而BbtForumServiceImpl是服务端的存根代码,在META-INF中还有XFire的服务配置文件。对于客户端来说,一般不需要服务端的代码,所以你可以将BbtForumServiceImpl和META-INF删除。

下面,我们利用XFire生成的BbtForumServiceClient对服务端的WebService进行调用:

packagecom.baobaotao.xfire.client;

publicclassStubClient{

publicstaticvoidmain(String[]args){

BbtForumServiceClientclient=newBbtForumServiceClient();

StringserviceUrl="http://localhost:8080/baobaotao/service/BbtForumService";

①获取对应服务窄接口实例

BbtForumServicePortTypeportType=client.getBbtForumServiceHttpPort(serviceUrl);

intcount=portType.getRefinedTopicCount(20);②对服务进行调用

System.out.println("count:"+count);

}

}

我们首先实例化一个BbtForumServiceClient,然后通过URL指定WebService的服务地址,然后创建一个服务的窄接口实例,如①所示,接着我们就可以使用这个窄接口实例进行WebService服务的调用了。

WebService的测试

在上一节里,我们学习了客户端调用服务端中WebService的方法。在实际应用中,在开放WebService之前需要进行严格的测试,以保证功能的正确性。在一般框架中,测试WebService往往这是一个炼狱般痛苦的过程。

XFire通过AbstractXFireTest极大地简化WebService的测试。AbstractXFireTest允许我们无需构造客户端调用程序,在SOAP报文层面开展对服务端代码的测试,AbstractXFireTest提供了一系列方便的方法对SOAP报文进行验证。XFire还特别为Spring环境下进行WebService测试提供了一个AbstractXFireSpringTest子类,仅通过启用Spring容器就可以完成WebService的测试。通过XFire精心设计的测试工具类,对WebService的测试工作已经是一项可以轻松应对的工作。

如果你在编写服务端WebService的同时,还需要编写客户端调用程序,这时不可避免的,你希望从客户端角度对WebService进行测试。由于客户端程序需要访问真实的WebService,所以需要开启Web服务器,让服务端的WebService能够提供服务共客户端访问调用。如果客户端和服务端都在同一个项目中开发,XFire允许你在不启动Web服务器的情况下测试客户端程序,其原理是让WebService运行于JVM模式下。

基于SOAP报文的纯服务端测试

AbstractXFireTest扩展于JUnit标准的TestCase类,提供了向某个WebService发送SOAP请求报文并对返回的SOAP响应报文进行检验的能力。该测试类提供了若干个方便的断言方法,分别介绍如下:

voidassertNoFault(Documentnode):确认SOAP响应报文无错误;

java.util.ListassertValid(java.lang.Stringxpath,java.lang.Objectnode):确认在DOM节点特定路径下有对应的元素,路径通过XPath表达式进行定义,该方法还将匹配的元素以List对象返回,你可以对匹配的元素进行进一步的检验;

voidassertXPathEquals(java.lang.Stringxpath,java.lang.Stringvalue,Documentnode):确认特定路径DOM节点为某一特定值;

assertInvalid(java.lang.Stringxpath,java.lang.Objectnode):确认DOM节点特定路径下未包含元素。

AbstractXFireSpringTest是AbstractXFireTest的子类,在Spring中你仅需要扩展该类并实现该类的抽象方法ApplicationContextcreateContext(),就可以对Spring容器中用XFire定义的WebService进行测试了。

为了测试WebService,我们必须准备一个SOAP请求报文,你可以简单地手工编写一个,或通过SOAP报文截取工具(如前面我们介绍的TcpTrace、SOAPScope、ApacheAxis的TCPMon等)获得一些可用的SOAP请求报文。下面是一个访问BbtForumService服务的请求SOAP报文:

代码清单6request_soap.xml:SOAP报文层面测试

<soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<soap:Body>

<getRefinedTopicCount

xmlns="http://server.xfire.baobaotao.com">

<in0xmlns="http://server.xfire.baobaotao.com">20</in0>

</getRefinedTopicCount>

</soap:Body>

</soap:Envelope>

我们将其保存在request_soap.xml文件中放置在类路径com/baobaotao/xfire/server下。当该SOAP请求报文发送给BbtForumService的WebService后,我们预计它应该返回对应代码清单7所示的正确的SOAP响应报文:

代码清单7SOAP响应报文

<?xmlversion="1.0"encoding="UTF-8"?>

<soap:Envelopexmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<soap:Body>

<getRefinedTopicCountResponsexmlns="http://www.baobaotao.com">

<out>32</out>

</getRefinedTopicCountResponse>

</soap:Body>

</soap:Envelope>

下面,我们着手编写测试BbtForumServiceWebService的测试类,以验证实际SOAP响应报文是否和代码清单7中的一样:

代码清单8TestBbtForumService

packagecom.baobaotao.xfire.server;

importorg.codehaus.xfire.spring.AbstractXFireSpringTest;

importorg.jdom.Document;

importorg.springframework.context.ApplicationContext;

importorg.springframework.context.support.ClassPathXmlApplicationContext;

publicclassTestBbtForumServiceextendsAbstractXFireSpringTest{

protectedApplicationContextcreateContext(){①创建包含WebService的Spring容器

returnnewClassPathXmlApplicationContext(

newString[]);

}

publicvoidtestUsingSoapRequest()throwsThrowable{

②通过SOAP请求报文访问BbtForumService的WebService,对返回

SOAP响应报文进行检测

Documentresponse=invokeService("BbtForumService",

"/com/baobaotao/xfire/server/request_soap.xml");

assertNoFault(response);③确认不包含错误

④为命名空间指定一个别名,方便后续XPath表示式的编写

addNamespace("k","http://www.baobaotao.com");

assertValid("//soap:Body/k:getRefinedTopicCountResponse",response);⑤

assertXPathEquals("//k:getRefinedTopicCountResponse/k:out/text()","32",response);⑥

printNode(response);⑦打印响应报文,以便肉眼查看

}

}

使用AbstractXFireSpringTest测试WebService首先要做的第一件事是通过实现createContext()方法构造Spring容器,如①所示。当Spring容器启动时,XFire将自动让容器中的WebService生效(仅进行测试,不能对外提供服务)。

第二步需要向WebService发送一个SOAP请求报文以得到一个SOAP响应报文,如②所示。接下来,就是通过AbstractXFireTest提供的检测DOM内容的方法对报文进行正确性验证。由于代码清单7的SOAP报文体中对应的<getRefinedTopicCountResponse>元素及内部元素都位于http://www.baobaotao.com命名空间中,报文体中没有为这个命名空间定义相应的别名,为了在后续断言方法中能够使用简单的方式定义XPath表达式,我们在④处为http://www.baobaotao.com命名空间定义了一个别名。

理解以上测试代码中几个断言方法的关键在于理解XPath表达式语言,XPath语法内容很丰富,不可能在这里逐一讲解,我们在这里介绍一些典型的XPath语法以满足常见的测试需求:

以“/”为前缀的路径表示从DOM根路径开始,如“/soap:Envelope/soap:Body”;

以“//”为前缀的路径表示从DOM任意元素开始查询,如“//out”表示任意元素为out的元素;

元素的属性通过@attrName表示,如“//xsd:complexType[@name="Book"]”表示DOM中任意元素名为complexType并且拥有一个值为Book的name属性的元素;

元素的值通过text()表示,如“//test:Response[text()='32']”表示DOM中任意值为32,元素名为Response,且位于test命名空间中的元素。

现在回过头来看⑤、⑥两处的断言方法,相信大家就可以很容易地理解断言规则了,⑤处的断言检测SOAP响应报文是否包含某一特定元素,而⑥处的断言则对元素内的值进行检测。我们也可以通过printNode()方法将一个节点打印到控制台上,方便肉眼查看。

在JVM模式通过客户端进行测试

能够不启动Web服务器的情况下通过客户端程序测试WebService的功能,这一崭新的测试方法对于开发人员来说一定深具吸引力。因为,这意味着你可以完全在IDE环境中运行测试,不需要外部环境的支持。

不过享受这一测试好处的应用必须保证客户端和服务端的WebService都位于同一JVM中,这时请求报文和响应报文直接在JVM内部通道中传输。当使用JVM内部通道传输请求和响应的SOAP报文时,我们仅需要调整服务的地址就可以了:

代码清单9TextBbtForumService:JVM模式测试

packagecom.baobaotao.xfire.client;

importorg.codehaus.xfire.client.XFireProxyFactory;

importorg.codehaus.xfire.service.Service;

importorg.codehaus.xfire.service.binding.ObjectServiceFactory;

importorg.codehaus.xfire.spring.AbstractXFireSpringTest;

importorg.springframework.context.ApplicationContext;

importorg.springframework.context.support.ClassPathXmlApplicationContext;

importcom.baobaotao.xfire.server.BbtForumService;

publicclassTextBbtForumServiceextendsAbstractXFireSpringTest{

protectedApplicationContextcreateContext(){

returnnewClassPathXmlApplicationContext(

newString[]);

}

publicvoidtestGetRefinedTopicCount()throwsThrowable{

ServiceserviceModel=newObjectServiceFactory().create(BbtForumService.class);

XFireProxyFactoryfactory=newXFireProxyFactory(getXFire());

①在JVM内部通道中进行SOAP请求和响应报文的传输,请注意粗体所示的服务地址

BbtForumServiceservice=(BbtForumService)factory.create(serviceModel,

"xfire.local://BbtForumService");

intcount=service.getRefinedTopicCount(20);

assertEquals(count,32);

}

}

以上代码中,①处的服务地址采用了JVM模式的地址,和其对应的HTTP地址则是http://localhost:8080/baobaotao/service/BbtForumService,所以仅需将服务名前的部分替换为“xfire.local://”就可以了。

小结

比之于Axis,XFire在实施WebService更加简洁高效,因此XFire在短短的时间里成为了WebService开发者的炙手可热的框架。更可贵的是XFire对Spring提供了强大的支持,可以非常方便地在Spring中使用XFire实施WebService。

XFire可以通过多种方式将Spring容器中的Bean导出为WebService,这包括使用XFireExporter导出器或JSR181注解。JSR181和STAX一起都将融入到JDK6.0中,因此,JSR181WebService定义方式将成为标准的实现。

XFire为客户端提供了多种访问WebService的方式,如果可以获取客户端的窄接口类,则可以采用窄接口类调用WebService。如果仅能获取WSDL,XFire也可以采用动态反射的机制调用WebService。XFire为Eclipse提供了一个可以根据WSDL生成客户端存根代码的插件,相信XFire也将为其它非Java语言提供类似的插件。

技术可用性的一个很大的标准是它是否方便测试,XFire为在Spring中测试WebService提供了一流的支持,通过JVM模式,你能够在不启动Web容器的情况下测试WebService,WebService的测试工作变得不再象原来那样让人畏惧。

相关推荐