Grails 中的 Spring Integration (第一部分)

工程 | Russ Miles | 2008 年 12 月 11 日 | ...

Spring Integration 上周发布了 1.0 GA 版本,因此,受到 SpringONE Americas 上 Adrian 的主题演讲(不,不是 Monty Python 小品,而是 Grails 现场编码示例)的启发,我认为展示如何在 Grails 应用这个稍微不同的环境中利用 Spring Integration 会很有趣。

请注意:本文转载自我的博客 @ www.russmiles.com

本系列文章将探讨如何在 Grails 中以多种配置方式添加 Spring Integration,最终形成一个完整的 Spring Integration Grails 插件。这更像是一本在线日记,你将有机会看到我们如何通过将 Spring Integration 引导(bootstrapping)到 Grails 应用中来迈出第一步,然后通过使用 Spring Integration 的一些更高级功能来跨不同基础设施桥接消息,最后应用这些经验来创建一个 Grails 插件,你可以使用该插件快速轻松地将 Spring Integration 添加到你自己的 Grails 项目中。

如果你想在阅读本文时拥有完成的项目,完整的 Grails 项目源代码可在此处下载(注意:基于 Grails 1.1 Beta 1 构建)

Grails (简单介绍)

Grails 是一个基于 Groovy 语言的动态的、约定优于配置的应用框架,它在底层使用了 Spring。这种对 Spring 的使用使得向 Grails 应用添加Spring 组合项目的功能变得非常容易,这正是我们将在本文中从 Spring Integration 项目的角度来看的内容。

Spring Integration (简单介绍)

对于任何还没有机会使用 Spring Integration 的人来说,简单来说,它是一组轻量级的面向消息的库集合,你可以在你的基于 Spring 的应用中使用它们。为此,Spring Integration 有一些核心概念,理解它们很有帮助
  • 消息(Messages) 消息是任何面向消息中间件的核心抽象,Spring Integration 也不例外。消息可以由任何 Java(因此也包括 Groovy)类型组成,然后使用通道从端点传递到另一个端点...
  • 端点(Endpoints) 端点是想要发送或接收消息的组件。有时,从消息发送到送达的管道链中涉及多个端点。为此,端点本身是一个奇怪的名字,因为实际上并没有什么在端点处真正结束。
  • 通道(Channels) 这些是当你的消息从一个端点传输到另一个端点时保存消息的管道。通道有多种类型,包括 Direct 通道(消息在同一线程中以阻塞方式从一个端点传递到另一个端点),这是未指定其他行为时的默认通道类型,以及 PublishAndSubscribe 通道(消息以异步、非阻塞方式发送给所有订阅的消费者)。

入门 - 创建示例应用

任何 Grails 开发的第一步是创建一个应用来开展工作。一个 Grails 应用由许多目录和文件组成,所有这些在 Grails 约定优于配置的方法下都有隐含的含义。

要创建你的 Grails 应用,你只需确保你的 Grails 安装已添加到路径中,然后从命令行输入以下命令

> grails create-app grails-plus-integration-demo

如果一切正常,你应该会看到以下输出

... 首先显示其他输出,然后 ...

已在 <你的目录>/grails-plus-integration-demo 创建 Grails 应用

Grails 现在已在你输入 grails create-app 命令的目录中创建了一个名为 grails-plus-integration-demo 的应用。切换目录到你的新 Grails 应用,然后运行它以确保一切正常

> cd grails-plus-integration-demo > grails run-app

你应该会看到如下输出

... 其他输出,然后 ...

服务器正在运行。请访问 http://localhost:8080/grails-plus-integration-demo

打开你选择的浏览器,导航到你的正在运行的 Grails 应用地址,应该是 http://localhost:8080/grails-plus-integration-demo,你应该会看到如下内容

图 1. 你的新 Grails 应用,已启动并运行

好的,目前为止没什么惊天动地的内容,但现在我们已准备好创建一些功能,这些功能最终将使用 Spring Integration 消息管道。要从命令行关闭你的 Grails 应用,请按 Ctrl-C。

创建领域对象

如果你正在实践领域驱动设计,那么典型的下一步是创建一些领域对象。Grails 的设计考虑到了这种方法,并提供了一些默认命令来帮助你入门。

对于本文中我们非常简单的示例应用,我们只需要一个领域对象,巧妙地命名为 "GreetingsMessage"。为此,请在你的新应用目录中运行以下 Grails 命令

> grails create-domain-class GreetingsMessage

然后你应该会看到如下内容

正在运行脚本 /Applications/SpringSource/grails/grails-1.1-beta1/scripts/CreateDomainClass.groovy 环境设置为 development 已创建 GreetingsMessage 的 DomainClass 已创建 GreetingsMessage 的测试

创建 GreetingsMessage 领域类后,是时候添加一些属性了。为此,你需要编辑当前位于 grails-app/domain 目录下的 GreetingsMessage.groovy 文件。我们的示例只需要添加一行,以便我们可以将字符串消息作为 GreetingsMessage 对象的内容发送出去

图 2. 为你的领域类添加一个属性

保存你更新后的 GreetingsMessage 领域类,就完成了。

创建控制器和表单视图

接下来我们需要创建一个控制器和一个视图,以便创建我们的 GreetingsMessage 实例,然后最终将它们发送通过 Spring Integration 管道。现在,如果我们只是简单地将 GreetingsMessage 对象持久化到数据库中,那么我们可以使用 grails generate-all 命令来立即获取我们需要的所有控制器和视图。然而,在这个示例应用中,我们只想创建一个控制器,允许用户输入消息内容的字符串,然后将其路由到特定的服务端点,所以我们将手动创建一个简单的控制器和视图来完成这项工作。

使用命令行,创建一个名为 "GreetingsMessageSender" 的 Grails 控制器

> grails create-controller GreetingsMessageSender

此命令应该会输出如下内容

正在运行脚本 /Applications/SpringSource/grails/grails-1.1-beta1/scripts/CreateController.groovy 环境设置为 development 已创建 GreetingsMessageSender 的 Controller [mkdir] 已创建目录: /Users/russellmiles/project/grails-plus-integration-demo/grails-app/views/greetingsMessageSender 已创建 GreetingsMessageSender 的测试

你现在已经创建了一个空的控制器及其功能的单元测试。Grails 还在你的 grail-app/views 目录下创建了一个名为 greetingsMessageSender 的空目录,当你添加一些控制器方法后,该目录将包含你的视图模板。

下一步是使用我们希望控制器支持的方法来更新它。打开并更新 grails-app/controllers/GreetingsMessageSender.groovy,使其与以下内容匹配

图 3. (部分)完成的 GreetingsMessageSender 控制器

如果你对 Groovy 不太熟悉,那么下面是该类的代码正在做的事情

  • 当访问控制器且没有调用其他特定操作时,会执行 index 闭包。目前,这个 index 闭包只是简单地将浏览器重定向到 send 操作。
  • 无论何时浏览器访问此控制器上的 send 操作时,都会执行 send 闭包。目前,此代码只是简单地在传递到 send 闭包的 HTTP 参数中查找 'content' 条目,然后,如果非空,则设置 flash 消息为一些简单文本,这些文本随后可以在关联的视图中渲染。
那么关联的视图在哪里呢?现在是时候创建一个了。保存你的控制器,然后在 grails-app/views/greetingsMessageSender 目录下创建一个新文件,命名为 send.gsp

send.gsp 视图中有很多代码,所以我建议你在此处下载代码,而不是自己全部输入。不过如果你很勇敢,你可以将下面显示的代码复制到你的 send.gsp 文件中

图 4. 完成的 send.gsp 视图

视图中需要关注的关键代码是 g:form 和 flash.message 部分。g:form 包含一个单行输入文本框,该文本框将表单中的内容字段填充到 HTTP 参数中,当表单提交时,GreetingsMessageSenderController 上的 send 闭包将获取这些参数。

flash.message 代码显示 flash 集合的内容,在本例中是我们由 send 闭包添加的消息。

你现在可以运行并尝试这个简单的界面了,尽管我们目前不期望它做太多事情。再次使用 grails run-app 命令运行你的应用,然后导航到你的应用主页来试用它

图 5. 你的 Grails 应用主页上新的控制器链接

点击 GreetingsMessageController 会执行 'index' 方法,然后你的浏览器会被重定向到 send 闭包,接着你应该会看到你的新表单

图 6. 你的发送表单

你可以在文本框中输入消息,点击“发送”,然后,嗯,什么都不会发生... 暂时不会。

加入一份 Spring Integration,充分混合

到目前为止,你创建了一个非常简单的 Grails 应用,但现在是时候加入一些 Spring Integration 了。Spring Integration 提供了一种将应用内部和外部组件进行松耦合的绝佳方式,这正是我们在这里要做的。

计划是创建一个服务,该服务接收一个字符串,然后简单地将其转换为大写。这没什么太令人兴奋的,但精彩之处在于:我们将使我们的前端控制器完全不知道该服务的控制器在哪里,也不知道它支持什么接口。当然,我们可以简单地将服务注入到控制器中,但这会在接口层面将控制器与服务紧密耦合。我们在这里要做的是将服务与控制器之间的契约体现为仅通过传递的消息类型,在本例中是包含单个字符串的消息。

注意: 当我提到契约和消息传递时,SOA 领域的人士可能会竖起耳朵,意识到这与服务的目标非常接近,在服务中你将契约声明为将与服务终点交换的消息类型。这绝对是有意为之的,但值得一提的是,尽管 Spring Integration 是 SOA 实践的绝佳促进者,但它远不像拥有一个分布式、单体 ESB 那样重量级,即使它共享一些相同的架构基础(如消息传递和端点)。典型的 ESB 实现往往会导致昂贵的 Web 服务调用来完成最微不足道的交换,并带来各种包袱,而 Spring Integration 则允许完全控制,可以从线程内消息传递、多线程进程内轮询,一直扩展到进程间/机器间交换(如果需要的话)(本系列后续文章中将详细介绍)。

第一步是将 Spring Integration 库添加到我们的示例应用中。Grails 为应用直接依赖的库提供了特定的、每个应用的存放位置。最简单的方法是将 Spring Integration 分发包的 dist 目录内容复制到我们的 Grails 应用的 lib 目录中。

复制 Spring Integration dist 目录的内容...

... 到你的 Grails 应用根目录下的 lib 目录中。

添加这些库使得 Spring Integration 可用于你的 Grails 应用。在创建了将为该管道提供服务的组件后,你现在已准备好创建你的 Spring Integration 管道了。

创建 Grails Service 作为管道端点

Grails 包含服务的概念,因此我们将创建一个简单的服务,它将接收一个传入的字符串消息并将其内容转换为大写。要创建该服务,请使用以下 Grails 命令

> grails create-service DemoBean

执行此命令应该会得到类似于以下的输出

正在运行脚本 /Applications/SpringSource/grails/grails-1.1-beta1/scripts/CreateService.groovy 环境设置为 development 已创建 DemoBean 的 Service 已创建 DemoBean 的测试

现在 Grails 已经在 grails-app/services 目录中创建了一个 DemoBeanService。它不是最有趣的服务,但它将用于说明 Spring Integration 机制。

要完成该服务,请打开 grails-app/services 目录下的 DemoBeanService.groovy 文件,并按如下所示完成它

图 7. 你完成的 DemoBeanService 类

该服务有一个方法,其目的是在返回任何传入字符串之前将其转换为大写。既然你已经有了一个服务,当你用户将字符串提交到控制器时想要调用它,那么是时候将这两者连接起来了。

现在,我们可以直接使用 DI,但对于这个示例,我们将通过使用 Spring Integration 来处理将传入的字符串传递给我们配置的特定服务,从而奠定更灵活架构的基础。

创建 Spring Integration 管道

为了连接我们的前端 Grails 控制器和后端服务,我们将使用一个简单的 Spring Integration 管道。Spring Integration 使用 Spring 配置的领域特定方言驱动,该方言有自己的命名空间,以使配置尽可能简洁。

Grails 支持使用约定将现有的 Spring XML 配置添加到应用中,该约定将配置保存在 grails-app/conf/spring 目录下的 resources.xml 文件中。为了节省你的打字时间,请在此处下载该文件,然后将其放入你的 grails-app/conf/spring 目录中。

接下来,用你喜欢的文本编辑器打开 resources.xml 文件,让我们看看里面有什么。

首先,请注意我们正在引入 Spring Integration 命名空间,以便我们可以使用与 Spring Integration 基于 XML 的领域特定语言相关的元素和属性。实际上,Spring Integration 命名空间被设置为文档的默认命名空间,因此我们必须用命名空间限定 bean 元素。

然后整个管道都在这个文档中表达。这个管道中的主要组件及其扮演的角色如下

  • 网关(Gateway),其 ID 是 messagingGateway 这个组件实际上是一个外观模式(facade),它将应用的其余部分与底层消息架构分离。组件可以像正常一样消费这个网关,并根据指定的服务接口同步调用它。在底层,网关是一个代理,它将执行必要的 Spring Integration 通道消息交换。在本例中,网关将获取任何方法调用的内容,并将其封装在 Spring Integration 消息中,然后将其放置在 default-request-channel 上。
  • 出站通道(Outgoing channel),其 ID 是 demoChannel 这个通道是放置出站消息的地方。实际上不需要入站消息通道,因为 Spring Integration 会为回复消息创建一个临时通道,该通道会自动连接到请求消息来自的网关。这是通过使用最初由网关设置的关联 ID 来实现的。
  • 服务激活器端点(Service activator endpoint),其 ID 是 localService 这个组件负责当消息出现在入站通道上时,调用特定 bean 上的特定方法。在本例中,demoBean 服务上的 doSomething 方法将通过消息内容被调用,根据 doSomething 方法的参数,该内容是一个字符串。

注意: Grails 中的所有服务类默认都会生成该类的单例服务对象,该对象在 Spring 应用上下文中以类名的驼峰命名法版本进行索引。因此,当服务激活器引用 demoBeanService bean 时,Grails 会自动将其解析为我们之前创建的 DemoBeanService 的单例实例。

就这样,我们有了一个完整的消息传递管道,它将通过内部队列传递消息,从而将任何消息提供者与消费者强力解耦。你会注意到,任何创建消息并调用网关的消费者都不需要知道消息最终在哪里,甚至不需要知道目标端点实现了什么接口。这里的契约是正在传递的消息类型,对于这个简单的示例来说,仅仅是字符串。

将网关连接到控制器

最后一步是将 Spring Integration 管道连接到 Grails 控制器中,以便它能够透明地传递和接收消息。就控制器而言,它只是调用一个满足特定接口的本地对象,但在底层,Spring Integration 正在将该消息路由到我们指定的 bean,即我们的 demoBeanService

为了连接这些松散的部分,你需要创建一个你愿意向控制器暴露的服务接口。请注意,端点引用的实际 bean 不一定需要实现相同的接口,这里重要的是消息参数。

要创建该接口,请在 src/java 目录下创建一个名为 MessageService.java 的 Java 接口,放在一个合适的包结构下,例如 com.russmiles.demo.grails.integration。完成该接口,使其与以下内容匹配

图 8. MessageService 接口的定义

现在你已经定义了网关接口,最后的工作是将网关本身连接到你的控制器中。在 Grails 中,你可以根据对象的名称自动为其提供依赖项,因此要从控制器内部访问网关,你只需声明一个属性,其名称与网关的 ID 匹配

图 9. 将网关依赖项添加到控制器中

现在你已经将网关连接到控制器,你可以修改你的 send 闭包来使用它了

图 10. 你完成的控制器

就这样,是时候启动使用 Spring Integration 的 Grails 应用了。

运行你的基于 Spring Integration 的 Grails 应用

一切都连接好后,剩下的就是运行你的应用了。使用常用命令运行 Grails 应用

> grails run-app

现在在你的浏览器中访问你的 GreetingsMessageSenderController,如下所示

图 11. 你的表单,准备好输入以触发你的 Spring Integration 管道

在表单中输入一些文本,然后点击发送

图 12. 表单中输入了一些数据,准备就绪

下图显示了接下来应该发生的事情

图 13. 你的消息,通过管道发送并处理后返回,作为 flash 消息的一部分显示

再次强调一下,当你点击“发送”按钮时,会发生以下事情。

当你点击发送时,你的控制器会调用网关,网关将消息作为 Spring Integration 消息传递给适当的通道,然后服务激活器会接收该消息,解包并调用 demoBean 服务。该服务接收字符串内容并将其转换为大写,然后通过为此交换设置的临时通道返回结果。网关上的方法返回,解除控制器的阻塞,然后控制器渲染视图,并在 flash 集合中显示结果。

总结

本文只是将 Spring Integration 消息管道引导到 Grails 应用中的首次尝试。下一步是做一些比仅仅在进程内从网关传递消息到服务激活对象更复杂的事情。在本系列下一篇文章中,我们将探讨如何利用我们灵活的 Spring Integration 管道,使用 JMS 适配器将消息传递到与我们的 Grails 应用不在同一进程中运行的服务端点。

获取 Spring 资讯

订阅 Spring 资讯,保持联系

订阅

领先一步

VMware 提供培训和认证,助你加速前进。

了解更多

获取支持

Tanzu Spring 通过简单订阅,提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

近期活动

查看 Spring 社区的所有近期活动。

查看全部