細心!用心!耐心!

          吾非文人,乃市井一俗人也,讀百卷書,跨江河千里,故申城一游; 一兩滴辛酸,三四年學業,五六點粗墨,七八筆買賣,九十道人情。

          BlogJava 聯系 聚合 管理
            1 Posts :: 196 Stories :: 10 Comments :: 0 Trackbacks

          Web服務的客戶端緩沖技術的設計與實現

          利用設計模式提高SOAP服務的運行效率

          developerWorks
          文檔選項
          將此頁作為電子郵件發送

          將此頁作為電子郵件發送

          未顯示需要 JavaScript 的文檔選項



          級別: 初級

          廖健 (Norwaywoods@21cn.com), 工程師, 美國Appeon軟件(深圳)公司

          2003 年 10 月 01 日

          本文通過設計和實現一個網上商品查詢系統,來展示如何利用緩沖管理、業務代理等設計模式加速Web服務的調用效率。

          概要

          時至今日,SOAP (Simple Object Access Protocol)已由最初的實驗性協議,走進了現實應用中。但是,當在分布式Java應用程序中引入SOAP作為底層通訊協議時,它所帶來的繁重的網絡開銷成為令人頭痛的問題。本文將通過設計和實現一個網上商品查詢系統,來展示如何利用緩沖管理、業務代理等設計模式加速Web服務的調用效率。





          回頁首


          引子

          眾所周知,Web服務的革命早已開始。SOAP(簡單對象訪問協議),一種XML RPC消息協議成為了Web服務的底層通信協議。從它的名字就可以知道,它是一種易于定義、易于使用的輕量級協議。SOAP使發送和接收XML,并把它轉化為異構語言對象變得容易。

          但是,在擁有易用性和靈活性的同時也是要付出效率上的代價的。協議的設計者把效率方面的復雜性留給了應用程序開發者。一個簡單的SOAP實現往往會給企業級應用程序帶來無狀態、運行效率低下等大量的問題。大數據量,頻繁的SOAP調用甚至可能會引起網絡阻塞。

          解決Web服務效率問題的方法,一般分為兩個層次:

          1. 容器層。Java Web服務的服務端一般都是一個Servlet應用程序,我們所說的容器層就是指運行Servlet的Web Server。例如,Apache Axis的服務端就可以運行在Tomcat這個Web Server上。
          2. 應用程序層。一般是指用戶自己編寫的代碼。這里可以是客戶端的代碼,也可以是服務器端提供Web服務的代碼。解決的方法,一般包括緩沖數據、壓縮信息量等等。

           

          下文將會介紹一種基于應用程序層的解決方案。通過緩沖模式,代理模式構造一個對商業邏輯透明的,提高Web服務效率的解決方案。本解決方案同時還支持自動同步緩沖區信息和UI自動更新。

          在這里假設讀者對SOAP已有一定的認識,并且懂得如何使用Apache Axis創建、發布Web服務。本文將不會描述SOAP的工作原理,但是會提供相關鏈接,在那里可以得到更多的信息。





          回頁首


          Web Service在應用程序中的問題

          當開發基于SOAP通信協議的胖客戶端Java應用程序時,必須注意三個問題:性能、性能、還是性能。如果客戶端程序會經常的訪問服務端的相同信息,然而實現方法采取每次訪問都要進行Web服務的調用,這樣效率肯定很低。但現實是成百上千的客戶端應用程序會為了這相同數據同時訪問服務器,并且它們的操作僅僅限于瀏覽,服務器性能下降的會更加明顯,甚至癱瘓。初步估計,一次SOAP調用的代價與執行一次關系型數據庫的SQL操作相當甚至更大(如果考慮到網絡的因素)。如下圖:



          但是,事實上,通常一個SOAP調用總是伴隨著一次SQL操作。所以,一個現實中的SOAP調用的代價包括:網絡的延時、Server端CPU操作SOAP的時間和數據庫服務器的SQL操作延時。



          由上圖可見,一次Web服務調用如果不考慮網絡的因素,就已經需要將近兩倍于數據庫SQL操作的時間。這樣的效率是肯定無法接受的。因此,有一種解決方法就是盡量減少進行Web服務調用的次數了。正統的解決方案,就是采取緩沖機制。其中,緩沖機制還可以分為3種:在Server端緩沖,在Client端緩沖和不緩沖(有些實際應用中,數據量很小。維護緩沖區比不維護的代價更大)。下文中的例子程序將會根據緩沖管理模式,設計一個簡單的應用程序。





          回頁首


          具體問題舉例 —— 網上商品價格查詢系統





          回頁首


          簡介

          這是一個常見的程序,在很多的WebService開發包中,都有類似的例子。如Apache Axis、IBM的ETTK等。本例子,僅僅包括一個提供商品報價的服務類,它只提供了一些很簡單的操作,如增加和刪除商品信息,獲取商品信息等等方法。通過WSDL描述這些操作,并且暴露給用戶,使其可以方便的開發自己的客戶端程序。本程序有一個特點,支持自動同步數據,準確的說應該是自動同步緩沖區數據。事實上,緩沖技術和自動同步技術并沒有任何關系,可以分開使用。最后,本程序還提供了一個簡單的GUI客戶端程序,可以執行部分Web服務方法。





          回頁首


          整體架構

          客戶端架構



          通過代理模式,很好的分離了GUI層和業務層邏輯。又利用監聽器模式實現了透明的緩沖機制。該實例程序客戶端包括兩個主面板(panel)。上方面版(panel)顯示所有可用的商品信息,而下方面板讓用戶可以添加和更新商品的信息。當客戶端開始運行,它會利用線程阻塞機制在服務端注冊一個數據變更監聽器,監聽所有的數據更新事件。然后,調用Get All Quotes 服務獲取所有的商品信息。當任意一個客戶機提交和更新了數據,該客戶端的監視線程就會返回,于是一次SOAP調用結束。這同時使到服務器端的數據改變事件被觸發,從而主動激活所有被阻塞的客戶端監視線程,并且觸發它們的緩沖區數據更新的SOAP調用,然后每個客戶端又會在服務器端注冊新的數據改變監視線程,如此反復。這樣,只有更新數據的時候,才會發送SOAP調用,一般的讀數據實際上是讀緩沖區內的數據,而不會進行代價頗高的Web服務調用,從而提高了速度,又實現了數據自動同步功能。

          服務器端架構

          在服務端設計中,充分利用接口(Interface)帶來的便利。全部用接口來描述功能,徹底的隱藏了具體實現細節。下面介紹,Web服務端設計的小技巧:

          根據接口(Interface)來產生你的WSDL文件

          之所以要這樣做,主要有兩個原因:

          1. 將接口發布為Web服務可以避免將一些不需要的方法發布為Web服務。例如main(String [ ] args)
          2. 對接口編程可以保證靈活性。你可以提供不同的服務實現類,并且無縫的切換。

           

          Apache Axis為此提供了方便的工具(Java2WSDL、 WSDL2Java),可以通過這些工具迅速的產生整個服務端架構。下面是整個Server端的類關系圖。



          下面列出了整個演示程序所有的包以及每個包的重要組成類。

          demo.productinfo.clientside

          • ClientNotificationHandler:

            客戶端數據同步處理器。用于處理本地緩沖類實例。其包含一個內部線程類,用于實現客戶端的線程阻塞。

           

          demo.productinfo.clientside.businessdelegates

          • DataChangeListener: 定義數據改變監聽器的行為。
          • NotificationServiceDelegate: 調用Web服務的代理類。
          • ProductInfoServiceDelegate: 用于調用Web服務的代理類。

           

          demo.productinfo.clientside.cache

          • CacheExpiredListener:定義了緩沖過期監聽器的行為。
          • SimpleCache: 簡單緩沖模式的實現類。

           

          demo.productinfo.clientside.soapstub
          這個包里面的所有類均由Apache Axis的內置工具Java2WSDL和WSDL2Java產生,這里就不對之進行說明了。

          demo.productinfo.exception

          • ProductInfoException: 自定義的異常類。通常在Java程序開發過程中,不要把系統底層的異常拋給用戶,而應該以更加有意義的異常類取而代之,這就是為什么要有這個類的原因。

           

          demo.productinfo.serverside
          在這個包中,通過接口類定義了服務器端所有的行為。其下面的子包都是這些接口的具體實現。

          • INotificationService: 定義了通告服務的服務方法。
          • IProduct: 定義了商品類的基本行為。
          • IProductInfoService: 定義了商品信息服務的方法。
          • NotificationServiceSoapBindingSkeleton: 該類是由Apache Axis工具產生的服務端的框架類。
          • ProductInfoServiceSoapBindingSkeleton: 該類是由Apache Axis工具產生的服務端的框架類。

           

          demo.productinfo.serverside.product
          在這個包內全部是具體的商品類。

          • ProductTemplate: 該類是實現了IProduct接口的模板類。它包含了一些最基本的參數和方法。提供給其他的具體商品類繼承之用。還有許多的商品類,這里就不一一羅列了。

           

          demo.productinfo.serverside.serviceimplement
          在這個包內是所有Web服務的具體實現類。

          • NotificationServiceImp: 該類是數據同步服務的具體實現。
          • ProductInfoImp: 商品查詢服務的具體實現。
          • ServerNotificationHandlerImp: 服務端數據同步處理器。

           





          回頁首


          業務邏輯代理模式的實現

          根據J2EE系統設計模式,業務邏輯代理通過向表現層隱藏了低層的實現,有效的減低表現層和商務邏輯層的耦合度,例如查找和調用EJB的細節。然而在本程序中,這種業務邏輯代理類對外隱藏了查找、調用Web服務和緩沖數據的復雜細節。

          IProductInfoService接口類為客戶端提供了關于商品信息的所有服務方法的描述。每個客戶端必須自己處理SOAP 調用的查找、調用和緩沖。ProductInfoServiceDelegate類,從它的名字可知,它就是客戶端的代理類。(在例子中,這個代理類并沒有實現所有的Web服務方法調用,有興趣的朋友可以自己補充完整)。

          ProductInfoServiceDelegate類首先初始化一個IProductInfoService接口的實例,并且通過它來處理一切與底層SOAP相關的操作。然后,實例化一個SimpleCache類的靜態實例,通過在所有的ProductInfoServiceDelegate類實例間共享這個實例來處理所有緩沖信息的操作。同時ProductInfoServiceDelegate類還包含一個DataChangeListener引用,用于觸發注冊的UI控件的界面更新事件,這里只是一個簡單的實現,因此,一個ProductInfoServiceDelegate僅僅對應一個UI 控件,可以擴展為一個EventListenerList類,從而一個ProductInfoServiceDelegate可以擁有多個UI監聽(但是更新UI又是一件危險的事,要注意只能在UI事件處理線程上刷新UI)。最后,ProductInfoServiceDelegate還實現了CacheExpiredListener接口,使它可以作為ClientNotificationHandler類的監聽者,監聽緩沖更新事件,以及時觸發DataChange事件。

          下面是ProductInfoServiceDelegate類的一些關鍵代碼:

          /**
                                  *<p>
                                  *Add a staff to server database and update the local cache.
                                  *</p>
                                  *
                                  *@param staff <code>IProdcut</code>
                                  *@return true if this operation is succeed or false for fail.
                                  */
                                  public boolean addStaff(IProduct staff) {
                                  boolean ret = false;
                                  if(staff != null) {
                                  try {
                                  synchronized(cache) {
                                  log.info("ProductInfo Service Delegate : Add Quote -> ["
                                  + staff.getName() + "]");
                                  ret = soap.addStaff(staff);
                                  if(ret == true) {
                                  log.info("ProductInfo Service Delegate : Updating Cache for [" +
                                  staff.getName() + "] ...");
                                  cache.addObject(String.valueOf(staff.getSN()), staff);
                                  }
                                  }
                                  }catch(ProductInfoException e) {
                                  log.error("ProductInfoException in addStaff: " + e.getMessage());
                                  }catch(RemoteException e) {
                                  log.error("RemoteException in addStaff: " + e.getMessage());
                                  }
                                  }
                                  return ret;
                                  }
                                  /**
                                  * <p>
                                  * Request the staff object. If we can find it from cache,then we got it and return. If
                                  * not we get it from the remote server and update the local cache.
                                  * </p>
                                  *
                                  * @param sn <code>String</code> - the staff's SN Number
                                  * @return The staff if we got it, or null for fail.
                                  */
                                  public IProduct getStaff(String sn) {
                                  IProduct staff = null;
                                  log.info(new Date() + "Prodcut Service Delegate : Getting Staff for [" + sn + "] ...");
                                  if(sn != null) {
                                  //Try to get this staff from the cache firstly.
                                  staff = (IProduct)cache.fetchObject(sn);
                                  //If we can not get it from local cache, do it again from the remote server.
                                  if(staff == null) {
                                  try {
                                  synchronized(cache) {
                                  //After the synchronized, we try it again to see if we can get it from cache.
                                  staff = (IProduct)cache.fetchObject(sn);
                                  if (staff != null) {
                                  return (staff);
                                  }
                                  staff = soap.getStaff(sn);
                                  if(staff != null) {
                                  cache.addObject(sn, staff);
                                  log.info("ProductInfo Service Delegate : Updating Cache for ["
                                  +staff.getName() + "] ...");
                                  }
                                  }
                                  }catch(RemoteException e) {
                                  log.error("RemoteException in getStaff: " + e.getMessage());
                                  }
                                  }
                                  }
                                  return staff;
                                  }
                                  /**
                                  * <p>
                                  * Get all of the staffs information.
                                  * </p>
                                  *
                                  * @return <code>java.util.Map</code> the map of all these staffs info.
                                  */
                                  public Map getAllStaffs() {
                                  log.info( new Date() + "ProductInfo Service Delegate : Getting All staffs...");
                                  if(cache == null || cache.isEmpty()) {
                                  try {
                                  synchronized (cache){
                                  if ( (cache != null) && (!cache.isEmpty())) {
                                  return cache.getAllObjectsHashtable();
                                  }
                                  Map tempMap = soap.getAllStaff();
                                  if(tempMap != null) {
                                  for(Iterator i = tempMap.keySet().iterator(); i.hasNext(); ) {
                                  String sn = (String)i.next();
                                  Object o  = tempMap.get(sn);
                                  cache.addObject(sn, o);
                                  }
                                  }
                                  }
                                  }catch(RemoteException e) {
                                  log.error("RemoteException in getAllStaffs: " + e.getMessage());
                                  }
                                  }
                                  return cache.getAllObjectsHashtable();
                                  }
                                  

          下面是操作流程圖:



          由上可以清楚的看到,使用業務代理模式可以使程序員不必去關心底層的通信細節,而把注意力完全集中在實現商業邏輯上來。





          回頁首


          緩沖管理模式的實現

          當調用業務代理類的getStaff ()方法時,會首先試圖從緩沖區中讀取,如果無法找到,再向服務器端發出SOAP請求。當一個客戶端程序添加或者更新了商品信息,它會首先調用Web服務方法更新服務器上的信息。這會觸發服務端的dataChanged事件,導致所有的客戶端都要更新本地緩沖區。然后,它在自己的本地緩沖區添加或更新該記錄。其它的服務方法,也是一樣的。目標就是將發送SOAP請求的次數減到最少。

          根據緩沖管理模式,SimpleCache類應該包含兩個方法,分別是addObject()和fetchObject()。一個CacheManager類通過ObjectKey來管理緩沖的信息。在本實現中,業務代理類就相當于CacheManager,這是由于每一個業務代理管理屬于自己的SimpleCache。而ProductTemplate類實例就相當于ObjectKey。至于Cache的內部數據結構,出于簡單的原因,采用了Hashtable。

          現實中的程序里,可以通過擴展SimpleCache類,用更加嚴謹的內存管理機制輕易的替換這種基于Hashtable的簡單實現(例如每次僅僅過期那些很少使用的緩沖信息),可以通過統計信息的點擊次數或者復雜度來決定將要清除那些緩沖信息。可以利用Java的特性,如弱引用等來提高JVM的垃圾回收機制對被緩沖對象的清理效率。還可以通過創建一個全局的緩沖實例來統一管理整個客戶端應用程序的緩沖信息。

          總之,優化的方法是有很多很多的。

          下面是,Cache類的簡單實現代碼:

          /**
                                  * Empty constructor
                                  */
                                  public SimpleCache(){
                                  cache = new Hashtable();
                                  }
                                  /**
                                  * Constructor
                                  * @param size int the nitial size of the cache
                                  */
                                  public SimpleCache(int size){
                                  cache = new Hashtable(size);
                                  }
                                  /**
                                  * puts the key value pair in the cache based on the storage mechanism.
                                  *
                                  * @param key Object representing the key against which the value will be stored.
                                  * @param Value Object
                                  */
                                  public synchronized void addObject(Object key, Object value){
                                  if (key != null && value != null){
                                  cache.put(key, value);
                                  }
                                  }
                                  /**
                                  * fetchs the value from the cache based on the key.
                                  *
                                  * @param key Object representing the key against which the value will be found.
                                  * @returns Object
                                  */
                                  public Object fetchObject(Object key){
                                  if (key == null) {
                                  return null;
                                  }
                                  return cache.get(key);
                                  }
                                  /**
                                  * expire the value object from cache
                                  *
                                  * @param key Object representing the key against which the value will be found.
                                  */
                                  public synchronized void expire(Object key){
                                  if (key != null) {
                                  cache.remove(key);
                                  }
                                  }
                                  /**
                                  * expire the entire cache
                                  */
                                  public void expire(){
                                  cache.clear();
                                  }
                                  

          下面的時序圖演示了數據是如何被緩沖和緩沖是如何被更新的:







          回頁首


          自動數據同步的實現

          在這個例子程序中,所有的客戶端都是自動同步數據的,也就是說一個客戶端程序更新或添加了數據,馬上就會在其它所有的客戶端中體現出來,這個功能是通過通告處理機制實現的。在這個示例程序中,有兩個數據同步處理類,分別是運行在服務器端的ServerNotificationHandlerImp和運行在客戶端的ClientNotificationHandler。

          下面將詳細介紹,在這個程序中是怎樣實現數據同步的。

          眾所周知,基于HTTP協議的SOAP是無法支持雙向傳播(Bi-directional Communication)的,可是又必須當服務端發生變化時,讓所有的客戶端程序接收到通告(例如,當其中一個客戶端向服務端數據庫添加了一條商品信息后,所有其它的客戶端都應該收到通告,以更新自己的數據視圖)。

          根據SOAP 綁定協議可以輕易的將底層的通信協議換成雙向傳播協議,如BEEP (Blocks Extensible Exchange Protocol)。但是這樣將失去HTTP協議的簡單性,可擴展性和防火墻可穿透性。

          因此,這里采用了阻塞的Web服務調用。為此專門提供了一個Web服務--NotificationService。所有調用該服務的客戶端線程都會被服務端的ServerNotificationHandler Singleton類實例的isDataChanged方法阻塞,直到數據改變事件觸發,該Web服務調用才會返回。在本例中,DataChanged是由另一個Web服務 --- ProductInfoImp觸發的。

          下面是ServerNotificationHandler類的兩個關鍵方法:

          public synchronized boolean isDataChanged() {
                                  while(true) {
                                  try {
                                  wait();
                                  break;
                                  }
                                  catch (Exception ignored) {
                                  }
                                  }
                                  return true;
                                  }
                                  public synchronized void dataChanged()  {
                                  notifyAll();
                                  }
                                  

          在客戶端,一個Singleton的ClientNotificationHandler單獨運行在一個名為ClientNotificationHandlerThread的線程上。每個客戶端都會創建這樣一個線程,在這個線程上通過NotificationServiceDelegate代理類調用服務端的INotificationService服務的isDataChanged方法,并且被阻塞,只有當ServerNotificationHandlerImp的dataChanged方法被調用時,執行了Java線程的notifyAll操作才能返回。當該調用返回時,ClientNotificationHandler將注冊的本地Cache全部過期,也就是觸發了緩沖更新的事件。

          下面ClientNotificationHandlerThread類的run()方法:

          public void run() {
                                  while (true)   {
                                  if ((nsbd != null) && (nsbd.isDataChanged()))       {
                                  expireAllCaches();
                                  }
                                  else
                                  {
                                  // Sleep for some time before creating the
                                  // NotificationServiceDelegate.
                                  gotoSleep();
                                  initNotificationServiceBusinessDelegate();
                                  }
                                  }
                                  }
                                  

          下面是通過Apache Axis自帶的TCP Monitor監視SOAP Call的截圖:



          由上圖可以清晰的看到,有一個SOAP調用一直沒有結束,總是保持Active的狀態,這就是對NotificationService的調用。當它返回的時候,必然會觸發一個getAllQuotes的SOAP調用,然后又會有一個Active的連接。

          這種方法也是有一定代價的。主要體現在它要求每一個客戶端都必須起一個線程來等待接收通告,而在服務器端對于每一個注冊的客戶端連結都有一個線程阻塞的等待發出通告。絕大多數的企業級應用程序服務器都可以游刃有余的對應幾百個客戶端。但當同時有上千個客戶端連結的時候,那又是另一回事了。這時候,只有采用非阻塞調用,ServerNotificationHandler將通知事件存儲到隊列中去,同時客戶端的ClientNotificationHandler周期性的調用NotificationService服務。并且出于演示的原因,這個例子的數據同步模塊已經被盡量的簡化。真正的實現并不會總是使所有客戶端的Cache全部過期。一個更具魯棒性的設計可以讓服務端的ServerNotificationHandlerImp傳遞需要被更新Cache數據的信息,甚至可以詳細到哪一個對象實例需要被更新。這樣一來,ClientNotificationHandler可以調用更加有效的代理類方法(如expiredOneCache),來更新局部的Cache信息。

          下圖展示了數據同步模塊的工作流程圖:







          回頁首


          客戶端界面

          客戶端主要兩個功能組件,第一個是StaffInfoViewPanel用于顯示最新的商品信息。另一個是DataEditPanel用于更新和添加商品信息。這些GUI組件都是通過封裝的很好的ProductInfoServiceDelegate代理類來與后臺通信的。

          下面是運行時界面截圖:



          可以清楚的看出,當一個客戶端程序更新后臺數據庫的時候,其它的客戶端也會實時的更新自己的數據視圖。





          回頁首


          具體實現中遇到的問題

          本文所帶的例子,是在Eclipse 2.1IDE上開發完成的,SOAP引擎選用了Apache Axis1.1 Final。這些工具提供了十分快捷的開發方式。下面僅對開發過程中遇到的一些代表性的問題進行簡單分析。

          • 如何用SOAP傳遞Hashtable

            這個問題在Apache Axis的官方 Mail List上,一直是一個非常火的問題。由于本文的例子也采用Hashtable這種數據結構,所以在此描述一下如何解決這個問題。首先,本例中傳送的Hashtable的key為字符串型,value為StockQuote類型,其中StockQuote類是符合標準JavaBean規則的。因此,可以使用Axis自帶的BeanSerializer和BeanDeserializer,但是在發布WebService的時候,必須配置服務端的序列化器,如下所示:

            <deployment xmlns="http://xml.apache.org/axis/wsdd/"
                                        xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
                                        <service name="ProductInfoService" provider="java:RPC" style="rpc" use="encoded">
                                        <parameter name="wsdlTargetNamespace" value="http://serverside.productinfo.demo"/>
                                        <parameter name="wsdlServiceElement" value="IProductInfoServiceService"/>
                                        <parameter name="wsdlServicePort" value="ProductInfoService"/>
                                        <parameter name="className" value="demo.productinfo.serverside.ProductInfoServiceSoapBindingSkeleton"/>
                                        <parameter name="wsdlPortType" value="IProductInfoService"/>
                                        <parameter name="allowedMethods" value="*"/>
                                        <parameter name="scope" value="Application"/>
                                        <typeMapping
                                        xmlns:ns="http://product.serverside.productinfo.demo"
                                        qname="ns: ProductTemplate"
                                        type="java:demo.productinfo.serverside.product.ProductTemplate"
                                        serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"
                                        deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"
                                        encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"      />
                                        <typeMapping
                                        xmlns:ns="http://exception.productinfo.demo"
                                        qname="ns:ProductInfoException"
                                        type="java:demo.productinfo.exception.ProductInfoException"
                                        serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"
                                        deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"
                                        encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                                        />
                                        </service>
                                        </deployment>
                                        

            請注意其中的<typeMapping/>節點,必須顯示的為ProductTemplate類指定序列化器和反序列化器。否則,服務端會拋出org.xml.sax.SAXException異常。

            在客戶端,可以直接使用通過Axis自帶的WSDL2JAVA工具產生的可序列化的ProductTemplate類,也可以使用自己的ProductTemplate類,只要嚴格符合JavaBean標準規范。

          • 如何調整后臺服務的生存周期

            由于本例子程序,沒有采用數據庫,而只是簡單的使用Hashtable做為存儲商品信息的數據結構。如何保證數據的持久性呢?一般的Web服務只提供無狀態的服務,類似于每一次客戶端的SOAP調用,服務器端都會創建一個新的類實例來進行處理,你可以把它想象成Stateless Session Bean。為此,Apache Axis支持簡單的三個級別的后臺服務,分別為:Request、Session和Application。Request級別是默認的選項,它代表每次SOAP請求,都會創建一個新的對象來處理。Session級別代表客戶端的每個Session有一個對應的后臺對象進行處理。Application級別代表所有客戶端的SOAP請求都會共享同一個后臺服務實例對象,并且要由程序員自己來保證其數據的同步問題。在本例子中就是采用了Application級的服務。

            實施的過程如下:

            服務端:在WSDD文件中的每個服務的描述下,添加這樣一行:

            <parameter name="scope" value="Application"/>
                                        

          • 如何避免Session Timeout

            Apache Axis1.1 Final的一個極具爭議性的改變就是將原本的無Session Timeout時間限制,默認設置為60秒。這對例子程序有很大的影響,因為例子中每個客戶端都需要與Server保持一個長連接,如果有超時設定,那么這個假設將會被破壞。因此,必須取消超時限制。取消方法如下:

            FooServiceLocator loc = new FooServiceLocator();
                                        FooService binding = loc.getFooService();
                                        org.apache.axis.client.Stub s = (Stub) binding;
                                        s.setTimeout(0);
                                        // 1 second, in miliseconds
                                        

            對于這個問題,Axis的 Mail List上也處在激烈的爭論中,也許在1.2版中就會取消默認60秒的設定。讓我們拭目以待吧!





          回頁首


          總結

          Web服務雖然已經取得了長足的發展,涌現出大量設計優良的SOAP引擎,如Apache Axis等,它們采用最新的技術在底層效率上已經做出了很大提高,但是由于SOAP協議的一些固有的特性,例如序列化和反序列化效率,傳輸效率等等問題,整體的效果仍然不盡如人意。但也不需擔心,利用設計模式,可以解決大部分的問題。我們有理由相信它美好的未來。

          本文中,在設計模式的幫助下,實現了一個透明的客戶端緩沖SOAP調用的機制。現實中的程序還必須充分考慮到數據庫持久性,全局Cache管理,資源回收,與其他Web服務平臺的互操作性,安全性和更加有效的異常處理等問題。

          posted on 2007-10-16 15:30 張金鵬 閱讀(1008) 評論(0)  編輯  收藏 所屬分類: 服務器編程
          主站蜘蛛池模板: 繁昌县| 伽师县| 吴江市| 贺州市| 望谟县| 阿拉尔市| 焦作市| 雅安市| 蓝田县| 靖宇县| 高州市| 红安县| 海盐县| 桐乡市| 忻州市| 台湾省| 弥渡县| 堆龙德庆县| 雷波县| 正阳县| 安乡县| 行唐县| 福泉市| 嫩江县| 赫章县| 荣昌县| 内乡县| 娱乐| 河南省| 玉门市| 调兵山市| 九寨沟县| 巴楚县| 榆社县| 德安县| 常宁市| 会同县| 中江县| 宜都市| 九江市| 龙海市|