纵观 Java 开发 2.0 中最新的几篇文章,我已经构建了一个简单的从云到移动终端的应用程序。该应用程序名为 Magnus,充当监听移动设备位置信息的 HTTP 端点。
它通过接收 HTTP PUT
请求来运行,每个请求包含一个 JSON 文档,指出给定时间内帐户的位置。到目前为止,我已经使用了 Web 框架 Play 来开发和扩展 Magnus(参见 参考资料)。
Play 提供一个 MVC 堆栈,就这点它与 Grails 很相似。使用 Play,您可以很容易地定义利用视图(JSP、GSP、模板等)的控制器(servlets),在某种程度上,控制器管理模型。模型是使用经 Hibernate、JPA 或其他类似 ORM 的好技术增强的 POJO(传统 Java 对象)实现的。
尽管 MVC 是较老的标准,随着 Grail 和 Play 等框架的出现,很多都已经发生了改变。回想一下曾经维护简单的 Web 请求-响应交互所需的工作量(比方说使用 Struts),您会明白我们为快速构建 MVC Web 应用程序而做了多大的改进。当然,并非所有的 Web 应用程序都需要 MVC 基础架构才能工作。如今,一些 Web 应用程序根本就不再需要 MVC “堆栈”。
为了反对这样一种反常的论调,在您关闭浏览器之前,回顾一下 Magnus。虽然为了演示,对 Magnus 进行了严格的设计,我的云到移动终端的应用程序不包含传统的视图组件,主要包含了现有的成功服务的模型。与 Twitter 或 Foursquare 一样,Magnus 接收来自世界各地不同设备的消息。广义上说,Magnus 是一个 Web 服务,而并不是每个 Web 服务都需要 MVC 堆栈框架才能完成工作。在某些情况下,您所需要的是一个超级轻量的 Web 框架,而不是 Web 堆栈。
本月,我们将着眼于以下内容之一:快速开发框架,太新以至于还没有自己的主页,或许并不需要主页。Gretty 的沿袭和隶属成员(分别包括 Netty 和 Groovy)具有足够的名望,它已经是 Java 2.0 Web 开发系列的一部分。它填补了一个许多开发人员仍然不知道他们已经具有的需求(这就是真正的 Web 2.0 风格,您知道吗?)。如果您愿意走狂野的一面,它也可足够稳定地用作生产之用。
老的足以记得何时第一次引入 Servlets API 的我们有理由对新的 “轻量级” 范式持怀疑态度;毕竟仅仅一个简单的 servlet 便让您构建一个 Web 服务,而不需要大量的代码和由此产生的 JAR 文件。Web 服务框架,比如 Restlet 或 Jersey,采取了稍微不同的开发加速方法,以类扩展、注释,甚至标准的 JSR 为基础来创建 RESTful Web 服务。在某些情况下,它们仍然是很好的选择。
但事实证明,一些新的轻量级(相对于旧的 轻量级)框架使得 Web 服务或简单的 HTTP 端点(也称为路由)极其易于定义。甚至比手动塞入一个 servlet 还要简单!
这些框架首次出现在其他平台上,尤其是用于 Ruby 的 Sinatra 和用于 Node.js 的 Express。但是针对 Java 平台的有趣项目也已经开始出现了。Gretty 就是其中之一,当然 Gretty 是为 Groovy 和 JVM 产生的。
就我而言,Gretty 至少有两点符合:首先是使用 Groovy 的 Grape(我不久将会详细地对其进行描述)以方便依赖性管理。其次是其简单的用于定义端点的 DSL 式的语法。 使用 Gretty,您可以非常快地(只用短短的几行代码)定义和部署一个工作的 Web 运行框架,该框架处理实际的业务逻辑。作为示例,请看我快速地写出清单 1 中的典型 hello world 示例:
清单 1. Hello, World:这就是 Gretty!
import org.mbte.gretty.httpserver.* @GrabResolver(name='gretty', root='http://groovypp./groovypp/libs-releases-local') @Grab('org.mbte.groovypp:gretty:0.4.279') GrettyServer server = [] server.groovy = [ localAddress: new InetSocketAddress("localhost", 8080), defaultHandler: { response.redirect "/" }, "/:name": { get { response.text = "Hello ${request.parameters['name']}" } } ] server.start() |
在 清单 1 中,我创建了一个服务器监听端口 8080,然后设置一个包含参数 name
的简单 root 端点。到其他端点的任何请求都将通过 defaultHandler
返回到 /
。简单地说,使用 /
的位置,处理程序为请求的客户端发送一个 HTTP 301 “moved permanently” 代码 。所有请求会收到一个包含字符串 “Hello” 和任何已传递参数值的响应(将 content-type 设置为 text/plain
);例如,/Andy
将会生成 “Hello Andy”。
那么 清单 1 中最有趣的是什么?首先,您在清单中所看到的都是您的应用程序所需要的。没有配置文件。不需要直接下载或安装任何东西(除了 Groovy 1.8)。要激活该示例,只要输入 groovy server.groovy
。
现在如果您的响应要求更复杂的文本而不是简单文本,该怎么办?对于该问题,Gretty 有很多选择,其中有两个是十分简单的。第一个是您可以简单地将响应类型设置为 HTML,正如我在清单 2 中所执行的:
清单 2. Gretty 中的 HTML 响应
"/:name": { get { response.html = "Hello ${request.parameters['name']}" } } |
在这种情况下,响应的 content-type 会被设置为 text/html
。另外,Gretty 可以利用静态和动态的模板。例如,我可以使用一个类似于 JSP/GSP 的简单构造函数来定义模板,类似于清单 3:
清单 3. Gretty 中的 HTML 模板
<html> <head> <title>Hello!</title> </head> <body> <p>${message}</p> </body> </html> |
然后可以在某个响应的主体部分引用该模板:
清单 4. Gretty 中的 Groovy++ 模板
"/:name" { get { response.html = template("index.gpptl", [message: "Hello ${request.parameters['name']}"]) } } |
Gretty 令人难忘的开发速度都归功于 Grape(参见 参考资料),Gretty 使用 Grape 来自动下载二进制文件的依赖关系或 JAR 文件。使用 Maven 的传递依赖来加载所有文件。在 清单 1 中我所需要做的就是输入注释 @Grab('org.mbte.groovypp:gretty:0.4.279')
,然后我会获得与 Gretty 相关联的 JAR 文件,以及 Gretty 的依赖项。注释 @GrabResolver(name='gretty', root='http://groovypp./groovypp/libs-releases-local')
表示 Grape 在何处可以发现所需的文件。
Grape 可能看起来简单,但是并不意味着它不适合生产。事实上,Grape 对所需依赖项的自动下载与 Maven 没有任何不同。只是 Grape 在运行时(首次运行应用程序时)下载,而 Maven 在构建时下载所需的依赖项。如果 Grape 可以在本地找到所需的依赖项,则不需要再进行下载。所需的 JAR 文件被自动放置在应用程序的类路径中。因此,您只需要为首次 运行某个已配置的 Grape 应用程序支付性能成本。当然,也会在您更改指定依赖项的所需版本时受到一个小的性能影响。
希望到目前为止您已经发现 Gretty 是简单的,这就更易于进行非常快速的开发。此外,Gretty(或与此类似的框架)特别适合于 Magnus(HTTP 端点数据监听)这样的应用程序。那么让我们看看当完全使用一个 Gretty 编写的更轻量的应用程序来替换一个相对轻量的框架(比如 Play 或 Grails)时将会发生什么。
对于 Magnus 的具体实现,我将使用 Morphia 和 MongoHQ,您可以回顾我的 Amazon Elastic Beanstalk 简介。为了利用具有新配置的 Groovy 的 Grape 实用工具,我需要将清单 5 中的注释添加到服务器中:
清单 5. 添加 Morphia 及其依赖项
@GrabResolver(name='morphia', root='http://morphia./svn/mavenrepo/') @Grab(group='com.google.code.morphia', artifactId='morphia', module="morphia", version='0.99') |
我的 Morphia 类与 Magnus 的早期具体实现中的一样:我有一个 Account
和一个 Location
。在此端点中,我只简单地更新了某个给定帐户的位置。因为 Morphia 的客户端会将 JSON 文档发送至 Gretty 端点,我还要使用 Jackson(一个非常好的处理 JSON 的框架),它已经是 Gretty 的一部分。得益于 Grape 传递依赖的处理,现在我可以访问用于解析传入的 JSON 文档并将其转换为一个简单的 Java Map
所需要的一切。
清单 6. 在 Gretty 中更新位置
def server = new GrettyServer().localAddress(new InetSocketAddress("localhost", 8080)). "/location/:account" { put { def jacksonMapper = new ObjectMapper() def json = jacksonMapper.readValue(request.contentText, Map.class) def formatter = new SimpleDateFormat("dd-MM-yyyy HH:mm") def dt = formatter.parse(json['timestamp']) def res = [:] try{ new Location(request.parameters['account'], dt, json['latitude'].doubleValue() , json['longitude'].doubleValue() ).save() res['status'] = 'success' }catch(exp){ res['status'] = "error ${exp.message}" } response.json = jacksonMapper.writeValueAsString(res) } } server.start () |
正如您在 清单 6 中所看到的,创建了一个传入 JSON 文档的 Map
(称为 json
),然后通过清单 7 中的 Location
类相应地将其插入 MongoDB:
清单 7. 创建位置文档 Gretty redux
import com.google.code.morphia.annotations.Entity @Entity(value = "locations", noClassnameStored = true) class Location extends AbstractModel { String accountId double latitude double longitude Date timestamp public Location(String accountId, Date timestamp, double lat, double lon) { this.accountId = accountId this.timestamp = timestamp this.latitude = lat this.longitude = lon } } |
另外,Location
有一个 Groovy 超类,如清单 8 所示:
清单 8. Location 的基类
import com.google.code.morphia.Morphia import com.google.code.morphia.annotations.Id import com.mongodb.Mongo import org.bson.types.ObjectId abstract class AbstractModel { @Id private ObjectId id; def save() throws Exception { def mongo = new Mongo("fame.mongohq.com", 32422) def datastore = new Morphia().createDatastore(mongo, "xxxx", "xxxx", "xxxx".toCharArray()) datastore.save(this) return this.id } } |
您可能记得出自 “Climb the Elastic Beanstalk” 中清单 3 的代码。为了 Gretty 的实现,我所做的惟一更改是将实际的文件名从 Location.java
改为 Location.groovy
,这意味着在激活服务器之前我不需要对其进行编译。我还添加了一个基类。通过从 URI 获得的传入参数 account
将位置与某个帐户相关联。
然后用 JSON 发送一个表示成功的响应。如果有错误,会产生另一个响应。
Gretty 是极其轻量级的。没有嵌入的 ORM 框架。除了简单的模板之外,没有强大的视图框架,但是插入一些其他的框架是完全可行的。所有这些是否意味着 Gretty 不适合日常使用?缺少测试框架是否也有同样的意思?答案是否定的:首先,Gretty 构建于 Netty 深受认可的代码之上,所以您大可放心。其次,您可以对 Gretty 进行自动或非自动的测试,就像您对任何其他 Web 端点所进行的测试一样。事实上,如果您想要了解 Gretty 是如何进行测试,请查看 Gretty 的源代码。Gretty 源代码中有大量的测试!
Gretty 与现代的全堆栈 Web 框架相对立,正是因为有时您不需要整个堆栈。如果您发现使用像 Gretty 这样的框架做了太多的工作,那么您可能最好使用许多全堆栈、存档完好的 Java Web 框架中的一个。同样地,如果您想要知道为什么需要整个堆栈来处理 Web 服务请求和响应,那么 Gretty 可能正是您所需要的。
学习
- “Groovy++ in action: Gretty/GridGain/REST/Websockets”(Alex Tkachman,DZone,2011 年 5 月):迄今为止很少有关于 Gretty 的书。作者编写的该书中提供了很多使用 Groovy++ 实现的示例应用程序。
- Java 开发 2.0:此 dW 系列探索了重新定义 Java 开发领域的技术。主题包括 Java 开发 2.0:攀登 Elastic Beanstalk(2011 年 2 月)、Java 开发 2.0:面向 Java 开发人员的 JavaScript(2011 年 4 月)、Java 开发 2.0:MongoDB:拥有 RDBMS 特性的 NoSQL 数据存储(2010 年 9 月)以及 NoSQL(2010 年 5 月)。
- Grape 用户指南:关于 Grape 的奥妙?从 Codehaus 用户指南获得简要介绍。
- Netty 主页:了解 Java NIO 客户端-服务器套接字框架。
- “Getting started with new I/O (NIO)”(Greg Travis,developerWorks,2003 年 7 月):此上机操作教程涵盖了 NIO 库,包括缓冲和通道、异步 I/O 以及直接缓冲。
- Knowledge path: Cloud computing fundamentals(2011 年 3 月):介绍云计算的概念和服务模型 IaaS、Paas 和 SaaS。
- 浏览 Java 技术书店 ,阅读有关这些主题和其他技术主题的图书。
- developerWorks 中国网站 Java 技术专区: 在这里可以找到数百篇关于 Java 编程的各个方面的文章。
获得产品和技术
- Play 框架:关注开发人员工作效率并以 RESTful 架构为目标。
- 获取 Gretty:Groovy 1.8.0 是您入门所必须的。
- 下载 IBM 产品评估试用版软件 或 IBM SOA 人员沙箱,并开始使用来自 DB2?、Lotus?、Rational?、Tivoli? 和 WebSphere? 的应用程序开发工具和中间件产品。
讨论
- 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。
Andrew Glover 是具有行为驱动开发、持续集成和敏捷软件开发激情的开发人员、作家、演说家和企业家。他是 easyb 行为驱动开发(Behavior-Driven Development,BDD)框架的创建者和三本书的合著者:持续集成、Groovy 在行动 和 Java 测试模式。您可以通过他的博客与他保持一致并在 Twitter(http://twitter.com/aglover)上关注他。