Grails 中的 Spring Integration(第 1 部分)

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

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

请注意:这是我自己的博客上的交叉发布,网址为 www.russmiles.com

本系列文章将探讨如何在多种配置中将 Spring Integration 添加到 Grails 中,这些配置最终将导致 Spring Integration 的完整 Grails 插件。更像是网络日记,您将有机会看到我们如何通过将 Spring Integration 引导到 Grails 应用程序中迈出第一步,通过使用 Spring Integration 的一些更高级的功能来跨不同的基础架构桥接消息,最终将此经验应用于创建您可以用来快速轻松地将 Spring Integration 添加到您自己的 Grails 项目中的 Grails 插件。

如果您希望在阅读文章时拥有完整的项目,则可以在 此处下载完整的 Grails 项目源代码(注意:构建于 Grails 1.1 Beta 1 版本之上)

Grails(为吹嘘者准备的)

Grails 是一个基于 Groovy 语言的动态、约定优于配置的应用程序框架,它在幕后使用 Spring。这种对 Spring 的使用使得将 Spring 产品组合项目 的功能添加到 Grails 应用程序中变得轻而易举,这正是我们将在本文中针对 Spring Integration 项目探讨的内容。

Spring Integration(为吹嘘者准备的)

对于任何没有机会使用 Spring Integration 的人来说,简而言之,它是一个轻量级的面向消息的库集合,您可以在基于 Spring 的应用程序中使用它。为此,Spring Integration 有一些核心概念需要理解
  • 消息 消息是任何面向消息的中间件的核心抽象,Spring Integration 也不例外。消息可以由任何 Java(因此还有 Groovy)类型组成,然后通过通道从一个端点传递到另一个端点……
  • 端点 端点是想要发送或接收消息的组件。有时,在消息发送到交付之间的一系列管道中会涉及多个端点。为此,端点本身是一个奇怪的名称,因为实际上没有任何东西在端点处真正结束。
  • 通道 这些是当消息在从一个端点到另一个端点传输时保存消息的管道。通道有多种类型,包括 直接 通道(消息以阻塞方式在同一线程中从端点传递到端点),这是如果没有指定其他行为的默认通道类型,以及 发布订阅 通道(消息以异步、非阻塞的方式发送到所有已订阅的使用者)。

入门 - 创建示例应用程序

任何 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

您应该会看到类似以下内容的输出

... 其他输出然后是 ...

服务器正在运行。浏览到 https://127.0.0.1:8080/grails-plus-integration-demo

打开您选择的浏览器并导航到正在运行的 grails 应用程序的地址,该地址应为 https://127.0.0.1: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 环境设置为开发 为 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 环境设置为开发 为 GreetingsMessageSender 创建了控制器 [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 包含一个单一的输入文本框,该文本框将表单中的 content 字段填充到 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 服务作为管道端点

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

> grails create-service DemoBean

执行此命令应提供类似于以下内容的输出

Running script /Applications/SpringSource/grails/grails-1.1-beta1/scripts/CreateService.groovy Environment set to development Created Service for DemoBean Created Tests for DemoBean

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

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

图 7. 您已完成的 DemoBeanService 类

该服务具有一种方法,其目的是在返回传入的字符串之前将其转换为大写。现在您有一个服务,当用户将字符串提交到控制器时,您希望调用该服务,现在是将这两个服务连接起来的时候了。

现在,我们可以直接使用依赖注入,但在此示例中,我们将通过使用 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 元素。

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

  • 网关,其 ID 为 messagingGateway 此组件实际上是一个外观,它将应用程序的其余部分与底层消息传递架构隔离开。组件可以使用此网关并像往常一样同步地调用它,根据指定的接口。在幕后,网关是一个代理,它将执行必要的与 Spring Integration 通道的消息传递交换。在本例中,网关将获取任何方法调用的内容,并将内容包装在 Spring Integration 消息中,然后将其放置在 default-request-channel 上。
  • 输出通道,其 ID 为 demoChannel 此通道是放置传出消息的位置。实际上不需要传入消息通道,因为 Spring Integration 将为回复消息创建一个临时通道,该通道自动连接到请求消息来自的网关。这是通过网关最初设置的相关 ID 实现的。
  • 服务激活器端点,其 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. 将对网关的依赖项添加到控制器中

现在您已将网关连接到控制器,您可以修改发送闭包以使用它

图 10. 您已完成的控制器

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

运行基于 Spring Integration 的 Grails 应用程序

将所有内容连接在一起后,剩下的就是让您的应用程序运行起来。使用通常的命令运行 Grails 应用程序

> grails run-app

现在转到浏览器中的 **GreetingsMessageSenderController**,如下所示

图 11. 您的表单,准备接收输入以触发您的 Spring Integration 管道

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

图 12. 一些数据已输入表单并准备就绪

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

图 13. 您的消息,通过管道发送并返回处理,以显示为闪存消息的一部分

重申一下,当您单击“发送”按钮时会发生以下情况。

当您单击发送时,您的控制器将调用网关,网关将作为 Spring Integration 消息将消息传递到相应的通道,然后服务激活器将获取该消息,将其解包并调用 demoBean 服务。该服务获取字符串内容并将其转换为大写,并通过为此交换设置的临时通道返回结果。网关上的方法返回,解除控制器的阻塞,然后控制器使用闪存集合中的结果呈现视图。

总结

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

获取 Spring 时事通讯

与 Spring 时事通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部