Spring MVC 3.2 预览:聊天示例

工程 | Rossen Stoyanchev | 2012年5月16日 | ...

最后更新于 2012 年 11 月 5 日 (Spring MVC 3.2 RC1)

之前的 博客 文章 中,我介绍了 Spring MVC 3.2 中基于 Servlet 3 的异步支持,并使用了 spring-mvc-showcase 和 Spring AMQP stocks 示例 来演示。本文将介绍一个聊天示例,其中外部事件不是 AMQP 消息,而是带有聊天消息的 HTTP POST 请求。在本文的第二部分,我将切换到一个分布式聊天,其中事件是 Redis 通知。

聊天并不是 Web 应用程序的常见需求。然而,它是一个只能通过实时通知满足的需求的良好示例。它比电子邮件或状态警报对时间延迟更敏感,而且在浏览器中与朋友聊天,或者在网络研讨会期间与同事聊天,或者与购物网站上的真人聊天的情况并不少见。您可以想象其他类型的在线协作。

示例

spring-mvc-chat 示例在 Github 上可用。虽然不是本文的重点,但客户端使用了 Thymeleafknockout.js 和 jQuery。Thymeleaf 是 JSP 的一个极好的替代品,它支持干净的 HTML 模板,并支持预览,允许设计师双击 HTML 模板查看它,而不像 JSP 需要 Servlet 容器。knockout.js 是一个客户端 MVC 框架,非常方便地为 HTML 元素添加行为。要快速了解它,请遵循其出色的 教程 之一。jQuery 用于 DOM 脚本和 Ajax 请求。

ChatController

ChatController 暴露了获取和发布聊天消息的操作。这是获取消息的方法:


@RequestMapping(method=RequestMethod.GET)
@ResponseBody
public DeferredResult<List<String>> getMessages(@RequestParam int messageIndex) {

  final DeferredResult<List<String>> deferredResult = new DeferredResult<List<String>>(null, Collections.emptyList());
  this.chatRequests.put(deferredResult, messageIndex);

  deferredResult.onCompletion(new Runnable() {
    @Override
    public void run() {
      chatRequests.remove(deferredResult);
    }
  });

  List<String> messages = this.chatRepository.getMessages(messageIndex);
  if (!messages.isEmpty()) {
    deferredResult.setResult(messages);
  }

  return deferredResult;
}

创建一个新的 DeferredResult,并将其保存在一个 Map 中,注册的 onCompletion 回调将在异步请求完成后将其移除。然后,该方法使用内存中的 ChatRepository 检查新消息。如果找到新消息,DeferredResult 将立即设置。否则,它将在新消息到达时稍后设置。

下面是保存聊天消息并更新所有已保存 DeferredResult 实例的方法:


@RequestMapping(method=RequestMethod.POST)
@ResponseBody
public void postMessage(@RequestParam String message) {

  this.chatRepository.addMessage(message);

  // Update all chat requests as part of the POST request
  // See Redis branch for a more sophisticated, non-blocking approach

  for (Entry<DeferredResult<List<String>>, Integer> entry : this.chatRequests.entrySet()) {
    List<String> messages = this.chatRepository.getMessages(entry.getValue());
    entry.getKey().setResult(messages);
  }
}

分布式聊天

上面的聊天示例使用了简单的内存持久化,并且仅在部署到单个服务器时有效。 redis 分支 使用了一个基于 Redis 的 ChatRepositoryRedis 是一个简单的键值存储,借助 Spring Redis 项目,在 Java 中使用起来非常方便。

RedisChatRepository 使用 Spring Redis 的 RedisTemplate 来查找和保存聊天消息。请随时 查看 代码。

保存新聊天消息的控制器方法现在只有一行:


@RequestMapping(method=RequestMethod.POST)
@ResponseBody
public void postMessage(@RequestParam String message) {
  this.chatRepository.addMessage(message);
}

接收新消息也非常简单。这涉及到实现 Spring Redis 的 MessageListener 接口,可以直接在控制器中完成:


@Controller
@RequestMapping("/mvc/chat")
public class ChatController implements MessageListener {

  // ...

  public void onMessage(Message message, byte[] pattern) {
    for (Entry<DeferredResult<List<String>>, Integer> entry : this.chatRequests.entrySet()) {
      List<String> messages = this.chatRepository.getMessages(entry.getValue());
      entry.getKey().setResult(messages);
    }
  }

}

聊天程序的 Redis 版本可以在集群中运行。消息可以发布到任何服务器,所有其他服务器都将收到 Redis 通知。Spring Redis 项目使得以消息驱动的 POJO 风格接收这些通知变得非常简单。

也可以从非 Java 的 Redis 客户端发布聊天消息。例如,使用 Redis 命令行 shell 连接,输入以下命令,聊天消息将分发到所有订阅的服务器和连接的浏览器:

redis 127.0.0.1:6379> RPUSH chat:archive "hello from the redis cli"
redis 127.0.0.1:6379> PUBLISH chat "a new chat message is available"

以上是关于 Spring MVC 3.2、基于 Servlet 3 的异步支持的博客文章的全部内容。感谢您的阅读!

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

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

了解更多

获得支持

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

了解更多

即将举行的活动

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

查看所有