使用JSR 181註解導出Web Service
在需要導出爲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提供了各種方便的方式:在可以獲取服務端窄接口類的情況下,可以根據服務地址和窄接口類創建客戶調用程序。 如果不能獲得服務窄接口類,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插件。 使用插件創建客戶端代碼存根 指定WSDL文件的位置,存根代碼的輸出地址及對應的類包,點擊Finish。 XFire插件將在生成客戶端代碼存根的同時生成服務端代碼的存根,如下圖所示: 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服務的調用了。單的配置就可以了,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允許你通過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的代價是一次性的,性能問題就可以忽略了。
File->New->Other...->XFire->Code generation from WSDL document;
彈出一個對話框,如圖3所示:
圖3創建客戶端代碼存根
圖4生成的代碼
XFire爲訪問服務端Web Service提供了各種方便的方式:在可以獲取服務端窄接口類的情況下,可以根據服務地址和窄接口類創建客戶調用程序。