見資源 |
by David Chappell and Tony Hong
直到最近,Web services的用途一直集中于通過HTTP實現的同步請求/回應模式(request/reply model)。這是很自然的,因為Web services的第一個用例就是集中在Remote Procedure Call(RPC)和分布式組件交互的。然而,Web services的這種應用情況忽視了當今IT前景的一個重大的方面:異步消息傳遞(asynchronous messaging),它有很大的價值,因為它給數據傳輸和處理帶來了一定的可靠性和靈活性,這些性能是同步消息傳遞很難實現的。通過異步消息傳輸機制(如JMS),把異步消息傳遞同SOAP結合起來就可以將如今的Web services標準應用到更廣的領域。
將這種技術結合用于重要的企業集成環境是最受歡迎的了。該領域一直是由Electronic Data Interchange (EDI)、Enterprise Application Integration(EAI)和B2B統治的——然而IT社團一直在尋找成本更低、更高效、更基于標準的方法。在這些領域中,用即時的同步RPC來換取一些功能會讓我們更滿意,這些功能包括可靠的傳遞能力、更適合面向企業交易的松散耦合的、粗粒度的(coarse-grained)接口;面向文檔的異步交互和對通訊協議和傳輸機制的支持。
讓我們看看JMS是如何成為一種可靠的異步SOAP消息傳遞的方法的。我們也將探討如何將JMS用于可靠的Web services調用。(你可以在這里下載樣例代碼;注意,你需要接受一個許可協議并在www.xmethods.net網站上注冊。)
![]() |
|
可靠的異步通訊是指,為了保持整個環境的健全,系統的所有方面不需要在任何特定的時候都是可用的。即使對于一個簡單的兩方客戶端/服務(client/service)交互來說,運用可靠的異步通訊就意味著,當客戶端對服務提出請求時,被調用的服務不需要是可用的。客戶端將請求傳遞到一個可靠的異步傳輸機制中,該請求暫時不能與服務進行對話。然后客戶端本身變成不可用的,當服務成為可用的時,就可以進行服務調用了。
處于等待狀態的(pending)交互
異步交互也不需要客戶端等待響應。一個Web service端點(end point)可以代表一個應用程序,它需要一些時間來執行計算、查找或轉換等任務。對一個服務提出異步請求的客戶端可以繼續執行其它任務,同時其Web service交互處于等待狀態。這就提高了并行性(concurrency)和可擴展性。
細粒度的RPC型的接口需要每個應用程序和服務都知道它們是如何同其它應用程序和服務進行通訊的。例如,一個訂單接口上可能有諸如getQuantity()、getTotal()或getBillToAddress()等方法。
粗粒度、文本型(document-style)的接口是指,每個應用程序或服務只需要關心封裝的XML文檔,該XML文檔包含所有相關的信息,服務可以與其它感興趣者共享這些信息,并可以以SOAP envelope的形式將這些信息發送給他們。同樣,在使用服務時,我們可以從XML文檔選擇相關的數據(例如<ShipTo>地址),進行相應的處理,然后將結果發送到其它目的地。當你將異步交互同文本型、粗粒度的接口結合起來時,你就為消息傳遞形式的調用做好了主要準備了。
![]() |
|
Java 2 Platform、Enterprise Edition(J2EE)架構圖總是將JMS看做是個隱藏在防火墻后面的組件,通過它我們在一個JSP servlet引擎和消息驅動的bean之間異步地傳遞消息。如果你在制作Web頁面并將它們同中間層商業邏輯結合起來,那么這個架構就是個有效的用例,但這并不是運用消息傳遞的唯一的方式。
JMS消息傳遞機制是跨Internet傳遞數據、SOAP或其它信息的一個完全可行的解決方案。一個JMS機制可以在哪里部署以及如何部署的細節取決于供應商。許多JMS供應商都提供某種通道性能(tunneling capabilities)。例如,在SonicMQ例子中,JMS客戶端到JMS服務器的通訊可以很容易地跨越公開的Internet、跨越防火墻、以一種運用HTTP、HTTPS、SSL或TCP sockets的安全的、可靠的方式進行。
將JMS作為一種SOAP傳輸機制
自1998年被引進到業界以來,JMS就作為一種基于標準的消息傳遞方法,在應用程序之間傳輸SOAP消息。它有五種消息類型,可以傳遞任何類型的數據(基于XML或不基于XML)。XML數據可以很容易地作為一個JMS BytesMessage中的二元數據或作為TextMessage中的串數據(string data)來傳遞。
不管對話兩端運用何種API,我們把傳輸層上運用的JMS看做是除了普通的、老的HTTP傳輸之外的另一種可選方法。在我們的例子中,我們將JMS的API和Apache SOAP結合起來了——運用JMS API來連接、發送和接收消息,運用Apache SOAP來建構(construct)和析構(deconstruct)SOAP envelope。在不久的將來,JMS將作為一個傳輸層被嵌入到Apache Axis中。JMS將會成為一個部署選項。
JMS也有一個內置的同步請求/回應模式。這個請求/回應模式也可以異步地工作。請求者不需要block請求,等待即時的響應。響應可以在稍后的時間異步地發送。JMS可以使最初的請求消息同相應的響應消息關聯起來,即使請求和響應在時間上是分離的,或者即使請求過程在失敗后又恢復正常。
XMethods中列出的BabelFish服務給AltaVista的很受歡迎的BabelFish翻譯工具提供了一個典型的PRC SOAP接口(見資源)。下面我們來看一下這個Web service的一個異步版本,稱為AsynchBabelFish。
從根本上說,該服務主要是異步交換SOAP文檔。客戶端給服務發送一個AsynchBabelFish轉換請求文檔,一段時間后,服務將一個AsynchBabelFish轉換結果文檔發送回客戶端。交換是在消息隊列上完成的。從這個簡單的例子,我們可以看到的一個明顯的好處就是,即使服務出了問題,客戶端也不會受影響;它可以將請求放到服務隊列中,該請求是肯定可以被完成的——即使不是在現在,在稍后時間服務恢復正常時就可以完成。而且,運用一個異步模式也可以給客戶端和服務器提供很多的靈活性,使它們可以控制消息的處理速度,例如,服務器可以從服務隊列取出消息,按自己的意愿隨時處理它們。
在客戶端和AsynchBabelFish服務之間可能交換了三個文檔:從客戶端發送到服務的轉換請求文檔、從服務發送回客戶端的轉換結果文檔、以及如果請求出了問題或如果轉換請求由于某種原因不能完成,發送回客戶端的一個替代結果文檔的SOAP fault envelope。
要對AsynchBabelFish服務提出一個請求,客戶端需要將轉換請求文檔創建成一個字符串,并將它作為一個TextMessage列在AsynchBabelFish服務請求隊列中。Envelope采用的形式見列表1。
轉換請求envelope的body部分包含SourceText元素,它帶有用戶想要轉換的文本。Xml:lang屬性為文本語言指定了編碼,traslateTo屬性為用戶想將文本轉換到的語言指定了編碼。所支持的語言包括English(en)、German(de)、French(fr)、Italian(it)和Portuguese(pt)。對于源文(source text),有150個字的限制。
注意SOAP header中運用的WS-Security header元素。AsynchBabelFish服務要求對XMethods用戶ID/密碼數據庫進行驗證,該元素用來提供請求者的身份憑證(credentials)。(關于WS-Security的更多信息,請參閱資源。)轉換結果envelope的body部分包含兩個子元素,SourceText和TranslationText。例如:
<?xml version="1.0" encoding= "UTF-8"?> <soap:Envelope xmlns:soap= "http://schemas.xmlsoap.org/ soap/envelope/"> <soap:Body> <SourceText xml:lang="en" xmlns:xml=http://www.w3.org/ XML/1998/namespace xmlns="http://xmethods.net/ babelfish">hello</SourceText> <TranslationText xml:lang="fr" xmlns:xml= "http://www.w3.org/XML/1998/ namespace" xmlns="http://xmethods.net/ babelfish">bonjour </TranslationText> </soap:Body> </soap:Envelope> |
SourceText只是重復傳遞到請求的內容。TranslationText包含轉換的文本及其語言的xml:lang指示符。如果請求envelope有問題,從而不可能完成請求,就會返回一個標準的SOAP fault envelope來替代轉換結果文檔。下面的例子顯示的是SOAP fault envelope;如果請求消息帶有的TextMessage不是格式規范的XML,那么這個特殊的fault就會被發送回客戶端:
<?xml version='1.0' encoding= 'UTF-8'?> <soap:Envelope xmlns:soap= 'http://schemas.xmlsoap.org/ soap/envelope/'> <soap:Body> <soap:Fault> <faultcode>soap:Client </faultcode> <faultstring>Incoming text is not XML</faultstring> </soap:Fault> </soap:Body> </soap:Envelope> |
通過一個基于JMS的Web service(如AsynchBabelFish),客戶端和服務器就不是直接進行交互的了;取而代之的是,SOAP請求可以通過消息隊列在客戶端和服務器之間進行傳遞,而不受時間的影響(time-independent)。
現在,讓我們看看一個消息是如何從客戶端傳遞到服務器的(見圖3)。客戶端創建一個轉換請求envelope,并將它放在該服務的請求隊列中。服務的所有客戶端都可以運用這個眾所周知的公用隊列,如同基于HTTP的服務運用固定的、眾所周知的HTTP端點URL一樣。客戶端也給請求消息提供了一個reply-to JMS header,它包含一個響應隊列的名字,從而讓服務知道將響應消息發送到哪里。AsynchBabelFish服務過程是否立即完成同該步驟(將請求放入隊列)是無關的。
![]() |
|
嘗試
服務將轉換結果文檔(或SOAP fault)放在JMS reply-to指定的響應隊列中。它包含JMS請求消息的ID,并設置相關的響應消息ID來匹配它,因此客戶端就可以根據其意愿進行相應的請求/響應操作了。然后,在需要的時候,客戶端就可以從響應隊列得到完成的轉換結果文檔了(或SOAP fault)。
![]() |
|
TranslatorRequestor可以讓你創建請求并將它發送到AsynchBabelFish請求隊列中(見列表2)。其運行如下所示:
java asynchBabelFish. TranslatorRequestor <username> <password> <text> <fromLanguage> <toLanguage> <responseQueue> |
讓我們來看看TranslatorRequestor的一些重點。首先,程序實例化一個到AsynchBabelFish服務隊列的JMS連接。然后實例化一個RequestEnvelope對象:
RequestEnvelope requestEnv= new RequestEnvelope(userID, password,sourceText, sourceLanguage,toLanguage); |
RequestEnvelope對象帶有你傳遞的參數并在內部構造一個SOAP envelope。然后,當你調用它的toString()方法時,它就會以字符串形式給你返回一個相應的轉換請求SOAP文檔。我們用這個字符串創建一個JMS TextMessage對象:
TextMessage message= queueSession.createTextMessage( requestEnv.toString()); |
將響應隊列的句柄(handle)添加到消息的JMS reply-to header:
// Get a handle to the reply-to // queue Queue replyToQueue=queueSession. createQueue(replyToQueueName); // and add it to the reply-to // field message.setJMSReplyTo( replyToQueue); |
形成的結果消息就會在控制臺打印出來并發送到服務隊列。在這個例子中,你可以看到,被調用的TranslatorRequestor程序用了一個我們設置的測試帳號“joe”和一個測試響應隊列(testResponseQueue)(見圖5)。
![]() |
|
java asynchBabelFish. ReplyProcessor <username> <password> <responseQueue> |
程序首先建立一個到命令行參數所指定的響應隊列的JMS連接。然后它將自己注冊為那個隊列的一個listener。在典型的JMS消息傳遞模式中,傳送的消息被反饋到ReplyProcessor 的onMessage()方法。
得到有趣的信息
對于進入響應隊列的每個新消息,onMessage()都從消息中讀取SOAP envelope(以一個TextMessage的形式),然后將來自TextMessage的字符串傳遞到ResponseEnvelope構造器中:
ResponseEnvelope response= new ResponseEnvelope( ((TextMessage)message). getText()); |
ResponseEnvelope(見列表4)是個輔助類(helper class),它可以讓我們更容易地處理轉換結果文檔。它的構造器以字符串形式讀取SOAP XML,并實例化一個Apache SOAP Envelope對象,它用get()方法處理這個對象來提取有趣的參數(源文本、轉換文本等等)。它的toString()方法也可以讓你得到SOAP消息的源XML文檔。然后響應消息和SOAP文檔本身的相關ID(correlation ID)就被打印出來了:
// The correlation ID matches the // JMS Message ID of the original // request message System.out.println("\nResponse: JMS Correlation ID = "+message. getJMSCorrelationID()+"\n"); // Print out the response SOAP // envelope System.out.println(response); |
最后,從轉換結果文檔中打印出我們感興趣的四個參數:源文本、源語言、轉換文本和轉換語言:
// And print out a summary of the // translation. System.out.println("\""+response. getSourceText()+"\" ( "+response.getSourceLanguage() +") translates to \""+response. getTranslationText()+"\" ( "+response. getTranslationLanguage() +")\n\n");; |
如果傳送的消息的確產生了一個SOAP fault,而不是一個有效的轉換結果文檔,那么要實例化ResponseEnvelope就會拋出一個異常,會從SOAP fault中形成錯誤字符串,并打印出對那個異常的堆棧跟蹤(stack trace)(見列表5)。當程序對我們先前用TranslatorRequestor發送的請求的響應文檔進行處理時,你就可以看到程序的結果(見圖6)。
![]() |
|
不管你選擇哪種SOAP客戶端類庫,你都需要考慮如何運用異步消息傳遞來在企業內部或跨企業實現有效的數據傳輸。AsynchBabelFish例子可能并不是你在日常工作中使用或實現的那種服務,但它的確說明了SOAP是如何同JMS整合在一起的。
關于作者:
David Chappel是Sonic Software的副總和主要技術傳播者。他也是Java Web Services (O’Reilly & Associates, 2002)、Professional ebXML Foundations (Wrox, 2001)和The Java Message Service (O’Reilly & Associates, 2000)的合著者。最近,他獲得了Java Pro雜志的“Java Community個人杰出貢獻獎”(請訪問www.sonicsoftware.com)。Tony Hong是Xmethods的合著者,是Web services目錄的出版商(請訪問www.xmethods.net)。Dave的聯系方式是chappell@sonicsoftware.com,Tony的聯系方式是thong@xmethods.net。