领先一步
VMware 提供培训和认证,助您加速进步。
了解更多上周,Cloud Foundry 上将 RabbitMQ 作为服务提供的可用性宣布。现在,任何运行在 Cloud Foundry 上的应用程序都可以通过 RabbitMQ 代理发送和接收消息,该代理可以通过单个命令(例如 'vmc create-service rabbitmq')作为服务进行配置。消息服务实例可以在应用程序之间共享,并且由于 RabbitMQ 是一个基于协议的代理,这些应用程序甚至可以用不同的语言编写。因此,对于那些对在云中运行的模块化、多语言、事件驱动应用程序感兴趣的人来说,这是一个激动人心的消息。我将发布一系列关注这些类型应用程序的博客。在这篇文章中,我将保持简单,重点介绍 Spring 开发人员的初始体验。
首先,我建议您查看本教程,即使您之前没有 Cloud Foundry 的经验,这也是最佳入门方式。在那里您将看到如何使用 Maven 构建一个简单的 Spring 应用程序,并使用 VMC 命令行工具将其部署到 Cloud Foundry。然后,该应用程序通过增强其 MVC 控制器以发布和检索消息来引入 RabbitMQ。它展示了如何通过 Spring AMQP 库配置和使用 RabbitMQ 服务。
此外,在最初的 Cloud Foundry 公告发布当天(与这些博客 文章在同一天),我发布了另一篇博客,其中涵盖了“cloud”命名空间支持的基础知识。阅读该文章也可能有助于为即将看到的内容奠定基础。具体来说,我们扩展了“cloud”命名空间以包含对 RabbitMQ ConnectionFactory 的支持,这将在我们下面的配置概述中介绍。
现在,我将介绍另一个演示简单聊天服务器的示例应用程序。RabbitMQ 为多功能聊天应用程序提供了一个极好的骨干,因为它支持不同类型的交换机,例如“direct”/点对点、“topic”基于发布/订阅和“fanout”用于简单广播。此外,RabbitMQ 支持多种语言绑定。再加上在云中启用消息传递基本上只需轻触开关,现在许多不同的应用程序可以轻松共享该服务。如上所述,我将逐步增强示例,并在稍后发布更多博客文章,以涵盖这些交换机类型和一些多语言聊天,但目前我的目标是通过扇出交换提供一个可访问的起始点,仅支持全局广播。应用程序的当前状态并不比教程中介绍的更复杂。我将介绍一些配置和代码,但如果您想跟着并深入了解更多细节,我建议从 GitHub 上的 SpringSource cloudfoundry-samples 仓库克隆示例。
这是运行中的应用程序的样子
该表单将使用 jQuery 提交 HTTP POST 请求
$('#chatForm').submit(
function() {
$.post(
$('#chatForm').attr("action"),
$('#chatForm').serialize(),
function(response) {
if (response) {
confirm(response.id);
}
});
$('#text').val("");
return false;
});
并且,聊天日志将通过轮询定期更新——也使用 jQuery 的 AJAX 支持
$.ajax({
url : "chatlog",
success : function(message) {
if (message && message.length) {
var messagesDiv = $('#messages');
messagesDiv.html(message);
messagesDiv.animate({ scrollTop: messagesDiv.attr("scrollHeight") - messagesDiv.height() }, 150);
}
timer = poll();
},
error : function() {
timer = poll();
},
cache : false
});
如果您克隆仓库并进入“rabbit-chat”目录,您将看到以下结构
├── pom.xml
├── src
│ └── main
│ ├── java
│ │ └── org
│ │ └── cloudfoundry
│ │ └── samples
│ │ └── rabbitmq
│ │ └── chat
│ │ └── ChatController.java
│ ├── resources
│ │ └── static
│ │ └── js
│ │ └── jquery.min.js
│ └── webapp
│ └── WEB-INF
│ ├── spring
│ │ └── servlet-context.xml
│ ├── views
│ │ └── chat.jsp
│ └── web.xml
pom.xml 文件声明了依赖项。特别值得关注的是以下几项
web.xml 文件声明了一个 Spring MVC DispatcherServlet 和一个单一的包罗万象的 servlet 映射 (“/”)。
如您所见,只有一个名为“ChatController”的控制器。它通过注解进行配置。它使用 @Controller 和 @RequestMapping 注解以及 @Autowired。由于 ChatController 确实是应用程序的核心(也是其唯一的 Java 代码),让我们快速浏览一下整个实现
@Controller
public class ChatController {
@Autowired
private volatile AmqpTemplate amqpTemplate;
private final Queue<String> messages = new LinkedBlockingQueue<String>();
@RequestMapping(value = "/")
public String home() {
return "WEB-INF/views/chat.jsp";
}
@RequestMapping(value = "/publish", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public void publish(@RequestParam String username, @RequestParam String text) {
this.amqpTemplate.convertAndSend(username + ": " + text);
}
@RequestMapping(value = "/chatlog")
@ResponseBody
public String chatlog() {
return StringUtils.arrayToDelimitedString(this.messages.toArray(), "<br/>");
}
/**
* This method is invoked when a RabbitMQ Message is received.
*/
public void handleMessage(String message) {
if (messages.size() > 100) {
messages.remove();
}
messages.add(message);
}
}
有 3 个控制器方法(用 @RequestMapping 注解的方法),每个方法只有一行代码。其中最简单的是 home(),它返回要渲染的 JSP 位置。我通常会使用 Spring MVC ViewResolver,但由于它只是一个使用 AJAX 的单页应用程序,因此这是唯一直接渲染的视图。如您所见,每当您请求应用程序根目录时,都会调用 home()。
publish(..) 方法用于处理对相对 URL “/publish” 的 HTTP POST 请求,它期望请求中有两个参数:username 和 text。这些参数由您在上一节中看到的 HTML 表单提供。该表单由 chat.jsp 渲染。publish 方法所做的只是将 username + text 值连接成一个字符串,然后将其转换为 AMQP 消息,并通过 AmqpTemplate 发送,之后它会以简单的 HTTP 200 (OK) 状态响应。模板实例已自动注入到控制器中。我们很快将查看 AmqpTemplate 配置以及支持在 Cloud Foundry 上使用 RabbitMQ 服务的底层 ConnectionFactory。
chatlog() 方法只是返回多达 100 条最新的聊天消息。它是上一节中所示的 AJAX 请求正在轮询的方法。handleMessage(..) 方法负责将这些聊天消息排队,因此它是连接到底层消息监听器的方法。这几乎涵盖了应用程序的功能。
现在,我们可以详细介绍此应用程序的配置。这本可以完全用 Java 注解完成,但希望您同意这是一个相当简洁的配置文件
<context:component-scan base-package="org.cloudfoundry.samples.rabbitmq.chat"/>
<mvc:annotation-driven/>
<mvc:resources location="file:./src/main/resources/static/,classpath:/static/" mapping="static/**"/>
<rabbit:queue id="chatQueue"/>
<rabbit:fanout-exchange name="chatExchange">
<rabbit:bindings>
<rabbit:binding queue="chatQueue"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
<rabbit:template connection-factory="rabbitConnectionFactory" exchange="chatExchange"/>
<rabbit:admin connection-factory="rabbitConnectionFactory"/>
<rabbit:listener-container>
<rabbit:listener queues="chatQueue" ref="chatController" method="handleMessage"/>
</rabbit:listener-container>
<cloud:rabbit-connection-factory id="rabbitConnectionFactory"/>
“component-scan”元素使带 @Contoller 注解的类注册为 Spring 管理的对象,并激活 @Autowired 支持。两个带“mvc”前缀的元素只是设置 MVC @RequestMapping 支持并启用静态资源的加载(在这种情况下用于“resources/static/js”目录中提供的 jQuery 支持)。
其余元素与 RabbitMQ 配置相关。“rabbit:admin”生成一个 RabbitAdmin 实例,该实例负责识别在同一应用程序上下文中定义的交换机、队列和绑定。请注意,队列元素将其 id 设置为“chatQueue”,但该 id 没有“name”属性。这将触发创建一个具有唯一生成名称的队列,该队列专属于此特定应用程序。换句话说,“id”属性的值不映射到队列的名称;它是 Spring bean 的 id,而不是 RabbitMQ 队列的 id。尽管它具有生成的名称,但它需要可识别以便在此应用程序上下文中进行引用。例如,您可以看到它在此处定义的“chatExchange”的绑定中被引用,该交换机被定义为扇出交换机。由于存在“rabbit:admin”元素,该交换机也将针对代理进行声明。
“rabbit:template”非常简单。它需要引用 ConnectionFactory(别担心,我们稍后会讲到),如果您希望其“send”方法发布到非默认的无名交换机,您可以在此处提供。我们正在发布到我们刚刚讨论过的“chatExchange”。
“rabbit:listener-container”与 Spring JMS 支持中同名元素几乎相同。这个监听器正在监听“chatQueue”,请记住这只是对 bean id 的引用,该特定队列的真实名称是由代理生成的。每当消息到达该队列时,我们之前看到的“handleMessage”方法将被调用。在方法参数是 String 的情况下,监听器容器的适配器将自动处理消息体的转换。由于该方法参数不需要接受实际的 Message 实例,并且方法名称可以是任何我们想要的名称,我们称之为“消息驱动的 POJO”。换句话说,它对消息 API 没有直接依赖。它被监听器容器调用是一种控制反转的形式。
最后,是连接工厂配置。在这种情况下,我们使用“cloud”命名空间及其“rabbit-connection-factory”元素。只要您的应用程序绑定到 Cloud Foundry 中的单个“rabbitmq”服务,创建 ConnectionFactory 实例就不需要其他信息。该命名空间支持底层的代码将从环境本身确定凭据。该命名空间支持由“cloudfoundry-runtime”库提供,您可以在此应用程序的pom.xml文件中看到其声明。
您可以使用 vmc 命令行工具或 SpringSource Tool Suite 运行应用程序。使用 vmc,您会看到如下内容
$ vmc push
Would you like to deploy from the current directory? [Yn]: y
Application Name: rabbit-chat-sample
Application Deployed URL: 'rabbit-chat-sample.cloudfoundry.com'?
Detected a Java SpringSource Spring Application, is this correct? [Yn]: y
Memory Reservation [Default:512M](64M, 128M, 256M, 512M or 1G)
Creating Application: OK
Would you like to bind any services to 'rabbit-chat-sample'? [yN]: y
Would you like to use an existing provisioned service [yN]? n
The following system services are available:
1. mongodb
2. mysql
3. postgresql
4. rabbitmq
5. redis
Please select one you wish to provision: 4
Specify the name of the service [rabbitmq-5e262]:
Creating Service: OK
Binding Service: OK
Uploading Application:
Checking for available resources: OK
Processing resources: OK
Packing application: OK
Uploading (3K): OK
Push Status: OK
Staging Application: OK
Starting Application: OK
如果使用 STS,您只需启用 Cloud Foundry 支持(可从仪表板的“Extensions”选项卡获得),然后创建一个新的服务器实例(有关所有详细信息,请参阅 入门指南),然后您可以简单地将应用程序拖到该实例上。将应用程序添加到服务器实例后,您可以通过 UI 配置和绑定服务。以下屏幕截图显示了“rabbit-chat”示例应用程序。
还记得关于“chatQueue”id 的讨论以及由于使用了 id 而不是 name 属性而生成的队列名称吗?嗯,我们在这里使用 id 而不是 name 的原因是,我们希望应用程序的每个实例都有自己独有的、实际上是匿名的队列。扇出交换机只有一个命名实例。应用程序的每个实例都会将其自己的队列绑定到该交换机。这种交换机和队列的解耦对于可扩展的云应用程序非常适用(特别是那些名称是生成的队列将在其拥有实例关闭时自动删除)。
要扩展应用程序,您可以在命令行使用 VMC
$ vmc instances rabbit-chat-sample +1
Scaling Application instances up to 2: OK
或者,您可以使用 STS 支持。这是从上面发布的相同屏幕截图中获取的“实例”配置的聚焦视图
本博客旨在成为系列文章的第一篇。在即将发布的文章中,我们将探讨以下内容