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 股票示例 来演示它。本文介绍了一个聊天示例,其中外部事件不是 AMQP 消息,而是带有聊天消息的 HTTP POST 请求。在本文的第二部分,我将切换到一个分布式聊天,其中事件是Redis 通知。

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

示例

spring-mvc-chat 示例可在 Github 上获得。虽然这不是本文的重点,但客户端使用Thymeleafknockout.js 和 jQuery。Thymeleaf 是 JSP 的一个极佳替代方案,它支持干净的 HTML 模板,并支持预览,允许设计人员双击 HTML 模板并查看它,这与需要 Servlet 容器的 JSP 不同。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);
    }
  }

}

Redist 版本的聊天将在集群中工作。可以在任何服务器上发布消息,所有其他服务器都将接收 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 时事通讯保持联系

订阅

获取支持

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

了解更多信息

即将举行的活动

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

查看全部