云端聊天:第一部分

工程 | Mark Fisher | 2011 年 8 月 16 日 | ...

上周,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 仓库克隆示例。

“rabbit-chat”示例应用程序

这是运行中的应用程序的样子

该表单将使用 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
});

Java 代码

如果您克隆仓库并进入“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 文件声明了依赖项。特别值得关注的是以下几项

  • spring-webmvc (3.0.5.RELEASE)
  • spring-rabbit (1.0.0.RC3)
  • cloudfoundry-runtime (0.7.1)

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(..) 方法负责将这些聊天消息排队,因此它是连接到底层消息监听器的方法。这几乎涵盖了应用程序的功能。

Spring 配置

现在,我们可以详细介绍此应用程序的配置。这本可以完全用 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 支持。这是从上面发布的相同屏幕截图中获取的“实例”配置的聚焦视图

下一步是什么?

本博客旨在成为系列文章的第一篇。在即将发布的文章中,我们将探讨以下内容

  • 增强消息传递功能,超越目前全局广播的范围,以演示点对点消息传递(用于与单个命名用户聊天)以及通过动态分配的交换机表示“聊天室”的发布/订阅。
  • 添加一个与 Java 应用程序并存的 Node.js 应用程序,并参与同一个聊天,因为这两个应用程序都绑定到同一个 RabbitMQ 服务实例。
  • 包含 Spring 3.1 配置文件支持,以展示如何修改同一个配置文件,使其在 Cloud Foundry 或替代部署(例如本地 Tomcat 实例)上同样良好地工作。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获得支持

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

了解更多

即将举行的活动

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

查看所有