從何時選擇移動 Web 服務到總體設計指導原則再到用于移動 Web 服務的值類型,本文提出了在設計用于移動設備的 Web 服務時需要考慮的許多設計事項。文中還介紹了許多設計移動 Web 服務方面的最佳實踐。從本文中,您可以了解如何決定何時使用 Web 服務、在設計 Web 服務時需要考慮什么事項,以及在規劃移動 Web 服務時必須謹記哪些問題。
Web 服務是一種集成技術。在集成異構系統時 Web 服務的價值可以得到最好的證明,因為其支持許多類型的編程語言、運行時環境和網絡。當需要從不兼容的環境連接應用程序時,Web 服務就有了用武之地。通過 Web 服務,您可以將業務應用程序從 java™ 2 Platform EnterPRise Edition (J2EE) 連接到 .NET。您還可以使用某個運行在 linux™ 中的應用程序將一個應用程序集成在 Windows™ 操作環境中。在本文中,我提供了一些針對移動 Web 服務的重要設計考慮事項,并且向您介紹了一些與之有關的最佳實踐。
首先,我將討論在開始之前 需要考慮哪些事項。
開始之前
在開始設計整個系統的體系結構之前,您必須做出如下決定——何時使用移動 Web 服務以及何時不使用移動 Web 服務。
對于移動設備,Web 服務是利用工作站的強大計算功能的一種最佳方式。Java Specification Request 172 (JSR-172) 定義了用于 Java 2 Platform Micro Edition (J2ME) 平臺的 Web 服務 API。由于移動服務主要從客戶端的角度進行編程并且是服務使用者,因此本文只需要介紹一部分遠程服務調用 API (JAX-RPC) 和 JAXP (Java API for xml Parsing)。
設計移動 Web 服務的主要目的在于使嵌入式設備能夠使用由服務器提供的服務,換句話說,移動 Web 服務是從 Web 服務使用者的角度進行設計的,目的在于支持輕量級設備共享服務器的計算功能和數據庫。
移動 Web 服務無縫地集成了運行在不同平臺上的兩種不同的應用程序,并且提供了它們之間的互操作性。通常,在考慮移動設備的參與時,有三種類型的集成技術可以運用:
與套接字通信和消息傳遞技術相比,Web 服務有一些突出的優勢。Web 服務使用可擴展標記語言 (XML) 來傳輸消息(包括結構良好的數據信息),使用簡單對象訪問協議 (SOAP) 來傳輸對象。如果您使用的是套接字通信,則必須完全負責定義要傳輸的數據結構。而且,如果客戶端和服務器是用不同的編程語言編寫的(例如 C++ 和 Java 編程語言),則您的工作量將大大增加——您必須負責數據傳輸和 C++ 和 Java 編程中的編碼細節。
消息傳遞軟件可能是一種解決方案,但如果您所關注的是性能,并且不擔心事務和安全級別,則消息傳遞軟件真的不是一個非常好的選擇。如果使用消息傳遞軟件,您將花大量的時間和精力解決安全問題,并且您的客戶很有可能站在您的門口問:“為什么這么慢?”
我不準備告訴您正確的選擇應該是什么,而給出一些理由來說明為什么 Web 服務可能是一個好的選擇。
其中的一個理由可能是服務器端編程。即使是像 Web 服務這樣好的機制,也仍然由于 XML 處理以及傳輸和接收 SOAP 消息的開銷的原因而不能滿足嚴格的實時處理需求。因此在設計時需要考慮兩個問題:
現在,我假定您已經決定使用 Web 服務。那么,在總體設計方面需要考慮哪些問題?
            在決定使用 Web 服務之后
您需要考慮的下一個問題是 Web 服務的總體設計。可以運用下列通用設計指導原則:
下面詳細介紹這些設計考慮事項。
管理 Web 服務粒度
關于粒度管理需要謹記以下幾點:
在本文中,我以 Task Management 系統中的登錄功能為例。驅動程序首先登錄到系統,如果登錄成功,則有兩列任務顯示在屏幕上。
對于此登錄問題有兩種解決方案:
對于第一種解決方案,它是細粒度登錄系統,而顯示任務列表需要兩次調用 Web 服務;這可能帶來很大的延遲。第二種解決方案是粗粒度登錄系統,它返回您在一次調用中需要的所有信息,并確保由于網絡延遲和系統 I/O 帶來的影響最小。
首先定義 Web 服務接口,然后進行實現。
這不僅從移動 Web 服務的角度來看是適用的,而且從 Web 服務設計和(更高的層次)面向對象的軟件設計來看也是適用的。正如您所知道的,接口是客戶端和服務提供程序之間的契約,而且保持穩定非常重要(因為正如您所知道的,實現容易改變)。
定義功能接口非常直接和直觀——只需考慮:
然后,您應該將該接口的相關要點寫下來,并在這些要點的指導下完成所有實現細節。
這是一條簡單的設計指導原則。了解您的目標非常重要,目標可以驅動您編寫測試用例,并且指導您編寫功能實現,這也是為什么測試驅動開發 (TDD) 廣泛地應用于各種開發技術的原因。
使用 Document/literal 作為編碼樣式
目前,JAX-RPC 支持三種操作模式:
在一些資料中,您還可能發現名為 Document/encoded(使用這種模式的人不多)和 Document/literal wrapped(由 Microsoft 定義,但是沒有相關規范,其缺點在于比其他模式復雜)的模式。對這些操作模式的詳細解釋可以在參考資料中的“Which style of WSDL should I use?”內找到。
在這些模式中,WS-I 標準僅支持 RPC/literal 和 Document/literal。對于移動 Web 服務,JAX-RPC 實現必須使用 Document/literal 將基于 Web 服務描述語言 (WSDL) 的服務描述映射到相應的 Java 表示形式。因此,如果您只使用 Document/literal 作為編碼樣式,則您是最安全的。
優先選擇 JavaBeanser 組件而不是 EJB 組件作為服務提供程序
在使用 Java 編程語言公開服務時,需要考慮兩種類型的服務提供程序:
雖然在某些情況下,EJB 組件非常有用,但是 JavaBeans 組件常常是更好的選擇,特別是在開發移動 Web 服務時。實現使用 JavaBean 組件生成的服務提供程序比較簡單和容易,而且與相應的會話 EJB 組件相比,JavaBean 組件更穩定。但是,如果您需要從使用 EJB 組件開發的現有 J2EE 應用程序公開 Web 服務,則請使用 EJB 組件。
避免 XML 元素嵌套太深
如果數組的數組、復雜類型的數組或包含另一個自定義復雜類型的復雜類型等嵌套太深,則將大大影響 Web 服務的性能。清單 1 顯示一個 XML 描述示例——一個自定義數據類型 Task 類的數組:
import java.io.Serializable;public class Task implements Serializable {    /**     * The id of the task     */    private int taskID = 0;    /**     * Owner name of the task     */    private String ownerName;    /**     * public default non-argument constrUCtor     *       */    public Task() {    }    /**     * Constructor of the Task class     *      * @param taskID     *            id of the task     * @param ownerName     *            owner name of the task     */    public Task(int taskID, String ownerName) {        this.taskID = taskID;        this.ownerName = ownerName;    }    /**     * @return Returns the ownerName.     */    public String getOwnerName() {        return ownerName;    }    /**     * @param ownerName     *            The ownerName to set.     */    public void setOwnerName(String ownerName) {        this.ownerName = ownerName;    }    /**     * @return Returns the taskID.     */    public int getTaskID() {        return taskID;    }    /**     * @param taskID The taskID to set.     */    public void setTaskID(int taskID) {        this.taskID = taskID;    }}
            如果一個方法返回如 清單 1 中定義的 Task 的數組,則該方法的源代碼包含在下面的清單中,方法 getTasks() 返回一個由五個 Task 對象組成的數組,如清單 2 所示。
    public Task[] getTasks(String name){		Task[] tasks = new Task[5];		for(int i=0; i<5; i++){			tasks[i] = new Task(i, name);		}		return tasks;	}當使用 getTasks()(如清單 3 所示)所屬的 JavaBean 組件公開 Web 服務時,Task 類映射到其中包含 Task 類的名稱空間的 tn2:Task。
<complexType name="Task">    <sequence     <element name="ownerName" nillable="true" type="xsd:string"/>     <element name="taskID" type="xsd:int"/>    </sequence></complexType>
同時,數據類型 Task[] 映射到 ArrayOf_tn2_Task;ArrayOf_tn2_Task 的 XML 描述如清單 4 所示:
<complexType name="ArrayOf_tns2_Task">    <sequence>     <element maxOccurs="unbounded" minOccurs="0" name="Task"        nillable="true" type="tns2:Task"/>    </sequence>   </complexType>
如清單 4 所示,為單個自定義復雜類型數組生成的 XML 描述很長。相反,Java 語言中的單個 String 類型映射到 xsd:string,而沒有生成 complexType 元素;諸如 boolean、int 和 byte 這樣的基元類型分別映射到 xsd:boolean、xsd:int 和 xsd:byte。
您可能已經注意到 XML 元素的嵌套(避免嵌套太深)和粒度考慮(使用粗粒度)之間的沖突。在實際運用中,嵌套和粒度之間應該有一個平衡。如果您更關注應用程序的性能,則應該仔細地權衡這兩個考慮事項,以獲得一個更好的解決方案。
移動 Web 服務的設計考慮事項
我已經討論了設計 Web 服務的指導原則,現在我將把重點放在移動 Web 服務的考慮事項上。在大多數情況下,當將 JAX-RPC 值類型用于移動 Web 服務時需要考慮一些事情。JAX-RPC 值類型(遵循 JSR-101)是 Java 類,其值可以在服務客戶端和服務端點之間移動。為了獲得一致的值類型,必須遵循一系列規則。我只列出其中的幾條,與本文關系最大的規則是:
您必須具有公共缺省構造器
在反序列化的過程中,SOAP 運行時環境使用缺省構造器來構造對象。如果您試圖在沒有公共缺省構造器的情況下編寫值類型(也稱為數據傳輸對象),在當 JAX-RPC 運行時嘗試序列化和反序列化數據對象時可能會遇到錯誤。對于像 IBM Rational® application Developer (RAD) 6.0 這樣的 IDE,將不為該數據類型生成序列化和反序列化 Helper 類(由 RAD 通過前綴 _Helper、_Ser 和 _Deser 生成),所以在調用與自定義數據類型相關的方法時會出現序列化錯誤。不帶參數的構造器確保可以根據序列化狀態遠程構造對象。
您必須具有用于網絡傳輸字段的 setter 和 getter 方法
            首先,看一看清單 5 中的類 FailTask 的源代碼:
public class FailTask {    /**     * The owner of the task     */    private int ownerid;    /**     * The name of the task     */    private String name;        /**     * Default public non-argument constructor      *     */    public FailTask(){    	    }    /**     * Constructor of FailTask class     * @param ownerid Owner of the task     * @param name Name of the task     */    public FailTask(int ownerid, String name){    	this.ownerid = ownerid;    	this.name = name;    }        /**     * Getter method     * @return the ownerid of the task     */    public int getOwnerid(){    	return ownerid;    }        /**     * Setter method     * @param ownerid the ownerid to be set     */    public void setOwnerid(int ownerid){    	this.ownerid = ownerid;    }  }您可以將清單 6 中所示的方法添加到 Web 服務中,該方法將返回單個 FailTask 對象。
public FailTask getFailTask(int ownerid, String name){		return new FailTask(ownerid, name);	}當使用 RAD 6.0 附帶的 Universal Test Client 中的 1 和 Rachel 參數調用 getFailTask() 方法時,所得到的響應如圖 1 中所示。
 name 字段在哪里?它不在這里,因為我沒有通過 getter 和 setter 方法提供 name 字段。Setter 和 getter 方法是必須提供的兩個方法。和 FailTask_Ser 類中一樣,name 字段 getter 方法用于將 name 字段值寫入 SOAP 消息。在 FailTask_Deser 類中,name 字段 setter 方法用于設置反序列化的 FailTask 對象的 name 值。
在處理數據集合時您應該使用數組
為了有效地使用 Web 服務,您必須或多或少地使用數據集合。但是,必須提醒:當處理許多值類型時,事情會變得比較麻煩,因此需要考慮以下問題。
當需要動態長度的數組時,請考慮 ArrayList。您已經反復聽說過,如果不考慮同步,則 ArrayList 比 Vector 更有效。但遺憾的是,JSR-101 JAX-RPC 規范沒有強制要求支持 Java Collection 類型。有些 Web 服務引擎可能沒有為 ArrayList 提供支持。例如,IBM Web 服務引擎只正式支持 Java Collection Framework 中的一小部分類,包括 java.util.Vector、java.util.HashTable 和 java.util.HashMap。
那么,嘗試一下另一個動態數組 Vector 會如何呢?如果在相同平臺上生成存根文件,它將正常工作。但是,如果在不同的平臺上生成存根文件,則將遇到一些問題。例如,在 Web 服務描述語言 (WSDL) 文件中,Vector 或其他 Collection 類型映射到 ArrayOfAnyType。其他平臺可能不知道將其映射到哪個 Collection 類型,而且 Vector 中包含的數據元素也映射到 WSDL 中的 AnyType。(這里存在的一個大問題是,其他的平臺不知道 AnyType 代表什么類型。)有關該主題的詳細信息,請參閱參考資料中的“ Web services programming tips and tricks: Improve the interOperability between J2EE and .NET ”。
使用數組的最后一個原因是,移動 Web 服務不支持 Java Collection 類型,這使得所有其他的解釋都顯得沒有必要。這意味著您可能無法從形式良好的 WSDL 文件為移動 Web 服務生成存根文件。
移動 Web 服務中的首選的一些數據類型
            使用基元類型 long 傳輸 Date 或 Calendar 表示形式
對于標準 JAX-RPC 運行時實現,有兩種支持的標準類型映射:
在 JAX-RPC 子集規范中,只需要第二種映射。表 1 顯示了從支持的 XML 數據類型到 Java 類型的映射的簡要列表;有關詳細信息,請參閱 JSR-172。
xsd:stringjava.lang.Stringxsd:intintxsd:longlongxsd:shortshortxsd:booleanbooleanxsd:bytebytexsd:floatjava.lang.String 或 floatxsd:doublejava.lang.String 或 doublexsd:QNamejavax.xml.namespace.QNamexsd:base64Binarybyte[]xsd:hexBinarybyte[]從表 1 中您可以清楚地看出,該列表中不存在像 xsd:dateTime、xsd:date 或 xsd:time 這樣的元素,而在標準 JAX-RPC 規范中,這三個元素確實是映射到 java.util.Calendar 的 XML 類型。請注意,在 JAX-RPC1.1 中定義的 Java 據類型映射到 XML 類型的映射中,java.util.Date 映射到 xsd:dateTime。
那么,在嘗試傳輸日期或時間表示形式時,您應該使用什么?改為使用 long 類型的時間。long 類型的日期格式與不同時區的時間表示形式無關,并且因為它是基元類型,所以比其他類型的 Java 對象更有效。
注意 float 和 double 類型的使用
首先需要注意的一點是,正如您所知,CLDC 1.0 (Connected Limited Device Configuration) 并沒有出于性能的原因而提供 float 和 double 本機類型,即使 CLDC 1.1 和 CDC 都為其提供了支持。那么,如果您必須使用針對 CLDC 1.0 的 Web 服務,您該如何做呢?JSR-172 為您提供了部分答案。
為了在 CLDC 1.0 中缺省支持 xsd:float 和 xsd:double,實現必須 生成代碼來將這些類型映射到 java.lang.String。為了支持為 float 和 double 提供本機支持的配置和平臺(CLDC 1.1 和 CDC),存根生成器實現也必須 生成代碼來將這些類型映射到適當的本機 Java 類型。(詳細信息,請參閱參考資料,以獲得指向 JSR-172: J2ME Web 服務規范的鏈接。)
我將演示一個添加兩個 float 數的簡單 Web 服務(清單 7)。
public class TaskWs {    public TaskWs() {    }    /**      * Adding two float numbers and return their sum     * @param a First number to add,     * @param b Second number to add     * @return The sum of a and b.     */    public float addTwo(float a, float b) {        return a + b;    }}}所生成的 WSDL 定義中的 XML 數據類型定義如清單 8 所示。
 <wsdl:types>  <schema targetNamespace="http://ws.test.ibm.com"     xmlns="http://www.w3.org/2001/XMLSchema"   xmlns:impl="http://ws.test.ibm.com" xmlns:intf="http://ws.test.ibm.com"   xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"   xmlns:xsd="http://www.w3.org/2001/XMLSchema">    <element name="addTwoResponse">     <complexType>      <sequence>       <element name="addTwoReturn" type="xsd:float"/>      </sequence>     </complexType>    </element>    <element name="addTwo">     <complexType>      <sequence>       <element name="a" type="xsd:float"/>       <element name="b" type="xsd:float"/>       <element name="b" type="xsd:float"/>      </sequence>     </complexType>    </element>  </schema>  </wsdl:types>
            對于針對 CLDC 1.0 的 Web 服務客戶端,所生成的存根如清單 9 所示。
 public interface TaskWs extends java.rmi.Remote {    public java.lang.String addTwo(java.lang.String _a, java.lang.String _b)            throws java.rmi.RemoteException, javax.xml.rpc.JAXRPCException;}所以,當調用 CLDC 1.0 中的 Web 服務時,您必須使用 addTwo() 方法提供兩個 String 參數,而對于針對平臺 CLDC 1.1 的 Web 服務客戶端,所生成的服務接口與清單 10 中所描述的類似:
public interface TaskWs extends java.rmi.Remote {    public float addTwo(float _a, float _b)     throws java.rmi.RemoteException, javax.xml.rpc.JAXRPCException;}這將 xsd:float 映射到所生成的客戶端存根中的 float 類型。看到 CLDC 1.0 和 CLDC 1.1 之間的不同之處了嗎?
在為移動設備開發 Web 服務時,請注意 float 和 double 類型,因為 CLDC 1.0 虛擬機實現無法加載為 CLDC 1.1 生成的存根(使用到 float 和 double 的本機映射)。同時針對 CLDC 1.0 和 CLDC 1.1 的 Java 2 Platform Micro Edition (J2ME) 應用程序的開發人員應該使用到 java.lang.String 的缺省映射,以獲得最好可重用性。
在處理輸入和輸出參數時注意可能出現的問題
JSR-172 指定了以副本的形式傳送并以副本的形式創建返回值的所有參數。但是,當處理數據集合時,零數組 (返回零)和空數組 (返回其本身)需要密切關注。
我的建議是盡可能地避免使用空數組。當處理移動 Web 服務時,空數組可能是一個問題。
假定您需要返回任務對象數組。原始代碼如清單 11 所示。
public class SimpleTask {    /**     * The name of the task     */    private String name;    /**     * The default constructor     *     */    public SimpleTask() {    }    /**     * @return Returns the name of the task.     */    public String getName() {        return name;    }    /**     * @param name     *            The name to set.     */    public void setName(String name) {        this.name = name;    }}Web 服務實現如清單 12 所示。
1  public SimpleTask[] getSimpleTasks(){2       SimpleTask[] tasks = null;3       /*4        * Your code dealing with DB goes here5        * ....6        * tasks = ...7        */8       return tasks;9	}當生成 Web 服務存根和使用生成的存根測試 Web 服務時,本例中的每一個部分都將正常工作。但是,因為在結束一個階段之前您需要使用 Jtest 進行完整的代碼檢查,所以當您對代碼片段運行 Jtest 時,您將看到一條建議:“Return zero-length arrays instead of null”。在猶豫片刻之后后,您將贊同 Jtest 的建議。如果您返回零數組,該代碼的客戶端必須編寫額外的代碼來檢查返回值是否為零(如清單 13 所示)。
   SimpleTask[] tasks = service.getSimpleTasks();       if(tasks != null){           int length = tasks.length;           //do something here       }當您將 SimpleTask[] tasks = null;(清單 12 中的第 2 行)修改為 SimpleTask[] tasks = new SimpleTasks[0]; 時,您只需將清單 13 編寫為:
   SimpleTask[] tasks = service.getSimpleTasks();           int length = tasks.length;
            在修改之后,您會認為代碼邏輯沒有更改,并再次運行客戶端來調用 Web 服務,但是現在卻引發了異常。到目前為止,您已經根據 Jtest 的建議做了許多小的修補——您忘記修改了什么——這可能導致需要花額外的時間來努力找到發生錯誤的原因。這個過程真的漫長而乏味。
那么,問題究竟出在什么地方呢?一般來說,對于零對象數組(如 SimpleTask),返回的 SOAP 消息如清單 14 所示。
<soapenv:Body><p147:getSimpleTasksResponse xmlns:p147="http://ws.test.ibm.com">  <getSimpleTasksReturn xsi:nil="true" /></p147:getSimpleTasksResponse></soapenv:Body>
對于空數組(如 SimpleTask[] tasks = new SimpleTask[0]),SOAP 消息如清單 15 所示。
<soapenv:Body><p147:getSimpleTasksResponse xmlns:p147="http://ws.test.ibm.com">  <getSimpleTasksReturn /> </p147:getSimpleTasksResponse> </soapenv:Body>
其不同之處在于 <getSimpleTasksReturn/> 和 <getSimpleTasksReturn xsi:nil = true> 之間。圖 2 說明了空數組參數大部分時間是無效的。對于自定義的數據類型(包括另一個自定類型的數組),不要將類變量初始化為空數組——相反,要將其初始化為零數組,盡管所生成的空數組和零數組的 WSDL 定義是相同的。
 結束語
在處理移動 Web 服務時,您需要更加謹慎,因為移動 Web 服務規范只支持部分 API。如果您計劃開發移動 Web 服務,則當您處理值類型和集合類型時,我向您介紹了一些竅門。此外,我還提供了以下信息:
參考資料
學習
            關于作者

Shu Fang Rui 畢業于中國上海交通大學。她對無線技術和 Web 服務非常感興趣。除了旅行之外,她還喜歡從事一些運動。
(出處:http://m.survivalescaperooms.com)
新聞熱點
疑難解答