2006 年 9 月 11 日
面向服務(wù)的體系結(jié)構(gòu)(Service-Oriented Architectures,SOA)當(dāng)前強(qiáng)調(diào)的重點在 Web 服務(wù)上,但很容易被所傳播的各種信息搞得昏頭轉(zhuǎn)向。本系列教程將對主要 Web 服務(wù)規(guī)范進(jìn)行全面說明,從簡單對象訪問協(xié)議(Simple Object Access Protocol,SOAP)一直介紹到 WS Business Process Execution Language (WS-BPEL)。本教程將介紹 Web 服務(wù)和 SOAP 基本概念,并說明如何構(gòu)建 SOAP 服務(wù)器和客戶機(jī)。
開始之前
本系列教程將說明構(gòu)建一家假想的報社 Daily Moon 的基于 Web 訪問的工作流系統(tǒng)的詳細(xì)步驟。主要針對的是希望了解有關(guān) Web 服務(wù)的更多信息的開發(fā)人員,以便他們能更為高效地創(chuàng)建應(yīng)用程序。非技術(shù)用戶也可以從中獲得一些好處,因為在討論如何編程前對相關(guān)概念進(jìn)行了解釋。
您應(yīng)該具有基本編程知識,如果希望按照實際編程示例進(jìn)行操作,還需要一定的 Java 知識。我們將討論 XML(但沒有必要對其進(jìn)行深入討論),并將涵蓋任何必要的概念。
關(guān)于本系列
本系列教程以假想的報社 Daily Moon 為例,為了提高在競爭激烈的環(huán)境中的生產(chǎn)力,其員工將使用各種 Web 服務(wù)來創(chuàng)建工作流系統(tǒng),我們將在此過程中講述各個 Web 服務(wù)基本概念。
第 1 部分比較簡單,將說明 Web 服務(wù)背后的基本概念,并演示如何使用 SOAP(以后要討論的大部分內(nèi)容的基礎(chǔ)規(guī)范)來將 Classifieds Department 連接到內(nèi)容管理系統(tǒng)。
本系列以后的部分將以這些基本概念為基礎(chǔ):
- 第 2 部分進(jìn)一步深入說明如何使用 Web 服務(wù)描述語言(Web Services Description Language,WSDL)定義 Web 服務(wù)預(yù)期產(chǎn)生的消息,從而使團(tuán)隊更方便地創(chuàng)建服務(wù)以及連接到服務(wù)的客戶機(jī)。
- 在第 3 部分中,團(tuán)隊希望準(zhǔn)備一系列服務(wù),并希望能方便地查找這些服務(wù)。與此對應(yīng),統(tǒng)一描述、發(fā)現(xiàn)和集成(Universal Description, Discovery and Integration,UDDI)提供了可用服務(wù)的可搜索注冊中心,以便將自己的服務(wù)發(fā)布給其他人。
- 第 4 部分和第 5 部分討論 WS-Security 和 WS-Policy,將詳細(xì)說明如何保證該報社的服務(wù)的安全,以及團(tuán)隊為了訪問這些剛提供了安全保護(hù)的服務(wù)需要進(jìn)行哪些更改。
- 第 6 部分重點討論的是互操作性,因為必須從單個系統(tǒng)訪問來自幾個不同實現(xiàn)的服務(wù)。第 6 部分討論了在 WS-I 證書中涉及的要求和測試。
- 最后,第 7 部分演示了如何使用業(yè)務(wù)流程執(zhí)行語言(Business Process Execution Language,WS-BPEL)來從各個服務(wù)創(chuàng)建復(fù)雜應(yīng)用程序。
接下來讓我們更為詳細(xì)了解一下本教程中將討論的內(nèi)容。
關(guān)于本教程
本教程將向您以及假想的 Daily Moon Classifieds Department 介紹 Web 服務(wù)的概念。您將了解該團(tuán)隊如何與現(xiàn)有 Web 服務(wù)系統(tǒng)集成以及如何創(chuàng)建服務(wù)的詳細(xì)過程。重點將是簡單對象訪問協(xié)議 (SOAP)。
在本教程中,您將了解以下內(nèi)容:
- Web 服務(wù)的基本概念
- XML 的基本知識
- SOAP 消息的結(jié)構(gòu)和用途
- 如何安裝應(yīng)用服務(wù)器以在其上運行 Web 服務(wù)應(yīng)用程序。
- 如何將 Web 服務(wù)實現(xiàn)安裝到應(yīng)用服務(wù)器中
- 如何以編程方式創(chuàng)建 SOAP 消息。
- 如果使用 Java 和 Apache Axis2 為基于 SOAP 的 Web 服務(wù)創(chuàng)建客戶機(jī)
- 如何使用 Java 和 Apache Axis2 創(chuàng)建基于 SOAP 的 Web 服務(wù)
在本教程中,Classifieds Department 將通過創(chuàng)建客戶機(jī)來與內(nèi)容管理系統(tǒng)集成。您還將了解該部門與之交互的一個服務(wù)的創(chuàng)建過程。編程示例采用 Java 語言,并使用了 Apache Axis2 項目,但其中的概念幾乎適用于任何語言和環(huán)境。
先決條件
為了按照本教程所示的處理代碼,您需要有以下軟件:
- Apache Geronimo 或其他應(yīng)用服務(wù)器。您將在本教程中創(chuàng)建各種 Web 服務(wù),需要在其上運行這些服務(wù)的應(yīng)用程序。當(dāng)然,由于 Web 服務(wù)應(yīng)當(dāng)可互操作,因此使用何種應(yīng)用服務(wù)器并不重要。在本教程中,我們將演示如何安裝和使用 Apache Geronimo(IBM? WebSphere? Community Edition 也是構(gòu)建于其上的)。還可以使用 WebSphere Application Server 等其他應(yīng)用服務(wù)器。可以從 Apache Geronimo Downloads 站點下載 Apache Geronimo。
- Apache Axis2 或其他 SOAP 實現(xiàn)??梢允謩觿?chuàng)建 SOAP 消息,也可以手動對其進(jìn)行解釋,但手邊如果有一個可用實現(xiàn)就會方便得多。您將使用的是 Apache Axis2,其中包含了各種 SOAP 相關(guān)的 API,可極大地簡化您的工作??梢栽谝韵戮W(wǎng)址下載 Apache Axis2:Apache.org。本教程使用的是 0.94 版,但應(yīng)該也能使用更高版本。
- Java? 2 Standard Edition 1.4 或更高版本。這兩個工具都基于 Java,和將在本教程中構(gòu)建的服務(wù)和客戶機(jī)一樣??梢詮?a href="http://java./j2se/1.5.0/download.jsp">此處下載 J2SE SDK。
- 另外,還需要 Web 瀏覽器和文本編輯器,但我想您已經(jīng)有了這兩個工具。如果愿意,還可以使用 Eclipse 之類的 IDE,但由于本教程的重點是技術(shù)而不是工具,因此我將使用文本編輯器和命令行來編輯和編譯文件。
什么是 Web 服務(wù)?
首先,讓我們從總體上了解一下什么是 Web 服務(wù),以及它們?yōu)楹螌浖_發(fā)重要。
究竟為什么重要呢?
如果您沒有聽說過有關(guān)面向服務(wù)的體系結(jié)構(gòu) (SOA) 和 Web 服務(wù)的大量信息,您就不會閱讀本文,那么問題就應(yīng)該是,為什么此內(nèi)容這樣重要?答案是,此內(nèi)容之所以重要,是因為這是應(yīng)用程序彼此進(jìn)行通信的方式的典型變化。SOAs 已經(jīng)存在很長很長時間了。SOA 最初主要由中間件應(yīng)用程序組成,至少進(jìn)行連接的兩端都屬于同一種類型的中間件。另一方面,Web 服務(wù)由一組標(biāo)準(zhǔn)組成,用于在不需要特定類型的中間件、編程語言甚至操作系統(tǒng)的前提下讓各種不同的系統(tǒng)進(jìn)行通信。接下來,讓我們了解一下其發(fā)展的歷程。
傳統(tǒng)應(yīng)用程序
首先從計算機(jī)發(fā)明開始,當(dāng)時給人感覺非常不錯。計算機(jī)能執(zhí)行奇跡般的任務(wù),可實現(xiàn)很多手動工作的自動化,包括復(fù)雜的計算、財務(wù)工作等等很多其他任務(wù)。
但傳統(tǒng)應(yīng)用程序是“豎井”(Silo) 型的。人力資源應(yīng)用程序無法與財務(wù)應(yīng)用程序真正通信,而后者又無法和分布應(yīng)用程序進(jìn)行真正的通信。所有這些應(yīng)用程序都有獨立的領(lǐng)域,在獨立的計算機(jī)上運行,盡管很有用,但并不能很好地在彼此間共享數(shù)據(jù)。當(dāng)時可以選擇對批處理流程進(jìn)行連接,以將數(shù)據(jù)從一個系統(tǒng)移動到另一個系統(tǒng),但這并不適合進(jìn)行實時集成。
分布式計算
在我們的進(jìn)化鏈中的第二步是分布式計算。分布式計算允許不同的應(yīng)用程序彼此進(jìn)行通信(即使位于不同的計算機(jī)上也是如此)。CORBA、MTS 和 Enterprise Java Bean (EJB) 等技術(shù)提供了包含各種類別的注冊中心的系統(tǒng),因此應(yīng)用程序可以找到其希望與之進(jìn)行交互的組件,然后像調(diào)用本地的組件一樣調(diào)用這些組件。
這些系統(tǒng)由可同時滿足這兩個要求的中間件(或更具體一些,面向消息的中間件)提供支持。現(xiàn)在能以特定的方式構(gòu)建應(yīng)用程序,即使位于不同的地理位置,也能訪問其他系統(tǒng)上的資源。
但仍然有一個問題。雖然系統(tǒng)可以自由地與系統(tǒng)內(nèi)的任何對象進(jìn)行通信,但仍然是一個封閉的系統(tǒng)。至少,客戶機(jī)應(yīng)用程序必須與服務(wù)器應(yīng)用程序使用相同的技術(shù)。另外,通常并不會將系統(tǒng)設(shè)計為從創(chuàng)建其的個體組織外進(jìn)行訪問。
Web 服務(wù)
此進(jìn)化鏈中下一個幾乎不可避免的鏈接點就是 Web 服務(wù)。“Web 服務(wù)”基于 XML 和 HTTP(大多數(shù)情況下),對很多人具有不同的含義,但在此處,我們要將 Web 服務(wù)作為系統(tǒng)間基于 SOAP 的消息交換進(jìn)行討論。
這些消息由 XML 組成(XML 是一個基于文本的開放標(biāo)準(zhǔn)),可由來自任何應(yīng)用程序(任何設(shè)計為接收此類消息的應(yīng)用程序)的任何人進(jìn)行訪問。這就擴(kuò)展了應(yīng)用程序的范圍,從而包含任何可通過網(wǎng)絡(luò)對其進(jìn)行訪問的任何人。(如果這讓您開始考慮安全問題,不要緊,您將在本系列的第 4 部分了解如何處理這方面的問題。)
基于 SOAP 的 Web 服務(wù)將要發(fā)送與清單 1 中所示類似的 XML 消息。
清單 1. 基于 SOAP 的 Web 服務(wù)
<SOAPenv:Envelope
xmlns:SOAPenv="http://schemas.xmlSOAP.org/SOAP/envelope/"
xmlns:xsd="http://www./2001/XMLSchema"
xmlns:xsi="http://www./2001/XMLSchema-instance">
<SOAPenv:Body>
<req:getNumberOfArticles xmlns:req="http:///CMS/">
<req:category>classifieds</req:category>
</req:getNumberOfArticles>
</SOAPenv:Body>
</SOAPenv:Envelope>
|
這些消息將從一個系統(tǒng)進(jìn)入另一個系統(tǒng)(通常通過 HTTP)。接收系統(tǒng)對消息進(jìn)行解釋,進(jìn)行應(yīng)該進(jìn)行從處理,然后發(fā)送另一個 SOAP 消息作為響應(yīng)。
這個系統(tǒng)很簡單,正因為如此,有很多企業(yè)級計算方面的內(nèi)容都尚未涉及。幸運的是,其中的很多方面已被納入考慮范疇,且具有自己的相關(guān)規(guī)范來確定此事務(wù)應(yīng)如何進(jìn)行,以包含傳統(tǒng)的面向消息的中間件的很多安全和其他方面的內(nèi)容。
其他類型的 Web 服務(wù)
如果我沒有說明 SOAP 并不是唯一的處理 Web 服務(wù)的方法,那就有些失職了。有很多其他基于 XML 的方法用于在系統(tǒng)間發(fā)送信息,其中一些適用于企業(yè)環(huán)境,而另一些則不適合此用途。例如,Amazon 是為公眾提供對其系統(tǒng)的 Web 服務(wù)訪問的第一批基于 Web 服務(wù)的公司之一。Amazon 包含了一個基于 SOAP 的服務(wù),但也提供了一個基于 Representational State Transfer (REST) 的服務(wù)。
REST 是一種 Web 服務(wù)類型,其中,用戶直接訪問 URL,相應(yīng)的響應(yīng)是與清單 2 中所示類似的簡單 XML 文檔。
清單 2. REST 響應(yīng)
<currentArticles>
<category>classifieds</category>
<subcategory>forsale</subcategory>
<article id="888204">
<articleHeadline></articleHeadline>
<articleText>30 ft ladder, only used once. Willing to let
go for half it‘s worth. Has slight dent near the middle.
Harder than a human head. $150 OBO.</articleText>
</article>
<article id="888242">
<articleHeadline></articleHeadline>
<articleText>Vintage 1963 T-Bird. Less than 300 miles.
Driven by my daughter until I took it away. Serious inquires only.
555-3264 after 7 PM.</articleText>
</article>
</currentArticles>
|
這些消息并沒有特定的格式??梢詾槿魏螖?shù)據(jù)。
另一種 Web 服務(wù)類型要使用 XML-RPC 之類的標(biāo)準(zhǔn)。在這種情況下,命令將通過與清單 3 中所示類似的 XML 發(fā)送到系統(tǒng)。
清單 3. XML-RPC
<?xml version="1.0"?>
<methodCall>
<methodName>CMS.getNumberOfArticles</methodName>
<params>
<param>
<value><string>classifieds</string></value>
</param>
<param>
<value><string>forsale</string></value>
</param>
</params>
</methodCall>
|
其響應(yīng)將采用類似的格式。
在學(xué)習(xí)使用 SOAP 的過程中,您可以會打心底里認(rèn)為 REST 和 XML-RPC 比基于 SOAP 的系統(tǒng)要簡單得多。的確如此。在某些情況下 REST 和 XML-RPC 比 SOAP 系統(tǒng)簡單。不過,我們討論的不是在網(wǎng)站上顯示天氣的簡單應(yīng)用程序。我們此處討論的是企業(yè)級應(yīng)用程序,而企業(yè)級的應(yīng)用程序需要企業(yè)級的屬性,如安全、互操作性等等。這些功能在有關(guān)基于 SOAP 的 Web 服務(wù)的其他規(guī)范中進(jìn)行了定義,因而,從長期來看,SOAP 更適合用于企業(yè)級應(yīng)用程序。
讓我們了解一下這些規(guī)范。
基本 Web 服務(wù)規(guī)范
Web 服務(wù)規(guī)范通常歸為兩類:基本 Web 服務(wù)規(guī)范和擴(kuò)展 Web 服務(wù)規(guī)范?;疽?guī)范有:
- SOAP:SOAP 規(guī)范是所有基于 SOAP 的 Web 服務(wù)的基礎(chǔ),詳細(xì)說明了實際消息的格式。其中還詳細(xì)說明了應(yīng)用程序應(yīng)如何對待消息的特定方面(如“Header”中的元素),從而可以創(chuàng)建特定類型的應(yīng)用程序,使其中的消息在達(dá)到最終的目的地前在多個中間層之間進(jìn)行傳遞。本教程將涵蓋 SOAP 規(guī)范的內(nèi)容。
- WDSL:Web 服務(wù)描述語言是詳細(xì)說明描述基于 SOAP 的 Web 服務(wù)的標(biāo)準(zhǔn)方式的規(guī)范,包括消息應(yīng)采用的形式以及應(yīng)將其發(fā)送到何處。其中還詳細(xì)說明了此類消息的響應(yīng)。當(dāng)與相應(yīng)的工具結(jié)合使用時,WSDL 允許以編程方式創(chuàng)建對 Web 服務(wù)的調(diào)用,甚至不用知道所查找的 Web 服務(wù)是什么;應(yīng)用程序可以從 WSDL 文件中提取這些詳細(xì)信息,并提供要使用的編程接口。我們將在本系列的第 2 部分討論 WSDL。
- UDDI:統(tǒng)一描述、發(fā)現(xiàn)和集成 (Universal Description, Discovery and Integration) 是一項從最初提出后發(fā)生了一系列變化的標(biāo)準(zhǔn)。其最初的目的是為了給各個公司提供在全球注冊中心中注冊服務(wù)并在此注冊中心中搜索可能想使用的服務(wù)的機(jī)制。不過,由于很多公司對于將其系統(tǒng)對外開放的問題上都相當(dāng)保守,這個目標(biāo)并沒有完全實現(xiàn)。但是,很多公司已將 UDDI 作為內(nèi)部的服務(wù)及服務(wù)信息注冊中心使用,本系列第 3 部分將對其使用進(jìn)行詳細(xì)論述。
以上是一些基礎(chǔ)知識。另外差不多還有數(shù)十種擴(kuò)展標(biāo)準(zhǔn),可進(jìn)一步擴(kuò)展基于 SOAP 的服務(wù)的用途。
擴(kuò)展 Web 服務(wù)規(guī)范
總共有數(shù)十種 WS-* 規(guī)范,其中幾種對企業(yè)尤為有用。即:
- WS-Security:此規(guī)范處理加密和數(shù)字簽名,允許創(chuàng)建特定類型的應(yīng)用程序,以防止竊聽消息,且能實現(xiàn)不可否認(rèn)功能。本系列的第 4 部分將討論 WS-Security。
- WS-Policy:此規(guī)范對 WS-Security 進(jìn)行了擴(kuò)展,允許更具體地說明誰可以采用何種方式使用服務(wù)。本系列的第 5 部分將討論 WS-Policy。
- WS-I:盡管 Web 服務(wù)應(yīng)設(shè)計成具有互操作性,但在實際中,各個規(guī)范對不同實現(xiàn)的解釋的靈活性常常足以導(dǎo)致出現(xiàn)問題。WS-I 提供了一組可用于防止出現(xiàn)各種問題的標(biāo)準(zhǔn)和實踐,并提供了標(biāo)準(zhǔn)化測試來檢查問題。WS-I 是本系列的第 6 部分將要討論的主題。
- WS-BPEL:單個服務(wù)很好處理,但應(yīng)用程序在大多數(shù)情況下則較難處理。企業(yè)級計算要求至少將多個服務(wù)組合為一個完整的系統(tǒng),而 WS-BPEL 提供了用于指定創(chuàng)建此類系統(tǒng)所必需的交互(如分支和并發(fā)處理)。本系列的第 7 部分將討論 WS-BPEL。
在 Web 服務(wù)中扮演重要角色的其他規(guī)范將不在本系列中討論,其中包括 WS-ReliableMessaging (允許確定檢索一個且僅一個消息副本)、Web 服務(wù)資源框架 (WSRF)(允許使用在無狀態(tài)環(huán)境中非常重要的狀態(tài))和 Web 服務(wù)分布式管理 (WSDM)(討論 Web 服務(wù)的管理和使用問題)。可以在本教程的參考資料中獲得有關(guān)這些規(guī)范及其他規(guī)范的更多信息。
將要完成的工作
在本教程中,您將了解 Daily Moon 報社的 Classifieds Department 如何將其自己的系統(tǒng)與內(nèi)容管理系統(tǒng)使用的基于 Web 服務(wù)的接口進(jìn)行集成。將創(chuàng)建一個應(yīng)用程序,以創(chuàng)建基于 SOAP 的消息,將其發(fā)送到服務(wù),然后檢索響應(yīng)。完成了此工作后,您將了解如何創(chuàng)建服務(wù)來響應(yīng)請求并發(fā)送自己的響應(yīng)。將通過創(chuàng)建 Java 應(yīng)用程序(使用或不使用應(yīng)用服務(wù)器)來完成此工作。
設(shè)置
現(xiàn)在已經(jīng)了解了所涉及的基本原理,接下來讓我們看看如何實際創(chuàng)建應(yīng)用程序。首先要使所需軟件就緒。
設(shè)置 Apache Geronimo
將需要的第一個軟件是 Web 應(yīng)用服務(wù)器。為什么需要 Web 應(yīng)用服務(wù)器呢?因為您會發(fā)現(xiàn),沒有 Web 應(yīng)用服務(wù)器很難提供 Web 服務(wù)。Web 應(yīng)用服務(wù)器將偵聽請求,將這些請求轉(zhuǎn)換為實際服務(wù)可以理解的內(nèi)容,然后進(jìn)行任何必要的處理。
此過程中幾乎可以使用任何 Web 服務(wù)器,但在大多數(shù)情況下都將使用能簡化此過程的軟件(通常要求使用某種類型的實際應(yīng)用服務(wù)器)。具體來說,本教程假定您將使用 Java 應(yīng)用服務(wù)器。(實際上,將使用 J2EE 應(yīng)用服務(wù)器。)
對于 J2EE 服務(wù)器,有很多選擇;在本例中將使用 Apache Geronimo。Geronimo 是作為 IBM WebSphere Application Server Community Edition 基礎(chǔ)的開放源代碼應(yīng)用服務(wù)器。Geronimo 很小,非常易于安裝和管理。它還能與其他 Apache 項目(如稍后將安裝的 Axis2)一起良好地工作。
請下載相關(guān)軟件(請參閱先決條件)并將文件提取到目標(biāo)目錄中。您會發(fā)現(xiàn)所提取的文件中包含自身的目錄,因此可以直接對其進(jìn)行解包,并可將其移動到任何位置。任何目錄都是可接受的,但要避免名稱中包含空格的目錄,如“Program Files”、“Documents and Settings”或其子目錄。例如,本教程的測試安裝使用的是 e:\geronimo-1.0。
就是這里。已經(jīng)在其中安裝了 Geronimo。這不是很簡單么?
要啟動此服務(wù)器,請打開命令提示符窗口,并執(zhí)行以下命令:
cd <GERONIMO_HOME> java -jar server.jar
要確保服務(wù)器正在運行,請打開瀏覽器,并指向清單 4 中所示的 URL:
應(yīng)該看到與圖 1 中所示類似的頁面。
現(xiàn)在讓我們開始安裝 Web 服務(wù)軟件。
安裝 Apache Axis2
完全可能從普通 HTTP 服務(wù)器提供 Web 服務(wù)。不過,這樣并不明智。處理 SOAP 消息需要進(jìn)行很多工作,但沒有必要完全從頭進(jìn)行所有工作。Apache Axis 項目幾年前就對此任務(wù)進(jìn)行了簡化,創(chuàng)建了可方便地創(chuàng)建和處理 Web 服務(wù)的環(huán)境。該軟件包含了多個應(yīng)用程序,可幫助從普通項目創(chuàng)建 Web 服務(wù),從 Web 服務(wù)創(chuàng)建 Java 對象,并對二者進(jìn)行處理。Apache 小組已推出了 Axis 的新版本 Axis2,該版本繼承了 Axis 上的所有已有功能,并通過更改體系結(jié)構(gòu)實現(xiàn)了更大的靈活性,從而將其推向了一個新的高度。這很重要,因為 Web 服務(wù)規(guī)范隨時都在增加。Axis2 的構(gòu)造允許它更方便與 WSS4J、Apache 的 WS-Security 實現(xiàn)等項目集成。由于我們將在以后使用這些項目,因此請現(xiàn)在安裝 Axis2(有關(guān)下載信息,請參閱先決條件)。
請確保同時下載 Binary Distribution 和 War Distribution。前者將幫助構(gòu)建客戶機(jī),而后者用于幫助構(gòu)建服務(wù)。
要將 Axis2 安裝到 Web 服務(wù)器中,請將 axis2.war 文件復(fù)制到 Geronimo 的 deploy 目錄。(您將需要確保已經(jīng)至少啟動過 Geronimo 一次,才能保證存在此目錄。)Geronimo 將自動檢測其是否存在,因此不必手動部署任何內(nèi)容。
驗證 Axis 的安裝
要確保已經(jīng)恰當(dāng)安裝了所有內(nèi)容,請將瀏覽器指向 http://localhost:8080/axis2/index.jsp 并單擊 Validate。
應(yīng)該看到與圖 2 中所示類似的頁面。
在繼續(xù)本教程的其他內(nèi)容前,請確保成功安裝了 Axis。
安裝示例服務(wù)
將要編寫的第一個應(yīng)用程序是客戶機(jī),因此將需要其可訪問的服務(wù)。幸運的是,Axis2 分發(fā)版提供了若干此類服務(wù)。首先,將按照以下所示安裝 MyService 服務(wù):
- 通過將瀏覽器指向 http://localhost:8080/axis2/Login.jsp 并登錄,從而通過身份驗證。缺省用戶名和密碼分別為
admin 和 Axis2 。
- 單擊 Upload service>Browse。
- 導(dǎo)航到 MyService.aar 文件??梢栽跇?biāo)準(zhǔn) Axis2 分發(fā)版的 samples\userguide 目錄下找到該文件。單擊 OK。
- 單擊 Upload。
應(yīng)該看到一個通知,指示服務(wù)已添加(請參閱圖 3)。在缺省情況下,Axis2 包含“熱部署”功能,因此不必進(jìn)行任何操作來激活。
現(xiàn)在,讓我們了解一下將要構(gòu)建的內(nèi)容。
理解 SOAP
現(xiàn)在已經(jīng)安裝了軟件,接下來可以開始著手處理實際的 Web 服務(wù)了。正如我在其他類型的 Web 服務(wù)中提到的,您可以選擇多種格式。在本系列中,將使用 SOAP。
XML 簡介
進(jìn)行傳遞的所有這些消息都基于可擴(kuò)展標(biāo)記語言(Extensible Markup Language,XML)。如果完全不熟悉 XML,在深入了解各個 Web 服務(wù)主題前,真的應(yīng)該進(jìn)行一些相關(guān)研究。不過,以下提供了繼續(xù)學(xué)習(xí)本教程所需的基本知識。
XML 是一種“標(biāo)記語言”,即給出了一種提供實際內(nèi)容的附加信息的方式。此信息以“標(biāo)記”的形式提供,這些標(biāo)記用于指示“元素”。例如,考慮一下清單 5 中所示的簡單 XML 文檔。
<article articleId="88271" categoryId="classifieds" subcategoryId="forsale">
<articleHeadline>Fun, fun, fun</articleHeadline>
<articleText>Vintage 1963 T-Bird. Less than 300 miles.
Driven by my daughter until I took it away. Serious
inquires only. 555-3264 after 7 PM.</articleText>
</article>
|
請留意此文本中的幾個值得注意的地方。首先,這是文本。這就使其可以供任何人閱讀,或在其中包含關(guān)于任何事物的內(nèi)容。其次,標(biāo)記使用 > 和 < 指示,開始標(biāo)記具有一個名稱,并可能帶有各種屬性(如文章 ID),而結(jié)束標(biāo)記以反斜杠 (/) 表示。元素必須為自包含的,并進(jìn)行了恰當(dāng)嵌套。也就是說,不能使用與清單 6 所示類似的 XML 文檔。
<article articleId="88271" categoryId="classifieds" subcategoryId="forsale">
<articleHeadline>Fun, fun, fun
<articleText></articleHeadline>Vintage 1963 T-Bird.
Less than 300 miles. Driven by my daughter until I
took it away. Serious inquires only. 555-3264 after
7 PM.</articleText>
</article>
|
XML 還提供了將內(nèi)容劃分為不同“命名空間”的方法,以便由應(yīng)用程序?qū)ζ溥M(jìn)行不同的處理。例如,SOAP 消息可能與以下的清單 7 類似。
<?xml version=‘1.0‘ ?>
<env:Envelope xmlns:env="http://www./2003/05/SOAP-envelope">
<env:Header>
</env:Header>
<env:Body>
<cms:getNumberOfArticles xmlns:cms="http://www./cms">
<cms:category>classifieds</cms:category>
<cms:subcategory>forsale</cms:subcategory>
</cms:getNumberOfArticles>
</env:Body>
</env:Envelope>
|
不要擔(dān)心消息的實際結(jié)構(gòu),但要注意存在兩種不同的“前綴”,每個前綴與特定的命名空間對應(yīng)。在這種情況下,我們是為了將 SOAP“信封”與實際的有效負(fù)載進(jìn)行區(qū)分。
再次說明,關(guān)于 XML 有很多需要學(xué)習(xí),但這些只是本教程需要了解的基礎(chǔ)知識。
SOAP 信封
Web 服務(wù)消息的基本單元是實際的 SOAP 信封。這是包含處理消息所必需的所有信息的 XML 文檔(請參見清單 8)。
<?xml version=‘1.0‘ ?>
<env:Envelope xmlns:env="http://www./2003/05/SOAP-envelope">
<env:Header>
</env:Header>
<env:Body>
</env:Body>
</env:Envelope>
|
在本例中,獲得了一個簡單的 Envelope ,其命名空間指定為 SOAP 1.2 版本。其中包含兩個子元素 Header 和 Body 。讓我們了解一下這兩個子元素所起的作用。
SOAP Header
SOAP 消息中的 Header 用于提供有關(guān)消息本身的信息,與用于應(yīng)用程序的信息相對。例如,Header 可以包括路由信息,像清單 9 中的示例類似。
<?xml version=‘1.0‘ ?>
<env:Envelope xmlns:env="http://www./2003/05/SOAP-envelope">
<env:Header>
<wsa:ReplyTo xmlns:wsa=
"http://schemas.xmlSOAP.org/ws/2004/08/addressing">
<wsa:Address>
http://schemas.xmlSOAP.org/ws/2004/08/addressing/role/anonymous
</wsa:Address>
</wsa:ReplyTo>
<wsa:From>
<wsa:Address>
http://localhost:8080/axis2/services/MyService</wsa:Address>
</wsa:From>
<wsa:MessageID>ECE5B3F187F29D28BC11433905662036</wsa:MessageID>
</env:Header>
<env:Body>
</env:Body>
</env:Envelope>
|
本例中有一個 WS-Addressing 元素,其中包含有關(guān)消息將送達(dá)何處以及應(yīng)將應(yīng)答送達(dá)何處的信息。Header 可包含關(guān)于消息本身的所有類型的消息。事實上,SOAP 規(guī)范中使用了大量篇幅說明哪些元素可以放入 Header 以及應(yīng)由“SOAP 中間層”如何對其進(jìn)行處理。也就是說,SOAP 規(guī)范并不假定消息將直接從一個點傳遞到另一個點(從客戶機(jī)到服務(wù)器)。規(guī)范考慮了 SOAP 消息在送達(dá)最終目的地的過程中可能實際由多個中間層處理的情況,很清楚地說明了中間層應(yīng)如何對待在 Header 中找到的信息。不過,對此的討論不在本教程的范圍之內(nèi)。因此,目前只要知道 Header 可以提供許許多多的功能(如果您需要)即可。
接下來讓我們看看實際的有效負(fù)載。
SOAP 體
發(fā)送 SOAP 消息時,都是有目的性的。您在嘗試告訴接收者執(zhí)行某種操作,或嘗試向服務(wù)器傳遞相關(guān)信息。此信息稱為“有效負(fù)載”。有效負(fù)載位于 Envelope 的 Body 中。它還具有自己的命名空間,在本例中其命名空間與內(nèi)容管理系統(tǒng)對應(yīng)。在此情況下,可以完全隨意地選擇命名空間。只需要與 SOAP 命名空間相異即可(請參見清單 10)。
<?xml version=‘1.0‘ ?>
<env:Envelope xmlns:env="http://www./2003/05/SOAP-envelope">
<env:Header>
...
</env:Header>
<env:Body>
<cms:addArticle xmlns:cms="http://www./cms">
<cms:category>classifieds</category>
<cms:subcategory>forsale</cms:subcategory>
<cms:articleHeadline></cms:articleHeadline>
<cms:articleText>Vintage 1963 T-Bird. Less than 300 miles.
Driven by my daughter until I took it away. Serious inquires only.
555-3264 after 7 PM.</cms:articleText>
</cms:addArticle>
</env:Body>
</env:Envelope>
|
在此例中,有效負(fù)載很簡單,其中包含將文章添加到 Daily Moon 的內(nèi)容管理系統(tǒng)的指令。
如何設(shè)計有效負(fù)載的選擇過程將涉及到樣式和編碼的內(nèi)容。
樣式和編碼
本系列教程的第 2 部分將更深入地了解此主題的內(nèi)容(該部分討論 Web 服務(wù)描述語言——WSDL),但在創(chuàng)建應(yīng)用程序時,您將需要確定要發(fā)送和接收的實際有效負(fù)載的結(jié)構(gòu)。為此,讓我們花點時間討論一下編程樣式和編碼。
簡單來說,有兩種不同的主流 SOA 消息編程樣式。第一種是 RPC 樣式,基于使用 SOAP 消息創(chuàng)建遠(yuǎn)程過程調(diào)用(Remote Procedure Call)的概念。在此樣式中,基本思路是在向服務(wù)器發(fā)送命令(如“添加文章”),并將該命令的參數(shù)(如要添加的文章和應(yīng)該添加到的類別)作為整個方法的子元素包含在其中,如清單 11 中所示。
<env:Envelope xmlns:env="http://www./2003/05/SOAP-envelope">
<env:Header>
|
RPC 樣式的替代方法將數(shù)據(jù)直接作為 SOAP 體的內(nèi)容處理,并在應(yīng)用服務(wù)器對消息進(jìn)行路由的信息中包含有關(guān)其所屬的過程或函數(shù)的信息。(請參見清單 12)。
<env:Envelope xmlns:env="http://www./2003/05/SOAP-envelope">
<env:Header>
</env:Header>
<env:Body>
<cms:addArticle xmlns:cms="http://www./cms">
<cms:category>classifieds</category>
<cms:subcategory>forsale</cms:subcategory>
<cms:articleHeadline></cms:articleHeadline>
<cms:articleText>Vintage 1963 T-Bird. Less than 300
miles. Driven by my daughter until I took it away.
Serious inquires only. 555-3264 after 7 PM.</cms:articleText>
</cms:addArticle>
</env:Body>
</env:Envelope>
|
RPC 樣式的一個變體就是與上面看到的 RPC/literal 相對的 RPC/encoded。在這種情況下,消息中包含類型信息,如清單 13 中所示。
<?xml version=‘1.0‘ ?>
<env:Envelope xmlns:env="http://www./2003/05/SOAP-envelope">
<env:Header>
</env:Header>
<env:Body>
<cms:addArticle xmlns:cms="http://www./cms">
<cms:category xsi:type="xsd:string">classifieds</category>
<cms:subcategory xsi:type="xsd:string">forsale
</cms:subcategory>
<cms:articleHeadline xsi:type="xsd:string" />
<cms:articleText xsi:type="xsd:string">Vintage 1963
T-Bird. Less than 300 miles. Driven by my daughter until
I took it away. Serious inquires only. 555-3264 after 7
PM.</cms:articleText>
</cms:addArticle>
</env:Body>
</env:Envelope>
|
第二個樣式稱為 document/literal 樣式,即將相應(yīng)的數(shù)據(jù)直接添加到消息中,如清單 14 中所示。
<?xml version=‘1.0‘ ?>
<env:Envelope xmlns:env="http://www./2003/05/SOAP-envelope">
<env:Header>
</env:Header>
<env:Body>
<category>classifieds</category>
<subcategory>forsale</subcategory>
<articleHeadline></articleHeadline>
<articleText>Vintage 1963 T-Bird. Less than 300 miles.
Driven by my daughter until I took it away. Serious
inquires only. 555-3264 after 7 PM.</articleText>
</env:Body>
</env:Envelope>
|
在這種情況下,消息本身并不包含有關(guān)數(shù)據(jù)所提交到的進(jìn)程的信息,此工作由路由軟件進(jìn)行。例如,所有對特定 URL 或端點的調(diào)用都可能指向特定的操作。另外,從技術(shù)上講,可以使用 document/encoded 樣式,但目前還沒有人這樣做,因此可以將其忽略。
在每個樣式中都涉及到不同的折衷,本系列的第 2 部分將進(jìn)一步對此進(jìn)行詳細(xì)討論。不過,務(wù)必知道還有第三種樣式“document wrapped”,并未正式地確定此樣式,但由于各種互操作性原因而大受歡迎。在此情況下,有效負(fù)載的內(nèi)容包裝為單個元素,但元素并不據(jù)數(shù)據(jù)所屬的過程或函數(shù)進(jìn)行命名。從肉眼來看,這些消息幾乎與 RPC/literal 消息完全相同。
消息交換模式
談到發(fā)送消息,您有很多選擇,可以發(fā)送請求并等待響應(yīng),發(fā)送請求但不等待響應(yīng),發(fā)送請求并在到達(dá)最終的目的地前通過多個中間層。但就實質(zhì)而言,只有兩個選擇:
- 請求/響應(yīng):在請求/響應(yīng)模式種,以 SOAP 消息的形式發(fā)送請求,然后直接等待發(fā)送回響應(yīng)。請求可以為同步的,也可以是異步的。
- 單向消息傳遞:這種情況也稱為“Fire and Forget”方法,發(fā)送請求但并不等待響應(yīng)。可以在僅傳遞信息時或并不關(guān)心接收者對此如何響應(yīng)時使用此方法。
現(xiàn)在,請注意并沒有使用術(shù)語“客戶機(jī)”和“服務(wù)器”。之所以這樣,是因為這些消息交換模式幾乎可以用于創(chuàng)建與上面提到的方法類似的任意數(shù)量的不同備選方法。例如,可以發(fā)送一條請求,然后依靠接收者對其進(jìn)行處理,并在將來完成應(yīng)完成的工作時發(fā)送一條消息。為此,將使用多個單向消息的組合,因此談“客戶機(jī)”和“服務(wù)器”并不合理,因為每個消息都有其接收方和發(fā)送方,所謂的客戶機(jī)和服務(wù)器的位置會發(fā)生對換。
構(gòu)建 SOAP 客戶機(jī)
上面已經(jīng)對理論進(jìn)行了介紹,現(xiàn)在讓我們開始構(gòu)建實際的實現(xiàn)。首先從客戶機(jī)開始。
以前的老方式
最初出現(xiàn)用于使用 SOAP 消息的 Java API 時,其用途非常特定化。它們的用途相當(dāng)直接,用于創(chuàng)建 SOAP 消息。需要創(chuàng)建消息、Envelope 、Header 、Body 等等。例如,可以構(gòu)建“舊式”客戶機(jī)來訪問前面安裝的 MyService 服務(wù)的 echo 函數(shù)(請參見清單 15)。
注意:為了編譯并運行此客戶機(jī),將需要 SAAJ 實現(xiàn)(如原始 Axis 軟件)??梢詮?http://ws./axis/ 下載 Axis。據(jù)說 Axis2 0.95 也包含此實現(xiàn),但本教程未針對其進(jìn)行測試。
import javax.xml.SOAP.*;
import javax.xml.transform.*;
import java.io.FileInputStream;
import javax.xml.transform.stream.*;
import org.w3c.dom.*;
public class SendSOAP {
public static void main(String args[]) {
try {
MessageFactory messageFactory = MessageFactory.newInstance();
SOAPMessage message = messageFactory.createMessage();
//Create objects for the message parts
SOAPPart SOAPPart = message.getSOAPPart();
SOAPEnvelope envelope = SOAPPart.getEnvelope();
SOAPBody body = envelope.getBody();
SOAPElement bodyElement =
body.addChildElement(envelope.createName("echo",
"req", "http://localhost:8080/axis2/services/MyService/"));
bodyElement.addChildElement("category")
.addTextNode("classifieds");
message.saveChanges();
SOAPPart SOAPpartbefore = message.getSOAPPart();
SOAPEnvelope reqenv = SOAPpartbefore.getEnvelope();
System.out.println("REQUEST:");
System.out.println(reqenv.toString());
//Now create the connection
SOAPConnectionFactory SOAPConnFactory =
SOAPConnectionFactory.newInstance();
SOAPConnection connection =
SOAPConnFactory.createConnection();
SOAPMessage reply = connection.call(message,
"http://localhost:8080/axis2/services/MyService");
SOAPPart SOAPpart = reply.getSOAPPart();
SOAPEnvelope replyenv = SOAPpart.getEnvelope();
System.out.println("\nRESPONSE:");
System.out.println(replyenv.toString());
connection.close();
} catch (Exception e){
System.out.println(e.getMessage());
}
}
}
|
請注意,您要直接創(chuàng)建 SOAPEnvelope 、SOAPBody 等內(nèi)容??梢韵蛳Ⅲw添加 echo 和 category 等元素。將從其中創(chuàng)建連接,進(jìn)行調(diào)用,同樣也能遍歷 SOAP 消息的結(jié)構(gòu)來獲取實際的內(nèi)容。如果要運行此客戶機(jī),應(yīng)該看到與清單 16 中所示類似的響應(yīng)。
REQUEST:
<SOAPenv:Envelope xmlns:SOAPenv=
"http://schemas.xmlSOAP.org/SOAP/envelope/"
xmlns:xsd="http://www./2001/XMLSchema"
xmlns:xsi="http://www./2001/XMLSchema-instance">
<SOAPenv:Body>
<req:echo xmlns:req=
"http://localhost:8080/axis2/services/MyService/">
<req:category>classifieds</req:category>
</req:echo>
</SOAPenv:Body>
</SOAPenv:Envelope>
RESPONSE:
<SOAPenv:Envelope xmlns:SOAPenv=
"http://schemas.xmlSOAP.org/SOAP/envelope/" xmlns:wsa=
"http://schemas.xmlSOAP.org/ws/2004/08/addressing">
<SOAPenv:Header>
<wsa:ReplyTo>
<wsa:Address>
http://schemas.xmlSOAP.org/ws/2004/08/addressing/role/anonymous
</wsa:Address>
</wsa:ReplyTo>
<wsa:From>
<wsa:Address>
http://localhost:8080/axis2/services/MyService</wsa:Address>
</wsa:From>
<wsa:MessageID>ECE5B3F187F29D28BC11433905662036</wsa:MessageID>
</SOAPenv:Header>
<SOAPenv:Body>
<req:echo xmlns:req=
"http://localhost:8080/axis2/services/MyService/">
<req:category>classifieds</req:category>
</req:echo>
</SOAPenv:Body>
</SOAPenv:Envelope>
|
echo 服務(wù)所進(jìn)行的所有工作實際就是對其所接收到的請求進(jìn)行應(yīng)答,通過這一點可以很好地了解舊式處理方法與新式處理方法間的差別。接下來讓我們看看二者的差異。
新方式
現(xiàn)在越來越趨向于對程序員隱藏使用基于 XML 的 Web 服務(wù)消息的復(fù)雜性。為此進(jìn)行了大量的工作,其中大部分的目標(biāo)都是希望盡可能讓 Web 服務(wù)編程與任何其他體系結(jié)構(gòu)的編程工作一樣進(jìn)行。
在 Axis2 中,實際上并不僅限于此。Axis2 引入了一種全新的方式來使用表示 SOAP 消息的 XML(盡管表面看來與使用文檔對象模型類似)。AxIs 對象模型(Axis Object Model,AXIOM)進(jìn)行了一系列更改,但我暫時將僅提一下其對消息的信息集的關(guān)注,消息集是元素和屬性中包含的實際信息,而不是通常看到的序列化版本。不過,更為重要的是,Axis2 將為您處理 SOAP 信封,從而可以將精力放在構(gòu)建有效負(fù)載上(或者,如果是實際的服務(wù),則是分析有效負(fù)載和創(chuàng)建響應(yīng))。接下來讓我們看看如何進(jìn)行此工作。
創(chuàng)建請求
要開始創(chuàng)建客戶機(jī),請確保 Axis2 lib 目錄中的所有 *.jar 文件——指 Standard 分發(fā)版,而不是 War 分發(fā)版——都在您的 CLASSPATH 上,并創(chuàng)建名為 ClassifiedClient 的新類。創(chuàng)建有效負(fù)載,如清單 17 中所示。
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.om.OMElement;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import java.io.StringWriter;
import org.apache.axis2.om.OMAbstractFactory;
import org.apache.axis2.SOAP.SOAPFactory;
import org.apache.axis2.om.OMFactory;
import org.apache.axis2.om.OMNamespace;
public class ClassifiedClient {
public static OMElement getEchoOMElement() {
SOAPFactory fac = OMAbstractFactory.getSOAP12Factory();
OMNamespace omNs = fac.createOMNamespace(
"http:///cms", "cms");
OMElement method = fac.createOMElement("echo", omNs);
OMElement value = fac.createOMElement("category", omNs);
value.addChild(fac.createText(value, "classifieds"));
method.addChild(value);
return method;
}
public static void main(String[] args) {
try {
OMElement payload = ClassifiedClient.getEchoOMElement();
} catch (Exception e) { //(XMLStreamException e) {
System.out.println(e.toString());
}
}
}
|
首先,創(chuàng)建工廠和命名空間,并使用其創(chuàng)建各個元素。在本例中,您將創(chuàng)建與前面示例完全相同的元素,因為將再次使用此客戶機(jī)訪問 echo 函數(shù)。(稍后將對其進(jìn)行更改,以訪問真正的服務(wù)。)
接下來,將創(chuàng)建請求。
創(chuàng)建請求
下一步將創(chuàng)建實際的請求。同樣,這方面也體現(xiàn)出技術(shù)隨時間的發(fā)展。這里不會直接將請求發(fā)送到 URL,而要設(shè)置“端點引用”,如清單 18 中所示。
...
public class ClassifiedClient {
private static EndpointReference targetEPR = new
EndpointReference(
"http://localhost:8080/axis2/services/MyService");
public static OMElement getEchoOMElement() {
...
}
public static void main(String[] args) {
try {
OMElement payload = ClassifiedClient.getEchoOMElement();
Options options = new Options();
options.setTo(targetEPR);
options.setTransportInProtocol(Constants.TRANSPORT_HTTP);
} catch (Exception e) { //(XMLStreamException e) {
System.out.println(e.toString());
}
}
}
|
對 WS-Addressing 的全面討論不在本教程的范疇之內(nèi),但完全可以說端點引用包含消息將定向到的 URL,還可以有選擇地包含其他信息,如應(yīng)答地址和其他資源屬性等。
首先,為請求創(chuàng)建 Options,以便為請求設(shè)置 EPR 和其他信息(如打算使用的傳輸協(xié)議)。完成了這些后,就可以實際發(fā)送請求了。
發(fā)送請求
完成了設(shè)置后,就可以發(fā)送請求了。對于 Axis 的 0.94 版,首選的方式是通過 ServiceClient 類發(fā)送消息,如清單 19 中所示。
...
public static void main(String[] args) {
try {
OMElement payload = ClassifiedClient.getEchoOMElement();
Options options = new Options();
options.setTo(targetEPR);
options.setTransportInProtocol(Constants.TRANSPORT_HTTP);
ServiceClient sender = new ServiceClient();
sender.setOptions(options);
OMElement result = sender.sendReceive(payload);
} catch (Exception e) { //(XMLStreamException e) {
System.out.println(e.toString());
}
}
}
|
將創(chuàng)建 ServiceClient 對象,并為其設(shè)置前面創(chuàng)建的 Options 。然后就可以直接發(fā)送消息了。由于希望得到響應(yīng),因此將使用 sendReceive() 方法(該方法用于 in/out 類型的消息)。將在本教程的單向服務(wù)部分討論單向消息的信息。sendReceive() 方法將在客戶機(jī)接收到實際響應(yīng)時將其返回。
輸出結(jié)果
實際上,sendReceive() 并不會返回整個響應(yīng),而僅返回有效負(fù)載。如果將結(jié)果輸出到命令行,應(yīng)該清楚地看到清單 20 中的內(nèi)容。
...
sender.setOptions(options);
OMElement result = sender.sendReceive(payload);
System.out.println(result.toString());
} catch (Exception e) { //(XMLStreamException e) {
System.out.println(e.toString());
}
}
}
|
運行此客戶機(jī)將獲得清單 21 中所示的響應(yīng)。
<cms:echo xmlns:cms="http:///cms"><cms:catego
ry>classifieds</cms:category></cms:echo>
|
當(dāng)然,接收到此數(shù)據(jù)后,可以使用其進(jìn)行任何工作。接下來,我們將構(gòu)建實際的 getNumberofArticles() 服務(wù)。
構(gòu)建 SOAP 服務(wù)
如果您覺得構(gòu)建 Web 服務(wù)客戶機(jī)的過程相當(dāng)簡單,事實的確如此。而就很多方面而言,構(gòu)建服務(wù)的過程也同樣簡單。
總體過程
創(chuàng)建 Axis2 Web 服務(wù)的整個過程涉及以下步驟:
- 創(chuàng)建服務(wù)清單
- 創(chuàng)建類
- 將其打包為 Axis 存檔文件
- 將 Axis 存檔文件上載到 Axis2 Web 應(yīng)用程序
- 重新啟動服務(wù)器(如果有必要)
這就是全部步驟。讓我們首先從服務(wù)清單開始。
創(chuàng)建清單
服務(wù)清單告知 Axis2 應(yīng)用程序(就更大的范圍而言,應(yīng)用服務(wù)器)哪個請求與哪個類對應(yīng)。例如,可以如清單 22 中所示的那樣指定兩個服務(wù)函數(shù)。
清單 22. 在清單中指定兩個服務(wù)函數(shù)
<service name="CMSService">
<description>
This is a sample Web Service for the newspaper‘s
Content Managment System.
</description>
<parameter name="ServiceClass" locked="false"
>CMSService</parameter>
<operation name="getNumberOfArticles">
<messageReceiver class=
"org.apache.axis2.receivers.RawXMLINOutMessageReceiver"/>
</operation>
<operation name="addArticle">
<messageReceiver class=
"org.apache.axis2.receivers.RawXMLINOnlyMessageReceiver"/>
</operation>
</service>
|
首先,定義服務(wù),提供其名稱和描述,并指定實際為請求服務(wù)的類。接下來,定義實際的操作。請注意,此示例指定了兩種不同類型的 messageReceiver 。第一個 RawXMLINOutMessageReceiver 用于傳統(tǒng)的請求/響應(yīng)服務(wù)。第二個 RawXMLINOnlyMessageReceiver 用于單向消息。操作的名稱與有效負(fù)載的根元素以及要執(zhí)行的方法對應(yīng)。
將此文件保存為 services.xml。
接下來,讓我們創(chuàng)建實際的應(yīng)用程序。
創(chuàng)建應(yīng)用程序
讓我們首先創(chuàng)建模擬前面看到的 echo 函數(shù)的類(將直接返回原始有效負(fù)載的副本),如清單 23 中所示。
清單 23. CMSService 類
import org.apache.axis2.om.OMElement;
import javax.xml.stream.XMLStreamException;
public class CMSService {
public OMElement getNumberOfArticles(OMElement element)
throws XMLStreamException {
element.build();
element.detach();
return element;
}
}
|
要編譯此應(yīng)用程序,請確保 <axis2_home>/lib 中的所有 *.jar 文件都在您的 CLASSPATH 上。
此應(yīng)用程序相當(dāng)簡單,僅包含一個與 getNumbereOfArticles 操作對應(yīng)的類。此函數(shù)和任何要作為操作的函數(shù)一樣,接收單個 OMElement 參數(shù)(表示有效負(fù)載)。此處,您將首先使用 build() 方法來確定已接收到所有數(shù)據(jù)——AXIOM 使用一個 pull 方法訪問數(shù)據(jù)——然后將元素從其當(dāng)前樹中分離,以便能夠?qū)⑵浞祷亍?/p>
如果喜歡冒險,可以自由地部署服務(wù)和訪問服務(wù),以訪問服務(wù)并查看結(jié)果。應(yīng)該看到與清單 24 中所示類似的結(jié)果輸出。
清單 24. CMSService 類響應(yīng)
<cms:getNumberOfArticles><cms:category>classifieds</cms:category></cms:
getNumberOfArticles>
|
接下來讓我們了解如何實際處理數(shù)據(jù)。
提取有效負(fù)載
為了從有效負(fù)載提取信息,將使用與 DOM 非常類似的技術(shù)來對接收到的有效負(fù)載元素進(jìn)行操作(請參見清單 25)。
清單 25. 提取有效負(fù)載信息
...
import javax.xml.stream.XMLStreamException;
public class CMSService {
public OMElement getNumberOfArticles(OMElement element)
throws XMLStreamException {
element.build();
element.detach();
String rootName = element.getLocalName();
OMElement categoryElement = element.getFirstElement();
String categoryElementName = categoryElement.getLocalName();
String categoryValue = childElement.getText();
return element;
}
}
|
請記住,有效負(fù)載的根是 getNumberOfArticles 函數(shù)接收的元素。在此情況下,將提取元素的名稱,然后移動到第一個元素子項(與第一個子項不同,后者可能是空格文本節(jié)點)并提取其名稱和值。請注意,使用的是 getText() 方法來提取實際上是 category 元素的文本節(jié)點子項的值。這無疑非常簡捷!
創(chuàng)建并返回響應(yīng)
最后,將需要使用從請求的有效負(fù)載提取數(shù)據(jù)來創(chuàng)建響應(yīng)。在本例中,將從第二個函數(shù)(在實際應(yīng)用程序中,該函數(shù)將進(jìn)行一些其他的工作)提供響應(yīng)(請參見清單 26)。
清單 26. 創(chuàng)建響應(yīng)
...
import javax.xml.stream.XMLStreamException;
public class CMSService {
public OMElement getNumberOfArticles(OMElement element)
throws XMLStreamException {
element.build();
element.detach();
String rootName = element.getLocalName();
OMElement childElement = element.getFirstElement();
String childName = childElement.getLocalName();
String categoryValue = childElement.getText();
SOAPFactory factory = OMAbstractFactory.getSOAP12Factory();
OMNamespace namespace = factory.createOMNamespace(
"http:///cms/", "resp");
OMElement resultElem = factory.createOMElement(
"numberOfArticles",namespace);
String actualValue =
(articleCount(categoryValue)).toString();
resultElem.setText(actualValue);
return resultElem;
}
private Integer articleCount(String catId){
//Perform some function such as searching the CMS
//database, and return the actual value. For our
//purposes, you‘ll hardcode it.
return new Integer(42);
}
}
|
首先,創(chuàng)建將用于創(chuàng)建所有其他對象的工廠,然后創(chuàng)建將添加到響應(yīng)的有效負(fù)載的命名空間。接下來,創(chuàng)建實際結(jié)果元素,在本例中為名為 numberOfArticles 的元素。
numberOfArticles 元素的內(nèi)容將為 articleCount() 函數(shù)返回的一個數(shù)字,在本例中,該函數(shù)可以為任何內(nèi)容。在實際的應(yīng)用程序中,將進(jìn)行所需進(jìn)行的任何工作來獲取此數(shù)據(jù)。獲取了此數(shù)據(jù)后,會將其設(shè)置為 numberOfArticles 元素的內(nèi)容,并直接返回該元素。
現(xiàn)在剩下的就是部署服務(wù)了。
部署服務(wù)
為了部署服務(wù),需要創(chuàng)建一個 Axis 存檔文件。此文件和 *.jar 或 *.war 文件類似,實際是使用特殊文件擴(kuò)展名(在本例中使用的是 .aar)的 zip 文件。請按照以下步驟創(chuàng)建此文件:
- 將 <AXIS2_HOME>/lib 目錄中的所有文件添加到 CLASSPATH 并編譯 CMSService.java 文件。
- 在與 CMSService.class 文件相同的目錄中創(chuàng)建名為 META-INF 的新目錄。
- 從包含 CMSService.class 文件的目錄中發(fā)出以下命令:<code type="section" width="100"> jar cvf CMSService.aar ./* </code> 應(yīng)該看到與以下類似的結(jié)果:<code type="section" width="100"> added manifest adding:CMSService.class(in = 513) (out= 330)(deflated 35%) adding:CMSService.java(in = 328) (out= 182)(deflated 44%) ignoring entry META-INF/ adding:META-INF/services.xml(in = 391) (out= 229)(deflated 41%) </code>
- 使用安裝示例服務(wù)中列出的步驟將此服務(wù)添加到服務(wù)器上。(如果看到 Web 接口上有 Servlet 錯誤,請確保登錄到了 Axis2 應(yīng)用程序。如果會話已過期,應(yīng)用程序?qū)⒉灰欢〞ㄖ赡軙苯语@示錯誤。)
- 如果有必要,請重新啟動 Geronimo。(將可能不必在添加服務(wù)后進(jìn)行此操作,但在進(jìn)行更改后可能必須這樣做。)
如果單擊 View services 鏈接,應(yīng)該看到與圖 4 中所示類似的內(nèi)容。
圖 4. 可用服務(wù)
訪問服務(wù)
現(xiàn)在已經(jīng)完成了服務(wù)構(gòu)建,接下來要通過客戶機(jī)對其進(jìn)行訪問。對前面創(chuàng)建的 ClassifiedClient.java 文件進(jìn)行以下更改(請參見清單 27)。
清單 27. 修改 ClassifiedClient
...
public class ClassifiedClient {
private static EndpointReference targetEPR =
new EndpointReference(
"http://localhost:8080/axis2/services/CMSService");
public static OMElement getEchoOMElement() {
SOAPFactory fac = OMAbstractFactory.getSOAP12Factory();
OMNamespace omNs = fac.createOMNamespace(
"http:///cms", "cms");
OMElement method = fac.createOMElement("getNumberOfArticles", omNs);
OMElement value = fac.createOMElement("category", omNs);
value.addChild(fac.createText(value, "classifieds"));
method.addChild(value);
return method;
}
public static void main(String[] args) {
try {
OMElement payload = ClassifiedClient.getEchoOMElement();
Options options = new Options();
options.setTo(targetEPR);
options.setTransportInProtocol(Constants.TRANSPORT_HTTP);
ServiceClient sender = new ServiceClient();
sender.setOptions(options);
OMElement result = sender.sendReceive(payload);
String response = result.getText();
System.out.println("There are "+response+" classifieds at the moment.");
} catch (Exception e) { //(XMLStreamException e) {
System.out.println(e.toString());
}
}
}
|
編譯并運行了此應(yīng)用程序后,應(yīng)看到清單 28 中所示的響應(yīng)。
清單 28. ClassifiedClient 響應(yīng)
There are 42 classifieds at the moment.
|
單向服務(wù)
繼續(xù)討論之前,讓我們了解一下處理單向服務(wù)(而非請求/響應(yīng)服務(wù))時涉及到的不同之處。
服務(wù)
創(chuàng)建單向服務(wù)非常簡單。此過程與創(chuàng)建請求/響應(yīng)服務(wù)完全類似,至少不會實際返回任何內(nèi)容。例如,可以為 CMSService 類創(chuàng)建 addArticle 操作,如圖 29 中所示。
...
private Integer articleCount(String catId){
...
}
public void addArticle(OMElement element)
throws XMLStreamException{
element.build();
System.out.println(element);
}
}
|
在 services.xml 文件中,將 addArticle 操作指定為“in only”操作,因此不會等待返回任何內(nèi)容,但即使這樣,也能看到會實際發(fā)生一些事項,會在命令行輸出接收到的有效負(fù)載。您將在 Geronimo 窗口中看到此信息。
在實際應(yīng)用程序中,此方法將從有效負(fù)載提取信息,并會實際添加到某種類型的數(shù)據(jù)庫或其他存儲庫。
客戶機(jī)
此服務(wù)的客戶機(jī)也與請求/響應(yīng)服務(wù)所使用的服務(wù)類似(請參見清單 30)。
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.om.OMElement;
import org.apache.axis2.SOAP.SOAPFactory;
import org.apache.axis2.om.OMAbstractFactory;
import org.apache.axis2.om.OMNamespace;
public class AddArticleClient {
private static EndpointReference targetEPR =
new EndpointReference(
"http://localhost:8080/axis2/services/CMSService");
private static OMElement getOMElement(){
SOAPFactory fac = OMAbstractFactory.getSOAP12Factory();
OMNamespace omNs = fac.createOMNamespace(
"http://", "cms");
OMElement method = fac.createOMElement("addArticle", omNs);
OMElement category = fac.createOMElement("category", omNs);
category.setText("classifieds");
OMElement subcategory =
fac.createOMElement("subcategory", omNs);
category.setText("wantads");
OMElement adtext = fac.createOMElement("article", omNs);
adtext.setText("Do you have good head for numbers"+
" and a great deal of patience? Do you like"+
" to sit for hours sorting objects by their"+
" size? If so, then you could be the"+
" next goober counter in the world famous"+
" Murphy Brothers peanut factory. "+
" Willingness to dress up as our mascot"+
" helpful, but not required.");
method.addChild(category);
method.addChild(subcategory);
method.addChild(adtext);
return method;
}
public static void main(String[] args) {
try {
OMElement payload = AddArticleClient.getOMElement();
ServiceClient serviceClient = new ServiceClient();
Options options = new Options();
serviceClient.setOptions(options);
options.setTo(targetEPR);
serviceClient.fireAndForget(payload);
} catch (AxisFault axisFault) {
axisFault.printStackTrace();
}
}
}
|
盡管有效負(fù)載不同,但正如您在 getOMElement() 方法中看到的,編程方面目前的唯一真正的更改是使用 fireAndForget() 方法替代 sendReceive() 方法。此方法并不會返回響應(yīng)。
如果運行此客戶機(jī),應(yīng)該在 Geronimo 窗口中看到與圖 5 中所示類似的輸出。
通過 GET 訪問服務(wù)
在 SOAP 1.2 推出之前,使用 HTTP 訪問基于 SOAP 的 Web 服務(wù)的唯一方法是使用 POST 請求。您將需要創(chuàng)建能創(chuàng)建 POST 請求并使用 SOAP 消息作為請求的內(nèi)容的客戶機(jī)。不過,SOAP 1.2 定義了使用 GET 請求訪問基于 SOA 的 Web 服務(wù)的方法。
GET 與 POST 對比
繼續(xù)我們的討論之前,務(wù)必了解通過 HTTP 的 GET 和 POST 請求的區(qū)別。盡管很多 Web 程序員所進(jìn)行的處理似乎表明二者之間是可以互換的,但實際上二者的用途并不相同。GET 中的所有關(guān)于所請求的資源的信息都包含在 URL(通常作為參數(shù)),僅用于等冪請求。這些請求是沒有“副作用”的請求。也就是說,應(yīng)該能夠數(shù)十次、數(shù)百次、數(shù)千次地調(diào)用這個請求,但這個請求不會更改任何東西。例如,請求 Albuquerque 的當(dāng)前氣溫的 Web 請求就是等冪請求。而將注釋傳入到博客數(shù)據(jù)庫的 Web 請求則不是。
這是因為 GET 請求可以添加到用戶的書簽,能在不會引發(fā)警告的情況下進(jìn)行訪問。還可以對其進(jìn)行引用,而不會引發(fā)警告。另一方面,POST 請求將其信息包含在請求的正文中,因此很難進(jìn)行隨機(jī)的重復(fù)。
就 SOAP 而言,這意味著應(yīng)該能夠?qū)H檢索信息而不進(jìn)行更改的 SOAP 請求使用 GET 。對于進(jìn)行更改的任何操作,仍然都應(yīng)使用 POST 。
訪問服務(wù)
在 Axis2 中,可以生成 GET 請求,服務(wù)器會將其轉(zhuǎn)換為 SOAP 消息,然后將有效負(fù)載作為結(jié)果返回。例如,請將瀏覽器指向清單 31 中所示的位置。
http://localhost:8080/axis2/services/CMSService/getNumberOfArticles?category=classifieds
|
如果使用 0.94 版,將看到清單 32 中所示的響應(yīng)。
<resp:numberOfArcticles>42</resp:numberOfArcticles>
|
不過,這并不十分準(zhǔn)確。根據(jù) SOAP 1.2 建議規(guī)范,應(yīng)該能夠看到整個 SOAP 響應(yīng)。這在 Axis2 將來的版本中可能會發(fā)生更改。
處理附件
簡單 SOAP 消息的另一個變體是附件。對于附件,多年來人們早已耳熟能詳,但由于現(xiàn)在某些擴(kuò)展規(guī)范要求使用附件,因此您必須對其進(jìn)行處理。
二進(jìn)制數(shù)據(jù)和 XML
盡管 XML 是基于文本的格式,但卻不能忽略實際上是采用二進(jìn)制進(jìn)行表示的。因為這樣,將會有需要向 Web 服務(wù)傳遞或從其檢索二進(jìn)制信息的情況。
可以采用兩種方式中的一種來處理這種情況。第一種選擇是將二進(jìn)制數(shù)據(jù)實際包含在您的文檔中。這種情況的一個例子是將 Microsoft Word 文檔另存為 XML 文件時。如果在該文檔中嵌入了任何圖形,Word 會將其作為二進(jìn)制數(shù)據(jù)嵌入到 XML 文檔中(采用 Base64 編碼)。第二種選擇是直接引用該數(shù)據(jù),以便處理該文檔的應(yīng)用程序能夠找到此數(shù)據(jù)。一個極為常見的例子是 Web 瀏覽器以及其處理從 XHTML 文件引用的圖像的方式。XHTML 文本包含一個 img 元素(或者,采用了更為先進(jìn)的技術(shù),則為 object 元素),該元素包含一個 src 屬性,其中有指向?qū)嶋H數(shù)據(jù)的 URL。應(yīng)用程序可以隨后從該位置加載數(shù)據(jù)并相應(yīng)地進(jìn)行使用。
SOAP 文檔也是這樣。假如,如果向基于 SOAP 的服務(wù)提交了一個圖像,有兩個選擇??梢詫⒃摂?shù)據(jù)嵌入在有效負(fù)載中,或可以想辦法引用該數(shù)據(jù)。曾經(jīng)由于涉及到帶寬的一些問題對此進(jìn)行過討論。
XML 二進(jìn)制優(yōu)化打包
XML 已經(jīng)比二進(jìn)制對應(yīng)項冗長得多了。正因為如此,它將使用更多的帶寬。那么,當(dāng)考慮使用向 XML 文本文檔添加二進(jìn)制數(shù)據(jù)時的首選方法(將其編碼為 Base64)時,會由于兩個或更多的因素而導(dǎo)致其尺寸增大,這就帶來了一個非常實際的問題。
事實上,在過去的兩三年,曾經(jīng)有很多人強(qiáng)烈地批評缺乏對二進(jìn)制數(shù)據(jù)的實時支持,幾乎充斥著不滿的聲音,最終 W3C 開始著手處理這個問題。其工作的成果就是 XML 二進(jìn)制優(yōu)化打包(XML-binary Optimized Packages,XOP)。此協(xié)議提供了在 XML 文檔中可靠地引用外部數(shù)據(jù)的方法。例如,SOAP with Attachments 規(guī)范規(guī)定二進(jìn)制數(shù)據(jù)可以作為多部分 MIME 文檔的的一部分發(fā)送,由 XML 數(shù)據(jù)組成第一部分,而二進(jìn)制數(shù)據(jù)作為附加部分添加到其中。這樣做的問題在于,盡管您的程序可能知道數(shù)據(jù)存在,但文檔并不知道這一點。同時,還不允許對文檔進(jìn)行選擇性優(yōu)化或?qū)ΠM(jìn)制數(shù)據(jù)的現(xiàn)有文檔進(jìn)行回溯處理。
XOP 通過提供一個特殊的機(jī)制來改進(jìn)這種情況,利用這種機(jī)制可選擇性地提取要優(yōu)化的信息,將其添加到多部分 MIME 消息中(其中也包括您的 SOAP 消息)并顯式地對其進(jìn)行引用。讓我們看一個例子。
例如,假定員工不想將新文章作為文本元素添加,而希望將其作為二進(jìn)制文檔從字處理程序添加。如果將該內(nèi)容包含在消息體中,將十分混亂,如清單 33 中所示:_
<?xml version=‘1.0‘ ?>
<env:Envelope xmlns:env="http://www./2003/05/SOAP-envelope">
<env:Header>
</env:Header>
<env:Body>
<cms:addArticle xmlns:cms="http://www./cms">
<cms:category>classifieds</category>
<cms:subcategory>forsale
</cms:subcategory>
<cms:articleHeadline><cms:articleHeadline>
<cms:articleText>wvetwenptiubnweoirntuwopeirt4m[456n09ew7nv
sa0tv043u6y304o5mu60ew9rebtm45bm4-69nw-0er9utnv3094nb-26204
95u6-49kv6-m34956h-wb09emjb-0n67u-340v,=qw-enr7w8b64b03278-
ANDLOTSMOREBASE64ENCODEDDATAHERE</cms:articleText>
</cms:addArticle>
</env:Body>
</env:Envelope>
|
相反,XOP 規(guī)定對數(shù)據(jù)進(jìn)行提取,然后使用一個引用其新位置的 Include 元素將其替換,如清單 34 中所示。
MIME-Version: 1.0
Content-Type: Multipart/Related;boundary=MIME_boundary;
type="application/xop+xml";
start="<soapmsg.xml@>";
start-info="text/xml"
Content-Description: An XML document with binary data in it
--MIME_boundary
Content-Type: application/xop+xml;
charset=UTF-8;
type="text/xml"
Content-Transfer-Encoding: 8bit
Content-ID: <soapmsg.xml@>
<env:Envelope xmlns:env="http://www./2003/05/SOAP-envelope">
<env:Header>
</env:Header>
<env:Body>
<cms:addArticle xmlns:cms="http://www./cms">
<cms:category>classifieds</category>
<cms:subcategory>forsale
</cms:subcategory>
<cms:articleHeadline><cms:articleHeadline>
<cms:articleText><xop:Include
xmlns:xop=‘http://www./2004/08/xop/include‘
href=‘cid:http:///tbird.doc‘
/></cms:articleText>
</cms:addArticle>
</env:Body>
</env:Envelope>
--MIME_boundary
Content-Type: application/vnd.oasis.openoffice
Content-Transfer-Encoding: binary
Content-ID: <http:///tbird.doc>
// binary octets for the word processing file
--MIME_boundary--
|
請注意,Include 元素中指定的位置與 Content-ID 減去協(xié)議 cid: 的值匹配?,F(xiàn)在要發(fā)送的是此消息,而不是純文本 SOAP 消息。
SOAP、二進(jìn)制數(shù)據(jù)和 Axis2
在 SOAP 文檔中使用 XOP 的過程稱為 MTOM(即 SOAP 消息傳輸優(yōu)化機(jī)制——Message Transmission Optimization Mechanism)。Axis2 提供了使用 SOA 數(shù)據(jù)的這個方法的支持,但必須確保對應(yīng)用程序進(jìn)行了恰當(dāng)配置。
具體來說,您必須在 axis2.war 文件內(nèi)的 axis2.xml 文件中啟用此支持(請參見清單 35)。
<axisconfig name="AxisJava2.0">
<!-- ================================================= -->
<!-- Parameters -->
<!-- ================================================= -->
<parameter name="hotdeployment" locked="false">true</parameter>
<parameter name="hotupdate" locked="false">true</parameter>
<parameter name="enableMTOM" locked="false">true</parameter>
<!-- Uncomment this to enable REST support -->
<!-- <parameter name="enableREST" locked="false">true</parameter>-->
<parameter name="userName" locked="false">admin</parameter>
<parameter name="password" locked="false">axis2</parameter>
...
|
如果有必要,可以提取 axis2.war 文件,進(jìn)行此更改,然后將其重新壓縮成 .war 文件。
要替換 Axis2 應(yīng)用程序,請使用清單 36 中所示的 URL 訪問 Geronimo 控制臺。
http://localhost:8080/console
|
作為 system/manager 登錄,并單擊 Application>Web App WARs,然后卸載并重新安裝 Axis2 應(yīng)用程序。(請記住,執(zhí)行此步驟后,必須重新加載 Web 服務(wù)。)
以編程方式使用 MTOM 不在本教程的討論范圍之內(nèi),但可以在 參考資料部分獲取有關(guān)此主題的更多信息。只是要注意,在 Axis2 的 0.95 版之前的版本上可能不會按照預(yù)期工作,因為該版本中包含了 SOAP with Attachments API for Java (SAAJ) 實現(xiàn)。
結(jié)束語
在系統(tǒng)間的互操作性非常重要的當(dāng)今世界,可將 Web 服務(wù)視為面向服務(wù)的體系結(jié)構(gòu)的基礎(chǔ)。而 SOAP 則是企業(yè)級 Web 服務(wù)的基礎(chǔ)。本教程介紹了 Web 服務(wù)的基礎(chǔ)知識,并對了解和在自己的應(yīng)用程序中使用 SOAP 所必需的概念和編程知識進(jìn)行了說明。在本教程中,您了解了以下內(nèi)容:
- 有關(guān) Web 服務(wù)的重要概念
- 如何安裝和使用 Geronimo 應(yīng)用服務(wù)器
- 如何安裝和使用 Axis2 Web 服務(wù)應(yīng)用程序
- 如何創(chuàng)建客戶機(jī)來訪問 SOAP 服務(wù)
- 如何創(chuàng)建 SOAP 服務(wù)
- 有關(guān) SOAP 服務(wù)的其他問題,如
GET 請求和附件
在本系列的第 2 將討論 Web 服務(wù)描述語言,您將從中學(xué)習(xí)如何使用 WSDL 來實現(xiàn)在本文中執(zhí)行的很多步驟的自動化,并提供更便于其他人訪問您構(gòu)建的服務(wù)的方法。
下載
描述 |
名字 |
大小 |
下載方法 |
Source code |
ws-understand-web-services1.zip |
12KB |
HTTP |
參考資料
學(xué)習(xí)
獲得產(chǎn)品和技術(shù)
- 從此處下載 Apache Geronimo。
- 從此處下載 Apache Axis2。本教程使用的是 0.94 版,但應(yīng)該也能使用更高版本。
- 從此處下載 J2SE SDK。
關(guān)于作者
 |
|
 |
Nicholas Chase 曾參與多家公司的網(wǎng)站開發(fā)工作,如 Lucent Technologies、Sun Microsystems、Oracle 和 Tampa Bay Buccaneers。Nick 擔(dān)任過高中物理教師、低輻射廢料處理設(shè)施管理員、在線科幻小說雜志編輯、多媒體工程師、Oracle 講師以及一家交互通信公司的首席技術(shù)官。他已經(jīng)出版了多本圖書,其中包括 XML Primer Plus (Sams)。
|
|