簡介:遠程過程調用(RPC)是基於 Simple Object Access Protocol(SOAP)或 Representational State Transfer(REST)的現代 Web 服務的前身。因為所有 Java™ 平台的 Web 服務 API 都構建 在從 RPC 引入的概念之上,所以要想用 Java 語言編寫有效且高效的 Web 服務,理解 Java API for XML-Based RPC(JAX-RPC)幾乎是必需的。本教程講解如何獲取、安裝和配置 JAX-RPC 並構建一個服務 器端 RPC 接收器和一個簡單的客戶端應用程序。
開始之前
關於本教程
本教程完整地介紹如何安裝、配置、構建和運行基於遠程過程調用(RPC)的 Web 服務。我們將下載 和安裝一個 Java API for XML-Based RPC(JAX-RPC)實現,學習如何在 Java 類和包中使用 JAX-RPC, 並構建客戶機和服務器來支持基於 RPC 的交互。此外,還討論配置選項,並幫助您熟悉如何部署基於 RPC 的應用程序。
目標
本教程全面介紹 JAX-RPC Web 服務的構建。更重要的是,學習所有 Web 服務的構建方式。本教程討 論在基於服務的體系結構中客戶機-服務器交互的基本知識,並把 RPC 作為這些原理的一種實現來研究。
還將在實踐背景下全面了解 JAX-RPC API。盡管我們並不使用每個類的每個方法,但是將討論在真實 環境中哪些類和方法是最基本的,以及哪些方法是不太 有用的。我們將在構建一個基於 RPC 的客戶機和 服務器的過程中講解這些概念。
因為基於服務的體系結構與傳統的客戶機-服務器 Web 交互(比如通過 HTML 前端向 Java servlet 發出 POST 請求)相比不太直接,而且比較難以管理,所以也 比較難實現。本教程討論構建 Web 服務的 一些最佳實踐和常見錯誤。
還將:
了解 JAX-RPC 的基礎知識,因為它們與廣泛的 Web 服務相關
了解基於 RPC 的服務與基於 SOAP 和 REST 的服務之間的差異
了解在什麼情況下 RPC 服務是合適的選擇
先決條件
本教程是為 Java 程序員編寫的。您應該熟悉 Java 應用程序開發,熟悉如何使用標准的和第三方的 Java API 和工具集。
還需要一個能夠駐留服務器端 Java 應用程序(servlet)的 Web 服務器。可以使用任何支持 Java 的 Web servlet 容器、應用服務器或駐留服務提供商。最流行的解決方案之一是 Apache Tomcat,這種 產品是免費的,而且有良好的文檔。由您自己決定是在(您公司或 ISP 的)遠程服務器上測試程序,還 是在本地機器上測試。只需在一台可訪問的機器上安裝和運行服務器即可。本教程會詳細介紹如何在這些 服務器上配置 JAX-RPC,所以目前您還不需要理解 JAX-RPC 的 servlet 和 Web 服務器之間的關系。
了解 Java servlet 和 Apache Tomcat(尤其是 servlet 駐留功能和 web.xml 部署描述符文件)會 有幫助,但不是必需的。
本教程主要關注 JAX-RPC 和 JAX-RPC 的 Apache Axis 開放源碼實現,但是不要求讀者具備 RPC、 JAX-RPC 或 Apache Axis 知識。本教程會詳細討論它們的下載、安裝和配置過程。
下載和安裝 Apache Axis
因為 JAX-RPC 並不是標准 Java 發行版的組成部分,所以需要單獨安裝和配置。(正如稍後所解釋的 ,實際上並不安裝 JAX-RPC 本身,而是安裝某種 JAX-RPC 實現)。目前不必考慮 RPC 是什麼;我們將 會進一步討論 RPC。目前,只需按照以下步驟讓 JAX-RPC 運行起來,這樣就可以通過實踐這個 API 和使 用這個 API 的程序來學習相關知識。
檢查 servlet 引擎
確認您的 servlet 引擎正在運行而且可以訪問它,還要查明通過 Web 浏覽器訪問服務器所用的主機 名和端口。如果在本地機器上安裝了 Apache Tomcat,那麼可以在 http://localhost:8080 上訪問服務 器的 Web 頁面,您應該會看到與圖 1 相似的頁面:
圖 1. 確認 Tomcat 正在運行
顯然,如果安裝了其他 servlet 引擎,或者已經設置了 Tomcat 中的內容,就會看到不同的頁面。無 論如何,只要有可訪問的 servlet 引擎,就可以繼續了。
不需要安裝 JAX-RPC
與 Java API for XML Binding(JAXB)或 Java API for XML Processing(JAXP),甚至 JDBC 等標 准 API 一樣,JAX-RPC 其實是一個 API 規范。換句話說,它僅僅是一個文檔,其中規定了一組 Java 類 和接口。這個文檔描述 JAX-RPC 類和接口的行為;它並沒有描述如何構建 JAX-RPC 應用程序,但是詳細 規定了涉及的組件以及如何用 Java 構造表示它們。
這個 API 還包含一組也稱為 JAX-RPC 的類和接口(不同的東西都稱為 “JAX-RPC”,這可能會引起 混淆)。這些類和接口有時候稱為語言綁定(尤其是在涉及 XML 規范時),但是它們僅僅 是由規范定義 的構造。沒有用來測試的示例類、示例代碼或偽服務。
JAX-RPC 包含的類和接口都放在 javax.xml.rpc 包和幾個子包中:
javax.xml.rpc.encoding
包中的三個接口是核心組件:
javax.xml.rpc.handler
javax.xml.rpc.handler.soap
javax.xml.rpc.holders
javax.xml.rpc.server
javax.xml.rpc.soap
javax.xml.rpc
javax.xml.rpc.Call
javax.xml.rpc.Service
javax.xml.rpc.Stub
在本教程中,您將了解關於這些接口和其他 JAX-RPC 包的更多信息。目前要注意,這三個核心組件是 接口 而不是類。實際上,核心 JAX-RPC 包只包含很少幾個具體類,其中的 NamespaceConstants 和 ParameterMode 實際上是實用程序類。那麼,類(也就是用 new 實例化的代碼)在哪裡呢?
JAX-RPC 把 API 與實現分隔開
JAX-RPC 的設計者定義了一個規范,然後編寫了許多接口。這些接口定義類名和行為,但是它們沒有 實現 這些行為。生產商可以編寫自己的 API 來實現 JAX-RPC 的標准接口。
您必須明白一點:JAX-RPC 本身沒什麼用。它有許多方法和接口,但是沒有支持和實現它們的代碼。 因此,實際上 “安裝 JAX-RPC” 是沒有意義的。安裝 JAX-RPC 實際上是指安裝 JAX-RPC 的一種實現。 為了方便,所有 JAX-RPC 接口都附帶有可用的實現,而且經過適當的打包。所以盡管可以下載 JAX-RPC 規范文檔,但是不需要安裝 JAX-RPC,只需安裝這個 API 的某種實現。
安裝 Apache Axis
本教程使用的 JAX-RPC 實現是 Apache Axis。Axis 是免費的、開放源碼且 得到良好的支持。本教程使用 Apache Axis 1.4 而不是 Axis 2.0,因為後者不太適合 RPC 應用程序。 Axis 1.4 仍然是當前支持的版本。
下載 Apache Axis 1.x
首先,訪問 Apache Axis 1.x Web 站點的 Releases 頁面。您會看到可以下載的版本列表,列表按版本號排序,見圖 2:
圖 2. 選擇以 “1” 開頭的 Apache Axis 最新版本
選擇最新版本;本教程使用 1.4 版。選擇一個版本之後,可以選擇一個鏡像站點。最後,選擇 適合自己平台的二進制下載文件。Windows® 用戶應該選擇以 .zip 結尾的文件。Mac OS X 或 Linux® 用戶應該選擇 .tar.gz 版本。所以對於 Mac OS X 平台,選擇 axis-bi-1_4.tar.gz; Windows 用戶選擇 axis-bin-1_4.zip。
展開 Apache Axis 並選擇安裝位置
展開您下載的包,會出現一個名稱與 axis-1_4 相似的目 錄。把這個目錄和其中的所有內容轉移到一個長期位置,最好是您保存所有其他 Java 程序的位置。例如 ,在我的系統上,我把 Axis 目錄移動到了 /usr/local/java:
[bdm0509:/usr/local/java] ls
apache-tomcat-6.0.16 axis-1_4 xalan- j_2_7_1
您可以選擇自己喜歡的任何位置;選擇 C:/Java 或主目錄下的子目錄是比較方便 的。只需確保文件位於便於訪問、不會被意外刪除的位置。
現在需要創建一個 Web 應用程序,做一些基本配置,然後啟動 Axis 服務。這是本教程要完成的下一 個步驟;但是,首先需要解決關於 JAX-RPC 的一些基本問題。
JAX-RPC 和本教程過時了嗎?
在安裝 Axis 1.x 和學習本教程的過程中,您會看到一些 JAX-WS 參考資料反復指出 JAX-WS 將要替 代 JAX-RPC。JAX-WS 確實將要替代 JAX-RPC;但是,這並不意味著 JAX-RPC 是完全無用或過時的。RPC 已經存在很長時間了,這是最干淨的一種 Web 服務形式:長期運行的服務器端程序根據需要向客戶機提 供服務。服務提供某種對本身的描述,包括它需要的參數和它返回的數據。
盡管 JAX-WS 是基於 Java 的 Web 服務未來的發展方向,但是它使用與 JAX-RPC 相同的概念。因此 ,盡管語法不同,但是在遷移到 JAX-WS 時本教程討論的原理仍然是非常有幫助的。另外,Axis 2.x 支 持 JAX-WS;所以在遷移到 JAX-WS 時,本教程對 Axis 框架的介紹仍然是有用的。
檢驗 Axis 安裝
在構建基於 RPC 的應用程序之前,先部署 Axis 附帶的示例服務。這樣可以非常簡便地測試 Axis 和 JAX-RPC 安裝,從而在進行開發之前確保系統正常。另外,通過這樣的測試,還可以體驗 RPC 的工作方 式、服務的運行方式以及客戶機如何訪問這些服務。
安裝 Apache Axis Web 應用程序
Apache Axis 附帶一個示例 Web 應用程序,這個程序可以部署在任何 servlet 容器中。只需把這個 Axis Web 應用程序復制到 servlet 容器中駐留 Web 應用程序的地方,然後測試 Axis。
復制 Axis Web 應用程序
找到 servlet 引擎中部署 Web 應用程序的目錄。這通常是一個稱為 webapps/ 的目錄。它常常直接 嵌套在 servlet 引擎的根文件夾中。如果使用 Tomcat,這個目錄直接嵌套在 Tomcat 根文件夾中;例如 ,在我的系統中,這是 apache-tomcat-6.0.16/webapps/ 文件夾。
現在,把 Axis 安裝中的 webapps/ 目錄中的 Axis 目錄復制到 servlet 引擎的 webapps/ 目錄。一 定要復制 這個目錄,而不是移動 它。這確保 Axis 安裝中存在原來的 Web 應用程序。這樣的話,如果 修改 servlet 引擎的版本,原來的應用程序會成為備份,可以輕松地恢復。所以需要執行清單 1 所示的 命令:
清單 1. 把 Axis Web 應用程序復制到 servlet 引擎的 webapps/ 目錄
[bdm0509:/usr/local/java/apache-tomcat-6.0.16/webapps]
cp -rp /usr/local/java/axis-1_4/webapps/axis .
[bdm0509:/usr/local/java/apache-tomcat-6.0.16/webapps] ls
ROOT docs host-manager
axis examples manager
啟動 servlet 引擎
現在啟動(或重新啟動)servlet 引擎。可以使用命令行或 Web 界面。對於 Tomcat,只需使用命令 關閉引擎並重新啟動:
[bdm0509:/usr/local/java/apache-tomcat-6.0.16/bin] sh shutdown.sh
Using CATALINA_BASE: /usr/local/java/apache-tomcat-6.0.16
Using CATALINA_HOME: /usr/local/java/apache-tomcat-6.0.16
Using CATALINA_TMPDIR: /usr/local/java/apache-tomcat-6.0.16/temp
Using JRE_HOME: /Library/Java/Home
[bdm0509:/usr/local/java/apache-tomcat-6.0.16/bin] sh startup.sh
Using CATALINA_BASE: /usr/local/java/apache-tomcat-6.0.16
Using CATALINA_HOME: /usr/local/java/apache-tomcat-6.0.16
Using CATALINA_TMPDIR: /usr/local/java/apache-tomcat-6.0.16/temp
Using JRE_HOME: /Library/Java/Home
只要沒有看到任何錯誤,就說明這個 Axis Web 應用程序已經安裝好了。
測試和檢驗安裝
一些 servlet 引擎會熱部署 Web 應用程序
可能不需要重新啟動 servlet 引擎。許多 servlet 引擎會自動部署放到引擎的 webapps/ 目錄中的 任何 WAR(Web 存檔)文件或應用程序。但是,停止並重新啟動引擎仍然是一種好做法,這會確保應用程 序生效。另外,這使 servlet 引擎能夠在啟動 Axis 安裝時報告錯誤或警告,這個預防措施有助於發現 潛在的問題。
找到剛才安裝的 Axis Web 應用程序。通常,只需輸入 servlet 引擎的 URL、前向斜槓(/)和 Web 應用程序的名稱:axis。因此,如果 servlet 引擎駐留在 http://localhost:8080,Axis 完整的 URL 就是 http://localhost:8080/axis/。應該會看到與圖 3 相似的屏幕:
圖 3. Axis 1.x 的默認 Web 頁面
這說明這個 Web 應用程序正在運行,但是並不 保證 Axis 所需的所有東西都已經安裝了。所以需要 進行檢驗。為了檢驗 Axis 安裝,單擊主頁上的第一個鏈接 “Validation”。應該會看到與圖 4 相似的 屏幕:
圖 4. Apache Axis 的檢驗頁面(包含幾個錯誤)
這是一個 JavaServer Page(JSP),它會檢驗安裝並報告缺少的組件。圖 4 所示的示例指出了三個 問題:
缺少必需的 javax.activation.DataHandler 類。
缺少 javax.mail.internet.MimeMessage helper 類。
缺少 org.apache.xml.security.Init helper 類。
這個頁面的優點是,它明確說明了應該如何處理這些錯誤。對於每個錯誤,都會報告缺少的類以及包 含這個類的 Java Archive(JAR)或庫,還提供下載缺少的組件的鏈接。
下載缺少的組件
得到缺少的組件的完整列表之後,應該下載所有這些組件。首先單擊各個組件的鏈接。下載引用的每 個庫,根據需要展開庫,找到 Axis 檢驗頁面上列出的 JAR 文件。
例如,對於 Java Activation Framework,單擊 Axis 頁面上的鏈接並單擊 java.sun.com 下載鏈接 。最終會下載一個 ZIP 文件,它可以展開成一個目錄:jaf-1.0.2。在這個目錄中有所需的 JAR 文件 activation.jar。把這個文件復制到 servlet 引擎的 lib/ 目錄:
[bdm0509:/usr/local/java/apache-tomcat-6.0.16/lib]
cp ~/Downloads/jaf-1.0.2/activation.jar .
對於缺少的其他組件,重復這個步驟。可能需要搜索引用的每個頁面,尋找正確的下載鏈接,但是對 於每個組件,只需一兩次單擊就能夠完成下載(還常常需要接受軟件許可協議)。
下載所有的庫之後,重新啟動 servlet 引擎。servlet 引擎無法動態地裝載庫,所以必須重新啟動。 然後,再次訪問 Axis 主頁,單擊 Validation 鏈接,檢查是否還有問題。
獲取(或省略)XML Security
Axis 有一個可選組件 XML Security(在 圖 4 中 Optional Components 下面列出),對於是否使用 這個組件,有很多爭議。XML Security 實際上需要一個第三方庫,在下載 XML Security 時並不會 在下 載包中得到這個庫。更糟糕的是,手工下載這個文件並不能解決問題。實際上,需要從源代碼構建 XML Security,這需要設置和運行 Ant、JUnit 和其他幾個第三方工具。因此,為了使用這個可能不常用的庫 ,需要完成許多與 RPC 不相關的工作。
如果您是經驗豐富的開發人員,那麼可以花時間下載 XML Security 源代碼。閱讀 XML Security 包 含的 INSTALL 文件,下載所有第三方庫,然後從源代碼構建 JAR 文件。然後,把這些 JAR 文件復制到 Tomcat 或 servlet 引擎中。
如果您不是 Java 高手,或者只想試試 JAX-RPC 和 Axis,那麼可以跳過這個步驟。在檢驗頁面上會 出現與圖 5 相似的結果:
圖 5. 沒有使用 XML Security 時的 Axis 檢驗頁面
本教程並不需要 XML Security,所以如果得到與圖 5 相似的結果,Axis 就安裝好了。
測試一個簡單的 Java Web 服務
在構建自己的 Web 服務之前,應該再執行一個確認步驟。到目前為止,已經檢驗了安裝,但是還沒有 測試實際的 Web 服務調用。再次訪問 Axis 主頁(見 圖 3),單擊 “Call” 鏈接。這會運行一個 Web 服務,從而確認客戶端和服務器端組件都正常工作。應該會看到與圖 6 相似的結果:
圖 6. Axis 附帶一個示例 Java Web 服務
這看起來不太明白。這是因為浏覽器(在圖 6 中是 Safari 浏覽器)嘗試把這個調用的輸出顯示為更 友好的形式。可以單擊 View > Source 來查看原始輸出。(根據您使用的浏覽器,可能不需要這個步 驟)。清單 2 給出 XML 源代碼:
清單 2. 示例 Web 服務返回 XML 版本的請求頭
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<listResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<listReturn soapenc:arrayType="xsd:string[8]" xsi:type="soapenc:Array"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<listReturn xsi:type="xsd:string">user-agent:Mozilla/5.0
(Macintosh; U; PPC Mac OS X 10_5_2; en-us) AppleWebKit/525.18
(KHTML, like Gecko) Version/3.1.1 Safari/525.18</listReturn>
<listReturn xsi:type="xsd:string">referer:http://localhost:8080/axis/
</listReturn>
<listReturn xsi:type="xsd:string">accept:text/xml,application/xml,
application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,
*/*;q=0.5</listReturn>
<listReturn xsi:type="xsd:string">accept-language:en-us</listReturn>
<listReturn xsi:type="xsd:string">accept-encoding:gzip, deflate</listReturn>
<listReturn xsi:type="xsd:string">cookie:JSESSIONID=
42A37ED6763ECC773D5FEB70484D57B1</listReturn>
<listReturn xsi:type="xsd:string">connection:keep-alive</listReturn>
<listReturn xsi:type="xsd:string">host:localhost:8080</listReturn>
</listReturn>
</listResponse>
</soapenv:Body>
</soapenv:Envelope>
這些頭仍然有點兒混亂(其中有許多與 SOAP 相關的 XML,而且為了提高可讀性,清單 2 的格式實際 上已經調整過了)。但這裡的要點是,響應是 XML,而不是一個錯誤。如果獲得與清單 2 相似的結果, 就說明系統正常。現在已經安裝了 Axis,簡單的 Java Web 服務調用也已經正常工作了。
構建一個程序,並將其發布為服務
在 JAX-RPC 和其他任何 Web 服務框架中,最出色的特性之一是,在編寫作為 Web 服務發布的程序時 不需要考慮 RPC 或 Web 服務。大多數 Web 服務最初並不是作為 Web 服務開發的;實際上,它們最初是 一般的程序,包含一些在調用時返回值的方法。如果您熟悉這個概念,就說明已經理解了 Web 服務的本 質:它們僅僅是可以通過 Web 而不是虛擬機訪問的程序。
所以,在開始關注 RPC 語法或 Web Services Description Language(WSDL)之前,我們需要一個可 供 Web 客戶機使用的類。
構建 Java 類
假設您希望開發一個簡單的圖書搜索工具。這個程序存儲與認知科學、學習理論和用戶界面設計相關 的圖書。但是,因為這些書的內容非常深奧,書名常常無法反映書的內容,所以這個程序必須能夠按照指 定的關鍵字搜索存儲庫,並返回與這個關鍵字相關的書。例如,對關鍵字 presentation 的搜索可能返回 Garr Reynolds 所著的 Presentation Zen 和 Dan Roam 所著的 The Back of the Napkin。第一個書名 本身就符合條件,但是這個程序很聰明,可以找到第二本書,而一般的搜索程序很可能找不到它。
目前,還不需要為 Web 服務或 JAX-RPC 操心。我們只需要一個搜索程序,以後將把它轉換為 Web 服 務。
定義類和方法調用
首先編寫一個簡單的類骨架,定義希望提供給程序用戶的方法。清單 3 給出一個 Java 類,它接受一 個搜索詞並返回一個書名列表:
清單 3. 返回書名的完整程序的骨架
package dw.ibm;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class BookSearcher {
private Map books;
public BookSearcher() {
books = new HashMap();
}
public void setBooks(Map books) {
this.books = books;
}
public void addBook(String title, List keywords) {
books.put(title, keywords);
}
public List getKeywords(String title) {
return (List)books.get(title);
}
public void addKeyword(String title, String keyword) {
List keywords = (List)books.get(title);
if (keywords != null) {
keywords.add(keyword);
// No need to manually "re-put" the list
} else {
keywords = new LinkedList();
keywords.add(keyword);
books.put(title, keywords);
}
}
public List search(String keyword) {
List results = new LinkedList();
// logic to be implemented
return results;
}
}
這相當簡單。每本書作為一個條目存儲在一個映射中。映射的鍵是書名,因此很容易在書名中搜索關 鍵字。另外,每本書有一個相關聯的關鍵字列表。清單 3 中沒有表現出這一點,但是這些關鍵字僅僅是 “presentation”、“cognitive science” 或 “marketing” 等字符串值。
這個程序包含實現以下功能的方法:
通過書名和關鍵字列表添加書
為給定書的添加關鍵字
獲取書的關鍵字
這些方法既是實用程序(供管理員使用),也具有功能性(幫助使用這個程序的用戶獲得關於書的信 息)。這個程序還有一個搜索方法,這是關鍵功能:給出一個關鍵字,就會返回所有匹配的書名。剩下的 工作僅僅是實現搜索邏輯並裝載一些圖書信息。
參數化列表和泛化類型在哪裡?
輸入或下載(參見 下載)清單 3 中的代碼並編譯。如果您仍然使用 Java 1.4,這段代碼可以正常編 譯。如果在 Java 5 或更高版本上編譯,就會收到幾個警告,因為 List 未經檢查而且無類型。很容易添 加類型,而且值得這麼做。為關鍵字列表設置類型非常有益:確保只能把字符串關鍵字添加到列表中,從 而使程序更安全。但是,目前的程序仍然非常明確,很容易理解,這對於本教程很重要。
編寫搜索功能
通過關鍵字搜索書名非常簡單,只需循環遍歷圖書的映射,檢查每本書的列表是否包含指定的關鍵字 。同樣,這裡不涉及任何 Web 服務概念;它僅僅是基本的程序邏輯。清單 4 給出 BookSearcher 中完整 的 search() 方法:
清單 4. 按照關鍵字搜索圖書的 search() 方法
public List search(String keyword) {
List results = new LinkedList();
for (Iterator i = books.keySet().iterator(); i.hasNext(); ) {
String title = (String)i.next();
List keywords = (List)books.get(title);
if (keywords.contains(keyword)) {
results.add(title);
}
}
return results;
}
這個方法循環遍歷存儲庫中的所有書,取出每本書的關鍵字列表。檢查列表中是否包含與指定的關鍵 字匹配的條目。然後,通過另一個列表返回匹配的書。
添加一些示例數據
最後,需要一些示例數據。一般情況下,這些數據可能存儲在數據庫中。但是,這個程序只是為了演 示 JAX-RPC 技術,所以只需用一個簡單的 addBooks() 方法(見清單 5)添加一些書名和關鍵字:
清單 5. 提供圖書數據的 addBooks() 方法
private void addBooks() { List keywords = new LinkedList();
keywords.add("presentation"); keywords.add("Keynote");
keywords.add("PowerPoint"); keywords.add("design");
addBook("Presentation Zen", keywords);
List keywords2 = new LinkedList();
keywords2.add("presentation"); keywords2.add("user interface design");
keywords2.add("pictures"); keywords2.add("visuals");
addBook("The Back of the Napkin", keywords2);
List keywords3 = new LinkedList();
keywords3.add("marketing"); keywords3.add("business");
keywords3.add("commercials"); keywords3.add("consumers");
addBook("Purple Cow", keywords3);
List keywords4 = new LinkedList();
keywords4.add("marketing"); keywords4.add("business");
keywords4.add("notecards"); keywords4.add("design");
keywords4.add("visuals"); keywords4.add("pictures");
keywords4.add("humor");
addBook("Indexed", keywords4);
List keywords5 = new LinkedList();
keywords5.add("marketing"); keywords5.add("business");
keywords5.add("design"); keywords5.add("emotion");
keywords5.add("functionality"); keywords5.add("consumers");
addBook("Emotional Design", keywords5);
keywords.clear();
}
這裡沒有什麼值得關注的地方;現在只需調用 addBooks() 方法。清單 6 給出 BookSearcher 的完整 版本,其中在構造函數中調用 addBooks(),從而自動地填充一些書名和關鍵字:
清單 6. 完整的 BookSearcher 類
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class BookSearcher {
private Map books;
public BookSearcher() {
books = new HashMap();
// for example purposes
addBooks();
}
public void setBooks(Map books) {
this.books = books;
}
public void addBook(String title, List keywords) {
books.put(title, keywords);
}
public void addKeyword(String title, String keyword) {
List keywords = (List)books.get(title);
if (keywords != null) {
keywords.add(keyword);
// No need to manually "re-put" the list
} else {
keywords = new LinkedList();
keywords.add(keyword);
books.put(title, keywords);
}
}
public List getKeywords(String title) {
return (List)books.get(title);
}
public List search(String keyword) {
List results = new LinkedList();
for (Iterator i = books.keySet().iterator(); i.hasNext(); ) {
String title = (String)i.next();
List keywords = (List)books.get(title);
if (keywords.contains(keyword)) {
results.add(title);
}
}
return results;
}
private void addBooks() {
List keywords = new LinkedList();
keywords.add("presentation"); keywords.add("Keynote");
keywords.add("PowerPoint"); keywords.add("design");
addBook("Presentation Zen", keywords);
List keywords2 = new LinkedList();
keywords2.add("presentation"); keywords2.add("user interface design");
keywords2.add("pictures"); keywords2.add("visuals");
addBook("The Back of the Napkin", keywords2);
List keywords3 = new LinkedList();
keywords3.add("marketing"); keywords3.add("business");
keywords3.add("commercials"); keywords3.add("consumers");
addBook("Purple Cow", keywords3);
List keywords4 = new LinkedList();
keywords4.add("marketing"); keywords4.add("business");
keywords4.add("notecards"); keywords4.add("design");
keywords4.add("visuals"); keywords4.add("pictures");
keywords4.add("humor");
addBook("Indexed", keywords4);
List keywords5 = new LinkedList();
keywords5.add("marketing"); keywords5.add("business");
keywords5.add("design"); keywords5.add("emotion");
keywords5.add("functionality"); keywords5.add("consumers");
addBook("Emotional Design", keywords5);
keywords.clear();
}
}
現在,有了一個可以運行的程序,但是其中沒有任何 JAX-RPC 代碼。這就是使用 JAX-RPC 這樣的 API 比編寫 Java servlet 或 JSP 更方便的原因。在編寫 servlet 或 JSP 時,代碼從一開始就與服務 器端的情況相關;也可以編寫一個類,但是 servlet 必須了解通過調用傳遞和返回的數據的細節。在使 用 JAX-RPC 時,編寫的是一般的 Java 類,不包含與服務器端或 Web 服務相關的調用,然後再添加 JAX-RPC。
(在轉換為服務之前)測試代碼
在把 JAX-RPC 集成到程序中時,會顯著增加復雜性:調用可以來自客戶機、JAX-RPC API、servlet 引擎等地方。應該在執行這個步驟之前測試代碼,確保業務邏輯和應用程序邏輯都符合預期。這樣的話, 如果以後遇到了麻煩,就可以確定問題(在大多數情況下)出現在 RPC 組件中,而與類的邏輯無關。
清單 7 是一個簡單的測試用例,可以從命令行運行它;它僅僅調用幾個方法並輸出結果,讓我們可以 檢驗結果是否正確:
清單 7. BookSearcher 的測試類
import java.util.Iterator;
import java.util.List;
public class BookSearchTester {
public static void main(String[] args) {
BookSearcher searcher = new BookSearcher();
List keywords = searcher.getKeywords("Purple Cow");
System.out.println("Keywords for 'Purple Cow':");
for (Iterator i = keywords.iterator(); i.hasNext(); ) {
System.out.println(" " + (String)i.next());
}
List books = searcher.search("design");
System.out.println("Books that match the keyword 'design':");
for (Iterator i = books.iterator(); i.hasNext(); ) {
System.out.println(" " + (String)i.next());
}
}
}
編譯並運行清單 7 中的代碼。應該會看到與清單 8 相似的結果集:
清單 8. 測試 BookSearcher 類的基本功能
[bdm0509:~/Documents/developerworks/jax-rpc] java BookSearchTester
Keywords for 'Purple Cow':
marketing
business
commercials
consumers
Books that match the keyword 'design':
Emotional Design
Indexed
把類轉換為 RPC 服務
有了 Java 類並在 servlet 引擎中設置和配置了 Axis 之後,就需要構建一個可供消費的 RPC 服務 。
但是 RPC 是 什麼?
RPC 是遠程(比如從另一台機器)過程(比如一個方法)調用。換句話說,RPC 意味著調用另一台機 器上的一個方法。它實際上就這麼簡單;編寫一個類,讓它的一個或多個方法可供程序調用,這些程序不 必在相同的虛擬機或物理機器中。
對於 BookSearcher,這意味著可以把 BookSearcher 類放在某處的一台 Web 服務器上,然後在本地 機器上運行使用這個類的程序。盡管肯定有一些與 RPC 相關的活動,尤其是在客戶機上,但是您的程序 可以像任何其他兩個類一樣進行交互。可以調用一個方法,向它發送參數,然後獲取響應。
為什麼不是遠程方法 調用?
RPC 技術早在 C# 和 Java 等面向對象語言成為主流之前就出現了。實際上,RPC 原來是為 C 應用程 序開發的,主要也用在這種應用程序中;在這種應用程序中,函數是公開行為的主要方法。當 RPC 服務 出現在 Java、C++ 和 C# 程序中時,因為許多開發人員熟悉 RPC 的概念,使用 RPC 這個詞更有意義, 所以沒有采用遠程方法調用(RMC)這樣的新詞匯。
因此,在 RPC 環境中,可以認為函數 和方法 是相似,不需要關注這兩個詞的語義差異。
服務器端類是 RPC 中的服務
現在,有了一個類(BookSearcher),它將放在服務器端。目前它只是一個一般的 Java 類,但是如 果可以通過 RPC 訪問其中的一些方法,這個類就成了一個服務。它向客戶機提供可以調用的函數(方法 )。進行調用的代碼被稱為客戶機 或調用者。
讓方法可供遠程調用使用就是公開了 這個方法。所以,必須選擇要通過 RPC 公開 哪些方法。可以公 開一個方法、所有方法或一部分方法。客戶機可以調用公開的方法,而且只能調用公開的方法。把類轉換 為服務,然後公開服務的一些方法,這個過程稱為發布 服務。所以我們要公開一個發布的服務的一些方 法。
但是我只有一台機器!
即使無法訪問 Web 服務器,也可以繼續學習本教程。在通過 Axis 以服務形式公開一個類之後,需要 通過網絡調用訪問這個類。但是,也可以在本地機器上運行另一個類來訪問這個服務,這是一種不錯的測 試用例,而且您不會錯過任何步驟。網絡調用會被路由回您自己的機器,不需要經過因特網;即使如此, 這仍然是網絡調用,這就夠了。即使您沒有另一台機器,在本地機器上運行所有代碼也是一樣的。
當然,如果能夠 把服務類放在另一台運行 servlet 引擎的機器,就能夠在完全成熟的遠程環境中看 到 RPC 的效果。還可以體驗到發出請求和接收響應所需的時間(這些時間與建立網絡連接所需的時間相 比可以忽略不計)。還會體驗真正的 RPC 環境。
用 Java Web Service(JWS)發布服務
在客戶機上,需要做一些工作來連接到啟用 RPC 的服務。但是,實際部署服務是很繁瑣的。因為需要 維護每個 RPC 包或工具集(比如 Axis)的與工具集相關的細節。不需要學習在 JAX-RPC 上構建的 API ,只需利用工具集來公開服務。
把 .java 文件復制到 .jws 文件中
在使用 Axis 時,發布服務最簡便最快速的方法是使用 JWS 文件。只需把 Java 源代碼文件(擴展名 為 .java)復制到一個擴展名為 .jws 的同名文件中。然後,把這個 .jws 文件放在 Axis Web 應用程序 中,這個應用程序應該在 servlet 引擎的 webapps/axis 目錄中。所以要想發布 BookSearcher,應該執 行清單 9 所示的命令:
清單 9. 把 Java 類轉換為 JWS 文件
[bdm0509:/usr/local/java/apache-tomcat-6.0.16]
cp ~/Documents/developerworks/jax-rpc/BookSearcher.java webapps/axis/BookSearcher.jws
這種做法看起來相當古怪;修改文件的擴展名通常不是好做法。但是,這正是 Axis 所需要的。當 Axis Web 應用程序在它的目錄中 “看到” .jws 文件時,它會把這個文件編譯為 Java Web 服務,並構 建所需的所有 SOAP 訪問代碼,讓客戶機可以訪問這個類。Axis 甚至會馬上部署這個服務。
通過 Web 浏覽器訪問服務
如果 servlet 引擎還沒有啟動的話,就啟動它。可以通過 http://hostname:port/axis 訪問 Axis Web 應用程序。如果在本地機器上使用 Tomcat 的默認安裝,這個 URL 就是 http://localhost:8080/axis。然後,加上一個前向斜槓、類名和 .jws 擴展名。所以要想訪問上面的示 例服務,應該輸入 URL http://localhost:8080/axis/BookSearcher.jws。Web 浏覽器應該會報告在這個 地址上確實有一個 Web 服務正在運行,見圖 7:
圖 7. Axis 部署服務並使它可以接受 Web 訪問
還會看到 “Click to see the WSDL” 鏈接。WSDL 是一個新詞匯,但這是一個非常重要的概念。
WSDL 描述服務
網絡服務描述語言(Web Services Description Language,WSDL)是一種 XML 詞匯表,用來為訪問 服務的客戶機代碼描述服務。WSDL 描述:
可用的方法
每個方法的參數
每個方法的返回值
從本質上說,WSDL 文件是與 Web 服務進行交互的規范。
單擊 BookSearcher Web 服務上的 “Click to see the WSDL” 鏈接,看看這個服務的 WSDL。如果 浏覽器嘗試解釋 XML,可以選擇 View > Source 來查看原始格式的 WSDL。BookSearcher 服務的 WSDL 見清單 10:
清單 10. BookSearcher 服務的 WSDL 描述可用的方法
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://localhost:8080/axis/BookSearcher.jws"
xmlns:apachesoap="http://xml.apache.org/xml-soap"
xmlns:impl="http://localhost:8080/axis/BookSearcher.jws"
xmlns:intf="http://localhost:8080/axis/BookSearcher.jws"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!--WSDL created by Apache Axis version: 1.4
Built on Apr 22, 2006 (06:55:48 PDT)-->
<wsdl:types>
<schema targetNamespace="http://xml.apache.org/xml-soap"
xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://localhost:8080/axis/BookSearcher.jws"/>
<import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
<complexType name="mapItem">
<sequence>
<element name="key" nillable="true" type="xsd:anyType"/>
<element name="value" nillable="true" type="xsd:anyType"/>
</sequence>
</complexType>
<complexType name="Map">
<sequence>
<element maxOccurs="unbounded" minOccurs="0"
name="item" type="apachesoap:mapItem"/>
</sequence>
</complexType>
<complexType name="Vector">
<sequence>
<element maxOccurs="unbounded" minOccurs="0"
name="item" type="xsd:anyType"/>
</sequence>
</complexType>
</schema>
<schema targetNamespace="http://localhost:8080/axis/BookSearcher.jws"
xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://xml.apache.org/xml-soap"/>
<import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
<complexType name="ArrayOf_xsd_anyType">
<complexContent>
<restriction base="soapenc:Array">
<attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:anyType[]"/>
</restriction>
</complexContent>
</complexType>
</schema>
</wsdl:types>
<wsdl:message name="addBookResponse">
</wsdl:message>
<wsdl:message name="addBookRequest">
<wsdl:part name="title" type="xsd:string"/>
<wsdl:part name="keywords" type="impl:ArrayOf_xsd_anyType"/>
</wsdl:message>
<wsdl:message name="searchRequest">
<wsdl:part name="keyword" type="xsd:string"/>
</wsdl:message>
<wsdl:message name="getKeywordsRequest">
<wsdl:part name="title" type="xsd:string"/>
</wsdl:message>
<wsdl:message name="addKeywordRequest">
<wsdl:part name="title" type="xsd:string"/>
<wsdl:part name="keyword" type="xsd:string"/>
</wsdl:message>
<wsdl:message name="setBooksResponse">
</wsdl:message>
<wsdl:message name="searchResponse">
<wsdl:part name="searchReturn" type="impl:ArrayOf_xsd_anyType"/>
</wsdl:message>
<wsdl:message name="setBooksRequest">
<wsdl:part name="books" type="apachesoap:Map"/>
</wsdl:message>
<wsdl:message name="addKeywordResponse">
</wsdl:message>
<wsdl:message name="getKeywordsResponse">
<wsdl:part name="getKeywordsReturn" type="impl:ArrayOf_xsd_anyType"/>
</wsdl:message>
<wsdl:portType name="BookSearcher">
<wsdl:operation name="setBooks" parameterOrder="books">
<wsdl:input message="impl:setBooksRequest" name="setBooksRequest"/>
<wsdl:output message="impl:setBooksResponse" name="setBooksResponse"/>
</wsdl:operation>
<wsdl:operation name="addBook" parameterOrder="title keywords">
<wsdl:input message="impl:addBookRequest" name="addBookRequest"/>
<wsdl:output message="impl:addBookResponse" name="addBookResponse"/>
</wsdl:operation>
<wsdl:operation name="addKeyword" parameterOrder="title keyword">
<wsdl:input message="impl:addKeywordRequest" name="addKeywordRequest"/>
<wsdl:output message="impl:addKeywordResponse" name="addKeywordResponse"/>
</wsdl:operation>
<wsdl:operation name="getKeywords" parameterOrder="title">
<wsdl:input message="impl:getKeywordsRequest" name="getKeywordsRequest"/>
<wsdl:output message="impl:getKeywordsResponse" name="getKeywordsResponse"/>
</wsdl:operation>
<wsdl:operation name="search" parameterOrder="keyword">
<wsdl:input message="impl:searchRequest" name="searchRequest"/>
<wsdl:output message="impl:searchResponse" name="searchResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="BookSearcherSoapBinding" type="impl:BookSearcher">
<wsdlsoap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="setBooks">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="setBooksRequest">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://DefaultNamespace" use="encoded"/>
</wsdl:input>
<wsdl:output name="setBooksResponse">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://localhost:8080/axis/BookSearcher.jws"
use="encoded"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="addBook">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="addBookRequest">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://DefaultNamespace" use="encoded"/>
</wsdl:input>
<wsdl:output name="addBookResponse">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://localhost:8080/axis/BookSearcher.jws"
use="encoded"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="addKeyword">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="addKeywordRequest">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://DefaultNamespace" use="encoded"/>
</wsdl:input>
<wsdl:output name="addKeywordResponse">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://localhost:8080/axis/BookSearcher.jws"
use="encoded"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getKeywords">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="getKeywordsRequest">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://DefaultNamespace" use="encoded"/>
</wsdl:input>
<wsdl:output name="getKeywordsResponse">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://localhost:8080/axis/BookSearcher.jws"
use="encoded"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="search">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="searchRequest">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://DefaultNamespace" use="encoded"/>
</wsdl:input>
<wsdl:output name="searchResponse">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://localhost:8080/axis/BookSearcher.jws"
use="encoded"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="BookSearcherService">
<wsdl:port binding="impl:BookSearcherSoapBinding" name="BookSearcher">
<wsdlsoap:address location="http://localhost:8080/axis/BookSearcher.jws"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
這個 WSDL 很詳細,也相當長。但是,它是理解 Web 服務和連接 Web 服務的代碼的關鍵。本教程的 下一節詳細分析這個 WSDL,為編寫遠程搜索圖書的代碼做准備。
分析 WSDL 來理解服務
如果仔細看看上一節中的 WSDL,就可以看出 WSDL 究竟描述了什麼。但是請牢記,在許多情況下,您 無法獲得要使用的服務的源代碼。您得到的只有 WSDL。在這種情況下,理解 WSDL 對於正確有效地使用 RPC 服務(或任何其他類型的 Web 服務)非常重要。
WSDL 包含大量名稱空間信息
程序員和文檔作者常常不重視(甚至完全忽視)WSDL 文件中的根元素聲明。但是,在 WSDL 中,這個 聲明包含大量信息,見清單 11:
清單 11. 根元素聲明
<wsdl:definitions targetNamespace="http://localhost:8080/axis/BookSearcher.jws"
xmlns:apachesoap="http://xml.apache.org/xml-soap"
xmlns:impl="http://localhost:8080/axis/BookSearcher.jws"
xmlns:intf="http://localhost:8080/axis/BookSearcher.jws"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
每個 xmlns: 屬性定義一個名稱空間和相關聯的前綴。所以這裡有 Apache SOAP 名稱空間、SOAP 編 碼名稱空間、WSDL 和 WSDL SOAP 名稱空間、XML Schema 名稱空間等等。還設置了目標名稱空間,它的 統一資源定位符(URI)是代表發布的服務的 JWS 文件。
好消息是,盡管這些名稱空間對於 SOAP、RPC、Axis、XML 和在 Web 服務中使用的幾乎所有其他技術 都很重要,但是不需要太為它們操心。WSDL 中的大多數元素由一個 WSDL 規范定義並與 wsdl 前綴相關 聯,XML Schema 名稱空間(以及相關聯的類型)與 xsd 前綴相關聯,知道這些就夠了。其他名稱空間也 有用,但是在編寫有效的 Web 服務客戶機時不需要理會它們。
WSDL 定義基於對象的類型
WSDL 的下一個關鍵部分包含在 <wsdl:types> 元素中,見清單 12:
清單 12. <wsdl:types> 元素
<wsdl:types>
<schema targetNamespace="http://xml.apache.org/xml-soap"
xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://localhost:8080/axis/BookSearcher.jws"/>
<import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
<complexType name="mapItem">
<sequence>
<element name="key" nillable="true" type="xsd:anyType"/>
<element name="value" nillable="true" type="xsd:anyType"/>
</sequence>
</complexType>
<complexType name="Map">
<sequence>
<element maxOccurs="unbounded" minOccurs="0"
name="item" type="apachesoap:mapItem"/>
</sequence>
</complexType>
<complexType name="Vector">
<sequence>
<element maxOccurs="unbounded" minOccurs="0"
name="item" type="xsd:anyType"/>
</sequence>
</complexType>
</schema>
<schema targetNamespace="http://localhost:8080/axis/BookSearcher.jws"
xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://xml.apache.org/xml-soap"/>
<import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
<complexType name="ArrayOf_xsd_anyType">
<complexContent>
<restriction base="soapenc:Array">
<attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:anyType[]"/>
</restriction>
</complexContent>
</complexType>
</schema>
</wsdl:types>
WSDL 和 Web 服務了解一些基本類型,比如 string、int 和 float,但是不了解比 Java 基本數據類 型更復雜的類型。但是,BookSearcher 類的一些方法能夠接受或返回列表和映射。為了處理這些基於對 象的復雜類型,WSDL 必須通過 XML Schema 類型定義它們。文檔的這個部分定義所有這些類型。例如, 清單 13 給出一個映射的定義,使 RPC 客戶機和服務可以理解這個映射:
清單 13. 定義 RPC 客戶機和服務可以理解的一個映射
<complexType name="mapItem">
<sequence>
<element name="key" nillable="true" type="xsd:anyType"/>
<element name="value" nillable="true" type="xsd:anyType"/>
</sequence>
</complexType>
<complexType name="Map">
<sequence>
<element maxOccurs="unbounded" minOccurs="0"
name="item" type="apachesoap:mapItem"/>
</sequence>
</complexType>
Vector 類型以同樣的方式代表列表,提供了列表的上限和下限。這對使用 WSDL 造成了一定的困難, 因為只有在編寫了一些服務和客戶機之後,才會逐漸熟悉 Java 對象和定制 WSDL 類型之間的基本映射。 盡管如此,如果看到一個方法以 Vector 作為參數,那麼只需在 <wsdl:types> 元素中尋找關於這 個類型的信息,包括對其中的值的約束。
每次發送和返回的都是消息
下一個元素由 <wsdl:message> 表示。這裡有一些源自 Java 的概念,它們主要關注與網絡和 SOAP 相關的問題。在向服務中的方法發送請求時,實際上是發送一個消息。如果請求的方法沒有參數, 消息就不包含方法所操作的任何數據。如果方法需要參數數據,就必須在消息中發送數據。
服務從方法返回時也是如此:返回的消息可以包含來自方法的數據,也可以沒有數據。但是,關鍵在 於發送和接收的是單獨的 消息。一個消息從客戶機發送到服務,另一個消息從服務返回到客戶機。這兩 個消息在邏輯上相關,但是在編程或技術范疇上沒有聯系。
因此,必須聲明和定義這些消息。以 BookSearcher 的 getKeywords() 方法為例。它接收一個字符串 標題參數,返回一個列表。必須在 WSDL 中聲明這兩個消息:
清單 14. WSDL 中的消息聲明
<wsdl:message name="getKeywordsRequest">
<wsdl:part name="title" type="xsd:string"/>
</wsdl:message>
<wsdl:message name="getKeywordsResponse">
<wsdl:part name="getKeywordsReturn" type="impl:ArrayOf_xsd_anyType"/>
</wsdl:message>
每個消息的名稱是方法名加上 Request 或 Response。每個消息有一個嵌套的 <wsdl:part> 元 素,它定義一個參數名和類型(請求的參數是字符串標題,響應的參數是一個命名的數組)。這使程序員 或代碼生成工具可以查明對 getKeywords() 的請求是什麼樣的,以及會返回什麼。
如果不發送參數或沒有返回值,就沒有 <wsdl:part> 子元素:
清單 15. 沒有 <wsdl:part> 子元素
<wsdl:message name="addKeywordResponse">
</wsdl:message>
addKeyword() 方法沒有返回值,所以它由一個空的 addKeywordResponse() 元素表示。
服務由端口類型表示
指定了消息之後,WSDL 現在可以通過 <wsdl:portType> 元素描述整個 Web 服務,見清單 16 :
清單 16. <wsdl:portType> 元素
<wsdl:portType name="BookSearcher">
<wsdl:operation name="setBooks" parameterOrder="books">
<wsdl:input message="impl:setBooksRequest" name="setBooksRequest"/>
<wsdl:output message="impl:setBooksResponse" name="setBooksResponse"/>
</wsdl:operation>
<wsdl:operation name="addBook" parameterOrder="title keywords">
<wsdl:input message="impl:addBookRequest" name="addBookRequest"/>
<wsdl:output message="impl:addBookResponse" name="addBookResponse"/>
</wsdl:operation>
<wsdl:operation name="addKeyword" parameterOrder="title keyword">
<wsdl:input message="impl:addKeywordRequest" name="addKeywordRequest"/>
<wsdl:output message="impl:addKeywordResponse" name="addKeywordResponse"/>
</wsdl:operation>
<wsdl:operation name="getKeywords" parameterOrder="title">
<wsdl:input message="impl:getKeywordsRequest" name="getKeywordsRequest"/>
<wsdl:output message="impl:getKeywordsResponse" name="getKeywordsResponse"/>
</wsdl:operation>
<wsdl:operation name="search" parameterOrder="keyword">
<wsdl:input message="impl:searchRequest" name="searchRequest"/>
<wsdl:output message="impl:searchResponse" name="searchResponse"/>
</wsdl:operation>
</wsdl:portType>
每個操作映射到一個方法,各種輸入和輸出消息被連接到每個操作。還指定了參數的次序。這些信息 完整地描述一個公開的方法,了解了它們的意義,就可以完全掌握方法的使用。
WSDL 提供一些與 SOAP 相關的低層信息
WSDL 已經提供了程序員需要的所有信息,但是還需要處理一些編碼和與 SOAP 相關的細節。這些信息 包含在 <wsdl:binding> 元素中,這些元素映射到已經定義的端口類型。大多數 <wsdl:binding> 處理編碼和名稱空間。例如,清單 17 定義與處理 getKeywords() 方法和操作相 關的 SOAP 信息:
清單 17. 與處理 getKeywords() 相關的 SOAP 信息
<wsdl:operation name="getKeywords">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="getKeywordsRequest">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://DefaultNamespace" use="encoded"/>
</wsdl:input>
<wsdl:output name="getKeywordsResponse">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://localhost:8080/axis/BookSearcher.jws"
use="encoded"/>
</wsdl:output>
</wsdl:operation>
WSDL 供誰使用?
WSDL 有雙重作用:
WSDL 讓 Web 服務和代碼生成工具了解連接服務所需的語義和 SOAP 細節。
WSDL 讓程序員了解可用的方法、這些方法需要的數據和返回的值。
JAX-RPC 和 Axis 框架提供的工具可以讀取 WSDL,然後構建和連接那些使用 RPC 服務的代碼。因此 ,WSDL 是支持代碼的固有部分,我們不必手工處理 SOAP 編碼語義。但是,WSDL 仍然很重要,尤其是在 不掌握服務源代碼的情況下。通過 WSDL 查明要發送什麼以及會返回什麼。
現在,您已經知道 WSDL 會提供關於每個服務方法和方法返回類型的信息。下面利用這些知識使用和 連接 Web 服務。