級別: 初級
廖健 (Norwaywoods@21cn.com), 工程師, 美國Appeon軟件(深圳)公司
2003 年 10 月 01 日
本文通過設(shè)計和實現(xiàn)一個網(wǎng)上商品查詢系統(tǒng),來展示如何利用緩沖管理、業(yè)務(wù)代理等設(shè)計模式加速Web服務(wù)的調(diào)用效率。
概要
時至今日,SOAP (Simple Object Access Protocol)已由最初的實驗性協(xié)議,走進了現(xiàn)實應用中。但是,當在分布式Java應用程序中引入SOAP作為底層通訊協(xié)議時,它所帶來的繁重的網(wǎng)絡(luò)開銷成為令人頭痛的問題。本文將通過設(shè)計和實現(xiàn)一個網(wǎng)上商品查詢系統(tǒng),來展示如何利用緩沖管理、業(yè)務(wù)代理等設(shè)計模式加速Web服務(wù)的調(diào)用效率。
引子
眾所周知,Web服務(wù)的革命早已開始。SOAP(簡單對象訪問協(xié)議),一種XML RPC消息協(xié)議成為了Web服務(wù)的底層通信協(xié)議。從它的名字就可以知道,它是一種易于定義、易于使用的輕量級協(xié)議。SOAP使發(fā)送和接收XML,并把它轉(zhuǎn)化為異構(gòu)語言對象變得容易。
但是,在擁有易用性和靈活性的同時也是要付出效率上的代價的。協(xié)議的設(shè)計者把效率方面的復雜性留給了應用程序開發(fā)者。一個簡單的SOAP實現(xiàn)往往會給企業(yè)級應用程序帶來無狀態(tài)、運行效率低下等大量的問題。大數(shù)據(jù)量,頻繁的SOAP調(diào)用甚至可能會引起網(wǎng)絡(luò)阻塞。
解決Web服務(wù)效率問題的方法,一般分為兩個層次:
- 容器層。Java Web服務(wù)的服務(wù)端一般都是一個Servlet應用程序,我們所說的容器層就是指運行Servlet的Web Server。例如,Apache Axis的服務(wù)端就可以運行在Tomcat這個Web Server上。
- 應用程序?qū)印R话闶侵赣脩糇约壕帉懙拇a。這里可以是客戶端的代碼,也可以是服務(wù)器端提供Web服務(wù)的代碼。解決的方法,一般包括緩沖數(shù)據(jù)、壓縮信息量等等。
下文將會介紹一種基于應用程序?qū)拥慕鉀Q方案。通過緩沖模式,代理模式構(gòu)造一個對商業(yè)邏輯透明的,提高Web服務(wù)效率的解決方案。本解決方案同時還支持自動同步緩沖區(qū)信息和UI自動更新。
在這里假設(shè)讀者對SOAP已有一定的認識,并且懂得如何使用Apache Axis創(chuàng)建、發(fā)布Web服務(wù)。本文將不會描述SOAP的工作原理,但是會提供相關(guān)鏈接,在那里可以得到更多的信息。
Web Service在應用程序中的問題
當開發(fā)基于SOAP通信協(xié)議的胖客戶端Java應用程序時,必須注意三個問題:性能、性能、還是性能。如果客戶端程序會經(jīng)常的訪問服務(wù)端的相同信息,然而實現(xiàn)方法采取每次訪問都要進行Web服務(wù)的調(diào)用,這樣效率肯定很低。但現(xiàn)實是成百上千的客戶端應用程序會為了這相同數(shù)據(jù)同時訪問服務(wù)器,并且它們的操作僅僅限于瀏覽,服務(wù)器性能下降的會更加明顯,甚至癱瘓。初步估計,一次SOAP調(diào)用的代價與執(zhí)行一次關(guān)系型數(shù)據(jù)庫的SQL操作相當甚至更大(如果考慮到網(wǎng)絡(luò)的因素)。如下圖:
但是,事實上,通常一個SOAP調(diào)用總是伴隨著一次SQL操作。所以,一個現(xiàn)實中的SOAP調(diào)用的代價包括:網(wǎng)絡(luò)的延時、Server端CPU操作SOAP的時間和數(shù)據(jù)庫服務(wù)器的SQL操作延時。
由上圖可見,一次Web服務(wù)調(diào)用如果不考慮網(wǎng)絡(luò)的因素,就已經(jīng)需要將近兩倍于數(shù)據(jù)庫SQL操作的時間。這樣的效率是肯定無法接受的。因此,有一種解決方法就是盡量減少進行Web服務(wù)調(diào)用的次數(shù)了。正統(tǒng)的解決方案,就是采取緩沖機制。其中,緩沖機制還可以分為3種:在Server端緩沖,在Client端緩沖和不緩沖(有些實際應用中,數(shù)據(jù)量很小。維護緩沖區(qū)比不維護的代價更大)。下文中的例子程序?qū)鶕?jù)緩沖管理模式,設(shè)計一個簡單的應用程序。
具體問題舉例 —— 網(wǎng)上商品價格查詢系統(tǒng)
簡介
這是一個常見的程序,在很多的WebService開發(fā)包中,都有類似的例子。如Apache Axis、IBM的ETTK等。本例子,僅僅包括一個提供商品報價的服務(wù)類,它只提供了一些很簡單的操作,如增加和刪除商品信息,獲取商品信息等等方法。通過WSDL描述這些操作,并且暴露給用戶,使其可以方便的開發(fā)自己的客戶端程序。本程序有一個特點,支持自動同步數(shù)據(jù),準確的說應該是自動同步緩沖區(qū)數(shù)據(jù)。事實上,緩沖技術(shù)和自動同步技術(shù)并沒有任何關(guān)系,可以分開使用。最后,本程序還提供了一個簡單的GUI客戶端程序,可以執(zhí)行部分Web服務(wù)方法。
整體架構(gòu)
客戶端架構(gòu)
通過代理模式,很好的分離了GUI層和業(yè)務(wù)層邏輯。又利用監(jiān)聽器模式實現(xiàn)了透明的緩沖機制。該實例程序客戶端包括兩個主面板(panel)。上方面版(panel)顯示所有可用的商品信息,而下方面板讓用戶可以添加和更新商品的信息。當客戶端開始運行,它會利用線程阻塞機制在服務(wù)端注冊一個數(shù)據(jù)變更監(jiān)聽器,監(jiān)聽所有的數(shù)據(jù)更新事件。然后,調(diào)用Get All Quotes 服務(wù)獲取所有的商品信息。當任意一個客戶機提交和更新了數(shù)據(jù),該客戶端的監(jiān)視線程就會返回,于是一次SOAP調(diào)用結(jié)束。這同時使到服務(wù)器端的數(shù)據(jù)改變事件被觸發(fā),從而主動激活所有被阻塞的客戶端監(jiān)視線程,并且觸發(fā)它們的緩沖區(qū)數(shù)據(jù)更新的SOAP調(diào)用,然后每個客戶端又會在服務(wù)器端注冊新的數(shù)據(jù)改變監(jiān)視線程,如此反復。這樣,只有更新數(shù)據(jù)的時候,才會發(fā)送SOAP調(diào)用,一般的讀數(shù)據(jù)實際上是讀緩沖區(qū)內(nèi)的數(shù)據(jù),而不會進行代價頗高的Web服務(wù)調(diào)用,從而提高了速度,又實現(xiàn)了數(shù)據(jù)自動同步功能。
服務(wù)器端架構(gòu)
在服務(wù)端設(shè)計中,充分利用接口(Interface)帶來的便利。全部用接口來描述功能,徹底的隱藏了具體實現(xiàn)細節(jié)。下面介紹,Web服務(wù)端設(shè)計的小技巧:
根據(jù)接口(Interface)來產(chǎn)生你的WSDL文件
之所以要這樣做,主要有兩個原因:
- 將接口發(fā)布為Web服務(wù)可以避免將一些不需要的方法發(fā)布為Web服務(wù)。例如main(String [ ] args)
- 對接口編程可以保證靈活性。你可以提供不同的服務(wù)實現(xiàn)類,并且無縫的切換。
Apache Axis為此提供了方便的工具(Java2WSDL、 WSDL2Java),可以通過這些工具迅速的產(chǎn)生整個服務(wù)端架構(gòu)。下面是整個Server端的類關(guān)系圖。
下面列出了整個演示程序所有的包以及每個包的重要組成類。
demo.productinfo.clientside
demo.productinfo.clientside.businessdelegates
- DataChangeListener: 定義數(shù)據(jù)改變監(jiān)聽器的行為。
- NotificationServiceDelegate: 調(diào)用Web服務(wù)的代理類。
- ProductInfoServiceDelegate: 用于調(diào)用Web服務(wù)的代理類。
demo.productinfo.clientside.cache
- CacheExpiredListener:定義了緩沖過期監(jiān)聽器的行為。
- SimpleCache: 簡單緩沖模式的實現(xiàn)類。
demo.productinfo.clientside.soapstub
這個包里面的所有類均由Apache Axis的內(nèi)置工具Java2WSDL和WSDL2Java產(chǎn)生,這里就不對之進行說明了。
demo.productinfo.exception
- ProductInfoException: 自定義的異常類。通常在Java程序開發(fā)過程中,不要把系統(tǒng)底層的異常拋給用戶,而應該以更加有意義的異常類取而代之,這就是為什么要有這個類的原因。
demo.productinfo.serverside
在這個包中,通過接口類定義了服務(wù)器端所有的行為。其下面的子包都是這些接口的具體實現(xiàn)。
- INotificationService: 定義了通告服務(wù)的服務(wù)方法。
- IProduct: 定義了商品類的基本行為。
- IProductInfoService: 定義了商品信息服務(wù)的方法。
- NotificationServiceSoapBindingSkeleton: 該類是由Apache Axis工具產(chǎn)生的服務(wù)端的框架類。
- ProductInfoServiceSoapBindingSkeleton: 該類是由Apache Axis工具產(chǎn)生的服務(wù)端的框架類。
demo.productinfo.serverside.product
在這個包內(nèi)全部是具體的商品類。
- ProductTemplate: 該類是實現(xiàn)了IProduct接口的模板類。它包含了一些最基本的參數(shù)和方法。提供給其他的具體商品類繼承之用。還有許多的商品類,這里就不一一羅列了。
demo.productinfo.serverside.serviceimplement
在這個包內(nèi)是所有Web服務(wù)的具體實現(xiàn)類。
- NotificationServiceImp: 該類是數(shù)據(jù)同步服務(wù)的具體實現(xiàn)。
- ProductInfoImp: 商品查詢服務(wù)的具體實現(xiàn)。
- ServerNotificationHandlerImp: 服務(wù)端數(shù)據(jù)同步處理器。
業(yè)務(wù)邏輯代理模式的實現(xiàn)
根據(jù)J2EE系統(tǒng)設(shè)計模式,業(yè)務(wù)邏輯代理通過向表現(xiàn)層隱藏了低層的實現(xiàn),有效的減低表現(xiàn)層和商務(wù)邏輯層的耦合度,例如查找和調(diào)用EJB的細節(jié)。然而在本程序中,這種業(yè)務(wù)邏輯代理類對外隱藏了查找、調(diào)用Web服務(wù)和緩沖數(shù)據(jù)的復雜細節(jié)。
IProductInfoService接口類為客戶端提供了關(guān)于商品信息的所有服務(wù)方法的描述。每個客戶端必須自己處理SOAP 調(diào)用的查找、調(diào)用和緩沖。ProductInfoServiceDelegate類,從它的名字可知,它就是客戶端的代理類。(在例子中,這個代理類并沒有實現(xiàn)所有的Web服務(wù)方法調(diào)用,有興趣的朋友可以自己補充完整)。
ProductInfoServiceDelegate類首先初始化一個IProductInfoService接口的實例,并且通過它來處理一切與底層SOAP相關(guān)的操作。然后,實例化一個SimpleCache類的靜態(tài)實例,通過在所有的ProductInfoServiceDelegate類實例間共享這個實例來處理所有緩沖信息的操作。同時ProductInfoServiceDelegate類還包含一個DataChangeListener引用,用于觸發(fā)注冊的UI控件的界面更新事件,這里只是一個簡單的實現(xiàn),因此,一個ProductInfoServiceDelegate僅僅對應一個UI 控件,可以擴展為一個EventListenerList類,從而一個ProductInfoServiceDelegate可以擁有多個UI監(jiān)聽(但是更新UI又是一件危險的事,要注意只能在UI事件處理線程上刷新UI)。最后,ProductInfoServiceDelegate還實現(xiàn)了CacheExpiredListener接口,使它可以作為ClientNotificationHandler類的監(jiān)聽者,監(jiān)聽緩沖更新事件,以及時觸發(fā)DataChange事件。
下面是ProductInfoServiceDelegate類的一些關(guān)鍵代碼:
/**
*<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();
}
|
下面是操作流程圖:
由上可以清楚的看到,使用業(yè)務(wù)代理模式可以使程序員不必去關(guān)心底層的通信細節(jié),而把注意力完全集中在實現(xiàn)商業(yè)邏輯上來。
緩沖管理模式的實現(xiàn)
當調(diào)用業(yè)務(wù)代理類的getStaff ()方法時,會首先試圖從緩沖區(qū)中讀取,如果無法找到,再向服務(wù)器端發(fā)出SOAP請求。當一個客戶端程序添加或者更新了商品信息,它會首先調(diào)用Web服務(wù)方法更新服務(wù)器上的信息。這會觸發(fā)服務(wù)端的dataChanged事件,導致所有的客戶端都要更新本地緩沖區(qū)。然后,它在自己的本地緩沖區(qū)添加或更新該記錄。其它的服務(wù)方法,也是一樣的。目標就是將發(fā)送SOAP請求的次數(shù)減到最少。
根據(jù)緩沖管理模式,SimpleCache類應該包含兩個方法,分別是addObject()和fetchObject()。一個CacheManager類通過ObjectKey來管理緩沖的信息。在本實現(xiàn)中,業(yè)務(wù)代理類就相當于CacheManager,這是由于每一個業(yè)務(wù)代理管理屬于自己的SimpleCache。而ProductTemplate類實例就相當于ObjectKey。至于Cache的內(nèi)部數(shù)據(jù)結(jié)構(gòu),出于簡單的原因,采用了Hashtable。
現(xiàn)實中的程序里,可以通過擴展SimpleCache類,用更加嚴謹?shù)膬?nèi)存管理機制輕易的替換這種基于Hashtable的簡單實現(xiàn)(例如每次僅僅過期那些很少使用的緩沖信息),可以通過統(tǒng)計信息的點擊次數(shù)或者復雜度來決定將要清除那些緩沖信息。可以利用Java的特性,如弱引用等來提高JVM的垃圾回收機制對被緩沖對象的清理效率。還可以通過創(chuàng)建一個全局的緩沖實例來統(tǒng)一管理整個客戶端應用程序的緩沖信息。
總之,優(yōu)化的方法是有很多很多的。
下面是,Cache類的簡單實現(xiàn)代碼:
/**
* 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();
}
|
下面的時序圖演示了數(shù)據(jù)是如何被緩沖和緩沖是如何被更新的:
自動數(shù)據(jù)同步的實現(xiàn)
在這個例子程序中,所有的客戶端都是自動同步數(shù)據(jù)的,也就是說一個客戶端程序更新或添加了數(shù)據(jù),馬上就會在其它所有的客戶端中體現(xiàn)出來,這個功能是通過通告處理機制實現(xiàn)的。在這個示例程序中,有兩個數(shù)據(jù)同步處理類,分別是運行在服務(wù)器端的ServerNotificationHandlerImp和運行在客戶端的ClientNotificationHandler。
下面將詳細介紹,在這個程序中是怎樣實現(xiàn)數(shù)據(jù)同步的。
眾所周知,基于HTTP協(xié)議的SOAP是無法支持雙向傳播(Bi-directional Communication)的,可是又必須當服務(wù)端發(fā)生變化時,讓所有的客戶端程序接收到通告(例如,當其中一個客戶端向服務(wù)端數(shù)據(jù)庫添加了一條商品信息后,所有其它的客戶端都應該收到通告,以更新自己的數(shù)據(jù)視圖)。
根據(jù)SOAP 綁定協(xié)議可以輕易的將底層的通信協(xié)議換成雙向傳播協(xié)議,如BEEP (Blocks Extensible Exchange Protocol)。但是這樣將失去HTTP協(xié)議的簡單性,可擴展性和防火墻可穿透性。
因此,這里采用了阻塞的Web服務(wù)調(diào)用。為此專門提供了一個Web服務(wù)--NotificationService。所有調(diào)用該服務(wù)的客戶端線程都會被服務(wù)端的ServerNotificationHandler Singleton類實例的isDataChanged方法阻塞,直到數(shù)據(jù)改變事件觸發(fā),該Web服務(wù)調(diào)用才會返回。在本例中,DataChanged是由另一個Web服務(wù) --- ProductInfoImp觸發(fā)的。
下面是ServerNotificationHandler類的兩個關(guān)鍵方法:
public synchronized boolean isDataChanged() {
while(true) {
try {
wait();
break;
}
catch (Exception ignored) {
}
}
return true;
}
public synchronized void dataChanged() {
notifyAll();
}
|
在客戶端,一個Singleton的ClientNotificationHandler單獨運行在一個名為ClientNotificationHandlerThread的線程上。每個客戶端都會創(chuàng)建這樣一個線程,在這個線程上通過NotificationServiceDelegate代理類調(diào)用服務(wù)端的INotificationService服務(wù)的isDataChanged方法,并且被阻塞,只有當ServerNotificationHandlerImp的dataChanged方法被調(diào)用時,執(zhí)行了Java線程的notifyAll操作才能返回。當該調(diào)用返回時,ClientNotificationHandler將注冊的本地Cache全部過期,也就是觸發(fā)了緩沖更新的事件。
下面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監(jiān)視SOAP Call的截圖:
由上圖可以清晰的看到,有一個SOAP調(diào)用一直沒有結(jié)束,總是保持Active的狀態(tài),這就是對NotificationService的調(diào)用。當它返回的時候,必然會觸發(fā)一個getAllQuotes的SOAP調(diào)用,然后又會有一個Active的連接。
這種方法也是有一定代價的。主要體現(xiàn)在它要求每一個客戶端都必須起一個線程來等待接收通告,而在服務(wù)器端對于每一個注冊的客戶端連結(jié)都有一個線程阻塞的等待發(fā)出通告。絕大多數(shù)的企業(yè)級應用程序服務(wù)器都可以游刃有余的對應幾百個客戶端。但當同時有上千個客戶端連結(jié)的時候,那又是另一回事了。這時候,只有采用非阻塞調(diào)用,ServerNotificationHandler將通知事件存儲到隊列中去,同時客戶端的ClientNotificationHandler周期性的調(diào)用NotificationService服務(wù)。并且出于演示的原因,這個例子的數(shù)據(jù)同步模塊已經(jīng)被盡量的簡化。真正的實現(xiàn)并不會總是使所有客戶端的Cache全部過期。一個更具魯棒性的設(shè)計可以讓服務(wù)端的ServerNotificationHandlerImp傳遞需要被更新Cache數(shù)據(jù)的信息,甚至可以詳細到哪一個對象實例需要被更新。這樣一來,ClientNotificationHandler可以調(diào)用更加有效的代理類方法(如expiredOneCache),來更新局部的Cache信息。
下圖展示了數(shù)據(jù)同步模塊的工作流程圖:
客戶端界面
客戶端主要兩個功能組件,第一個是StaffInfoViewPanel用于顯示最新的商品信息。另一個是DataEditPanel用于更新和添加商品信息。這些GUI組件都是通過封裝的很好的ProductInfoServiceDelegate代理類來與后臺通信的。
下面是運行時界面截圖:
可以清楚的看出,當一個客戶端程序更新后臺數(shù)據(jù)庫的時候,其它的客戶端也會實時的更新自己的數(shù)據(jù)視圖。
具體實現(xiàn)中遇到的問題
本文所帶的例子,是在Eclipse 2.1IDE上開發(fā)完成的,SOAP引擎選用了Apache Axis1.1 Final。這些工具提供了十分快捷的開發(fā)方式。下面僅對開發(fā)過程中遇到的一些代表性的問題進行簡單分析。
-
如何用SOAP傳遞Hashtable
這個問題在Apache Axis的官方 Mail List上,一直是一個非常火的問題。由于本文的例子也采用Hashtable這種數(shù)據(jù)結(jié)構(gòu),所以在此描述一下如何解決這個問題。首先,本例中傳送的Hashtable的key為字符串型,value為StockQuote類型,其中StockQuote類是符合標準JavaBean規(guī)則的。因此,可以使用Axis自帶的BeanSerializer和BeanDeserializer,但是在發(fā)布WebService的時候,必須配置服務(wù)端的序列化器,如下所示:
<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/>節(jié)點,必須顯示的為ProductTemplate類指定序列化器和反序列化器。否則,服務(wù)端會拋出org.xml.sax.SAXException異常。
在客戶端,可以直接使用通過Axis自帶的WSDL2JAVA工具產(chǎn)生的可序列化的ProductTemplate類,也可以使用自己的ProductTemplate類,只要嚴格符合JavaBean標準規(guī)范。
-
如何調(diào)整后臺服務(wù)的生存周期
由于本例子程序,沒有采用數(shù)據(jù)庫,而只是簡單的使用Hashtable做為存儲商品信息的數(shù)據(jù)結(jié)構(gòu)。如何保證數(shù)據(jù)的持久性呢?一般的Web服務(wù)只提供無狀態(tài)的服務(wù),類似于每一次客戶端的SOAP調(diào)用,服務(wù)器端都會創(chuàng)建一個新的類實例來進行處理,你可以把它想象成Stateless Session Bean。為此,Apache Axis支持簡單的三個級別的后臺服務(wù),分別為:Request、Session和Application。Request級別是默認的選項,它代表每次SOAP請求,都會創(chuàng)建一個新的對象來處理。Session級別代表客戶端的每個Session有一個對應的后臺對象進行處理。Application級別代表所有客戶端的SOAP請求都會共享同一個后臺服務(wù)實例對象,并且要由程序員自己來保證其數(shù)據(jù)的同步問題。在本例子中就是采用了Application級的服務(wù)。
實施的過程如下:
服務(wù)端:在WSDD文件中的每個服務(wù)的描述下,添加這樣一行:
<parameter name="scope" value="Application"/>
|
-
如何避免Session Timeout
Apache Axis1.1 Final的一個極具爭議性的改變就是將原本的無Session Timeout時間限制,默認設(shè)置為60秒。這對例子程序有很大的影響,因為例子中每個客戶端都需要與Server保持一個長連接,如果有超時設(shè)定,那么這個假設(shè)將會被破壞。因此,必須取消超時限制。取消方法如下:
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秒的設(shè)定。讓我們拭目以待吧!

 |

|
總結(jié)
Web服務(wù)雖然已經(jīng)取得了長足的發(fā)展,涌現(xiàn)出大量設(shè)計優(yōu)良的SOAP引擎,如Apache Axis等,它們采用最新的技術(shù)在底層效率上已經(jīng)做出了很大提高,但是由于SOAP協(xié)議的一些固有的特性,例如序列化和反序列化效率,傳輸效率等等問題,整體的效果仍然不盡如人意。但也不需擔心,利用設(shè)計模式,可以解決大部分的問題。我們有理由相信它美好的未來。
本文中,在設(shè)計模式的幫助下,實現(xiàn)了一個透明的客戶端緩沖SOAP調(diào)用的機制。現(xiàn)實中的程序還必須充分考慮到數(shù)據(jù)庫持久性,全局Cache管理,資源回收,與其他Web服務(wù)平臺的互操作性,安全性和更加有效的異常處理等問題。
|