Web服務是什麼或者應該是什麼有許多定義,每個或多或少都可以理解。很有趣的事情是最簡單的東西往往是最難描述的。眼前就有一個很好的例子:一個Web服務是一個可以和外部世界交換文檔的實體。這個實體是自我描述的並且擁有一個唯一的特性。
這個文檔內容是XML;嚴格來說是SOAP。SOAP(簡單對象存取協議)定義Web服務消費和生成的XML文檔的內部結構。SOAP被認為是一個行業標准並且被許多跨平台軟件供應商、硬件平台、操作系統或者編程語言廣泛地采用。
每個Web服務都有一個地址。這是它的ID。這個地址由一個URI(亦稱URL)定義。一個Web服務存在於並且被它的URI標識。這個地址經常被稱為一個終端。這個ID和內容的安全性毫無關系。本教程的服務存在於http://localhost:6060/ChatService/。
Web服務帶有它自己的描述。這告訴你它交換的是什麼類型的文檔。它說明了服務存在於什麼地方(URI地址)。並且它還說明了它可以使用哪個傳輸協議來交換文檔。Web服務描述使用的語言是WSDL(Web服務定義語言)。一個Web服務完全由它的WSDL文檔描述。為了和一個獨立Web服務通訊,你只需要WSDL文檔。即使WSDL在它自己的上下文中描述Web服務,它還是無法描述多個Web服務組合起來所形成的美妙的樂章。
Web服務有一個地址,但是為了訪問它,你需要發現它在哪兒。換句話說,你需要一個電話號簿列表Web服務。UDDI (通用描述、發現和集成)是行業標准的電話號碼薄。UDDI處理寄存器並且發現Web服務。
綜合起來,一個Web服務就是和現實世界交換SOAP文檔的一個實體,使用一些URI定位,使用WSDL文檔描述並且可以使用UDDI注冊來列出並發現。
一個真實的示例
無數類型的文檔可以被發送到Web服務並來源於Web服務。它可以是一個鋼廠中的周期的溫度報道,它可以是一個地方政府定義的退稅請求,或者它可以是一個從一個軟件組件到另一個組件的RPC調用的文檔風格表現。同樣的情況也發生在文檔交換方案中。你可以設想單向的消息或者請求-響應消息傳送,這裡面都需要跟隨一個響應文檔的請求文檔。 Web服務大部分當前的用法遵循XML模式上的遠程過程調用。這就是為什麼開發者可以很容易轉到開發Web服務,而不要對現有的應用程序做重大的修改,或者之需要付出最小的努力就能創建用於Web服務的客戶端。Web服務框架經常可以隱藏底層體系結構的復雜性。他們通常提供了用於從語言到WSDL和從WSDL到語言生成的工具。展示象Web服務這樣的語言結構比手工處理XML文檔快得多並且容易的多。現在,Web服務經常用作一個集成工具,讓開發者和系統設計師互連不同的應用程序。
即使本文中的Web服務和它的客戶端遵循XML模式上的遠程過程調用,但是Web服務的應用范圍是非常寬廣的。近期,有許多基於松散耦合的純文檔交換模式實現。
一個簡單的聊天服務器
在本文中,我試圖創建一個比著名的股票行情服務更具挑戰的應用程序,但是仍然簡單易讀。它是一個聊天服務器應用程序。聊天服務器的功能是非常簡單的客戶端或者發送新消息並讀取它們。
這個聊天服務器實現是與WASP和Web服務完全互不相關的。它可以成功地被編譯並且用於任何其他的Java環境。這個聊天服務器還描述預存在的業務邏輯。請查看代碼來領會。你可以在這裡下載用於聊天實現的源代碼(而不是Web服務,那個還沒有寫)。
設計聊天服務器
聊天服務器的全部的實現被分割到好幾個類中。接口定義在程序包com.systinet.simplechat.server.iface中。它由三個類組成;聊天服務器的接口類IChatService.java和用於聊天消息的表現兩個結構和participants-ChatMessage.java和ChatPerson.java。
聊天服務器的實現在程序包com.systinet.simplechat.server.impl中。它把簡單的接口的實現從後端業務邏輯中分離出來。我使用了適配器設計模式,那就是說,到ChatService.java的調用被委托給一個後端處理機。業務邏輯是相當簡單的。它是在一個集合中保存消息的SimpleBackendImpl.java類的單一實例。
初看起來,置配器/後端實現看起來對於用於一個簡單的教程太復雜了。然而,即使復雜的現有的業務邏輯可以被重使用來變成一個沒有任何改變它的代碼的需要的Web服務。這是一個常見的現實的使用案例。即使應用程序沒有使用原來的適配器模式,編寫一個象ChatService這樣的包裝類,不需要重寫原始的後端程序邏輯。然而,後端可以使用JDBC和一個關系數據庫來實現,否則作為一個EJB,甚至調用外部非Java應用程序的本地方法。可能發生的事是無窮盡的。適配器模式只允許你很快地觸發新的後端邏輯。
把一個聊天服務器轉為一個Web服務
現在,這個聊天服務器只是一個Java實現。它還不是SOAP。它只是一個用於運行在相同JVM上的客戶端的可工作的聊天服務器。下一步就是生成聊天服務器發言SOAP並且創建一個描述它的WSDL文檔。
這個示例使用Systinet的Web Applications and Services Platform(WASP)軟件來創建Web服務應用程序。WASP軟件是平台無關的並且工作在幾乎任何Java環境中。這個平台提供了兩個不同的部署情況:編程部署和聲明部署。在這兩種情況下,取得部署的是簡單的舊的Java對象。編程部署(也稱運行時間發布)發生在一個應用的運行時間並且能動態的部署應用程序為Web服務。你只要在你的應用程序中逐步開始WASP,並且使用到WASP應用編程接口的調用注冊它的對象。換句話說,你事實上把WASP嵌入到你的應用程序。聲明部署意味著把你的應用程序包裝到一個部署程序包並且把這個發布到一個WASP的運行實例中。為了簡單和便於訪問起見,我們使用運行時間發布方法。
圖1:導入現有的代碼
安裝聊天服務器到Eclipse中
假定你讓帶有WASP Developer的Eclipse啟動並運行,安裝示例代碼。
在Eclipse中創建一個新建項目(File - > New - > Project...)。選擇Systinet Web Services/Web Service Project。
在下面的對話框中輸入項目名,ChatServiceProject和位置。結束向導。
從File菜單中,選擇Import...命令,並且選擇Zip file方法。
在對話框中選擇前面源代碼中下載的壓縮文件,並且點擊Finish按鈕。
你的工程現在應該包含兩個帶有Chat Service的源代碼的程序包。你可以通過檢查Package Explore視圖來檢查。
SOAP啟動聊天服務器
現在你有了一個簡單的聊天服務器的一個工作實現。為了能與SOAP通信,你要把它部署到使用運行時間發布方法的Web服務服務器。
啟動絕對地址的服務器並且在服務器的地址下注冊這個服務。服務器地址+服務路徑組成了Web服務的完整的URI。
這裡是啟動SOAP服務器並且把聊天服務注冊為一個Web服務的應用程序代碼類。這個源代碼文件應該(很明顯)應該被存為ChatServerApp.java。
package com.systinet.simplechat.server.impl;
import org.systinet.wasp.*;
import org.systinet.wasp.webservice.Registry;
public class ChatServerApp {
public static void main(String[] args) throws Exception {
Wasp.startServer("http://localhost:6060");
Registry.publish("/ChatService/", ChatService.class);
System.out.println("The Chat server is up and running.");
}
}
它很簡單,只有兩行代碼:
Wasp.startServer("http://localhost:6060");
Registry.publish("/ChatService/", ChatService.class);
WASP.startServer方法啟動一個Java應用程序內的服務器。Registry.publish方法編程地部署ChatService類為一個Web服務。這個服務的URI由服務器的地址和服務器的服務路徑組成。WASP還自動地創建描述服務WSDL文檔並且把它發布在SERVICE_URI/wsdl。
圖2:運行服務器。
構造並運行聊天服務器
聊天服務器現在可以運行了。在構造完畢之後(Project->Build Add),你可以啟動它。首先,創建一個Web服務運行器。這個運行器保證服務器有所有需要的庫並且被適當的配置。為了創建運行器,從com.systinet.simplechat.server.impl程序包中選擇ChatServerApp並且從IDE菜單中選擇Run->Run...。
打開的對話框是工程中的所有的運行器的管理器。雙擊WASP Java Application運行模板。創建了一個稱為ChatServerApp的新的運行器。總是使用這個運行器來運行ChatServerApp類。按下Run按鈕來啟動聊天服務器。
服務器啟動並且運行,准備與SOAP通信。它的位置是http://localhost:6060/ChatService/。動態生成WSDL文檔的位置是http://localhost:6060/ChatService/wsdl。
業務邏輯現在使用Runtime API被編程部署到服務器上。
圖3:運行服務器的輸出。
客戶端
現在,你需要一個消費這個ChatService的客戶端。
假定你對這個服務所了解的所有的東西只是它的WSDL描述的位置: http://localhost:6060/ChatService/wsdl。你不能訪問原始的Web服務的源代碼。你甚至不知道它是使用什麼語言來實現的。你還不知道ChatService的精確位置,但是你知道每個Web服務都在它的WSDL文檔中完全描述。
服務器創建一個代理,隱藏所有的SOAP並且從開發者垂直傳送,並且讓它們使用單一方法調用來調用Web服務。為了運行,這個代理必須知道Web服務的WSDL描述的位置,Web服務的URI和它的Java接口。你知道ChatService的WSDL位置,並且服務的URI包含在這個WSDL中。現在你所需要的是取得服務的Java接口。手工創建它意味著非常了解WSDL和XML Schema,並且它甚至仍然是一個很難的任務。幸運的是,WASP Developer包含一個從它的WSDL生成一個Web服務的Java接口的工具。這個工具,稱為WSDL2Java,可用作命令行實用程序和一個ANT任務,所以它非常便於與使用ANT的構造整合。
圖4:創建客戶端程序包。
客戶端的代碼
首先,創建一個用於客戶端的新的程序包。從菜單選擇File - > New - > Package。在New Package對話框中輸入名為com.systinet.simplechat.client的程序包,並且按下finish按鈕。
WASP Developer需要WSDL文件存在於工程中以便生成客戶端類。下載聊天服務的WSDL文檔到工程中。選擇com.systinet.simplechat.client程序包,並且從菜單選擇File - > New - > Other...。New對話框現在打開了。在左邊的工具欄中選擇Systinet Web Services。所有可用的向導現在出現在右邊的工具欄中。選擇WSDL from Internet條目並且轉到下一窗囗。
在第二個窗口中填入WSDL文件的位置並且選擇Next。ChatService的WSDL文件位於http://localhost:6060/ChatService/wsdl。
在最後一個窗口中,填充下載的WSDL文件的名稱。設置它為ChatService並且點擊finish。
WSDL文件現在在你的工程中了。為了生成客戶端類,從它的上下文菜單選擇Generate Client...條目。
當Generate Client from WSDL向導打開的時候,所有的值都設置為它們的默認值並且結束向導。
現在你在兩個新程序包中創建了幾個文件;com.systinet.simplechat.client.iface和com.systinet.simplechat.client.iface.struct。前一個程序包包含接口定義,ChatService.java和一個被WASP框架使用的支持文件ChatService.xmap。後一個程序包包含使用在ChatService.java接口- ChatMessage.java和ChatPerson.java的附加的結構。如果你檢查這個生成的類,它們應該為你所熟知。它們不是原始的ChatService接口類准確無誤的副本,但是它們精確地描述它的應用編程接口。
還有一個WASP Developer從WSDL文件創建的類。看看com.systinet.simplechat.client程序包。那裡有一個新的文件,ChatServiceClient.java。這是一個客戶端應用程序生成的骨架。列表1顯示了這個文件中的類。
首先,幾個WASP類被導入,然後你看看ChatService接口的導入的內容和從WSDL文件中生成的結構。這段代碼的有意思的部分在main()方法中:
ServiceClient serviceClient = ServiceClient.create
(wsdlURI, ChatService.class);
serviceClient.setServiceURL(serviceURI);
serviceClient.setWSDLServiceName(new QName
("http://systinet.com/wsdl/com/systinet/simplechat/server/impl/", "ChatService"));
serviceClient.setWSDLPortName("ChatService");
service = (ChatService) Registry.lookup(serviceClient);
這些代碼創建客戶端使用來遠程調用聊天服務的代理。WASP Developer生成比簡單客戶端需要的更多代碼。因為WSDL文件可以包含有關多數的Web服務的信息,生成的代碼有些復雜,所以它能用於任何WSDL文件。然而,下面的一行代碼將以同樣的方法用在我們的簡單的例子:
service = (ChatService) Registry.lookup
("http://localhost:6060/ChatService/wsdl/", ChatService);
服務對象現在准備調用。它的方法的每個調用導致聊天服務的遠程調用。結束客戶端來調用聊天服務。列表2顯示了擴展的自生成的代碼。
構造和運行客戶端既然客戶端被實現,你需要構造並且運行它。為了構造代碼,從菜單運行Project - > Build All命令。
為了運行客戶端,你將需要它的Web服務運行器。
選擇工程中的ChatServiceClient類並且從菜單選擇Run - > Run...。雙擊WASP Java Application條目。一個稱為ChatServiceClient的新的運行器被創建並且隨時可使用客戶端。總是使用這個運行客戶端。按下Run按鈕來啟動客戶端。
客戶端連接到聊天服務並且調用它的兩個方法。在客戶端的兩次執行之後到控制台的輸出應該如圖5所示。
圖5:聊天客戶端的輸出。