内容


Java 开发 2.0

使用 Gretty 的超轻量级 Java Web 服务

Gretty 丢弃 Web 堆栈来实现真正的快速应用程序开发

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: Java 开发 2.0

敬请期待该系列的后续内容。

此内容是该系列的一部分:Java 开发 2.0

敬请期待该系列的后续内容。

纵观 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 风格,您知道吗?)。如果您愿意走狂野的一面,它也可足够稳定地用作生产之用。

快速 Java 开发的历史

老的足以记得何时第一次引入 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

就我而言,Gretty 至少有两点符合:首先是使用 Groovy 的 Grape(我不久将会详细地对其进行描述)以方便依赖性管理。其次是其简单的用于定义端点的 DSL 式的语法。 使用 Gretty,您可以非常快地(只用短短的几行代码)定义和部署一个工作的 Web 运行框架,该框架处理实际的业务逻辑。作为示例,请看我快速地写出清单 1 中的典型 hello world 示例:

清单 1. Hello, World:这就是 Gretty!
import org.mbte.gretty.httpserver.* 

@GrabResolver(name='gretty', 
  root='http://groovypp.artifactoryonline.com/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']}"])
  }
}

Getty 和 Grape 的依赖性管理

Gretty 令人难忘的开发速度都归功于 Grape(参见 参考资料),Gretty 使用 Grape 来自动下载二进制文件的依赖关系或 JAR 文件。使用 Maven 的传递依赖来加载所有文件。在 清单 1 中我所需要做的就是输入注释 @Grab('org.mbte.groovypp:gretty:0.4.279'),然后我会获得与 Gretty 相关联的 JAR 文件,以及 Gretty 的依赖项。注释 @GrabResolver(name='gretty', root='http://groovypp.artifactoryonline.com/groovypp/libs-releases-local') 表示 Grape 在何处可以发现所需的文件。

Grape 可能看起来简单,但是并不意味着它不适合生产。事实上,Grape 对所需依赖项的自动下载与 Maven 没有任何不同。只是 Grape 在运行时(首次运行应用程序时)下载,而 Maven 在构建时下载所需的依赖项。如果 Grape 可以在本地找到所需的依赖项,则不需要再进行下载。所需的 JAR 文件被自动放置在应用程序的类路径中。因此,您只需要为首次 运行某个已配置的 Grape 应用程序支付性能成本。当然,也会在您更改指定依赖项的所需版本时受到一个小的性能影响。

Gretty 适合 Magnus

希望到目前为止您已经发现 Gretty 是简单的,这就更易于进行非常快速的开发。此外,Gretty(或与此类似的框架)特别适合于 Magnus(HTTP 端点数据监听)这样的应用程序。那么让我们看看当完全使用一个 Gretty 编写的更轻量的应用程序来替换一个相对轻量的框架(比如 Play 或 Grails)时将会发生什么。

对于 Magnus 的具体实现,我将使用 Morphia 和 MongoHQ,您可以回顾我的 Amazon Elastic Beanstalk 简介。为了利用具有新配置的 Groovy 的 Grape 实用工具,我需要将清单 5 中的注释添加到服务器中:

清单 5. 添加 Morphia 及其依赖项
@GrabResolver(name='morphia', root='http://morphia.googlecode.com/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 已就绪

Gretty 是极其轻量级的。没有嵌入的 ORM 框架。除了简单的模板之外,没有强大的视图框架,但是插入一些其他的框架是完全可行的。所有这些是否意味着 Gretty 不适合日常使用?缺少测试框架是否也有同样的意思?答案是否定的:首先,Gretty 构建于 Netty 深受认可的代码之上,所以您大可放心。其次,您可以对 Gretty 进行自动或非自动的测试,就像您对任何其他 Web 端点所进行的测试一样。事实上,如果您想要了解 Gretty 是如何进行测试,请查看 Gretty 的源代码。Gretty 源代码中有大量的测试!

Gretty 与现代的全堆栈 Web 框架相对立,正是因为有时您不需要整个堆栈。如果您发现使用像 Gretty 这样的框架做了太多的工作,那么您可能最好使用许多全堆栈、存档完好的 Java Web 框架中的一个。同样地,如果您想要知道为什么需要整个堆栈来处理 Web 服务请求和响应,那么 Gretty 可能正是您所需要的。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Cloud computing, Web development, Open source
ArticleID=761000
ArticleTitle=Java 开发 2.0: 使用 Gretty 的超轻量级 Java Web 服务
publish-date=09252011