从何时选择移动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服务
·消息传递技术(如 WebSphere® MQ Everyplace)
与套接字通信和消息传递技术相比,Web服务有一些突出的优势。Web服务使用可扩展标记语言(XML)来传输消息(包括结构良好的数据信息),使用简单对象访问协议(SOAP)来传输对象。如果您使用的是套接字通信,则必须完全负责定义要传输的数据结构。而且,如果客户端和服务器是用不同的编程语言编写的(例如 C++ 和 Java 编程语言),则您的工作量将大大增加——您必须负责数据传输和 C++和Java编程中的编码细节。
消息传递软件可能是一种解决方案,但如果您所关注的是性能,并且不担心事务和安全级别,则消息传递软件真的不是一个非常好的选择。如果使用消息传递软件,您将花大量的时间和精力解决安全问题,并且您的客户很有可能站在您的门口问:“为什么这么慢?”
我不准备告诉您正确的选择应该是什么,而给出一些理由来说明为什么Web服务可能是一个好的选择。
其中的一个理由可能是服务器端编程。即使是像Web服务这样好的机制,也仍然由于XML处理以及传输和接收SOAP消息的开销的原因而不能满足严格的实时处理需求。因此在设计时需要考虑两个问题:
·与普通的HTTP访问和专用的消息传递方法相比,每次Web服务调用的开销都比较大,所以当您主要考虑性能时,您可能需要首先选择另一项技术。
·由于开销的原因,如果您只需要在应用程序的各层之间进行通信,就不必选择Web服务。例如,不要将Web服务放在应用程序的视图层和控制器层之间。
现在,我假定您已经决定使用Web服务。那么,在总体设计方面需要考虑哪些问题?
在决定使用Web服务之后
您需要考虑的下一个问题是Web服务的总体设计。可以运用下列通用设计指导原则:
·管理Web服务的粒度。
·首先定义Web服务接口,然后加以实现。
·使用Document/literal 作为编码样式。
·优先选择JavaBean组件而不是 Enterprise JavaBean(EJB)组件作为服务提供者。
避免XML元素嵌套太深,因为这可能大大延长解析、封送处理和取消封送处理的时间。
下面详细介绍这些设计考虑事项。
管理Web服务粒度
关于粒度管理需要谨记以下几点:
·始终优先考虑粗粒度的Web服务;决不要在分布式系统之间使用细粒度Web服务调用。
·Web服务是一个很好的工具集,但是当您以细粒度的方式使用Web服务时,它可能对应用程序的性能有很大的影响,因为 XML解析、序列化和反序列化的开销很高;这种开销可能占处理时间的百分之三十。
在本文中,我以Task Management系统中的登录功能为例。驱动程序首先登录到系统,如果登录成功,则有两列任务显示在屏幕上。
对于此登录问题有两种解决方案:
·一种解决方案是首先调用登录方法,然后当该方法调用返回true时,调用一个方法来获取相应的任务。
·其他的事情将在一步中完成。如果驱动程序登录到Task Management系统,则返回驱动程序的角色以及分配的任务和启动的任务。如果登录失败,则返回指示驱动程序未登录到该系统的信息。
对于第一种解决方案,它是细粒度登录系统,而显示任务列表需要两次调用Web服务;这可能带来很大的延迟。第二种解决方案是粗粒度登录系统,它返回您在一次调用中需要的所有信息,并确保由于网络延迟和系统I/O带来的影响最小。
首先定义Web服务接口,然后进行实现。
这不仅从移动Web服务的角度来看是适用的,而且从Web服务设计和(更高的层次)面向对象的软件设计来看也是适用的。正如您所知道的,接口是客户端和服务提供程序之间的契约,而且保持稳定非常重要(因为正如您所知道的,实现容易改变)。
定义功能接口非常直接和直观——只需考虑:
·您的目标
·您需要获取什么信息
·您需要将何种结果返回到客户端
然后,您应该将该接口的相关要点写下来,并在这些要点的指导下完成所有实现细节。
这是一条简单的设计指导原则。了解您的目标非常重要,目标可以驱动您编写测试用例,并且指导您编写功能实现,这也是为什么测试驱动开发(TDD)广泛地应用于各种开发技术的原因。
使用Document/literal作为编码样式
目前,JAX-RPC支持三种操作模式:
·RPC/encoded。其优点在于简单,接收方可以轻松地将消息发送到操作的实现。其缺点在于类型编码信息的开销较大,这会降低吞吐量性能。
·RPC/literal。其优点与RPC/encoded相同,而且遵循WS-I组织制定的规范(请参阅参考资料)。
·Document/literal。其优点在于没有类型编码信息,任何XML验证器都可以验证消息。其缺点在于SOAP消息中缺少操作名,所以发送消息很难甚至不可能。
在一些资料中,您还可能发现名为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编程语言公开服务时,需要考虑两种类型的服务提供程序:
·从JavaBean组件生成Web服务
·使用无状态会话EJB组件
虽然在某些情况下,EJB组件非常有用,但是JavaBeans组件常常是更好的选择,特别是在开发移动Web服务时。实现使用 JavaBean组件生成的服务提供程序比较简单和容易,而且与相应的会话EJB组件相比,JavaBean组件更稳定。但是,如果您需要从使用EJB组件开发的现有J2EE应用程序公开Web服务,则请使用EJB组件。
避免XML元素嵌套太深
如果数组的数组、复杂类型的数组或包含另一个自定义复杂类型的复杂类型等嵌套太深,则将大大影响Web服务的性能。清单1显示一个XML描述示例——一个自定义数据类型Task类的数组:
清单 1.自定义数据类型
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所示。
清单 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。
清单 3. WSDL定义中的XML数据类型
清单 4. ArrayOf_tn2_Task 的XML描述
如清单 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类,其值可以在服务客户端和服务端点之间移动。为了获得一致的值类型,必须遵循一系列规则。我只列出其中的几条,与本文关系最大的规则是:
·您必须具有公共缺省构造器。
·您必须具有用于需要在网络上传输的字段的setter和getter方法。
·在处理数据集合时您应该使用数组。
·移动 Web 服务中有一些首选的数据类型。
·在处理输入和输出参数时注意可能出现的问题。
·您必须具有公共缺省构造器
在反序列化的过程中,SOAP运行时环境使用缺省构造器来构造对象。如果您试图在没有公共缺省构造器的情况下编写值类型(也称为数据传输对象),在当 AX-RPC运行时尝试序列化和反序列化数据对象时可能会遇到错误。对于像IBM Rational® Application Developer (RAD) 6.0这样的IDE,将不为该数据类型生成序列化和反序列化Helper 类(由 RAD 通过前缀 _Helper、_Ser 和 _Deser 生成),所以在调用与自定义数据类型相关的方法时会出现序列化错误。不带参数的构造器确保可以根据序列化状态远程构造对象。
您必须具有用于网络传输字段的setter和getter方法
首先,看一看清单 5中的类 FailTask 的源代码:
清单 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 对象。
清单 6. 返回 FailTask 对象的方法
public FailTask getFailTask(int ownerid, String name){
return new FailTask(ownerid, name);
}
当使用 RAD 6.0 附带的 Universal Test Client 中的 1 和 Rachel 参数调用 getFailTask() 方法时,所得到的响应如图 1 中所示。
图 1. Universal Test Client 中的响应视图
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 运行时实现,有两种支持的标准类型映射:
·Java 类型到 XML 数据类型
·XML 数据类型到 Java 类型
在 JAX-RPC 子集规范中,只需要第二种映射。表 1 显示了从支持的 XML 数据类型到 Java 类型的映射的简要列表;有关详细信息,请参阅 JSR-172。
注意 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)。
清单 7. 添加两个 float 数
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 所示。
清单 8. 与清单 7 对应的 WSDL 定义
http://www.w3.org/2001/XMLSchema”>http://www.w3.org/2001/XMLSchema”
http://ws.test.ibm.com/”>http://ws.test.ibm.com” http://ws.test.ibm.com/”>http://ws.test.ibm.com”
http://schemas.xmlsoap.org/wsdl/”>http://schemas.xmlsoap.org/wsdl/”
http://www.w3.org/2001/XMLSchema”>http://www.w3.org/2001/XMLSchema“>
清单 9. 为 CLDC 1.0 生成的客户端存根
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 中所描述的类似:
清单 10. 为 CLDC 1.1 生成的客户端存根
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 所示。
清单 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 所示。
清单 12. 返回值对象的数组的方法
1 public SimpleTask[] getSimpleTasks(){
2 SimpleTask[] tasks = null;
3 /*
4 * Your code dealing with DB goes here
5 * ….
6 * tasks = …
7 */
8 return tasks;
9 }
当生成 Web 服务存根和使用生成的存根测试 Web 服务时,本例中的每一个部分都将正常工作。但是,因为在结束一个阶段之前您需要使用 Jtest 进行完整的代码检查,所以当您对代码片段运行 Jtest 时,您将看到一条建议:“Return zero-length arrays instead of null”。在犹豫片刻之后后,您将赞同 Jtest 的建议。如果您返回零数组,该代码的客户端必须编写额外的代码来检查返回值是否为零(如清单 13 所示)。
清单13. 用于调用 Web 服务的客户端代码片段
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 所示。
清单 14. 返回零数组时的 SOAP 消息
清单 15. 返回空数组时的 SOAP 消息
其不同之处在于
图 2. 根据 JSR-172 编码零数组参数和空数组参数
结束语
在处理移动 Web 服务时,您需要更加谨慎,因为移动 Web 服务规范只支持部分 API。如果您计划开发移动 Web 服务,则当您处理值类型和集合类型时,我向您介绍了一些窍门。此外,我还提供了以下信息:
设计前考虑事项,例如何时使用 Web 服务,以及如何分析使用 Web 服务、套接字或消息传递技术之间的利弊。
在决定使用 Web 服务后需要考虑的设计事项,包括:
1. 管理粒度
2. 设计 Web 服务接口
3. 使用 Document/literal 作为编码样式
4. 为什么您应该使用 JavaBeans 组件而不是 EJB 技术作为您的服务提供程序
5. 为什么您应该避免 XML 元素嵌套太深
6. 嵌套和粒度之间的平衡
在使用 JAX-RPC 值类型时需要考虑的移动 Web 服务设计事项,包括:
1. 公共缺省构造器
2. Setter 和 getter 方法
3. 使用数组类型而不是 Java Collection 类型
4. 确定首选数据类型
5. 处理输入和输出参数
从表 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 对象更有效。
我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。
我原创,你原创,我们的内容世界才会更加精彩!
【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】
微信公众号
TechTarget
官方微博
TechTarget中国
作者
相关推荐
-
SAP收购CallidusCloud 与Salesforce竞争
一直被称为后台办公巨头的SAP现在似乎也想在前台办公大展拳脚。 最新的迹象是SAP收购CallidusClou […]
-
API设计如龙生九子 各不相同
IT咨询管理公司CA Technologies对API产业做了个问卷调查,问卷内容涉及API设计风格以及管理部署的新动向。调查结果表明,JSON与XML可谓两分天下。
-
集成社交化商务平台五大实践之ZipfWorks、EngageSciences
对于社交化战略来说,有比健壮的平台更重要的事情,以数据驱动的方式对客户如何与公司网站交互进行洞察,是适合组织需求的技术方案及社交化平台选择之必备。
-
从头开始实现领域驱动设计
领域描述业务;它是驱动企业的概念和逻辑的集合。如果遵循领域驱动设计(DDD)这一本质,那么领域就是应用程序中最重要的组成部分。