H5W3
当前位置:H5W3 > 其他技术问题 > 正文

设计移动Web服务需要考虑的三个设计层次

      从何时选择移动 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 服务接口,然后加以实现。
  • 使用 Document/literal 作为编码样式。
  • 优先选择 JavaBean 组件而不是 Enterprise JavaBean (EJB) 组件作为服务提供者。
  • 避免 XML 元素嵌套太深,因为这可能大大延长解析、封送处理和取消封送处理的时间。

下面详细介绍这些设计考虑事项。

关于粒度管理需要谨记以下几点:

  • 始终优先考虑粗粒度的 Web 服务;决不要在分布式系统之间使用细粒度 Web 服务调用。
  • Web 服务是一个很好的工具集,但是当您以细粒度的方式使用 Web 服务时,它可能对应用程序的性能有很大的影响,因为 XML 解析、序列化和反序列化的开销很高;这种开销可能占处理时间的百分之三十。

    在本文中,我以 Task Management 系统中的登录功能为例。驱动程序首先登录到系统,如果登录成功,则有两列任务显示在屏幕上。

对于此登录问题有两种解决方案:

  • 一种解决方案是首先调用登录方法,然后当该方法调用返回 true 时,调用一个方法来获取相应的任务。
  • 其他的事情将在一步中完成。如果驱动程序登录到 Task Management 系统,则返回驱动程序的角色以及分配的任务和启动的任务。如果登录失败,则返回指示驱动程序未登录到该系统的信息。

    对于第一种解决方案,它是细粒度登录系统,而显示任务列表需要两次调用 Web 服务;这可能带来很大的延迟。第二种解决方案是粗粒度登录系统,它返回您在一次调用中需要的所有信息,并确保由于网络延迟和系统 I/O 带来的影响最小。

如果一个方法返回如 清单 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 数据类型

<complexType name="Task">
    <sequence
     <element name="ownerName" nillable="true" type="xsd:string"/>
     <element name="taskID" type="xsd:int"/>
    </sequence>
</complexType>
      

     同时,数据类型 Task[] 映射到 ArrayOf_tn2_TaskArrayOf_tn2_Task 的 XML 描述如清单 4 所示:清单 4. ArrayOf_tn2_Task 的 XML 描述

<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 类,其值可以在服务客户端和服务端点之间移动。为了获得一致的值类型,必须遵循一系列规则。我只列出其中的几条,与本文关系最大的规则是:

  • 您必须具有公共缺省构造器。
  • 您必须具有用于需要在网络上传输的字段的 setter 和 getter 方法。
  • 在处理数据集合时您应该使用数组。
  • 移动 Web 服务中有一些首选的数据类型。
  • 在处理输入和输出参数时注意可能出现的问题。

您必须具有公共缺省构造器

    在反序列化的过程中,SOAP 运行时环境使用缺省构造器来构造对象。如果您试图在没有公共缺省构造器的情况下编写值类型(也称为数据传输对象),在当 JAX-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 中的响应视图

Universal Test Client 中的响应视图

    name 字段在哪里?它不在这里,因为我没有通过 getter 和 setter 方法提供 name 字段。Setter 和 getter 方法是必须提供的两个方法。和 FailTask_Ser 类中一样,name 字段 getter 方法用于将 name 字段值写入 SOAP 消息。在 FailTask_Deser 类中,name 字段 setter 方法用于设置反序列化的 FailTask 对象的 name 值。

在处理数据集合时您应该使用数组

    为了有效地使用 Web 服务,您必须或多或少地使用数据集合。但是,必须提醒:当处理许多值类型时,事情会变得比较麻烦,因此需要考虑以下问题。

    当需要动态长度的数组时,请考虑 ArrayList。您已经反复听说过,如果不考虑同步,则 ArrayListVector 更有效。但遗憾的是,JSR-101 JAX-RPC 规范没有强制要求支持 Java Collection 类型。有些 Web 服务引擎可能没有为 ArrayList 提供支持。例如,IBM Web 服务引擎只正式支持 Java Collection Framework 中的一小部分类,包括 java.util.Vectorjava.util.HashTablejava.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。

表 1. 从 XML 数据类型到 Java 类型的映射

简单 XML 类型 Java 类型
xsd:string java.lang.String
xsd:int int
xsd:long long
xsd:short short
xsd:boolean boolean
xsd:byte byte
xsd:float java.lang.Stringfloat
xsd:double java.lang.Stringdouble
xsd:QName javax.xml.namespace.QName
xsd:base64Binary byte[]
xsd:hexBinary byte[]

    从表 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)。清单 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;
    }
}
}
      

当生成 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 消息

<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 所示。清单 15. 返回空数组时的 SOAP 消息

<soapenv:Body>
<p147:getSimpleTasksResponse xmlns:p147="http://ws.test.ibm.com">
  <getSimpleTasksReturn />
 </p147:getSimpleTasksResponse>
 </soapenv:Body>
      

    其不同之处在于 <getSimpleTasksReturn/><getSimpleTasksReturn xsi:nil = true> 之间。图 2 说明了空数组参数大部分时间是无效的。对于自定义的数据类型(包括另一个自定类型的数组),不要将类变量初始化为空数组——相反,要将其初始化为零数组,尽管所生成的空数组和零数组的 WSDL 定义是相同的。图 2. 根据 JSR-172 编码零数组参数和空数组参数根据 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. 处理输入和输出参数

参考资料

学习

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文
  • "Web 服务编程技巧及窍门: 改善 J2EE 与 .NET 之间的互操作性(developerWorks,2005 年 1 月)向您介绍在处理标准 Web 服务时如何管理集合、数组和原型数据类型。
  • JSR-172:J2ME Web 服务规范定义了一个可选的包,其提供了从 J2ME 到 Web 服务的标准访问。
  • "我应该采用哪一种 WSDL 样式?" (developerWorks,2005 年 5 月)描述了本文所提供的操作模式(以及另外两种模式):RPC/encoded、RPC/literal、Document/encoded、Document/literal 和 Document/literal wrapped 模式。
  • Web Services – Interoperability Organization (WS-I) 是一个开放的行业组织,旨在促进跨平台、操作系统和编程语言之间的互操作。
  • "Tips and tricks: XML does the job(developerWorks,2002 年 3 月)说明了如何使用 XML-RPC 来定义移动 Web 服务客户端。
  • "Cross-platform programming with Java technology and the IBM Web Services Toolkit for Mobile Devices(developerWorks,2003 年 2 月)帮助确保您的 Java 应用程序在不需要修改的情况下作为尽可能多的平台运行。
  • JSR-101: Java APIs for XML-based RPC 讨论了如何使用 JAX-RPC 值类型。
  • "Using Mobile Devices with the WSTK(developerWorks,2002 年 12 月)说明了 Web Services Tool Kit for Mobile Devices 如何帮助开发在小型移动设备上使用 Web 服务的应用程序。
  • "交付 Web 服务至移动式应用程序(developerWorks,2003 年 1 月)说明了如何使用支持 J2ME 的移动设备和 kSOAP 库访问 Web 服务。
  • "为移动设备开发 Web 服务客户端 (developerWorks,2003 年 3 月)指导您完成构建 J2ME MIDP 设备上的移动 Web 服务客户端的必要步骤。
  • Connected Limited Device Configuration 1.0(CLDC;JSR-30 和 JSR-139)定义了用于资源约束型设备的一组基本 API 和虚拟设备。提供了一个功能强大的 Java 平台,用于开发与 Mobile Information Device Profile (MIDP) 结合在具有有限内存、处理能力和图形功能的设备上运行的应用程序。
  • Best practices for Web services 系列(developerWorks,2002 年 10 月)详细介绍了 Web 服务设计考虑事项。
  • 请查阅 Safari eReference Bookstore,以找到许多特定于移动和其他技术的主题。
  • developerWorks Wireless technology 专区专门发布有关基于移动和普及计算的解决方案的各方面的文章。

获得产品和技术

  • Jtest 可以自动完成 Java 单元测试,从正在运行的应用程序自动生成 JUnit 测试用例,以及测试单个类或大型的复杂应用程序。
  • Web Services Tool Kit for Mobile Devices (alphaWorks) 是成熟的技术——弄清楚该技术的应用领域。

关于作者

Shu Fang Rui 毕业于中国上海交通大学。她对无线技术和 Web 服务非常感兴趣。

本文地址:H5W3 » 设计移动Web服务需要考虑的三个设计层次

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址