使用 IBM Worklight 开发客户端和服务器 mashup 移动应用程序

本文将介绍一个混合移动应用程序的开发流程,该应用程序组合了来自客户端和服务器端上的多个来源的数据、演示和功能。这类 mashup 应用程序的主要特征是不同来源的组合、可视化和聚合。利用了各种已发布的 Web 来源,以及来自某个企业托管的关系数据库的数据。您会看到,IBM® Worklight V5 是构建这类针对各种移动平台的基于 mashup 的混合应用程序的理想平台。 本文来自于 IBM WebSphere Developer Technical Journal

Walter M. Jenny, 业务解决方案架构师, IBM

Walter Jenny 是 Business Performance and Services Optimization (BPMO) 团队的业务解决方案架构师,有超过 20 年从业经验。他已经在 IBM Software Group 工作了三年。他专长的领域包括业务集成和业务过程管理。Walter 拥有计算机学科硕士学位、企业管理硕士学位和企业管理博士学位。



2013 年 1 月 31 日

免费下载:IBM® Worklight Mobile Platform
下载更多的 IBM 软件试用版,并加入 IBM 软件下载与技术交流群组,参与在线交流。

简介

立即获取 Worklight

立即免费下载 IBM Worklight Developer Edition 5.0,该版本永不过期!

IBM Worklight V5 提供了一个开放、全面、先进的移动应用程序平台,可帮助您高效地开发、运行和管理 HTML5、混合和原生应用程序。

使用基于标准的技术和工具、移动优化的中间件、各种安全机制以及集成的管理和分析功能,Worklight 简化了跨多种移动平台(包括 iOS、Android、BlackBerry 和 Windows® Phone)的应用程序开发生命周期。作为一个基于 Eclipse 的可视开发环境,Worklight 使用开放技术(比如 HTML5、Apache Cordova 和 JavaScript™)和流行的 JavaScript 框架(比如 Dojo、jQuery 和 Sencha Touch)帮助您加速移动应用程序的开发、测试和交付。

要确定在开发移动应用程序时使用哪些技术,您需要理解移动 Web、混合和原生应用程序的不同开发方法的性质:

  • 移动 Web 应用程序可在几乎任何设备上运行,因为它们与设备的任何原生特性(比如本地文件存储、推送通知等)没有任何关系。这些应用程序通常基于 HTML5 与 CSS 和 JavaScript 的协作。使用这些既定的技术有助于降低您的开发成本,因为您可重用可用的技能,无需原生平台开发人员的帮助。单一的部署、维护和测试生命周期并不太复杂。此外,您无需担忧如何在专用的应用商店中启动应用程序,这可能减少您的潜在收入并使应用程序维护更加棘手。
  • 混合应用程序由 HTML5、CSS 和 JavaScript 组成,能够访问移动设备的原生特性。它们不仅提供了移动 Web 应用程序的所有优点(包括应用程序在许多平台上执行的能力),还可利用许多原生的设备特性。此方法通常利用一个框架来 “虚拟化” 原生设备功能。这样的抽象是通过 JavaScript 桥提供的,比如 Apache Cordova(PhoneGap 框架的前身)。这种组合最大限度地增强了用户体验,在跨广泛的平台开发应用程序上提供了最高的灵活性和可伸缩性。不同的形状规格和设备功能可通过皮肤解决。
  • 原生应用程序在您希望利用设备的所有原生特性以及原生观感时很有用。在需要内存密集型、复杂的设备功能时,通常推荐使用此方法。但是,因为必须开发、维护和测试针对不同设备的原生代码,所以此方法通常更昂贵且扩展性更差。

混合应用程序方法采纳了 Worklight 融合这两个领域的最佳成果而形成的强大功能,主要支持使用 HTML5、CSS 和 JavaScript 编写的应用程序,但也支持在移动客户端运行时中通过 Cordova 全面访问设备功能。Cordova 是一个移动框架,支持您将设备上的一个嵌入式 WebView 或 Webkit 的单一代码库原生地用于所有智能电话。

在本文中,我们将利用许多 Worklight 特性来创建一个混合应用程序,该应用程序混合了来自客户端和服务器端上的多个来源的数据、演示和功能。文中提供了样例代码,以便您在自己的环境中测试示例。


Worklight 架构

本文中描述的场景使用了多个 Worklight 组件(参见图 1):

  • IBM Worklight Studio 是一个基于 Eclipse 的 IDE,支持您执行所有必要的编码和集成任务来开发全功能的应用程序。
  • IBM Worklight Server 是应用程序、外部服务和企业后端基础架构之间的一个基于 Java 的网关。Worklight Server 包含的安全特性为连接、多来源数据提取和操作、身份验证、Web 和混合应用程序的直接更新、分析和操作管理功能提供了支持。Worklight Server 可在 Java EE 应用服务器(比如 WebSphere Application Server)上作为一个专用应用程序运行。出于本文的目的,我们将使用 Worklight Studio 随带的轻量型的 Jetty HTTP 服务器。
  • IBM Worklight 设备运行时组件 为一般环境和各种针对性的环境提供了 SDK 和客户端移动应用程序运行时。它还签入了服务器集成功能的客户端部分。
  • IBM Worklight Console 是一个基于 Web 的 UI,专用于持续监视和管理 Worklight Server 及其部署的应用程序、适配器和推送功能。
图 1. 主要的 Worklight 组件
图 1. 主要的 Worklight 组件

对于此场景,您可以设想自己必须提供来自多个来源的信息,而所有这些信息都与一个特定位置相关。这可能是来自现场办事处或地理区域的与销售相关的信息,所有信息以各种不同的格式提供。为了简便起见,我们仅利用著名的公共互联网来源。

所有代码(HTML、CSS 和 JavaScript)将在 Worklight Studio 中开发,然后部署到 Worklight 服务器。Worklight 提供了一个适配器集成框架来连接后端系统,向移动应用程序提供数据或从中获取数据,并在此数据上执行任何必要的服务器端逻辑。Worklight Console 对控制应用程序很有帮助,也可以将它用作一个发布点。

样例应用程序的基本控制流如图 2 所示。

图 2. 控制流
图 2. 控制流
  1. 移动 Web 应用程序是从 Worklight Server 加载的。
  2. 作为初始 HTML 文件的一部分,创建了一个自定义小部件,以便调用 Google Maps API 来呈现地图,最初通过 HTML5 地理位置特性定位用户当前的位置。
  3. 该应用程序调用服务器端适配器来提供一组位置。
  4. 服务器端 LocationAdapter 通过 JDBC 从 DB2® 数据库检索一个位置数组并将它发送回移动客户端。
  5. 只要用户选择一个新位置,地图上的位置就会更新,并会通过 NewsAdapter 检索相关信息。
  6. NewsAdaptor 首先从 Google News Feed 收集数据。
  7. 然后它会使用链接调用 StockAdapter 从 Yahoo! Finance 检索股票数据并扩充这两部分数据。

让我们看看构建此应用程序所需的两个主要部分:客户端和服务器部分,然后在移动模拟器上运行它们。


客户端部分

首先看看客户端部分。

  1. 在 Worklight Studio 中,选择 File > New > Worklight Project 或单击 Create Worklight Artifacts 来创建一个新 Worklight 项目(参见图 3)。
    图 3. 创建 Worklight 项目
    图 3. 创建 Worklight 项目
  2. 命名 Project Mashup,选择一个 Hybrid Application 模板,然后单击 Next(参见图 4)。
    图 4. 选择一个 Hybrid Application
    图 4. 选择一个 Hybrid Application
  3. 将应用程序命名为 Mashup。选择 Add Dojo Toolkit,这使您的应用程序能够利用 Dojo Javascript 库功能,然后单击 Finish(参见图 5)。
    图 5. 创建 Worklight 应用程序
    图 5. 创建 Worklight 应用程序

这将创建您的基本 Worklight 项目结构(参见图 6)。(请参阅 IBM Worklight 用户文档使用 Worklight,第 1 部分:开始开发您的第一个 Worklight 应用程序,了解这些主要元素的详细信息。)

图 6. 基本 Worklight 项目结构
图 6. 基本 Worklight 项目结构

接下来的一些步骤涉及到创建逻辑来呈现填充了地理位置信息的地图。可直接使用随 Dijit 框架一起发布的 Dojo 工具包(包含一组强大且全面的小部件)在 Mashup.js 中编辑所需的 JavaScript 代码来实现此目的。但是,如果希望构建可供您开发团队的其他人重用的小部件,自定义小部件是一个不错的方法。

创建 Dijit 自定义小部件

对于此场景,您需要使用一个自定义小部件在地图中显示与特定位置相关的数据。开发的第一部分涉及到显示当前的位置,并在一个标准 Google 地图中呈现它。然后将开发一个自定义小部件来封装这些可重用的 UI 元素和一部分特定的附加功能。创建自定义 Dojo 小部件的步骤包括:

  1. 为自定义小部件创建文件结构

    一般认为拥有自定义 Dijit 小部件的合适文件结构是一种不错的实践,并且 Worklight Studio 支持该方法。您将创建一个名为 custom 的文件夹,它将代表您的命名空间。(您可使用您喜欢的任何名称,但有意义的名称最好,比如应用程序或组织的名称。)

    Worklight Studio 使此步骤变得非常简单:

    1. 右键单击 Explorer 视图中的 /Mashup/apps/Mashup/common 文件夹并选择 New > Dojo Widget。此时会出现 New Dojo Widget 向导。对于 Module Name,输入 custom;对于 Widget Name,输入 LocationViewer。小部件路径的 HTML 模板和样式表会自动填充(参见图 7)。
      图 7. New Dojo Widget 向导
      图 7. New Dojo Widget 向导
    2. 单击 Finish。在 common 文件夹下创建了 3 个文件(参见图 8)。
      图 8. New Dojo Widget 向导
      图 8. New Dojo Widget 向导
  2. 使用 declare 创建小部件类

    Worklight Studio 在编辑器中自动打开 LocationViewer JavaScript 源文件,其中包含生成的 Dojo 语句(参见清单 1,已删除了注释)。

    清单 1. 创建自定义小部件 JavaScript 代码
    define("custom/LocationViewer", [ "dojo", "dijit", "dijit/_Widget",
    		"dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin",
    		"dojo/text!custom/templates/LocationViewer.html" ], function(dojo,
    		dijit, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin) {
    	return dojo.declare("custom.LocationViewer", [ dijit._Widget,
    			dijit._TemplatedMixin, dijit._WidgetsInTemplateMixin ],
    {
    templateString : dojo.cache("custom",	"templates/LocationViewer.html"),
    constructor : function() {
    		},
    	postMixInProperties : function() {
    		},
    	postCreate : function() {
    		}
    	});
    });

    在创建自定义模块时,AMD 模块格式将 dojo.provide 和 dojo.require 替换为 define。这些 define 调用等同于 require 调用,但回调返回一个值,该值被保存并被用作模块的解析值。Worklight Studio 声明了您的自定义 LocationViewer 小部件,并使用 dijit/_WidgetBase 和 dijit/_TemplatedMixin 作为基础。

    提示:如果您希望创建相同的模板而不使用向导(例如,当使用现有的 JS 文件时),那么还可以使用内置的代码生成引擎,方法是键入 decl 并按下 Ctrl+Space 键(参见图 9)。

    图 9. 不使用向导声明 Dijit 小部件
    图 9. 不使用向导声明 Dijit 小部件

    接下来,您需要定义一些属性,使小部件知道它将收到哪些类型的属性(清单 2)。

    清单 2. 设置属性
    zoom			: 10,
    address		: "",
    // Define reasonable defaults for mobile devices
    containerWidth	: "320px",
    containerHeight	: "460px",

    当继承超类的所有变量都 “混合在一起” 时,将调用 postMixInProperties 函数。postMixInProperties 的常见操作是修改或分配模板 HTML 文件中定义的小部件属性变量的值。您在这里要做的只是调用超类方法(清单 3)。

    清单 3. 设置 “混合的” 属性
    postMixInProperties : function() {
    	this.inherited('postMixInProperties', arguments);
    },

    在小部件的 DOM 结构准备好时,就会调用 postCreate 方法,但该调用发生在将它插入到页面中之前。这通常是放入任何类型的初始化代码的最佳位置。目前您将调用真正干活的函数:refreshMap 函数,它创建包含空地址且不含任何信息的 Google 地图。为了保留构造函数链,可调用超类方法(清单 4)。

    清单 4. 初始化自定义小部件
    postCreate : function()
    {
    this.refreshMap(this.address, "");
    	this.inherited('postCreate', arguments);
    },

    真正的功能在 refreshMap 函数中实现。当使用一个空地址调用且没有任何信息时,您可以使用 HTML5 地理位置特性来获取当前位置。否则,您可以尝试使用 Google 的地理编码器在地图上查找给定的地址。InfoWindow 的内容可作为一个参数进行传递(清单 5)。

    清单 5. 绘制地图
    refreshMap : function(address, information)
    {
      var localZoom = this.zoom;
      var mapOptions = {
                zoom : localZoom,
                mapTypeId : google.maps.MapTypeId.ROADMAP
      };
      var map = new google.maps.Map(this.mapNode, mapOptions);
      if (address == null || address == "")
      {
        if(navigator.geolocation) // Try HTML5 geolocation
        {
          navigator.geolocation.getCurrentPosition(function(position)
          {
            var pos = new google.maps.LatLng(position.coords.latitude,
                                             position.coords.longitude);
            var infoWindow = new google.maps.InfoWindow({map : map,
                                             position :        pos,
                                             content : 'This your current location<br>' +
    		Found using <a href="http://dev.w3.org/geo/api/spec-source.html">HTML5 
    		Geolocation</a>'
          });
          map.setCenter(pos);
          infoWindow.open(map);
        }, function() {
          handleNoGeolocation(true);
          });
        }
        else // Browser doesn't support Geolocation
        {
          handleNoGeolocation(false);
        }
        return;
      }
      var geocoder = new google.maps.Geocoder();
      geocoder.geocode({'address' : address
      }, function(results, status) {
        if (status == google.maps.GeocoderStatus.OK)
        {
          map.setCenter(results[0].geometry.location);
          var infoWindow = new google.maps.InfoWindow({ content  : information,
    maxWidth : 230
          });
          var marker = new google.maps.Marker({ map      : map,
                                                position : results[0].geometry.location
          });
          infoWindow.open(map, marker);
          }
          else
          {
            alert("Error: " + status);
          }
        });
      },
      handleNoGeolocation : function (errorFlag)
      {
        …
      }

什么是 AMD?

AMD (Asynchronous Module Definition, 异步模块定义) 格式是用于 Dojo 1.7 及更高版本的新模块格式,它取代了 dojo.provide、dojo.require、dojo.requireIf、dojo.requireAfterIf、dojo.platformRequire 和 dojo.requireLocalization。它在旧有 Dojo 模块样式的基础上提供了许多增强,这些增强包括完全异步的操作、真正的包可移植性、更好的依赖性管理和改善的调试支持。AMD 也是一种社区驱动的标准,这意味着写入 AMD 规范的模块可用于其他任何符合 AMD 的加载器或库。请参阅 定义模块,了解有关的更多信息。

  1. 创建自定义小部件 HTML 代码

    模板应始终拥有一个父包装元素,其中包含其他所有元素,该元素可是您想要的任何元素,但重要的是它只有一个根元素。对于这里的基本小部件,可以使用 div 作为包装元素。

    在 Explorer 视图中,双击 templates/LocationViewer.html 打开小部件的 HTML 模板并完成代码,如清单 6 所示。

    清单 6. 自定义小部件 HTML 代码
    <div>
    	<div dojoAttachPoint="mapNode"
                 style="width:${containerWidth}; height:${containerHeight}">
    	</div>
    </div>

    在使用 dijit/_TemplatedMixi 时,您可以使用 ${attribute} 语法直接插入一些值,比如 containerWidth。应该为节点提供一个附加点,指示可以在您的小部件代码中使用名称直接引用该节点。这类似于调用 getElementById 并提前设置引用。

  2. 适当设置样式

    在需要特殊的 CSS 指令时,您可自定义 themes/LocationViewer.css。

  3. 在应用程序中使用自定义小部件

    自定义小部件现在可从 Worklight Studio 面板的 Other Dojo Widgets 部分中获得(参见图 10)。为了在项目中使用这个小部件,Worklight Studio 的 Rich Page Editor 提供了大量支持。

    图 10. Worklight Studio 模板中的自定义部件
    图 10. Worklight Studio 模板中的自定义部件

    在 Explorer 视图中,双击 js/Mashup.html 打开 HTML 模板。删除文本 “Mashup”。将一个 dojox.mobile.View 从 “Dojo Mobile Widgets” 面板拖到 Mashup.html 中,并将它放在注释 <!-- application UI goes here --> 的旁边。将 locationViewer 图标拖到 Mashup.html 中,并将它放在 dojox.mobile.View div 内。添加对 Google 地图 API 的引用(清单 7 中加粗的一行),在代码中或通过 Properties 选项卡为 LocationViewer 添加一个 ID。

    可以根据您的个人偏好手动编写此代码,或者在将一个小部件放在容器小部件上时,利用 Rich Page Editor 中的 Design Mode 指导代码的放置。可视提示突出显示了可能的放置位置,弹出提示表明了可供选定小部件使用的编辑功能。例如,当从面板将 LocationViewer 拖放到视图上时,您会获得放置位置的可视提示。Rich Page Editor 使用嵌入式浏览器在 Design 视图中生成一个网页的可视表示。(参阅 Module 03.5 - Rich Page Editor,了解有关的更多细节。)

    图 11. Rich Page Editor 的 Design 模式
    图 11. Rich Page Editor 的 Design 模式

    Worklight Studio 将插入其他所有必要代码(清单 7)。

    清单 7. 主要 HTML 文件中的自定义小部件
    <!DOCTYPE html>		
    <html>
    	<head>
    		<meta charset="utf-8" />
    <meta name="viewport"
    	content="width=device-width, initial-scale=1, maximum-scale=1, 
    	user-scalable=no" />
    <title>Mashup</title>
    		<link rel="shortcut icon" href="images/favicon.png" />
    		<link rel="apple-touch-icon" href="images/apple-touch-icon.png" />
    		<link rel="stylesheet" href="css/reset.css" />
    		<link rel="stylesheet" href="css/Mashup.css" />
    <script type="text/javascript"
    	data-dojo-config="isDebug: false, async: true, parseOnLoad: true"
    	src="dojo/dojo.js"></script>
    <script type="text/javascript" src="dojo/core-web-layer.js"></script>
    <script type="text/javascript" src="dojo/mobile-ui-layer.js"></script>
    <script type="text/javascript" src="dojo/mobile-compat-layer.js"></script>
    <!--  add this line -->
    <script src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
    <script type="text/javascript">
    require(
    // Set of module identifiers
    [ "dojo", "dojox/mobile/parser", "dojox/mobile/View", "dojox/mobile", 
    	"dojox/mobile/compat", "dojox/mobile/deviceTheme", "custom/LocationViewer" ],
    // Callback function, invoked on dependencies evaluation results
    function(dojo) {
    	dojo.ready(function() {
    
    	});
    });
    </script>
    <meta name="apple-mobile-web-app-capable" content="yes">
    </head>
    	<body onload="WL.Client.init({})" id="content" style="display: none">
    	<div data-dojo-type="dojox.mobile.View" id="view0"
    		data-dojo-props="selected:true">
    		<div data-dojo-type="custom.LocationViewer" id="locationViewer"></div>
    	</div>
    
    	<script src="js/Mashup.js"></script>
    		<script src="js/messages.js"></script>
    		<script src="js/auth.js"></script>
    	</body>
    </html>

运行应用程序

要运行和测试应用程序的第一个版本,则需要将它发布到本地的 Worklight 服务器。Worklight Studio V5 包含一个嵌入式本地服务器,您可使用它测试上面创建的代码。右键单击 apps/Mashup 并选择 Run as > Build All and Deploy。这将在端口 8080 上启动本地服务器,激活项目,然后部署应用程序,以便在浏览器中测试它。

图 12. 在 Worklight Console 中部署的应用程序
图 12. 在 Worklight Console 中部署的应用程序

您可通过单击该图标或 Preview as Common Resources 链接在浏览器中打开该应用程序。正如您所预料的,此时会显示当前位置(参见图 13)。

图 13. 预览应用程序
图 13. 预览应用程序

服务器部分

下一步是添加对后端资源的访问。所选的工具是 Worklight Adapter,它们连接到后端系统,向移动应用程序提供数据,从移动应用程序中获取数据,并在此数据上执行任何必要的服务器端逻辑。适配器框架提供了:

  • 灵活且强大的服务器端 JavaScript,生成简洁且可读的代码,用这些代码集成后端应用程序并处理它。您还可以使用 XSL 将后端数据分层转换为 JSON。
  • 支持对后端系统采用只读和事务性访问模式。
  • 灵活的身份验证简化了创建与后端系统的连接的过程。适配器提供了对与其建立连接的用户身份的控制。
  • 从后端应用程序检索的数据以一种一致的方式公开,以便您可统一地访问数据,无论它的来源、格式或协议是什么。

创建 LocationAdapter

Worklight 提供了以下适配器:

  • SQL 适配器
  • HTTP 适配器(同时支持 REST 和 SOAP)
  • Cast Iron 适配器

您将利用 SQL 适配器访问一个关系数据库中包含的信息。

适配器过程使用 XML 声明,通常在 JavaScript 中实现,但因为适配器是一个服务器端实体,所以您可在适配器代码中使用 Java 代码段。一个过程可在调用该服务之前或之后处理数据。

LocationAdapter 是一个非常基本的适配器,它查询包含清单 8 中所示的表的 DB2 数据库。

清单 8. 创建 DB2 数据库的 SQL 代码
CREATE SCHEMA TRAVEL;
SET SCHEMA = TRAVEL;
	
DROP TABLE travel.locations;
CREATE TABLE travel.locations
	(
	city varchar(10),
	country varchar(20),
	stock varchar(10)
	);

DELETE FROM travel.locations;
INSERT INTO travel.locations values ( 'Paris', 'France', 'FCHI');
INSERT INTO travel.locations values ( 'London', 'UK', 'FTSE');
INSERT INTO travel.locations values ( 'New York', 'US', 'DJI');
INSERT INTO travel.locations values ( 'Frankfurt', 'Germany', 'GDAXI');
INSERT INTO travel.locations values ( 'Tokyo', 'Japan', 'N225');
INSERT INTO travel.locations values ( 'Madrid', 'Spain', 'IBEX');

您可在 Worklight Studio 的 Database 透视图中运行此 SQL 代码,或者在任何类似的工具中运行。要创建 LocationAdapter,请右键单击 Explorer 视图中的 apps/Mashup 文件夹并选择 New > Worklight Adapter。将显示 New Worklight Adapter 向导(参见图 14)。

图 14. New Worklight Adapter 向导
图 14. New Worklight Adapter 向导

对于适配器类型,请选择 SQL Adapter;对于适配器名称,请输入 LocationAdapter。这会创建如图 15 中所示的项目文件结构。

图 15. 适配器文件结构
图 15. 适配器文件结构

您需要更新 LocationAdapter.xml,以便使用正确的数据库连接。在此情况下,配置一个 DB2 数据库的数据源并将标准过程重命名为 getLocations(清单 9)。

清单 9. LocationAdapter 连接
<displayName>LocationAdapter</displayName>
<description>LocationAdapter</description>
<connectivity>
	<connectionPolicy xsi:type="sql:SQLConnectionPolicy">
		<dataSourceDefinition>
			<driverClass>com.ibm.db2.jcc.DB2Driver</driverClass>
			<url>jdbc:db2://localhost:50001/TRAVELDB</url>
		<user>user</user>
		<password>password</password> 
		</dataSourceDefinition>
</connectionPolicy>
<loadConstraints maxConcurrentConnectionsPerNode="5" />
</connectivity>
<procedure name="getLocations"/>

也可以右键单击 LocationAdapter.xml 并选择 Open With > Adaptor Editor 来打开配置(参见图 16)。

图 16. Adapter Editor
图 16. Adapter Editor

适配器逻辑在 adapters/LocationAdapter/LocationAdapter-impl.js 中实现。在适配器的 XML 文件中声明的过程必须在相应的 JavaScript 文件中实现。对于这个基本的 SQL 语句,Worklight 适配器框架提供了两个方法供您使用:

  • WL.Server.createSQLStatement() 创建了一个准备好的 SQL 语句供 WL.Server.invokeSQLStatement 在以后调用。该方法只可以在 SQL 适配器内声明的某个过程中使用。它必须在任何 JavaScript 函数范围以外的地方使用。该方法接受任何包含问号 (“?”) 作为参数占位符的有效 SQL 语句,返回一个表示已准备好的语句的对象。
  • WL.Server.invokeSQLStatement() 接受以下参数:
    • preparedStatement,由上面的方法返回
    • 已准备好的语句的一个可选的参数数组
    • transformation,一种针对响应的可选的 XSL 转换

    在执行可选的处理后,该方法返回已准备好的语句的结果集。(参阅 IBM Worklight V5.0 开发人员参考指南,了解有关的详细信息。)

在清单 10 中所示的实现中使用这两个方法。

清单 10. LocationAdapter 实现
var procedure1Statement = WL.Server.createSQLStatement("select * from travel.locations 
	order by city");
function getLocations() {
	return WL.Server.invokeSQLStatement({
		preparedStatement : procedure1Statement,
		parameters : []
	});
}

您一定注意到了代码的简单和优雅。像这样的适配器可在任何 Worklight 应用程序内调用,无论是在客户端上,还是在服务器端上。它允许调用方与任何数据源进行通信,且不会受到同源约束。

您还需要一个 JDBC 驱动程序。如果在一个预先配置的 WebSphere Application Server 上部署 Worklight 控制台,那么可能已在其类路径中拥有 DB2 JDBC 驱动程序。对于本示例,您将使用内置的 Jetty HTTP 引擎,您需要从 DB2 安装中提供该引擎的 JDBC 驱动程序(如果使用类型 4 驱动程序,那么该驱动程序为 db2jcc4.jar),将它复制到 server/lib 文件夹中,或者将它的位置添加到 Jetty 类路径中。

要对适配器配置和代码进行快速测试,请右键单击 adapters/LocationAdapter 并选择 Run As > Invoke Worklight Procedure

图 17. 调用 LocationAdapter
图 17. 调用 LocationAdapter

在 Edit Configuration 向导中,选择 getLocations 作为过程名称并单击 Run。这将从浏览器内调用目标适配器(参见图 18)。

图 18. 调用结果
图 18. 调用结果

您现在已准备好部署适配器了。为此,请右键单击该适配器并选择 Run As > Deploy Worklight Adapter(参见图 19)。

图 19. 部署 LocationAdapter
图 19. 部署 LocationAdapter

Worklight Studio 会对适配器代码进行归档,并将它部署在 Worklight Server 上。您可在 Worklight Console 中看到已部署的适配器和最初的 Mashup 应用程序(参见图 20)。

图 20. 部署的 LocationAdapter
图 20. 已部署的 LocationAdapter

适配器链

通过一种类似的方式,创建其他两个适配器作为 HTTPAdapter 实例。回想一下图 2,只要需要特定位置的相关信息,客户端就会调用 NewsAdapter。您可以将此位置信息传递到服务器端,并聚合来自两个来源(Google News Feed 和 Yahoo! Finance)的相关数据,实际上它们是来自不同来源的一个服务器端 mashup。这样的聚合也称为适配器链(参见图 21)。

图 21. 适配器链
图 21. 适配器链

适配器链支持在单个客户端调用中完成所有服务器端处理。这样的 “粗粒度” 交互被视为不错的实践,您使用该模式的方式取决于您的具体需求;例如,必须检索多少数据、服务器端处理逻辑有多昂贵、您能否缓存在客户端上并处理过时的数据,等等。当然,这些架构考虑因素不是新鲜事物,或者说它们并非仅限于移动领域,而是在任何多层架构中制定的重要决策。

创建剩余的适配器

您首先可能希望实现链中的最后一个链接 StockAdaptor。它是对 Yahoo! Finance 服务的一次简单调用。连接的配置如清单 11 所示。

清单 11. StockAdapter 连接
>displayName>StockAdapter>/displayName>
>description>StockAdapter>/description>
>connectivity>
	>connectionPolicy xsi:type="http:HTTPConnectionPolicyType">
			>protocol>http>/protocol>
			>domain>finance.yahoo.com>/domain>
			>port>80>/port>			
	>/connectionPolicy>
	>loadConstraints maxConcurrentConnectionsPerNode="2" />
>/connectivity>
>procedure name="getStock"/>

对 Yahoo! Finance 服务的调用需要一个特定的股票代号(这里使用国家股价指数作为示例,例如 DJI 或 FTSE)以及您想要的准确数据的细节。该服务返回一个逗号分隔值 (CSV) 形式的数据流。出于此原因,请将 returnedContentType 设置为 csv(清单 12)。这是 Adapter 框架提供的另一个不错的特性。

清单 12. StockAdapter 实现
function getStock(stock) {
	var input = {
	    method : "get",
	    returnedContentType : "csv",
	    path : "d/quotes.csv",
		parameters : {
			"s" : "^" + stock,
			"f" : "sl1c1c"
		}
	};
	return WL.Server.invokeHttp(input);
}

要调用 Google News 服务,则需要设置连接,如清单 13 所示。

清单 13. NewsAdapter 连接
>displayName<NewsAdapter>/displayName<
>description<NewsAdapter>/description<
>connectivity<
	>connectionPolicy xsi:type="http:HTTPConnectionPolicyType"<
		>protocol<http>/protocol<
		>domain<news.google.com>/domain<
		>port<80>/port<			
	>/connectionPolicy<
	>loadConstraints maxConcurrentConnectionsPerNode="2" /<
>/connectivity<
>procedure name="getNews"/<

您传递位置信息并获取针对这个具体主题的新闻 RSS 频道。RSS 频道包含项数组,为了使数据简明扼要,我们仅挑选了数组中的第一个项。我们也可以使用 XSL 过滤您真正需要的内容。

适配器链中的下一个链接 StockAdapter 在后期处理中调用。接下来将会讨论执行调用的方法。两部分数据都聚合到 newsData 对象中(清单 14)。

清单 14. NewsAdapter 实现
function getNews(interest, stock)
{
	var input =
	{
		method : "get",
		returnedContentType : "html",
		path : "news",
		parameters :
		{
			"q" : interest,
			"output" : "rss"
		}
	};
	var newsData = WL.Server.invokeHttp(input).rss.channel.item[0];
	var stockData = WL.Server.invokeProcedure(
	{
		adapter : 'StockAdapter',
		procedure : 'getStock',
		parameters : [ stock ]
	});
	newsData["stock"] = stockData.text;
	return newsData;
}

简言之,NewsAdapter 从您将处理的两个资源返回一个 mashup,并在客户端中显示它。

调用适配器

您已完成了适配器的服务器端实现。现在,您可能希望从客户端调用它们。首先,需要使用一个 UI 元素来存储检索的位置数据。将一个基本的 ComboBox 插入到 Mashup.html 文件中(清单 15)。

清单 15. 为位置添加一个 ComboBox
>body onload="WL.Client.init({})" id="content" style="display: none"<
>div data-dojo-type="dojox.mobile.View" id="view0"
	data-dojo-props="selected:true"<
	Available Locations >select id="availableLocations"
		onchange="javascript:changeLocation();"<
		>option value="" disabled="disabled"<Select a location>/option<
	>/select<
	>div data-dojo-type="custom.LocationViewer" id="locationViewer"<>/div<
>/div<

将实际的实现(它调用适配器)放在 js/Mashup.js 中。此文件是自动生成的,是应用程序的主要 JavaScript 文件。它包含将在 Worklight 框架初始化完成后的应用程序启动期间调用的 wlCommonInit() 函数。它是添加应用程序的初始化代码的最佳位置。此函数也用在特定于环境的 JavaScript 函数中,以便得到一个通用的初始化起点。

清单 16. 初始化代码
var busyIndicator = null;
var adapterData = new Array();

function wlCommonInit()
{
	busyIndicator = new WL.BusyIndicator("view0");
	getLocations();
}

创建一个 busyIndicator,它为用户提供一个指示潜在的长期操作正在进行的反馈。getLocations() 方法是向 ComboBox 填充可用的位置的第一个适配器调用(清单 17)。

清单 17. 调用 LocationAdapter
function getLocations()
{
  busyIndicator.show();
  var invocationData =
  {
    adapter    : "LocationAdapter",
    procedure  : "getLocations",
    parameters : []
  };
  WL.Client.invokeProcedure(invocationData,
  {
    onSuccess : function(response)
    {
      busyIndicator.hide();
      if (response.invocationResult.resultSet.length != 0)
      {
        var locationList = response.invocationResult.resultSet;
        var availableLocations = document.getElementById("availableLocations");
        availableLocations.length[1] = null;
        for ( var i = 0; i < locationList.length; i++)
        {
          newLocation = new Option(locationList[i].CITY, locationList[i].CITY + ", "
 + locationList[i].COUNTRY + ":" +
                                  locationList[i].STOCK, false, true);
          availableLocations[i + 1] = newLocation;
        }
        availableLocations[0].selected = "selected";
      }
      else
      {
        WL.SimpleDialog.show("Mashup", "Adapter can't load locations. Check your 
database connection", [
        {
          text : "Reload app (via WL.Client.reloadApp)",
          handler : WL.Client.reloadApp
        } ]);
      }
    },
    onFailure : function()
    {
      busyIndicator.hide();
      WL.SimpleDialog.show("Mashup", "Adapter can't load locations. Check your 
database connection", [
      {
        text    : "Reload app (via WL.Client.reloadApp)",
        handler : WL.Client.reloadApp
      } ]);
    }
  });
}

要调用适配器过程,则需要一个 invocationData 对象(您向该对象传递要调用的适配器和过程的名称)和一个可选参数的数组。成功完成异步调用后,调用 onSuccess 函数作为包含响应对象的回调。如果适配器调用失败,则调用 onFailure 函数。如果所有设置都正确,那么此初始化代码将会填入您的 ComboBox 中(参见图 22)。

图 22. 通过适配器检索的可用的位置
图 22. 通过适配器检索的可用的位置

当选择一个位置时,会调用 changeLocation() 函数。这里,您维护适配器数据的本地缓存。如果尚未获取与位置相关的信息,可调用 getNews() 来检索该数据。否则,可使用缓存的数据并在合适的地图位置显示它(清单 18)。

清单 18. 更改位置
function changeLocation()
{
	var value = document.getElementById("availableLocations").value;
	var newLocation = value.split(":")[0];
	var newStock = value.split(":")[1];
	// Lazily load the Stock and News information
	if (adapterData[newLocation] == undefined)
	{
		busyIndicator.show();
		WL.Logger.debug("Load adapterData for " + newLocation);
		getNews(newLocation, newStock);
	}
	else
	{
		dijit.byId("locationViewer").refreshMap(newLocation,
				adapterData[newLocation]);
		busyIndicator.hide();
	}
}

如果有必要,此函数会从 NewsAdapter 调用 getNews 过程,在地图上使用前面讨论的相同调用模式来显示它的返回值(清单 19)。

清单 19. 调用 NewsAdapter
function getNews(location, stock)
{
  var invocationData =
  {
    adapter    : 'NewsAdapter',
    procedure  : 'getNews',
    parameters : [ location, stock ]
  };
  WL.Client.invokeProcedure(invocationData,
  {
    onSuccess : function(response)
    {
      var stockData = response.invocationResult.stock.split(',');
      var news = ">b>" + location + ":>/b>>br>" +
          stockData[0].substring(2, stockData[0].length - 1) + ": " +
         stockData[1] + ">/b>" + ">font color=" +
         (stockData[2] > 0 ? "green>" : "red>" ) + " (Change: " +
         stockData[3].substring(2, stockData[3].length - 1) + ")>br>>a href=" +
         response.invocationResult.link + ">" +
         response.invocationResult.title + ">/a>";
      adapterData[location] = news;
      dijit.byId("locationViewer").refreshMap(location, news);
      busyIndicator.hide();
    },
    onFailure : function()
    {
      busyIndicator.hide();
      WL.SimpleDialog.show("Mashup",
                           "NewsAdapter can't load content for location " + location,
                 [{
                     text : "OK"
                 }]);
    }
  });
}

图 23 显示了在尝试一些位置时发生的情形。

图 23. 通过适配器检索的可用位置
图 23. 通过适配器检索的可用位置

添加 Worklight 环境

“简介” 中已经介绍,Worklight 提供了一个开发环境和一个运行时环境来支持针对特定目标的应用程序构造,并使用了一个称为环境的概念。与移动 Web 应用程序类似,在您添加一个支持的环境时,Worklight Studio 会创建特定于目标的工件。

例如,要添加一个 iPhone 环境,可以右键单击 apps/Mashup 并选择 New > Worklight Environment。在 New Worklight Environment 向导中选择 iPhone(参见图 24)。

图 24. 添加 Worklight 环境
图 24. 添加 Worklight 环境

Worklight Studio 现在已创建了在 iPhone 设备上运行刚刚创建的应用程序所需的所有工件。一个名为 iphone 的新文件夹已自动添加到项目中,仅在您构建应用程序并将其部署到 Worklight Server 上后显示。

构建和部署应用程序,刷新 Worklight Console 就会在移动浏览器模拟器中看到一个启动应用程序的新图标,可在模拟器中选择 Apple 的 iPhone4 和 Samsung 的 Galaxy Ace(参见图 25):

图 25. 在移动浏览器模拟器中启动应用程序
图 25. 在移动浏览器模拟器中启动应用程序

结束语

本文介绍了如何开发一个混合移动应用程序,该应用程序在客户端和服务器端上使用了 mashup 技术。您创建了一个自定义 Dijit 小部件,并使用它作为应用程序的重要部分,还开发并链接了多个可从各种来源检索后端数据的适配器。


下载

描述名字大小
代码样例Mashup-160812.zip6.8 MB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere
ArticleID=856900
ArticleTitle=使用 IBM Worklight 开发客户端和服务器 mashup 移动应用程序
publish-date=01312013