Groovy + Java = 快速构建有效的应用程序
现在,我将展示如何结合使用 Groovy 和 Google 的 App Engine 快速创建一个有效的应用程序。我将使用一个简单的 HTTP 页面、一个 Groovlet 以及一个增强了 JDO 的 Java 类来持久化事件(在本例中为 triathlon)。我将在这里保持简单性,但是您将会看到这个应用程序可以不断演变来包括其他特性,并且在本系列后续文章中,您将实现这些特性(当然,使用不同的基础设施和技术)。
快速 JDO
Google App Engine 提供了使用 JDO 持久化数据的能力,JDO 是一个 Java 持久化标准(见 参考资料)。对于大部分 Java 开发人员来说,持久化数据常常意味着将信息保存到一个关系数据库中;然而,对于 Google 来讲,底层存储机制就是它的 Big Table,而后者并不是关系型的。也就是说,这一点无关紧要:Google 如何持久化特定属性的细节在很大程度上已经被隐藏。可以这样说,您可以使用普通的 Java 对象(或 Groovy 对象,就本文而言)来构建一个应用程序,这个应用程序可以像任何其他应用程序那样存储信息。这就是 Google 的方法,您必须使用 JDO。(Hibernate 无疑是面向 Java 的最流行的 ORM 框架,但它并不能用于 App Engine)。
JDO 非常简单。您将创建 POJO — 老式普通 Java 对象(可以和其他 Java 对象建立联系),您通过类级别的 @PersistenceCapable
注释将其声明为具有持久能力。通过 @Persistent
注释指定要进行持久化的对象的属性。例如,我希望存储 triathlon 事件(目前而言,我将关注事件而不是与 triathlon 有关的各种结果)— 就是说,事件拥有一个名称(triathlon 的名称),可能还有一个描述(triathlon 的类型)和一个日期。目前为止,我的 JDO 看上去类似清单 2:
清单 2. 一个简单的 triathlon 事件 JDO
import java.util.Date;import javax.jdo.annotations.PersistenceCapable;import javax.jdo.annotations.Persistent;import javax.jdo.annotations.IdentityType;@PersistenceCapable(identityType = IdentityType.APPLICATION)public class Triathlon { @Persistent private Date date; @Persistent private String name; @Persistent private String description;}
无论使用哪一种底层机制(即关系型或 Google 的 Big Table),数据持久性始终需要涉及键(key)的概念:一种为了避免数据崩溃而确保数据的不同方面具有惟一性的方法。例如,对于 triathlon,它的键可以是 triathlon 的名称。如果两个 triathlon 拥有相同的名称,那么可以将名称和日期组合起来作为键。不管您使用何种方式通过 Google App Engine 和 JDO 表示键,必须通过 @PrimaryKey
注释在 JDO 对象中指定一个键。您还可以为键的生成方式选择一些策略 — 由您或 Google 生成。我将使用 Google 生成并保持简单性:我的 triathlon 对象的键被表示为一个普通的 Java Long
对象,并且我将通过指定一个值策略 来让 Google 确定实际的值。清单 3 添加了一个主键:
清单 3. 为 triathlon JDO 添加一个主键
- import java.util.Date;
- import javax.jdo.annotations.IdGeneratorStrategy;
- import javax.jdo.annotations.PersistenceCapable;
- import javax.jdo.annotations.Persistent;
- import javax.jdo.annotations.PrimaryKey;
- import javax.jdo.annotations.IdentityType;
- import org.apache.commons.lang.builder.EqualsBuilder;
- import org.apache.commons.lang.builder.HashCodeBuilder;
- import org.apache.commons.lang.builder.ReflectionToStringBuilder;
- @PersistenceCapable(identityType = IdentityType.APPLICATION)public class Triathlon {
- @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Long id;
- @Persistent private Date date; @Persistent private String name;
- @Persistent private String description; public Triathlon(Date date, String name, String description) { super();
- this.date = date; this.name = name; this.description = description; } //...setters and getters left out public String toString() {
- return ReflectionToStringBuilder.toString(this); }
- public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); }
- public boolean equals(Object obj) {
- return EqualsBuilder.reflectionEquals(this, obj); }}
如清单 3 所示,我的 triathlon JDO 拥有一个由 Google 基础设施管理的键,并且添加了一些标准的方法(toString
、hashCode
和 equals
),为调试、登录以及适当的功能提供了极大的帮助。我并没有亲自编写这些内容,相反,我使用了 Apache commons-lang 库(见 参考资料)。我还添加了一个构造函数,与调用大量 setter 方法相比,这个构造函数可以更加轻松地创建完全初始化的对象。
我有意维持了 JDO 的简单性,但是正如您所见,并没有包含多少内容(就是说,为了保持简单性,我去掉了所有的关系并忽略了 getter 和 setter 方法)。您只需对域进行建模并随后使用一些注释来修饰模型,然后剩下的工作就由 Google 来完成。
将对象定义为具有持久性后,还剩下最后一个步骤。要与底层的数据存储交互,需要使用 PersistenceManager
,这是一个 JDO 标准类,顾名思义,它的作用就是在一个底层数据存储中保存、更新、检索和删除对象(非常类似于 Hibernate 的 Session
对象)。这个类通过一个工厂(PersistenceManagerFactory
)创建,这个工厂非常复杂;因此,Google 建议创建一个独立的对象来管理工厂的单个实例(后者在您需要时返回一个合适的 PersistenceManager
)。相应地,我可以定义一个简单的独立对象来返回 PersistenceManager
的实例,如清单 4 所示:
清单 4. 返回 PersistenceManager
实例的简单独立对象
- import javax.jdo.JDOHelper;
- import javax.jdo.PersistenceManager;
- import javax.jdo.PersistenceManagerFactory;
- public class PersistenceMgr {
- private static final PersistenceManagerFactory instance = JDOHelper.getPersistenceManagerFactory("transactions-optional");
- private PersistenceMgr() {}
- public static PersistenceManager manufacture() {
- return instance.getPersistenceManager(); }}
可以看到,我的 PersistenceMgr
非常的简单。manufacture
方法从 PersistenceManagerFactory
的单个实例返回一个 PersistenceManager
实例。您还会注意到,清单 4 中没有出现任何特定于 Google 的代码或任何其他利用 JDO 的代码 — 所有引用都是指向标准 JDO 类和接口的。
新添加的两个 Java 对象位于我的项目的 src 目录中,并且我将 commons-lang 库添加到了 war/WEB-INF/lib 目录中。
利用定义好的简单 triathlon JDO POJO 和方便的 PersistenceMgr
对象,我已经有了很好的起点。我所需要的就是能够捕获 triathlon 信息。
回页首
通过 Web 接口捕获数据
大多数 Web 应用程序都遵循相同的模式:通过 HTML 表单捕捉信息,然后将它们提交到服务器端资源以进行处理。当然,这一过程中还混合了许多其他技术,但是不管底层技术或基础设施如何,模式始终保持不变。Google App Engine 也是如此 — 我已经编码了服务器端资源来处理保存的 triathlon 数据。剩下的工作就是捕捉信息 — 表单 — 以及将服务器端与表单连接起来。按 Model-View-Controller (MVC) 的话说,我需要一个控制器(通常为一个 servlet);我将利用 Groovlet 替代,因为我希望编写更少的代码。
我的 HTML 表单非常简单:我所需做的就是创建一个 HTML 页面,利用某些简单的 Cascading Style Sheets (CSS) 代码来创建表单,如图 1 所示,看上去更接近 Web 2.0,而不是 1998 年出现的 HTML 页面:
图 1. 一个简单的 HTML 表单
可以从图 1 中看到,表单捕捉到一个名称、描述和一个日期。然而,日期并不简单 — 它实际上是一个日期的三个属性。
快速 Groovlet
Groovlets 使得编写控制器变得非常简单:它们需要更少的代码并自动提供了所需的对象。在 Groovlet 中,您分别通过 request
和 response
对象隐式地访问 HTML 请求和响应。在我的 Groovlet 中,我可以通过 request.getParameter("name")
调用获得提交的 HTML 表单的所有属性,如清单 5 所示:
清单 5. Groovlets 的实际操作
- def triname = request.getParameter("tri_name")
- def tridesc = request.getParameter("tri_description")
- def month = request.getParameter("tri_month")
- def day = request.getParameter("tri_day")
- def year = request.getParameter("tri_year")
前面编写的 JDO 使用了一个 Java Date
对象;然而,在清单 5 中,我处理了 Date
的三个不同属性。因此我需要一个 DateFormat
对象来将 month
、day
、year
三者的组合转换为一个普通的 Java Date
,如清单 6 所示:
清单 6. 数据格式化
def formatter = new SimpleDateFormat("MM/dd/yyyy")def tridate = formatter.parse("${month}/${day}/${year}")
最后,从已提交 HTML 表单获得所有参数后,我可以使用清单 7 的代码,通过我的 JDO 和 清单 4 的 PersistenceMgr
对象将它们持久化到 Google 的基础设施中:
清单 7. 使用 JDO 轻松实现持久化
def triathlon = new Triathlon(tridate, triname, tridesc)def mgr = PersistenceMgr.manufacture()try { mgr.makePersistent(triathlon)} finally { mgr.close()}
就是这么简单!当然,随着更多的页面加入到我的简单应用程序中(比如捕捉特定 triathlon 的结果),我可能需要转发或重定向到另一个表单,这将捕捉额外的信息,与向导十分类似。不管怎样,通过一些简短的代码片段,我快速组合了一个简单的 Web 应用程序,它可以通过 JDO(使用普通 Java 编码)和一个 Groovlet(当然,使用 Groovy 编码)将数据持久化到 Google 的基础设施中。部署应用程序非常简单,只需在 appengine-web.xml 文件中指定一个版本并单击 Deploy 按钮。
但是,这个用于捕捉 triathlon 事件的只包含一个表单的 Web 应用程序并没有试图实现全部的功能,所以说,我仅仅是将应用程序部署到一个不规则的、普遍存在的环境中。我不需要触发一个 Web 容器甚至指定在哪里 部署应用程序。(它位于 California、我的硬盘或者是月球上?)妙处在于这并不重要 — Google 负责处理这个问题。注意,是解决所有问题。此外,可以肯定的是,Google 已经知道如何进行全球性扩展,这样位于印度的用户在查看应用程序时会拥有和阿根廷用户相同的体验。
综上所述,您的确有必要牢记一些东西。Google 的基础设施支持 Java 技术,但是并不意味着所有内容;如果您回忆一下多年前 J2ME 问世的情景,那么 App Engine 的限制可能在本质上有些类似。也就是说,并非所有核心 Java 库和相关开源库都受支持。如前所述,Hibernate 就不受支持(主要是因为使用 App Engine 时,您无法拥有关系数据库)。我在使用某些内置了 base64 编码的开源库时还遇到了一些挑战(Google 要求您使用它的 URL Fetch 服务)。App Engine 是一个平台 — 您必须以它为方向进行开发,就目前而言,这是一个单向的过程。
参考资料
学习
java.lang
API。
获得产品和技术
转自:http://www.ibm.com/developerworks/cn/java/j-javadev2-1/#N100EC