引言
Web Services 是一種面向服務的技術,通過標准的 Web 協議提供服務,目的是保證不同平台 的應用服務可以互操作。依據 web services 規范實施的應用之間,無論它們所使用的語言、 平台或內部協 議是什麼,都可以相互交換數據,這就是 web services 的好處。本文選擇 IBM WebSphere Application Server 作為 web services 的運行環境,並選擇 IBM Rational Application Developer(以下簡稱 RAD) for WebSphere 就是作為本文的開發平台。RAD 針對 IBM WebSphere Application Server 的測試環境進行了 優化,減少了開發人員因配置環境而消耗的時間。本文中展示的所有示例都是在 RAD for WebSphere 平台上 開發、測試的。
Web Services 與 JAX-WS
Web Services 發展至今已有兩種形式:REST 和 SOAP。REST Web Services 基於 HTTP 協議,SOAP Web Services 支持多種傳輸協議:HTTP、SMTP、MIME 等 等。本文主要介紹 SOAP web services。對於 JAVA,目前有兩種 SOAP Web Services 規范:JAX-WS 和 SAAJ 。
SOAP Web Services 通常要求服務器端提供一個機器可讀的描述(通常基於 WSDL),以便客戶端辨 識服務器端提供的 Web 服務。
JAX-WS (Java API for XML Web Services) 是一組專門用於實現 XML Web Services 的 Java API。JDK 1.6 自帶 JAX-WS 版本為 2.1。不過,JAX-WS 只提供 web services 的基 礎功能,所以如果你希望實現 web services 的復雜功能,比如 WS-Security,WS-Policy,WS-RM 等,那就 需要切換到 Apache CXF 、Metro 或者 Axis。
本文的目標對象是初次接觸 web services 或者 JAX- WS 的開發人員。所以本文將分享以下內容:
Web Services 服務器端的開發
Web Services 客戶端的開發
基於 https 協議的 web services 通信
利用 @MTOM 優化網絡大數據傳輸
JAX-WS web services 開發
RAD 是一個基於 Eclipse 的全功能集成開發環境(IDE),所以熟悉 Eclipse 的開發人員可以很快的上手使用 RAD 平台。
服務器端開發:
首先,在 RAD 平台上, 創建一個最簡單的 web service,這個 service 只向客戶端返回一個字符串 – “Hello World”。服務器端 的工作流程如下:完成 web services 編寫,發布 web services 生成服務描述文件(WSDL),以供客戶端獲 取。接下來,等待客戶端發來的 SOAP 請求消息,解析其中的方法調用和參數格式。根據 WSDL 和 WSML 的描 述,調用相應的對象來完成指定功能,並把返回值放入 SOAP 回應消息返回給用戶。
首先在 RAD 中新 建一個 Web Project – WebProjectDemo,
圖 1. 創建 Web Project
選擇相應的 Target Runtime(確認“Servers” view 中已創建 Server Runtime),並確保 “Add project to an EAR”是勾選 上的,這樣就不需要再手動創建 EAR 了。
圖 2. 選擇運行環境,完成 Project 創建
完成 Web Project 的創建。
接下來,開始編寫 web service 類,開發方法很簡單,只需用 @WebService 標注 Java 類為 web service 類,@WebMethod 標注類方法為 web service 方法。這些被標記的類和方法, 在 service 發布之後,就能被客戶端調用了。
清單 1. 服務端代碼
@WebService public class HelloWorld { @WebMethod public String sayHello(){ return "Hello World!"; }
簡短的五行代碼就是 HelloWorld web service 類啦! HelloWorld 只有一個 web service 方法 – sayHello()。
接著就是發布 HelloWorld service 了,
圖 3. 發布 HelloWorld Service
發布完成,在浏覽器中敲入
http://WEBSERVER:9080/WebProjectDemo/HelloWorldService?wsdl,如果能看到以下界面,就表示發 布成功了。接著,我們就開始 客戶端 的開發。
圖 4. WSDL 文件
客戶端開發 :
本文選用 Eclipse 作用客戶端的開發平台,因為 RAD 內置了 WAS JRE Runtime 庫,易與 JAVA JRE Runtime 沖突,所 以本文就換用 Eclipse 開發客戶端。
客戶端的工作流程如下:取得服務器端的服務描述文件 WSDL, 解析文件內容,了解服務器端的服務信息以及調用方式(生成客戶端 Stub)。編寫客戶端 SOAP 請求消息 ( 指定調用的方法以及調用的參數 ),發送給服務器端。等待服務器端返回的 SOAP 回應消息,解析得到的返回 值。
有多種生成客戶端 Stub 的方式可以選擇,如:axis2、jax-ws、xfire 等,不過需要注意的是各 開源軟件對 soap 協議解析方式不同,所以生成的的客戶端也不盡然相同。本文采用 jax-ws 生成客戶端 Stub,步驟如下:
首先確認從客戶端機器可以訪問 HelloWorld Service:
http://WEBSERVER:9080/WebProjectDemo/HelloWorldService?
在 Eclipse 中,新建 Java Project – WebServiceClient
打開命令控制台,運行命令: /jdk/bin/wsimport.exe -d C:\WebServiceClient\\bin -s C:\WebServiceClient\\src – keep http://WEBSERVER:9080/WebProjectDemo/HelloWorldService?wsdl
刷新 Project,會發現 src 文件夾下多了一些文件,見圖 5。
圖 5. 客戶端文件結構
修改 HelloWorldService.java 。找到
wsdlLocation = "***.wsdl" 和 url = new URL(baseUrl, "***.wsdl") ,把 ***.wsdl 替換成
http:// WEBSERVER:9080/WebProjectDemo/HelloWorldService?wsdl.
配置完成。現在我們就可以調用 Stub 與服務器端通信啦!新建 HelloWorldClient.java,
清單 2. 客戶端代碼
public class HelloWorldClient { public static void main(String[] args) { HelloWorldService service = new HelloWorldService(); HelloWorld proxy = service.getHelloWorldPort(); System.out.println(proxy.sayHello()); } }
運行 HelloWorldClient.java,如果控制台輸出“Hello World”,就表明客戶端和服務器端通信 成功了。
Https 與 web services
Web Services 采用網絡傳輸數據,數據很容易暴露在外。在 這種情況下,我們可以采用 Https 協議傳輸數據,建立一個信息安全通道,來保證數據傳輸的安全。當客戶 端與服務器端建立 https 連接時,客戶端與服務器端需要經過一個握手的過程來完成身份鑒定,確保網絡通 信的安全。
對此,我們需要在客戶端代碼裡做相應的處理:
利用浏覽器導出 server 端證書, 並保存為 demo.crt 。不同浏覽器的導出步驟不一樣,請查詢相應的導出方法。
在客戶端創建 truststore:/java/bin/keytool.exe -genkey -alias MYDOMAIN -keyalg RSA -keystore clienttruststore.jks
把 demo.crt 加入到 truststore 中: /java/bin/keytool.exe -import - trustcacerts -alias STOREALIAS -file demo.crt -keystore clienttruststore.jks
在客戶端 HelloWorldClient.java 加入如下代碼:
清單 3. Https 與 web services
public class HelloWorldClient { public static void main(String[] args) { System.setProperty ("javax.net.ssl.trustStore","PATH\\clienttruststore.jks"); //(truststore) System.setProperty ("javax.net.ssl.trustStorePassword", "password"); //(truststore 密碼 ) System.setProperty ("javax.net.ssl.trustStoreType", "JKS"); //(truststore 類型 ) HelloWorldService service = new HelloWorldService(); HelloWorld proxy = service.getHelloWorldPort(); System.out.println(proxy.sayHello()); } }
身份鑒定完成,可以繼續 web services 的探險了。
MTOM 與 web services
我們先來了解 默認情況下 SOAP 是如何傳輸數據的:
在 SOAP 消息中所有的二進制數據都必須以編碼之後的形態存 在於 XML 文件中(為避免字符沖突)。正常文本 XML 使用 Base64 對二進制數據進行編碼,這就要求每三個 字節對應四個字符,從而使得數據的大小增加三分之一。如果我們需要傳送 10M 的文件,編碼之後文件大小 就 13M。這種情況下,JAVA 引入了 MTOM(消息傳輸優化機制)消息編碼。MTOM 就是針對 SOAP 消息傳輸的 基礎上提出的改進辦法。對於大量數據的傳遞,不會進行 Base64 編碼,而是直接以附件的二進制原始數據的 形式封裝在 SOAP 消息的 MIME 部分,進行傳輸。使用 MTOM 的目的在於優化對較大的二進制負載的傳輸。對 於較小的二進制負載來說,使用 MTOM 發送 SOAP 消息會產生顯著的開銷,但是,當這些負載增大到幾千個字 節時,該開銷會變得微不足道。
現在,我們利用 MTOM 機制實現一個提供上傳和下載文件功能的 HelloWorld Web Service。首先,在服務器端增加 upload() 和 download() 兩個方法,並通過添加標注 @MTOM,在服務器端開啟 MTOM 消息傳輸功能,同時選用 datahandler 類型封裝傳輸文件。並使用 @XmlMimeType("application/octet-stream") 標注 datahandler,以表示這是一個附件類型的二 進制數據。
清單 4. MTOM 服務端代碼
@MTOM @WebService public class HelloWorld { private static final String REPOS = "/home/webserviceTest"; @WebMethod public String sayHello(){ return "Hello World!"; } /** * 上傳文件到 Server,並命名為 @fileName */ @WebMethod public String upload( @XmlMimeType("application/octet-stream") DataHandler handler , String fileName){ try { File file = new File (REPOS + "/" + fileName); OutputStream output = new BufferedOutputStream( new FileOutputStream(file)); handler.writeTo(output); output.close(); } catch (IOException e) { e.printStackTrace(); } return "Success"; } /** * 從 Server 下載名為 @fileName 的文件 */ @WebMethod public @XmlMimeType("application/octet-stream")DataHandler download( String fileName){ DataHandler dh = new DataHandler (new FileDataSource(REPOS + "/" + fileName)); return dh; } }
每次重新發布 web service 之後,都需要在客戶端重新運行 wsimport,保持客戶端 Stub 與 web service 一致。現在,我們就在客戶端調用 HelloWorld 的 upload() 方法上傳 test.txt 文件到服務器,並 通過 download() 從 Server 下載名為 wsDemo 文件。
客戶端開啟 MTOM 的方式和 Server 端不同, 請參見客戶端代碼。
清單 5. MTOM 客戶端代碼
HelloWorldService service = new HelloWorldService(); HelloWorld proxy = service.getHelloWorldPort(new MTOMFeature()); // 開啟 MTOM File file = new File(test.txt"); DataHandler dataHandler = null; dataHandler = new DataHandler(new FileDataSource(file)); try { // 上傳 test.txt 文件到 Server,並命名為 wsDemo System.out.println("Hello World Upload Function : " + proxy.upload(dataHandler,"wsDemo")); } catch (SOAPFaultException ex) { System.out.println(ex.getMessage()); } // 下載文件 wsDemo,並保存到本地。 DataHandler data = proxy.download("wsDemo"); File serverFile = new File(".\\data\\server_test.txt"); data.writeTo(new FileOutputStream(serverFile));
我們利用 upload() 方法上傳 5 個不同大小的文件 (1M,10M,50M,100M,300M),並統計上傳這些文件分別 消耗的時間:
圖 6. Upload() 消耗時間統計
圖 7. Upload() 消耗時間線性圖
可以看出,隨著文件大小的增長,上傳文件所消耗的時間幾乎是呈線性增長的。並且實際消耗時間和預 期消耗時間(以上傳 1M 文件的時間為基准,線性計算)的相差值也是在可以接受的范圍內。這完全符合我們 的預期期望。
結束語
本文展示利用 JAVA Web Services 規范 JAX-WS 實現 web services 客 戶端和服務器端通信。SOAP Web Services 使用 HTTP 傳送 XML,不過由於 HTTP 的限制以及需要額外的消耗 解析 XML 文件,使得 SOAP 的通信速度低於其它方案。但另一方面,XML 是一個開放、健全、有語義的訊息 機制,而 HTTP 是一個廣泛又能避免許多關於防火牆的問題,從而使 SOAP 得到了廣泛的應用。所以,如果效 率對項目是一項很重要的指標的話,就需要慎重考慮是否使用 SOAP 實現 Web Services。