领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多上周,RabbitMQ 作为 Cloud Foundry 上的服务正式上线的消息被宣布。现在,任何运行在 Cloud Foundry 上的应用程序都可以通过 RabbitMQ 代理发送和接收消息,该代理可以通过单个命令(例如 'vmc create-service rabbitmq')进行配置。消息服务的实例可以在应用程序之间共享,而且由于 RabbitMQ 是一个基于协议的代理,这些应用程序甚至可以用不同的语言编写。因此,对于那些对在云中运行的模块化、多语言、事件驱动的应用程序感兴趣的人来说,这是一个令人兴奋的消息。我将发布一系列关注这类应用程序的博客文章。在本文中,我将保持简单,重点关注 Spring 开发人员的初始体验。
首先,我建议您查看本教程,这是即使您以前没有 Cloud Foundry 经验的最佳入门方法。在那里,您将看到如何使用 Maven 构建一个简单的 Spring 应用程序,并使用 VMC 命令行工具将其部署到 Cloud Foundry。然后,该应用程序引入了 RabbitMQ,并增强了其 MVC 控制器以发布和检索消息。它展示了如何通过 Spring AMQP 库配置和使用 RabbitMQ 服务。
此外,在 Cloud Foundry 正式发布的当天(与这些博客 文章同一天),我发布了另一篇博客,其中涵盖了“cloud”命名空间支持的基础知识。阅读这篇文章也可能有助于您了解接下来将要看到的内容。具体来说,我们扩展了“cloud”命名空间以包含对 RabbitMQ ConnectionFactory 的支持,这将在下面的配置概述中介绍。
现在,我想介绍另一个演示简单聊天服务器的示例应用程序。RabbitMQ 为多功能聊天应用程序提供了强大的支持,因为它支持不同类型的交换机,例如“direct”/点对点、“topic” based 发布/订阅和用于简单广播的“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
});
如果您克隆存储库并 cd 到 '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-mapping ("/")。
如您所见,只有一个名为“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()。
对相对 URL“/publish”的 HTTP POST 请求将调用 publish(..) 方法,并且它需要请求中的两个参数:username 和 text。这些参数由您在上一节中看到的 HTML 表单提供。它由 chat.jsp 渲染。publish 方法只不过是将 username + text 值连接成一个字符串,将其转换为 AMQP Message 并由 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”元素使带有 @Controller 注解的类能够注册为 Spring 管理的对象,并且它还激活了对 @Autowired 的支持。带有“mvc”前缀的两个元素只是设置 MVC @RequestMapping 支持并启用静态资源的加载(在本例中用于“resources/static/js”目录中提供的 jQuery 支持)。
其余元素与 RabbitMQ 配置相关。“rabbit:admin”生成一个 RabbitAdmin 实例,负责识别在同一应用程序上下文中定义的交换机、队列和绑定。请注意,queue 元素的 **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 正文的转换。由于该方法参数不需要接受实际的 Message 实例,并且方法名称可以是任何我们想要的名称,因此我们将此称为“消息驱动的 POJO”。换句话说,它不直接依赖于消息传递 API。侦听器容器调用它是一种控制反转的形式。
最后,是 Connection Factory 配置。在本例中,我们使用“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 支持(可从仪表板的“扩展”选项卡获得),然后创建一个新的服务器实例(有关所有详细信息,请参阅入门指南),您可以简单地将应用程序拖到该实例。将应用程序添加到服务器实例后,您可以通过 UI 配置和绑定服务。以下屏幕截图显示了“rabbit-chat”示例应用程序。
还记得关于“chatQueue”id 的讨论以及如何生成队列名称,因为它使用的是 id 而不是 name 属性吗?好吧,我们在这里使用 id 而不是 name 的原因是我们希望应用程序的每个实例都有自己的专用队列,并且实际上是匿名的。只有一个名为 Fanout Exchange 的实例。应用程序的每个实例都将其自己的队列绑定到该交换机。这种交换机和队列的解耦非常适合可扩展的云应用程序(尤其是在关闭拥有实例时,名称生成的队列将自动删除)。
要扩展应用程序,您可以在命令行使用 VMC
$ vmc instances rabbit-chat-sample +1
Scaling Application instances up to 2: OK
或者,您可以使用 STS 支持。以下是上面发布的同一屏幕截图中“实例”配置的重点视图。
本博客旨在作为系列文章的第一篇。在接下来的文章中,我们将探讨以下内容: