 | 级别: 初级 Roland Barcia (barcia@us.ibm.com), 资深技术人员, IBM Steve Ims ( steveims@us.ibm.com), 资深技术人员, IBM
2007 年 10 月 29 日 在 Project Zero 系列的第 1 部分中,获得 Project Zero 的动手指导以便创建、组装和部署功能强大的 Web 应用程序。首先,您将接触到受社区驱动的 Project Zero 的简单介绍以及它对创建 RESTful Web 服务的一些约定。之后,通过一个逐步示例,向您介绍如何设置环境、创建 Zero 项目、构建 RESTful 服务来公开数据、测试应用程序并导入示例应用程序以便使用此 RESTful 服务。
引言
Project Zero 是 IBM
的一个孵化器项目,侧重遵从面向服务的体系结构( Service-Oriented Architecture,SOA)的 Web 2.0 应用程序的敏捷开发。将 Web 2.0 应用于 SOA,可以利用 Web 工件来扩展 SOA 的能力范围。可以将其看成是经过 Web 扩展的 SOA。
 | |
Project Zero 引入了一种简单的环境来基于流行的 Web 技术创建、组装和执行应用程序。Project Zero 环境包括面向 Groovy 和 PHP 的脚本运行时,且具有应用程序编程接口,可加以优化用来生成 Representational State Transfer (REST) 风格的服务、集成 mashup 和富 Web 接口。Project Zero 是一个孵化器项目,发起于 IBM® 内部,侧重于下一代动态 Web 应用程序的敏捷开发。
Project Zero 现在已经可以通过社区驱动的商业开发进行公开开发。
|
|
Web 扩展的 SOA 是 SOA 的一个子集,侧重于超文本传输协议(Hypertext Transfer Protocol,HTTP)和基本的 RESTful 原理。Web 扩展的 SOA 倡导设计模式的使用,设计模式曾让 Web 大获成功,所以在这里也很有意义,应用程序设计也遵从这种模式对大家都有好处。好处包括:
- 可伸缩性:通过 缓存 和无状态交互
- 简单性:其前提条件要求必须是典型的 HTTP 和 XML 或 JavaScript Object Notation(JSON)
解析和呈现
- 源于共同标准的广泛 “网络效应”:比如,站点可以使用和聚合 Atom 或 RSS 提要,而无需知道关于内容的更多细节
Project Zero 的目标既有技术方面的也有社会方面的。技术方面的目标是提供能在三个重要层面简化应用程序开发的可伸缩平台:
-
创建
-
利用对脚本语言(当前为
Groovy 和 PHP)的支持、能促使 RESTful 模式的约定以及可重用资源的编目简化开发。
-
组装
- 允许快速访问并将全异的服务集成到统一的应用程序,包括数据流、编排和自定义中介。
-
部署
- 基于稳定的 Java™ Virtual Machine (JVM) 提供一种以应用程序为中心的运行时环境,JVM 被优化以便进行敏捷开发(占用内存少,重启迅速)。
社会方面的目标与开发过程本身相关。Zero 将会作为社区驱动的商业开发(CD/CD)进入公开领域。用户社区将能参与和影响有关 Zero 的技术决策。用户能够直接接触到 Zero 开发团队和源代码本身。这种程度的商业软件开发透明性在 IBM 还不多见;我们期望它能够带来高效的产出。
为了与 CD/CD 一致,所有有关 Zero 的信息都可以在 www.projectzero.org 找到。本系列文章旨在对底层概念进行结构性介绍,并不揭示有关 Zero 的最新信息。
RESTful 服务
术语
REST
描述了可用来实现联网系统的一种设计模型。REST 既不是一种技术也不是一种标准;它是用来通过 Web 公开资源的一种架构风格。RESTful 架构遵从以下几个原则:
 | | Representational State Transfer(REST)是面向分布式超媒体系统(例如 World Wide Web)的软件架构风格。 |
|
REST 是表示 Web 中的服务的关键技术。REST 在公开基于数据的服务方面效果最好,理解这一点非常重要。这些数据服务进而可以混合和匹配以便构建新的应用程序(通常称为 mashup)。如下的示例展示了客户机是如何对待 RESTful 服务的。
用 http://<host>/customer:
用 http://<host>/customer/roland:
- GET:返回 Roland 客户记录
- PUT:更新 Roland 记录
- DELETE:删除 Roland 记录
在本例中,所公开的资源是 Customer。此 Customer 用 URI /customer 表示。特定的客户则通过在 Customer 之后追加标识符加以表示,例如 /customer/ims。HTTP 报头方法定义了访问此资源的意图。
Project Zero 的核心编程概念
Project Zero 是一种新的、专用平台的基础,该平台将 Web 2.0 和 SOA 结合在一起以支持下一代基于 Web 的业务应用程序。本节涵盖了其中一些核心的概念。Project Zero Developer's Guide(参见 参考资料)提供了有关此概念的很多细节。
脚本编写和以 Java 作为系统语言
简化开发的一个重要趋势是脚本语言。Zero 的目标是通过提供围绕脚本的简化应用程序编程接口(API)来帮助减少开发服务的负荷。默认的脚本语言是 Groovy,它基于的是 Java 并让 Java 程序员能够轻松转换到 Groovy。通过扩展模块,Project Zero 也支持将
PHP 作为一种脚本语言。
事件
Zero 是一种基于事件的系统。该系统的所有关键行为都以事件集及合适的事件状态的形式公开给应用程序。作为应用程序开发人员,您的主要的工作是通过提供可钩挂到已知的系统事件的处理程序集来获得所需的应用程序行为。标准 Zero 事件对应用程序来说就是一些有意义的行为。
例如,此系统的一个 HTTP 请求可以发出一组事件,开发人员可针对这些事件编写处理程序。图 1 展示了这个概念,并且还包括了处理安全性方面问题的事件和特定的 HTTP 方法(GET | POST)事件。(有关事件处理的更多信息,请参阅
Project Zero Developer’s
Guide 中事件处理一节。
图 1. 事件
全局上下文
Project Zero 中的事件处理程序没有显式的输入和输出参数,例如请求和响应。由于 Project Zero 中的所有事件处理程序都不能跨不同的调用保持同一个变量状态,因而是无状态的。Project Zero 提供了全局上下文 来作为访问和维持所有状态的一种手段。全局上下文提供了有关应用程序当前事件的所有有趣数据,此外还包括了在应用程序的组件间存储和共享这些信息的机制。
全局上下文被划分成几个区,每个区都保存具有不同可见性和不同生命周期的数据,如 图 2 所示。例如,User 区保存的是给定客户的会话状态,只对该客户可见,而且会保持此数据直到用户变为不活动的。
Request 区保存的是当前活动请求的状态,只对为该请求而执行的组件可见,而且此状态只会保持到该请求结束。
图 2. 全局上下文
约定和应用程序目录结构
Zero 环境提供了几个约定,如能遵循,一定会极大地简化 Zero 应用程序开发和减少所需指定的配置信息。Zero 的目标是拥有尽可能少的配置信息。某些约定非常常见,而且也在意料之中。例如,可以将脚本放在应用程序的公共文件夹内并对应于没有配置数据的某个 HTTP 资源运行该脚本。
其他的约定则针对于特定的某个模式。图 3 显示的是将 Groovy 脚本存储在某个特定文件夹下的例子。这种方式会自动将这些方法注册在此文件夹中以响应 HTTP REST 事件。在本文稍后的内容中,还会介绍如何使用 RESTful
模式快速开发 RESTful 服务以公开数据。更多信息,请参阅
Project Zero Developer's Guide 的 Virtual Directory 一节。
图 3. 虚拟目录
示例场景
现在您就可以使用 Zero 平台构建自己的第一个 RESTful 服务了。让我们首先通过如下的示例先来全面地浏览一下其中的概念。
概览
贯穿本系列,将通过构建一个公共的示例来展示各种不同的概念。该示例的主题是折扣优惠。客户经常会竭力想让自己符合享受折扣优惠的条件,而提供商则需要让优惠折扣可用。图 4 所示的这个用例图表展示了各种行为。在本文中,我们将侧重于优惠提供者这一角色及其折扣优惠管理用例。
图 4. 用例
提供者需要拥有一种将优惠放入系统并进行维护的途径;一个优惠就是我们所感兴趣的一条主要数据。图 5 显示了此优惠应用程序的数据模型。
图 5. 数据模型
开始时只有单一一个表。在后续的文章中,我们将会扩展这个数据模型以加入各种关系并通过使用 REST 范型来探究更加复杂的数据。
设计 RESTful 端点
数据集大体准备完毕后,就可以开始将数据映射到 RESTful 名称空间。通常,为映射某资源而针对此特定条目创建一个表的做法将非常有用。表 1 给出了到我们的这个资源的 RESTful 映射。您将亲自构建此 RESTful 服务。
表 1. REST 数据
|
资源
|
URI
|
方法
|
表示
|
描述
| | Incentive 表 | /incentive | GET | JSON 对象数组 | 检索优惠列表 | | Incentive | /incentive | POST | JSON 对象 | 创建新优惠 | | Incentive | /incentive/<incentiveId> | GET | JSON 对象 | 检索某个优惠 | | Incentive | /incentive/<incentiveId> | PUT | JSON 对象 | 更新单个优惠 | | Incentive | /incentive/<incentiveId> | DELETE | | 删除单个优惠 |
这里选择 JSON 表示数据格式,这是因为我们将会使用富 Internet 应用程序(RIA)作为系统的前端。很多基于 Ajax 的工具箱都能很好地理解 JSON,原因是 JavaScript 是浏览器内面向 Ajax 的主要编程语言。有关 JSON 的更多信息,请参看 参考资料。
系统需求
要运行本文中的示例,需要如下内容:
其他有用的工具还有:
用 Project Zero 构建第一个 RESTful 服务
大致介绍了 RESTful 映射之后,现在就可以着手构建该示例了。
- 首先,创建一个 Zero 项目,如下所示。
- 从 Eclipse 主菜单,选择 New > Project,如图 6 所示。
图 6. 创建新项目
- 从 Project Wizard,选择 Zero > Project Zero Application。单击 Next,如图 7 所示。
图 7. Project Zero
应用程序
- 将项目命名为
RebateIncentiveServices,并单击 Finish。
图 8.
RebateIncentiveServices
- 下一步要做的是检查 Project Zero 目录结构和工具。
- 图 9 给出了 Zero 项目中的几个重要目录的概览。突出显示的文件夹是能在其中编写代码的几个地方。大多数这类目录都位于项目的 app 文件夹之下。在 Java 透视图的 Explorer 视图下,这些目录显示为 Java 源文件夹。
图 9. 应用程序目录结构
- Java
- Java 文件夹是提供可供应用程序使用的 Java 代码的地方。可以编写基于 Java 的处理程序并将这些程序放在这里。
- Public
- public 文件夹是放置您希望服务于浏览器的所有工件的地方。示例中包含了 HTML 文件、JavaScript 文件、Groovy 文件和可在浏览器执行的模板或 PHP
脚本。
- Resources
- /app/resources 文件夹包含为响应 RESTful 调用而被调用的脚本(Groovy 或
PHP)。它们对应于各种 HTTP 方法。此目录中的脚本会自动作为处理程序注册。在本例中将用到此目录。
- Errors
- /app/errors 文件夹包含脚本模板文件,比如可基于错误自动呈现的 Groovy 模板。
此文件的命名模式定义了所呈现的是哪个响应。例如,error5xx.gt 就是对应于服务器 500 错误而被调用的 Groovy 模板。
- Scripts
- /app/scripts 目录让您可以定义私有的 Groovy 脚本或类(与 Java 目录很像)。在这里,可以定义可由公共或资源 Groovy 脚本调用的 GroovyBeans 或方法。这种方式让您可以在构建应用程序时遵循 model-view-controller
(MVC)模式。
- Views
- 在 /app/views 目录中放置可对应于某些事件而呈现的模板文件。与 public 目录中的脚本(基于直接的 URL 访问而运行)不同,这些视图可对应于另一个脚本而执行。控制器脚本可访问某些后端逻辑,然后再将控制权转给视图脚本以加以呈现。
在 Project
Zero Developer’s Guide(请参见 参考资料)中可以获得有关应用程序目录结构的更多信息。
- 图 10 中突出显示了 Config 目录,它包含 Zero 应用程序所需的一些重要文件。
图 10. Config 目录
- Ivy.xml
- Zero 平台使用 Ivy
框架 维护依赖项。Zero 应用程序被打成多个包。利用 ivy.xml 可以定义所依赖于的是哪些包。Zero 包存储于 Ivy 存储库,Ivy 运行时则会分解这些包。
Ivy 是管理(记录、跟踪、分解和报告)项目依赖项的工具。其特征是:
更多有关 Ivy 的信息,请参阅 Apache incubator 或 Project Zero Developer's Guide 的 Dependency and
Packaging 节。
- logging.properties
- logging.properties 文件是配置属性的地方。Zero 使用 Java Development Kit (JDK) 进行日志记录(有关 Zero logging 的更多信息,请参看 Project Zero Developer's Guide 的 Logging 一节)。
- Zero.config
- zero.config 文件定义了 Zero 应用程序中的主要配置文件。在这里可以配置工件,比如处理程序、安全性、数据资源和所需要的其他任何配置。可以有不止一个 zero.config 文件。在本例中将使用 zero.config 定义某些数据库配置。由于本例使用的是 /app/resources
文件夹,所以无需显式地配置处理程序,但要依赖于 Zero 的预定义的处理程序进行 REST 调用。(有关配置的更多信息,请参阅 Project Zero Developer's Guide 中的 Configuration 一节。)
图 11 突出显示了 Eclipse 工具的一些方面。Zero 插件提供了可帮助管理依赖项的工具。借助它就能管理 Ivy 框架可用来下载和分解 Zero 应用程序依赖项的本地和远端存储库。分解依赖项就意味着向 Zero 应用程序中添加包。
图 11. 依赖项管理
- Manage repositores
- 提供了查看本地存储库和从远端存储库下载包的工具,如图 12 所示。
图 12. 管理存储库
- Resolve 图标
- resolve 图标会在工作区分解来自本地和配置了的远端存储库的依赖项。
- Resolve 菜单项
- resolve 菜单项会分解来自本地存储库的依赖项。
- 为了构建 RESTful 服务,需要在数据库中创建一个表。此外,为本例还提供了一个动态链接库(DLL)。在这个练习中使用的是 Derby 的网络版(还记得吧,我们也已经安装了针对 Eclipse 的 Derby
插件)。
- 右键单击 RebateIncentiveServices 并选择 Import,如图 13 所示。
图 13. 导入工件
-
图 14. 文件系统
- 若已经将 示例代码 zip 文件解压缩到 C 盘中,C:\ProjectZeroArticleSeries\Part1 就应该已经存在于 From
directory 字段。Into folder 字段应该为 RebateIncentiveServices。选中 dbscripts 文件夹,如图 15 所示,然后单击 Finish。
图 15. DB 脚本
- 在项目中应该能够看到一个文件夹,名为 dbscripts。在这个文件夹中,打开 Incentives.sql。此脚本创建了优惠 表并在该表中插入了两条记录。
图 16. Incentives.sql
此脚本如清单 1 所示。
清单 1. 数据库脚本
CONNECT 'jdbc:derby://localhost:1527/INCENTIVEDB;create=true;';
drop table Incentive;
create table Incentive (
incentiveid INTEGER NOT NULL
GENERATED ALWAYS
AS IDENTITY,
name VARCHAR
(256)
NOT NULL,
description VARCHAR
(256)
NOT NULL,
providername VARCHAR
(50)
NOT NULL,
incentivetype VARCHAR(128),
validfrom TIMESTAMP,
validto TIMESTAMP,
website VARCHAR(256)
);
alter table Incentive
ADD CONSTRAINT Incentive_PK
PRIMARY KEY (incentiveid);
INSERT INTO Incentive (name,description, providername,incentivetype,validfrom,validto,website)
values (
'Key Account Energy Efficiency Grant Program',
'Alameda P&T offers its commercial customers grants of up to $25,000
for energy efficient building design and equipment in new commercial
construction, or other approved unique energy efficient projects.',
'Alameda Power and Telecom','Energy',
TIMESTAMP('2007-06-22 00:00:00.000'),
TIMESTAMP('2007-10-30 00:00:00.000'),
'http://www.alamedapt.com/electricity/com_savings.html' );
INSERT INTO Incentive (name,description, providername,incentivetype,validfrom,validto,website)
values (
'Power Management for Personal Computer (PC) Networks',
'Avista Utilities Power Management for Personal Computer (PC)
Networks offers incentives to commercial customers.',
'Avista Utilities','Energy',
TIMESTAMP('2007-09-01 00:00:00.000'),
TIMESTAMP('2008-06-30 00:00:00.000'),
'http://www.avistautilities.com/saving/conservation/power_management.asp' );
|
- 若已经安装了针对 Eclipse 的 Apache Derby 插件,右键单击此项目,如图 17 所示,并选择 Apache > Add Apache Derby
nature。
此外,也可以使用命令行启动 Derby 并手动执行 ij。
图 17. Apache Derby 插件
- 再次右键单击此项目并选择 Apache Derby > Start
Derby Network Server,如图 18 所示。
图 18. 启动 Derby 网络服务器
- 检查控制台以确保数据库启动并正在运行。
图 19. 运行 Derby 数据库
- 右键单击 Incentive.sql 脚本并选择 Apache Derby > Run
sql script running 'ij'。(这是运行 Derby
数据库脚本的命令行工具。)
图 20. 使用 ij 运行 SQL 脚本
- 检查控制台以确保表已创建且记录已经插入。
图 21. 数据库脚本结果
- 之前,您曾经见过 ivy.xml 文件。在创建 Zero 项目时,就得到了核心的 Zero 包和 Webtools 包。核心包中包含必需的库以创建事件处理程序和 RESTful 服务及与全局上下文和安全性进行交互。Web Tools 项目则包含一组有用的工具,比如默认索引页面、请求检查器、错误发现工具等等。更多信息,可以在 Project Zero Developer's Guide 的 Developer Web tools 一节中找到(参见 参考资料)。
在本步骤中,可以向应用程序中添加两个包。数据访问库和 Derby 客户机 Java Archive (JAR) 文件都需要与此数据库相联系。
- 打开 ivy.xml 文件,如图 22 所示。
图 22. ivy.xml
- 本步骤操作会打开 ivy.xml 编辑器。单击 Add,如图 23 所示。
图 23. 添加 Zero 包
- 找到 zero:zero.data 包,如图 24 所示。确保过滤器设为 Latest minor version 并单击 OK。
图 24. zero.data 包
- 重复之前的步骤并添加 org.apache.derby:derbyclient。 本步操作会添加连接到 Derby 所需的 Java Database Connectivity (JDBC) 客户机 JAR。
图 25. Derby 客户机
- 选择 Update 依赖项图标。该工具会读取 ivy.xml 并查找远端和本地存储库的最新版本(在本例的稍后部分,还会使用 Resolve 选项。)
图 26. 更新依赖项
- 单击 OK 以允许该工具查找远端版本。
图 27. 选择要运行的依赖项
- 应用程序具有了所需的库之后,就需要设置运行时的数据库配置。zero.config 文件使用的是一种 INI 风格的配置。config/zero.config 文件的内容被组织成多个相关键/值对 “节(stanza)”。
这些节与指令相关,比如 “存储到全局上下文” 和 “包括另一个配置文件”。虽然 Project Zero 的目标之一就是最小化运行应用程序所需的配置,但某些配置还是必需的。
- 展开 Config 文件夹并打开 zero.config 文件,如图 28 所示。
图 28. zero.config
- 图 29 显示的是需要添加的那个节。
图 29. 配置数据库
注意到这个节是以一个指令开始的。在本例中,我们指向了一个位置,在这个位置中我们衡量所要存储的键/值对。这个测试如清单 2 所示。也可以将其从 C:\ProjectZeroArticleSeries\Part1\dbconfig.txt 粘贴过来。
清单 2. 数据库配置
[/app/db/incentiveDB/config]
class=org.apache.derby.jdbc.ClientDataSource
serverName=localhost
portNumber=1527
databaseName=INCENTIVEDB
connectionAttributes=create=true
|
Project Zero 数据库在访问时会读取此配置。注意 图 29 中突出显示的 /app/db 后的 incentiveDB 名称。这也是此代码中使用的名称。稍后,在构建 RESTful 服务时还会看到。

 |

|
创建 RESTful 服务以公开数据
至此,就可以构建 RESTful 服务来公开 incentive 数据了。在 Project Zero 中构建服务有多个方法。一个目标是将对自动公开 Groovy 脚本的支持应用到 RESTful 服务。
需要使用针对 Project Zero 的 Eclipse 插件。请记住,这些约定在 Eclipse 之外、使用 Zero 的命令行版本时也可用。您很可能会发现使用 Project Zero 可以快速创建基于 REST 的服务。
创建 Groovy 脚本
Groovy 脚本会匹配您希望调用 REST 服务所用的那个 URL 的名称。对于
/resources/incentive,所需的文件名为 incentive.groovy。
- 右键单击文件夹 /app/resources 并选择 New > File,如图 30 所示。
图 30. 新建文件
- 将文件命名为
incentive.groovy。
图 31. incentive.groovy
- 如果系统提示要向项目中添加 Groovy 支持,单击 Yes。
图 32. Groovy 支持
管理优惠
要管理优惠,就是要能够列出所有的优惠;获得各个优惠;创建、更新和删除优惠。第一个任务就是创建列出优惠的功能。客户机能够调用 /resources/incentive 以查看优惠列表。
- 打开刚刚创建的 incentive.groovy 文件。
图 33. Incentive.groovy
- 如果直接加入脚本代码,就会创建对应于任一 HTTP 方法的脚本。但我们想要的是基于 HTTP 方法有不同的动作。可以定义几个特殊的能自动响应 HTTP
方法的处理程序方法。如下所示的表总结了这些回调。
|
URI
|
HTTP 方法
|
Groovy 方法
|
描述
| | /incentive | GET | onList() | 返回优惠列表 | | /incentive | POST | onCreate() | 创建新的优惠并返回资源位置 | | /incentive | PUT | onPutCollection() | 更新优惠集 | | /incentive | DELETE | onDeleteCollection() | 删除优惠集 | | /incentive/<incentiveId> | GET | onRetrieve() | 根据 ID 检索优惠 | | /incentive/<incentiveId> | PUT | onUpdate() | 更细单个优惠 | | /incentive/<incentiveId> | DELETE | onDelete() | 删除单个优惠 | | /incentive/<incentiveId> | POST | onPostMember() | 向所选定的位置发布单个成员 |
在本文中,将要构建第 1、第 2、第 5、第 6 和第 7 个回调。
- 要构建和测试这个列表用例,需要添加清单 3 所示的代码片段。或者,也可以从 C:\ProjectZeroArticleSeries\Part1\list.txt 粘贴。
清单 3. 优惠列表
import zero.data.groovy.Manager;
def onList()
{
def dm = Manager.create("incentiveDB");
request.json.output = dm.queryList("select * from incentive");
request.view="JSON";
render();
}
|
此段代码:
- 使用了 Project Zero 的数据 API 的
Manager 对象。Manager 对象提供了一些很方便的方法来执行 SQL 查询。有两种版本可用:Java 版和 Groovy 版。虽然 Java 版在 Groovy 也可以使用,但 Groovy 版则更充分利用了 Groovy 的捷径,比如将 GStrings 作为输出传递、闭包的使用等等。当发出 create 时,我们会传递进在配置 /app/db/incentiveDB/config 中使用的字符串。
- 从
Manager 选择完整列表并将其赋值给 request.json.output。这看起来像是我们在将结果赋值给常规 Groovy 变量,但事实远远不止于此。还记得么,所有状态都在全局上下文中保持。Project Zero 提供了一个 Java API,用来从 GlobalContext 存储和检索数据。
例如,代码 String data = GlobalContext.get("/request/myData");
|
会从全局上下文的 Request 区获得一个字符串。Project Zero groovy 脚本会进一步对全局上下文数据添加构建支持。它们已经为每个区(app、user、request 和 event)都预定义了 4 个变量。所以在 Groovy,如下代码:
与
GlobalContext.put("/request/myData","Hello);
|
是相同的。
利用 Groovy 这些捷径将减少所需编写的代码量。在本例中,我们会将列表存储进 /request/json/output 下的 request 区。更多有关全局上下文的信息,请参阅 Project Zero Developer's Guide(参考资料)。
在 GlobalContext 内存储结果之后,会用到另一个名为 ViewEngine 的对象。Project Zero 支持以不同的方式呈现结果上下文。可以显式地写输出流(直接呈现)。应用程序代码和事件处理程序可使用 OutputStream
(/request/outputstream)和
PrintWriter
(/request/writer)对象直接向响应条目中写。
如下所示的是一个 Java 示例:
// Content of a Java class
PrintWriter writer = (PrintWriter) GlobalContext.get("/request/writer");
writer.println("Hello World."); |
针对所有 Groovy 脚本(包括模板)的默认输出是响应编写器,这就让应用程序开发人员能够利用 println 方法直接向该响应写。有关呈现的更多信息,请参阅 Project Zero Developer's Guide 的 Rendering
Results 一节。
Project Zero 提供了呈现器库以处理共同响应。调用呈现器总的来说会涉及到如下步骤:
- 将全局上下文中
/request/view 的值设置为对应的呈现器。
- 将其他一些特定于呈现器的值设置到
GlobalContext 内。
- 调用
zero.core.views.ViewEngine.render() |
在 Groovy REST Handler 内,单独调用 render() 会导致调用 View Engine 呈现器。
zero.core 包括 View、Error、XML 和 JSON
呈现器。也可以添加 Atom 包,其中包含了 Atom
呈现器。
此示例使用了 JSON 处理程序。另一个可选项是使用 Groovy 模板。可以将 Groovy 模板存储于 /app/views 文件夹下并将 request.view 设为此模板的名称。
正如您所见,这些约定极大地降低了所需编写的代码量。
通过启动应用程序进行测试
通过启动应用程序可以很容易地测试 onList。
- 右键单击该项目并选择 Run as > Project Zero
Application,如图 34 所示。
图 34. 运行 Project Zero
应用程序
- 检查此控制台。应用程序应该已经启动且正在端口
8080 侦听。
图 35. 在端口 8080 上运行的应用程序
- 启动浏览器并指向
http://localhost:8080,这应该会开启默认的 zero 页面。(这就是之前提到的 Web Tools 库中的工具之一。)将会看到默认的索引页面。如果您添加了自己的
index.html 文件,该文件就会优先于默认的文件。
图 36. 默认主页
- 通过选择 View > SideBar > Poster 启动 Firefox Poster 工具。
图 37. 运行 Poster 插件
- 为此 URL 输入
http://localhost:8080resources/incentive,并单击 GET。
图 38. GET 优惠
- 带有响应的有效负载的 Response 窗口应该会弹出,如下所示。 有效负载的格式应该是一个 JSON 数组,其中每一项都是一个 JSON 对象。
图 39. JSON 结果
- 也可以选择测试 XML 呈现的效果。将视图类型更改为 XML,如清单 4 所示。
清单 4. XML 结果
def onList()
{
def dm = Manager.create("incentiveDB");
request.xml.output =
dm.queryList("select * from incentive");
request.view="XML";
render();
}
|
- 从 Poster 插件再次单击 GET 以查看 XML 中的结果,如图 40 所示。
图 40. XML 结果
请确保将代码更改回 JSON。
访问服务
接下来,将会创建 onRetrieve 方法以通过传递其标识符来访问特定的 RESTful 服务。
- 在 incentive.groovy 文件内,输入
onRetrieve 方法和如图 41 或清单 5 所示的代码。也可以将其从 C:\ProjectZeroArticleSeries\Part1\retrieve.txt 粘贴过来。
图 41. 检索优惠
清单 5. 检索单一优惠
def onRetrieve()
{
def dm = Manager.create("incentiveDB");
request.json.output = dm.queryFirst(
"select * from incentive where incentiveid =
${request.params.incentiveId[0].get()}");
request.view="JSON";
render();
}
|
与利用 onList 方法所做的类似,现在将会创建 Manager 的一个实例并执行查询。本例有如下几个要点:
- 该方法通过执行如下 URI 得以执行: /resources/incentive/1。1 是所需优惠的标识符。将此传递进
where 子句,需要从此 URI 对其进行访问。Zero 会用路径信息自动填充 GlobalContext。例如,request.params.incentiveId[0] 会返回 1。如下的表给出了如何访问参数的几个例子。
|
URI
|
参数
| | /incentive/1 | request.params.incentiveId[0] == 1 | | /incentive/1/provider?location=NJ | request.params.location[0] == NJ |
- 查询内用到了对 Groovy
GStrings 的内置支持。GString 让您可以使用表达式语言在字符串字母内引用变量,所以如下所示的查询字符串 literal:
select * from incentive where incentiveid = ${request.params.incentiveId[0].get()}
|
就能够直接访问 GlobalContext。此外,数据访问库中也有一些 API 可以让您能够传递进 Plain Old Java Objects (POJO) 并使用 :property 风格的输入,此外还有对 ? 类型参数的支持。更多信息,请参阅 Project Zero Developer's Guide(参考资料)的 Project Zero and the
Data Access 一节中的数据部分的 JavaDocs 。
-
保存文件。回到 Firefox Poster 工具,输入 URL
http://localhost:8080/resources/incentive/1
并单击 GET,如图 42 所示。结果应该是一个 JSON 对象,只有单一一条记录。
图 42. 单一优惠的 JSON 结果
向示例中添加
现在,可以添加 onCreate()、onUpdate() 和 onDelete() 方法。
- 将清单 6 中的代码输入到 incentive.groovy 或从 C:\ProjectZeroArticleSeries\Part1\updateMethods.txt 中粘贴过来。
清单 6. 创建、更新和删除优惠
def onCreate()
{
def dm = Manager.create("incentiveDB");
def incentive = zero.json.JsonType.fromData(request.input.get()).getJson();
def newKey = dm.insert("insert into incentive
(name,description,providername,incentivetype,validfrom,validto,website)
values(${incentive.name},${incentive.description},${incentive.providername},
${incentive.incentivetype},${incentive.validfrom},${incentive.validto},
${incentive.website})",['incentiveid']);
locationUri = zero.core.utils.URIUtils.getAbsoluteUri(request.path) + '/' + newKey
request.headers.out.Location = locationUri
request.status = HttpURLConnection.HTTP_NO_CONTENT
}
def onUpdate()
{
def dm = Manager.create("incentiveDB");
def incentive = zero.json.JsonType.fromData(request.input.get()).getJson();
dm.update("update incentive set name =
${incentive.name},description = $incentive.description ,providername =
$incentive.providername,incentivetype=$incentive.incentivetype,validfrom=
$incentive.validfrom,validto=$incentive.validto,
website=$incentive.website where
incentiveid=${request.params.incentiveId[0].get()}");
request.status = HttpURLConnection.HTTP_NO_CONTENT
}
def onDelete()
{
def dm = Manager.create("incentiveDB");
def newKey = dm.update("delete from incentive where incentiveid=
${request.params.incentiveId[0].get()}");
request.status = HttpURLConnection.HTTP_NO_CONTENT;
}
|
在这段代码中:
- 数据访问 API 具有一些可用来执行更新的方法。
onDelete 和
onUpdate 使用的是 generic 更新方法,这就让您可以传递进任何的数据库 Insert、Update 和 Delete 语句。onCreate() 也使用了一种特殊定义的可返回主键的插入方法。这在由数据库生成主键的情况下非常有用。
-
onCreate 方法会设置新创建的资源的 URL。这是 REST 模式所要求的。
- 所有更新都会将返回类型设置为
no Content。
- 对特定的
zero.json.JsonType.fromData 的使用会将消息主体转变成 JSON 对象的 Java 表示。
- 使用 Poster,输入
http://localhost:8080/resources/incentive。
在 Content to Send 内,添加清单 7 中的 JSON 对象(也可以从 C:\ProjectZeroArticleSeries\Part1\insertUpdatePayload.txt 粘贴过来)。
清单 7. 创建 JSON 有效负载
{"providername":"Hawaii Gas Company ",
"website":"http://www.hawaiigas.com/rebate/commercial.html",
"description":"Existing Commercial Utility Gas customers of The Gas Company
may apply for a rebate for new gas equipment..",
"name":"Commercial Switch to Gas Program",
"incentivetype":"Energy","validto":"2007-09-01 13:01:23.123456",
"validfrom":"2008-06-30 13:01:23.123456"}
|
- 选择 POST,如图 43 所示。
图 43. POST 优惠
- 响应应该是 204,位置则应设置为新的资源:http://localhost:8080/resources/incentive/3,如图 44 所示。
图 44. POST 结果
- 选择此响应并回到 Poster 工具。输入新 URL
http://localhost:8080/resources/incentive/3,然后单击 GET 以验证此项已创建。
图 45. 用 GET 创建的优惠
- 所得到的响应应该与所创建的记录匹配。
图 46. GET 新优惠
- 从 C:\ProjectZeroArticleSeries\Part1\insertUpdatePayload.txt 重新粘贴此有效负载。
- 将
incentiveType 更改为 Gas,如清单 8 中的 JSON
对象所示。
清单 8. 更新 JSON 有效负载
{"providername":"Hawaii Gas Company ",
"website":"http:\/\/www.hawaiigas.com\/rebate\/commercial.html",
"description":"Existing Commercial Utility Gas customers of The Gas
Company may apply for a rebate for new gas equipment..",
"name":"Commercial Switch to Gas Program",
"incentivetype":"Gas",
"validto":"2007-09-01 13:01:23.123456",
"validfrom":"2007-09-01 13:01:23.123456"}
|
- 输入资源的 URL
http://localhost:8080/resources/incentive/3 和有效负载,然后单击 PUT。
图 47. PUT 优惠
- 应该得到一个 204 响应。
图 48. 204 结果
- 在此资源上发起另一个 GET 以确保更新成功。
图 49. 删除优惠
- 最后,在 URL
http://localhost:8080/resources/incentive/3 上,单击 Delete。应该得到一个 204 响应,如下所示。
图 50. Delete 的 204 结果
- 发起一个 GET。得到的结果应该为空。(本系列后续文章将会将错误处理添加到代码中。)

 |

|
导入一个示例 RIA 客户机
RESTful 服务已就绪,现在就可以导入一个能使用 RESTful 服务的示例 RIA。本文无意过多介绍该客户机,但会对此导入的客户机以及 REST 为何是 mashup 应用程序的基础之一做简要说明。
 | | 本系列的后续文章会论及 Zero 中的 UI 支持。 |
|
目前,Project Zero 包含 Dojo Toolkit 4.3。对于本例,我们会使用基于 Dojo 的应用程序。您将使用基本的、带某些导入的帮助程序的 Dojo 支持。
要向此项目中添加 Dojo 包:
- 正如之前所做的,打开 Config 文件夹下的 ivy.xml 并向应用程序中添加 dojo:dojo(0.4.3)。
图 51. 添加 Dojo 包
- 右键单击此项目并选择 Zero Tools Resolve。这会让 Dojo 从本地存储库中分解出来。
也可以使用 Update
Dependency 图标来从远端存储库进行加载,正如之前所做的那样。
图 52. 分解 Dojo
- 从控制台视图终止此应用程序,方法是单击 Stop,如图
53 所示。与代码更改不同,当添加新库或更新 zero.config 时,必须要重启此应用程序。
图 53. 终止应用程序
在此处,可以导入或检查此代码:
-
右键单击项目的 public 文件夹并选择 Import。
图 54. 导入 UI 代码
- 在 From 目录,输入
C:\ProjectZeroArticleSeries\Part1\webapp。
选中 webApp 框并确保 Into 文件夹为 public,如图 55 所示。
图 55. Import 向导
让我们简要地查看一下此代码:
- 打开 index.html,如图 56 所示。
图 56. 查看索引
- 我们已经将此优惠客户机作为小部件构建完毕。将 RESTful 客户机作为 Dojo 小部件构建对于提供模块式的客户机至关重要,这类客户机可以在一定的基础上糅合在一起以便集成。图 57 表明了优惠 小部件为何是页面上的单一声明。
此示例还有另外一部分
JavaScript 以模拟另外一个小部件。该小部件会订阅优惠小部件并显示该 RESTful 事件。
图 57. 两个小部件
- 在 zero.incentive.widget 文件夹下,会找到构建 Dojo 小部件所需的代码。一个 Dojo 小部件主要由 HTML 模板、CSS 文件和 JavaScript 函数构成。就如何构建 Dojo 小部件完全可以单独撰写一篇文章。更多有关 Dojo 工具箱和小部件的信息,请参见 参考资料。图 58 突出显示了这些小部件文件。
图 58. Dojo 小部件代码
- 清单 9 突出显示了 IncentiveWidget.js 文件的一部分,尤其突出了 Dojo 小部件是如何调用 RESTful 服务的。此处提供了示例 JSONRestService JavaScript 类,稍候会详细加以介绍。此示例展示了这个小部件是如何调用
listData 方法以调用该服务的。另外一个重要的方面是 Dojo 发布和订阅函数的使用以便发出有关 RESTful 服务何时被调用的事件。这也是相邻的小部件如何能发觉数据何时被访问的原因。
请务必记住,有些数据可能本身就是安全的。来自不可靠资源的纯 JavaScript mashup 有可能会十分危险。本系列后续文章会详细探究更多的安全模式。
清单 9. JSON rest 调用
postCreate: function(){
dojo.debug("postCreate, setting subscriptions");
//Load Widget
dojo.event.topic.subscribe(this.inputTopic, this, this.listIncentives);
//Get Incentives Events
dojo.event.topic.subscribe(this.getAllTopic, this, this.bindIncentives);
listIncentives:function (){
zero.rest.JsonRestService.listData(this.restUri,this.getAllTopic,this.errorListTopic);
},
bindIncentives:function(data){
dojo.debug("Binding to -> " + this.incentiveTable);
for(i = 0; i < data.length ; i++)
{
data[i].validto = dojo.date.fromIso8601 (data[i].validto);
data[i].validfrom = dojo.date.fromIso8601 (data[i].validfrom);
data[i].website = '<a href=\"' +
data[i].website + '\" target="_blank">' + data[i].website + '</a>';
}
this.incentiveTable.store.setData(data);
this.showIncentiveFormButton.show();
this.editIncentiveFormButton.show();
this.deleteIncentiveButton.show();
}
|
- 打开 zero.rest 文件夹下的 JsonRestService.js 文件,如图 59 所示。
在代码中,请注意针对我们所有 RESTful 事件的
callback 方法。我们使用 dojo.io.bind() 方法和 dojo publish/subscribe 来在事件完成时进行通知。该代码也可在
Developer's Guide 中找到。
图 59. JsonRestService.js
现在,若要运行此客户应用程序:
- 重启此应用程序。方法是使用 Eclipse 运行箭头,如下所示。
图 60. 重启应用程序
- 启动浏览器并指向
http://localhost:8080。注意到调用的是我们的索引页而非 Web Tools 索引页。incentive 小部件调用了列表数据,而相邻的小部件则显示此事件。
图 61. 所加载的应用程序
- 通过单击 Add New Incentive 添加新的优惠。
图 62. 添加新的优惠
- 输入一些数据(可以参照 JSON 有效负载,也可以查看图
63)。
图 63. Add new incentive 表单
- 单击 Submit。新创建的资源应该已经被添加到此表。
图 64. 更新优惠
- 突出显示新创建的资源并单击 Edit Selected Incentive。
选择此资源时,会执行 onRetrieve 方法以获得单个资源。
- 更新所需字段并单击 Submit。
在 Dojo 的 4.3 版,下拉数据小部件以显示表单数据的功能有一个小 bug。请访问 Dojo 站点以获得相应的更新,或直接填充这个小部件。
图 65. Dojo 4.3 中的 bug
- 最后,突出显示此资源并单击 Delete Selected Incentive。
图 66. 删除优惠
- 此列表应该已经更新,如图 67 所示。
图 67.优惠删除后
- 查看相邻的小部件,它会显示 REST 事件和有效负载。
。
图 68. Mashed-up 向导
祝贺您!您已经用 Project Zero 构建了自己的第一个 RESTful 服务!
结束语
本文向您简单介绍了 Project Zero,一种用来构建面向 Web 的应用程序或 Web 扩展的 SOA 的新平台。您了解了 Project Zero 的体系结构以及如何构建 RESTful 服务以便有助于 Web 2.0 开发。本文给出的这个相对简单的示例使用的是一种经常执行的模式。
请继续关注本系列的第 2 部分,该部分将向您展示如何为更复杂的数据构建 RESTful 服务,并将介绍该平台其他方面的内容以帮助您组装、保护和交付功能完善的经过 Web 扩展的 SOA。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 示例代码 | i-zero1code.zip | 13KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
作者简介
 | 
|  | Steve Ims 是一名 IBM 资深技术人员和 Project Zero
运行时 “core” 的领头人。在 Project Zero 论坛 上可以找到 Steve。 |
对本文的评价
|  |