概述
XFire是與Axis 2並列的新一代Web Service框架,通過提供簡單的API支持Web Service各項標準協議,幫助你方便快速地開發Web Service應用。XFixe內建在STAX的基礎上之上,STAX是基於流的XML解析引擎,這使得XFire擁有很高的性能。
相對Axis來說,目前它的人氣指數相當高,加上其提供了和String集成的支持,在目前的Web Service開源社區擁有衆多的追隨者,XFire被多個開源項目所使用。XFire爲Spring提供了支持,這使得我們可以很容易在Spring中 使用XFire構建Web Service應用。本文講述瞭如何使用XFire在Spring中開發Web Service的具體過程。
XFire特性
XFire是Web Service框架的後起之秀,它從現有的框架中借鑑了許多優秀的理念,力爭將Web Service的應用開發難度降到最低。此外,還提供了各種綁定技術、支持多種傳輸協議,對Web Service體系中許多新的規範提供了支持。簡單來說,它具有以下一些特性:
- 支持重要的Web Service規範,如SOAP、WSDL、WS-I Basic Profile,、WS-Addressing、WS-Security等;
- 高性能的SOAP 棧設計;
- 可插拔的綁定,支持POJO、XMLBeans、JAXB1.1、JAXB2以及Castor;
- 支持JSR 181規範,該規範通過JDK 5.0註解將POJO導出爲Web Service;
- 支持多種傳輸協議:HTTP、JMS、XMPP、In-JVM等,其中In-JVM允許我們在不啓動Web應用器的情況下,象一般的程序一樣測試Web Service應用;
- 易用的API,XFire API簡潔明瞭,便於使用;
- 支持Spring、Pico、Plexus、Loom等容器;
- 支持JBI(Java Business Integration:Java 業務整合)規範,JBI是JSR 208的實現;
- 能夠根據WSDL創建客戶端和服務器端的存根代碼;
- 率先對JAX-WS提供了支持,JAX-WS是JAX-RPC的替代者。
XFire 體系及重要API
ServiceFactory
ServiceFactory是XFire的核心類,它可以將一個POJO生成爲一個Web Service。讓我們通過一個最簡單的例子瞭解ServiceFactory的用途,假設我們在一個POJO中定義了一個業務,現在希望將其導出爲 Web Service,通過ServiceFactory可以輕而易舉地達到目的:
XFire xfire = XFireFactory.newInstance().getXFire();
ServiceFactory factory = new ObjectServiceFactory(xfire.getTransportManager(), null);
Service service = factory.create(YourService.class);
這樣我們就爲YourService類創建基於SOAP 1.1封裝的Web Service。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類:
import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.handler.AbstractHandler;
public class YourHandler extends AbstractHandler
{
public void invoke(MessageContext context)
{
// Do Sth...
}
}
Handler可以註冊到Service或Transport(代表SOAP輸入輸出的的傳輸對象)中,在服務請求和響應管道里,Service和Transport註冊的Handler將執行額外的處理操作。你可以按如下方式註冊Handler:
…
Servic s = factory.create(YourService.class);
s.addInHandler(new YourHandler1());①添加一個Handler,對輸入SOAP進行處理
s.addOutHandler(new YourHandler2());②添加一個Handler,對輸出SOAP進行處理
s.addFaultHandler(new YourHandler3());③添加一個Handler,對錯誤SOAP進行處理
…
下面的Handler對SOAP頭進行處理,添加一些特定的信息:
import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.handler.AbstractHandler;
public class YourHandler extends AbstractHandler
{
…
public QName[] getUnderstoodHeaders()
{
return new QName[] { new QName("YourHeader", "urn:your:header:ns") };
}
}
在管道中以流方式處理SOAP
XFire是完全基於流數據處理進行工作的系統,這意味着XFire不是將整個SOAP文檔緩存在內存中,而是以管道的方式接收SOAP流數據。這種工作方式的轉變帶來了可觀的性能回報,同時節省了內存的佔用。
對於習慣了Axis、GLUE等這些基於DOM處理模型Web Service框架的開發者來說,需要一些時間來適應這種轉變。
XFire從管道中接收一個SOAP請求到返回一個SOAP響應,會經歷一系列的階段。在管道調用的任何一個階段,XFire都可以添加一些額外的 Handler,在對消息進行加工處理後再傳入到下一個階段中。圖1展示了XFire管道從接收SOAP請求到返回SOAP響應所經歷的所有階段:
圖1 XFire Web Service請求和響應的過程
在SOAP請求消息對Web Service發起真正調用之前,分別會經過傳輸(Transport)、預轉發(PreDispatch)、轉發(Dispatch)、策略實施 (Policy)、用戶信息處理(User)、預調用(PreInvoke)、服務調用(Service Invocation)等階段。當,Web Service調用後,XFire生成響應SOAP消息並通過管道發送給客戶端請求者,這一過程會先後經歷調用後(PostInvoke)、用戶信息處理 (User)、策略實施(Policy)、傳輸(Transport)這四個階段。每一個階段都是一個可控點,通過編寫並註冊一些相應的Handler就 可以實施一些額外處理邏輯,如審計、SOAP消息加密、簽名、壓縮等。
將POJO Bean導出爲Web Service
通過XFire爲Spring提供的服務導出器可以輕鬆地將POJO導出爲標準的Web Service,此外,XFire還允許我們使用JSR 181註解對POJO進行標註,無需使用XML配置就可以導出爲Web Service,各種複雜的轉換細節被巧妙地隱藏在XFire之中。
使用導出器導出Web Service
XFire爲Spring提供了方便易用的導出器XFireExporter,藉助XFireExporter的支持,我們可以在Spring容器 中將一個POJO導出爲Web Service。BbtForum是Baobaotao論壇業務服務類,它擁有衆多的業務方法,我們現在希望將其提供查詢最近幾天精華帖子數的業務方法開 放爲Web Service。爲了避免過多地開放不必要的接口方法,需要定義了一個BbtForumService窄接口,它定義那些需要開放爲Web Service的業務方法:
package com.baobaotao.xfire.server;
public interface BbtForumService {
int getRefinedTopicCount(int lastDay);①查詢最近幾天論壇精華帖子數的服務接口
}
將一個業務類所有需要開放爲Web Service的方法通過一個窄接口來描述是值得推薦的作法,這讓Web Service的接口顯得很“乾淨”。其次,XFire的導出器也需要服務接口的支持,因爲它採用基於接口的動態代理技術。真實的業務類當然需要實現 Web Service窄接口:
package com.baobaotao.service;
import javax.jws.WebService;
import com.baobaotao.xfire.server.BbtForumService;
public class BbtForum implements BbtForumService{①實現Web Service窄接口
public int getRefinedTopicCount(int lastDay) {②該方法在窄接口中定義,導出爲Web Service服務
if(lastDay <= 2) return 10;
else if(lastDay <= 5) return 20;
else return 32;
}
…
}
BbtForum中的方法在真實的系統中應該引用其它的業務類或DAO獲取數據庫中的真實數據,爲了簡化實例,我們通過一段簡單的代碼進行模擬,如②所示。
在擁有了窄接口之後中,剩餘的工作就是在Spring配置文件中通過XFireExporter將BbtForum#getRefinedTopicCount()方法導出爲Web Service,具體配置如代碼清單1所示:
代碼清單1 applicationContext.xml:將POJO導出Web Service
<beans>
①引入XFire預配置信息
<import resource="classpath:org/codehaus/xfire/spring/xfire.xml" />
②使用XFire導出器
<bean id="BbtForumService" class="org.codehaus.xfire.spring.remoting.XFireExporter">
<property name="serviceFactory" ref="xfire.serviceFactory" />②-1:引用xfire.xml中定義工廠
<property name="xfire" ref="xfire" />②-2:引用xfire.xml中定義的xfire實例
<property name="serviceBean" ref="bbtForum" />②-3:業務服務Bean
<property name="serviceClass"②-4:業務服務Bean的窄接口類
value="com.baobaotao.xfire.server.BbtForumService" />
<property name="name" value="BbtForumServiceUT"/>②-5:Web Service名稱
</bean>
<bean id="bbtForum" class="com.baobaotao.service.BbtForum" />
</beans>
上面的配置將BbtForum所有定義在BbtForumService窄接口中的方法導出爲Web Service。在XFire核心JAR包中擁有一個預定義的Spring配置文件,它定義了XFire在Spring中必須用到的一些Bean和資源, 需要引入這個預定義的配置文件,如①所示。緊接着,就可以使用XFireExporter將業務類導出爲Web Service了。②-1、②-2爲導出器引入XFire環境,對於任何導出器,這都是標準的配置,所以如果有多個導出器,可以將這兩個屬性通過一個父 <bean>標籤進行抽象。而②-3、②-4分別定義了業務服務類及需要導出爲Web Service方法的窄接口。Web Service的默認名稱是窄接口的類名,即BbtForumService,你可以通過name屬性顯式指定Web Service的名稱,如②-5所示。
通過這個簡單的配置,就完成了將業務服務類開放爲Web Service的工作,接下來,我們就可以通過配置Web 服務器的web.xml,將其通過HTTP傳輸協議開放出去。
配置web.xml
一般情況下,我們通過HTTP作爲Web Service的傳輸協議,這樣你只需啓動一個Web服務器(如Tomcat),客戶端就可以通過HTTP訪問到Web Service服務了。爲了集成Spring容器,XFire專門提供一個XFireSpringServlet,我們可以在web.xml中配置該 Servlet,將Spring容器中定義的Web Service在某個URI下發布,如代碼清單2所示:
代碼清單2 web.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下開放Web Service服務
</servlet-mapping>
</web-app>
首先,我們指定在代碼清單1中所配置的Spring配置文件,如①所示。然後定義一個XFireSpringServlet,讓其截取所有/service URI下的請求,如②和③所示。
創建一個Tomcat配置文件baobaotao.xml,並編寫一行映射配置(這裏使用Tomcat 5.5),這種方式無須將Web應用打包成WAR,方便開發測試:
<Context path="/baobaotao" docBase="D:/masterSpring/chapter16/webapp"/>
將baobaotao.xml放置到<TOMCAT_HOME>/conf/Catalina/localhost 目錄下,啓動Tomcat服務,鍵入http://localhost: 8080/baobaotao/service/BbtForumService?wsdl,你將可以看到BbtForumService對應的 WSDL,如圖2所示:
圖2 BbtForumService的WSDL
閱讀這個WSDL文檔,我們可以知道BbtForum的getRefinedTopicCount已經被成功地發佈爲Web Service了。只要拿到這個WSDL就可以開發相應的客戶端調用程序了。
使用JSR 181註解導出Web Service
前面兩小節中,我們領教了XFireExporter導出器的威力。在需要導出爲Web Service的業務類數目不大時,XFireExporter的配置方式非常優雅。但是,如果有很多需要導出爲Web Service的業務類,你必須分別爲它們配置一個XFireExporter,這讓我們回憶起了 TransactionProxyFactoryBean(每一個需要事務功能的業務類需要分別配置)。在學習過@Transaction註解後,我們自 然而然地希望使用類似註解技術完成Web Service導出的工作。
JSR 181就是爲此目的而提出的,它是BEA領導的一個Web Service規範。XFire已經支持JSR 181 2.0,你既可以使用JDK 5.0的註解,也可以在JDK 5.0之前的版本中使用commons-attributes註解。
使用JSR 181的明顯好處是,你僅需在業務類和窄接口標註JSR 181註解,不管你有多少需要導出爲Web Service的業務類,僅須在Spring中配置一個XFire提供的JSR 181註解增強Bean就可以了。
註解增強處理器會對Spring容器中所有標註JSR 181註解的業務類進行處理,並分別將它們導出爲Web Service。使用JSR 181時,必須將XFire的依賴類庫xfire-jsr181-api-1.0-M1.jar添加到類路徑中。
如果輸入、輸出的對象類型僅包括基本類型的屬性,僅需要在業務類和窄接口中分別使用@WebService註解進行簡單的配置就可以了,XFire將根據默認約定導出Web Service。
窄接口僅需要定義一個@WebService註解,並指定SOAP的命名空間就可以了:
package com.baobaotao.xfire.server;
import javax.jws.WebService;
@WebService(targetNamespace = "http://www.baobaotao.com")①指定SOAP的命名空間
public interface BbtForumService {
int getRefinedTopicCount(int lastDay);
}
XFire應用JSR 181比較怪的一點是,除了需要在窄接口中提供註解外,在實現業務類中也需要提供相應的註解:
package com.baobaotao.service;
import javax.jws.WebService;
import com.baobaotao.xfire.server.BbtForumService;
@WebService(serviceName = "BbtForumService",①指定導出的Web Service名稱
endpointInterface = "com.baobaotao.xfire.server.BbtForumService")②對應的窄接口
public class BbtForum implements BbtForumService{
…
}
如果碰到以下應用場景:輸入、輸出對象是複雜的對象(如未使用泛型的集合類),當返回類型是一個對象但不希望輸出所有的結果,或者不希望使用默認的屬性名。
這時可以在業務方法中通過@WebMethod、@WebResult等註解提供額外的信息來達到目的。更多關於JSR 181的信息請參考:http://dev2dev.bea.com/webservices/jwsm.html。
按照相似的方式,可以爲應用中其它的業務類進行Web Service標註,在完成標註後,需要在Spring配置中啓用XFire JSR 181處理器,對Spring容器中所有標註@WebService的Bean進行統一的處理,以便執行真正Web Service的導出工作。XFire在Spring中對應的配置如下所示:
代碼清單3 applicationContext.xml:使用JSR 181導出Web Service
<beans >
<import resource="classpath:org/codehaus/xfire/spring/xfire.xml" />
<bean id="webAnnotations"①該Bean獲取Spring容器中所有標註@WebService的Bean
class="org.codehaus.xfire.annotations.jsr181.Jsr181WebAnnotations" />
<bean id="jsr181HandlerMapping"②對標註@WebService的Bean進行處理,完成導出工作
class="org.codehaus.xfire.spring.remoting.Jsr181HandlerMapping">
<property name="xfire" ref="xfire" />
<property name="webAnnotations" ref="webAnnotations" />
</bean>
③該Bean標註了@WebService註解
<bean id="bbtForum" class="com.baobaotao.service.BbtForum" />
</beans>
重啓Tomcat,查看http://localhost:8080/baobaotao/service/BbtForumService?wsdl,你依舊可以看到如圖2 BbtForumService的WSDL所示的WSDL。
各種客戶端調用方式
XFire爲訪問服務端Web Service提供了各種方便的方式:在可以獲取服務端窄接口類的情況下,可以根據服務地址和窄接口類創建客戶調用程序。
如果不能獲得服務窄接口類,XFire允許你通過WSDL文件生成客戶端調用程序,通過指定服務接口的方式調用服務。鑑於這種調用方式不夠面向對象,XFire提供了一個根據WSDL生成客戶端存根代碼的工具,這樣你就可以方便以面向對象的方式編寫客戶端程序了。
使用服務端的窄接口類
如果客戶端可以獲取服務端的Web Service的窄接口類,這時可以使用XFire的ObjectServiceFactory將對應地址的Web Service轉換爲窄接口實例進行調用,如代碼清單4所示:
代碼清單4使用窄接口調用Web Service應用
package com.baobaotao.xfire.client;
import org.codehaus.xfire.client.XFireProxyFactory;
import org.codehaus.xfire.service.Service;
import org.codehaus.xfire.service.binding.ObjectServiceFactory;
import com.baobaotao.xfire.server.BbtForumService;
public class WithClassClient {
public int getRefinedTopicCount() {
①根據窄接口創建Service模型
Service serviceModel = new ObjectServiceFactory().create(BbtForumService.class);
②服務對應URL地址
String serviceURL = "http://localhost:8080/baobaotao/service/BbtForumService";
BbtForumService service = null;
try {
③將Web Service轉換爲窄接口實例
service = (BbtForumService) new XFireProxyFactory().
create(serviceModel, serviceURL);
} catch (Exception e) {
throw new RuntimeException(e);
}
return service.getRefinedTopicCount(20);④調用Web Service方法
}
public static void main(String[] args) {
WithClassClient client = new WithClassClient();
System.out.println("topic count is:"+client.getRefinedTopicCount());
}
}
XFire根據Service模型對象及Web Service的URL地址就可以構造出Web Service的調用實例。在服務端Tomcat啓動的情況下,運行以上的客戶端代碼,將可以獲得正確的輸出。
使用WSDL文件構造客戶端程序
並不是任何時候都可以獲得Web Service服務端的窄接口類,但我們必然可以獲取Web Service對應的WSDL文檔。XFire允許我們僅通過Web Service對應的WSDL文件構造客戶端訪問程序。
這無疑給創建客戶端程序帶來了極大的便利性,你可以直接通過URL指定WSDL,也可以將WSDL保存在本地系統中,通過InputStream的方式獲取WSDL內容。下面,我們使用InputStream的方式提供WSDL:
代碼清單5通過WSDL創建客戶端程序
package com.baobaotao.xfire.client;
import java.net.URL;
import org.codehaus.xfire.client.Client;
public class OnlyWsdlClient {
public int getRefinedTopicCount() {
try {
String wsdl = "com/baobaotao/xfire/client/BbtForumService.wsdl";①對應的WSDL文件
Resource resource = new ClassPathResource(wsdl);
Client client = new Client(resource.getInputStream(), null);②根據WSDL創建客戶實例
③調用特定的Web Service方法
Object[] results = client.invoke("getRefinedTopicCount",new Object[]);
return (Integer) results[0];
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
OnlyWsdlClient client = new OnlyWsdlClient();
System.out.println("topic count is:" + 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服務名和輸入參數調用Web Service的服務方法了,如③所示。
可能會有讀者認爲這種完全根據WSDL創建客戶端程序的方式會帶來低劣的運行性能,筆者通過測試發現,確實會造成一定的性能降低,但也不象想象中那樣低效。
使用基於窄接口的客戶端程序和使用基於WSDL的客戶端程序訪問一次BbtForumService的時間依次是1300 ms和1450 ms。如果WSDL文檔很複雜,由於需要解析整個WSDL文檔,這種客戶端程序的性能會受到更多的挑戰。不過,如果只要在程序中緩存Client實例,由 於創建Client的代價是一次性的,性能問題就可以忽略了。
根據WSDL生成客戶端代碼
XFire允許通過運行Ant任務,根據WSDL文件生成訪問Web Service的客戶端代碼存根,同時XFire還提供了一個Eclipse插件完成相同的任務。本節裏,我們將學習通過XFire Eclipse插件生成BbtForumService客戶端存根代碼的知識。
安裝Eclipse XFire 插件
1.Help->Software Updates->Find and Install...
2.選擇“Search for new features to install”,並點擊Next;
3.選擇“New Remote Site...”,創建一個Name爲XFire,URL爲
http://dist.codehaus.org/xfire/update/的網站;
4.點擊Finish安裝XFire插件。
使用插件創建客戶端代碼存根
- File->New->Other...->XFire->Code generation from WSDL document;
- 彈出一個對話框,如圖3所示:
圖3創建客戶端代碼存根
指定WSDL文件的位置,存根代碼的輸出地址及對應的類包,點擊Finish。
- XFire插件將在生成客戶端代碼存根的同時生成服務端代碼的存根,如下圖所示:
圖4生成的代碼
BbtForumServiceClient是BbtForumServicePortType的工廠類,它提供了若干個獲取 BbtForumServicePortType實例的重載方法。BbtForumServicePortType對應服務端的窄接口 BbtForumService類。而BbtForumServiceImpl是服務端的存根代碼,在META-INF中還有XFire的服務配置文件。 對於客戶端來說,一般不需要服務端的代碼,所以你可以將BbtForumServiceImpl和META-INF刪除。
下面,我們利用XFire生成的BbtForumServiceClient對服務端的Web Service進行調用:
package com.baobaotao.xfire.client;
public class StubClient {
public static void main(String[] args) {
BbtForumServiceClient client = new BbtForumServiceClient();
String serviceUrl = "http://localhost:8080/baobaotao/service/BbtForumService";
①獲取對應服務窄接口實例
BbtForumServicePortType portType = client.getBbtForumServiceHttpPort(serviceUrl);
int count = portType.getRefinedTopicCount(20);②對服務進行調用
System.out.println("count:" + count);
}
}
我們首先實例化一個BbtForumServiceClient,然後通過URL指定Web Service的服務地址,然後創建一個服務的窄接口實例,如①所示,接着我們就可以使用這個窄接口實例進行Web Service服務的調用了。
Web Service的測試
在上一節裏,我們學習了客戶端調用服務端中Web Service的方法。在實際應用中,在開放Web Service之前需要進行嚴格的測試,以保證功能的正確性。在一般框架中,測試Web Service往往這是一個煉獄般痛苦的過程。
XFire通過AbstractXFireTest極大地簡化Web Service的測試。AbstractXFireTest允許我們無需構造客戶端調用程序,在SOAP報文層面開展對服務端代碼的測試, AbstractXFireTest提供了一系列方便的方法對SOAP報文進行驗證。XFire還特別爲Spring環境下進行Web Service測試提供了一個AbstractXFireSpringTest子類,僅通過啓用Spring容器就可以完成Web Service的測試。通過XFire精心設計的測試工具類,對Web Service的測試工作已經是一項可以輕鬆應對的工作。
如果你在編寫服務端Web Service的同時,還需要編寫客戶端調用程序,這時不可避免的,你希望從客戶端角度對Web Service進行測試。由於客戶端程序需要訪問真實的Web Service,所以需要開啓Web服務器,讓服務端的Web Service能夠提供服務共客戶端訪問調用。如果客戶端和服務端都在同一個項目中開發,XFire允許你在不啓動Web服務器的情況下測試客戶端程序, 其原理是讓Web Service運行於JVM模式下。
基於SOAP報文的純服務端測試
AbstractXFireTest擴展於JUnit標準的TestCase類,提供了向某個Web Service發送SOAP請求報文並對返回的SOAP響應報文進行檢驗的能力。該測試類提供了若干個方便的斷言方法,分別介紹如下:
- void assertNoFault(Document node):確認SOAP響應報文無錯誤;
- java.util.List assertValid(java.lang.String xpath, java.lang.Object node):確認在DOM節點特定路徑下有對應的元素,路徑通過XPath表達式進行定義,該方法還將匹配的元素以List對象返回,你可以對匹配的元素 進行進一步的檢驗;
- void assertXPathEquals(java.lang.String xpath, java.lang.String value, Document node):確認特定路徑DOM節點爲某一特定值;
- assertInvalid(java.lang.String xpath, java.lang.Object node):確認DOM節點特定路徑下未包含元素。
AbstractXFireSpringTest是AbstractXFireTest的子類,在Spring中你僅需要擴展該類並實現該類的抽象 方法ApplicationContext createContext(),就可以對Spring容器中用XFire定義的Web Service進行測試了。
爲了測試Web Service,我們必須準備一個SOAP請求報文,你可以簡單地手工編寫一個,或通過SOAP報文截取工具(如前面我們介紹的TcpTrace、 SOAPScope、Apache Axis的TCPMon等)獲得一些可用的SOAP請求報文。下面是一個訪問BbtForumService 服務的請求SOAP報文:
代碼清單6 request_soap.xml:SOAP報文層面測試
<soap:Envelope xmlns: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">
<in0 xmlns="http://server.xfire.baobaotao.com">20</in0>
</getRefinedTopicCount>
</soap:Body>
</soap:Envelope>
我們將其保存在request_soap.xml文件中放置在類路徑com/baobaotao/xfire/server下。當該SOAP請求報 文發送給BbtForumService的Web Service後,我們預計它應該返回對應代碼清單7所示的正確的SOAP響應報文:
代碼清單7 SOAP響應報文
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns: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>
<getRefinedTopicCountResponse xmlns="http://www.baobaotao.com">
<out>32</out>
</getRefinedTopicCountResponse>
</soap:Body>
</soap:Envelope>
下面,我們着手編寫測試BbtForumService Web Service的測試類,以驗證實際SOAP響應報文是否和代碼清單7中的一樣:
代碼清單8 TestBbtForumService
package com.baobaotao.xfire.server;
import org.codehaus.xfire.spring.AbstractXFireSpringTest;
import org.jdom.Document;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestBbtForumService extends AbstractXFireSpringTest {
protected ApplicationContext createContext() {①創建包含Web Service的Spring容器
return new ClassPathXmlApplicationContext(
new String[]);
}
public void testUsingSoapRequest() throws Throwable {
②通過SOAP請求報文訪問BbtForumService的Web Service,對返回
SOAP響應報文進行檢測
Document response = 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測試Web Service首先要做的第一件事是通過實現createContext()方法構造Spring容器,如①所示。當Spring容器啓動時,XFire 將自動讓容器中的Web Service生效(僅進行測試,不能對外提供服務)。
第二步需要向Web Service發送一個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服務器的情況下通過客戶端程序測試Web Service的功能,這一嶄新的測試方法對於開發人員來說一定深具吸引力。因爲,這意味着你可以完全在IDE環境中運行測試,不需要外部環境的支持。
不過享受這一測試好處的應用必須保證客戶端和服務端的Web Service都位於同一JVM中,這時請求報文和響應報文直接在JVM 內部通道中傳輸。當使用JVM內部通道傳輸請求和響應的SOAP報文時,我們僅需要調整服務的地址就可以了:
代碼清單9 TextBbtForumService:JVM模式測試
package com.baobaotao.xfire.client;
import org.codehaus.xfire.client.XFireProxyFactory;
import org.codehaus.xfire.service.Service;
import org.codehaus.xfire.service.binding.ObjectServiceFactory;
import org.codehaus.xfire.spring.AbstractXFireSpringTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.baobaotao.xfire.server.BbtForumService;
public class TextBbtForumService extends AbstractXFireSpringTest {
protected ApplicationContext createContext() {
return new ClassPathXmlApplicationContext(
new String[]);
}
public void testGetRefinedTopicCount() throws Throwable {
Service serviceModel = new ObjectServiceFactory().create(BbtForumService.class);
XFireProxyFactory factory = new XFireProxyFactory(getXFire());
①在JVM內部通道中進行SOAP請求和響應報文的傳輸,請注意粗體所示的服務地址
BbtForumService service = (BbtForumService) factory.create(serviceModel,
"xfire.local://BbtForumService");
int count = service.getRefinedTopicCount(20);
assertEquals(count,32);
}
}
以上代碼中,①處的服務地址採用了JVM模式的地址,和其對應的HTTP地址則是http://localhost:8080/baobaotao/service/BbtForumService,所以僅需將服務名前的部分替換爲“xfire.local://”就可以了。
小結
比之於Axis,XFire在實施Web Service更加簡潔高效,因此XFire在短短的時間裏成爲了Web Service開發者的炙手可熱的框架。更可貴的是XFire對Spring提供了強大的支持,可以非常方便地在Spring中使用XFire實施Web Service。
XFire可以通過多種方式將Spring容器中的Bean導出爲Web Service,這包括使用XFireExporter導出器或JSR 181註解。JSR 181和STAX一起都將融入到JDK 6.0中,因此,JSR 181 Web Service定義方式將成爲標準的實現。
XFire爲客戶端提供了多種訪問Web Service的方式,如果可以獲取客戶端的窄接口類,則可以採用窄接口類調用Web Service。如果僅能獲取WSDL,XFire也可以採用動態反射的機制調用Web Service。XFire爲Eclipse提供了一個可以根據WSDL生成客戶端存根代碼的插件,相信XFire也將爲其它非Java語言提供類似的插 件。
技術可用性的一個很大的標準是它是否方便測試,XFire爲在Spring中測試Web Service提供了一流的支持,通過JVM模式,你能夠在不啓動Web容器的情況下測試Web Service,Web Service的測試工作變得不再象原來那樣讓人畏懼。