Spring WebSocket教程(二)
实现目标
第一步:聊天实现原理
第二步:服务端基础代码
- public class UserChatCommand {
- private String name;
- private String chatContent;
- private String coordinationId;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getChatContent() {
- return chatContent;
- }
- public void setChatContent(String chatContent) {
- this.chatContent = chatContent;
- }
- public String getCoordinationId() {
- return coordinationId;
- }
- public void setCoordinationId(String coordinationId) {
- this.coordinationId = coordinationId;
- }
- @Override
- public String toString() {
- return "UserChatCommand{" +
- "name='" + name + '\'' +
- ", chatContent='" + chatContent + '\'' +
- ", coordinationId='" + coordinationId + '\'' +
- '}';
- }
- }
通过这个bean来接收到web端发送的消息,然后在服务端转发,接下来就是转发的逻辑了,不过首先需要介绍一下Spring WebSocket的一个annotation。
- /**
- * WebSocket聊天的相应接收方法和转发方法
- *
- * @param userChat 关于用户聊天的各个信息
- */
- @MessageMapping("/userChat")
- public void userChat(UserChatCommand userChat) {
- //找到需要发送的地址
- String dest = "/userChat/chat" + userChat.getCoordinationId();
- //发送用户的聊天记录
- this.template.convertAndSend(dest, userChat);
- }
怎么这么简单?呵呵,能够这么简单的实现后台代码,全是Spring的功劳。首先,我们约定好发送地址的规则,就是chat后面跟上之前发送过来的id,然后通过这个“template”来进行转发,这个“template”是Spring实现的一个发送模板类:SimpMessagingTemplate,在我们定义controller的时候,可以在构造方法中进行注入:
- @Controller
- public class CoordinationController {
- ......
- //用于转发数据(sendTo)
- private SimpMessagingTemplate template;
- <pre name="code" class="java"> @Autowired
- public CoordinationController(SimpMessagingTemplate t) {
- template = t;
- }
- .....
- }
现在就已经将用户发送过来的聊天信息转发到了一个约定的空间内,只要web端的用户订阅的是这个空间的地址,那么就会收到转发过来的json。现在来看看web端需要做什么吧。
第三步:Web端代码
),并且绑定到发送按钮onclick事件。我们要做的事情大概是以下几步:- //发送聊天信息
- function sendName() {
- var input = $('#chat_input');
- var inputValue = input.val();
- input.val("");
- stompClient.send("/app/userChat", {}, JSON.stringify({
- 'name': encodeURIComponent(name),
- 'chatContent': encodeURIComponent(inputValue),
- 'coordinationId': coordinationId
- }));
- }
其中,name和coordinationId是相应的用户信息,可以通过ajax或者jsp获取,这里就不多说了。
- //用户聊天订阅
- stompClient.subscribe('/userChat/chat' + coordinationId, function (chat) {
- showChat(JSON.parse(chat.body));
- });
将消息体转为json,再写一个显示聊天信息的方法就可以了,显示聊天信息的方法不再解释,如下:
- //显示聊天信息
- function showChat(message) {
- var response = document.getElementById('chat_content');
- response.value += decodeURIComponent(message.name) + ':' + decodeURIComponent(message.chatContent) + '\n';
- }
因为之前处理中文问题,所以发到后台的数据是转码了的,从后台发回来之后,也需要将编码转回来。
第四步:聊天记录缓存实现
- private Map<Integer, Object[]> coordinationCache = new HashMap<Integer, Object[]>();
这里我存的是一个Object数组,是因为我写的程序中,除了聊天信息的缓存,还有很多东西要缓存,只是将聊天信息的缓存放在了这个数组中的一个位置里。
我反正没在jdk中看到),所以我就自己实现了这样的一个队列,实现非常的简单,类名叫LimitQueue,使用泛型,继承自Queue,类中定义两个成员变量:- private int limit;
- private Queue<E> queue;
limit代表队列的上限,queue是真正使用的队列。创建一个由这两个参数形成的构造方法,并且实现Queue的所有方法,所有的方法都由queue对象去完成,比如:
- @Override
- public int size() {
- return queue.size();
- }
- @Override
- public boolean isEmpty() {
- return queue.isEmpty();
- }
其中,有一个方法需要做处理:
- @Override
- public boolean offer(E e) {
- if (queue.size() >= limit) {
- queue.poll();
- }
- return queue.offer(e);
- }
加入元素的时候,判断是否达到了上限,达到了的话就先出队列,再入队列。这样,就实现了固定大小的队列,并且总是保持最新的记录。
- /**
- * WebSocket聊天的相应接收方法和转发方法
- *
- * @param userChat 关于用户聊天的各个信息
- */
- @MessageMapping("/userChat")
- public void userChat(UserChatCommand userChat) {
- //找到需要发送的地址
- String dest = "/userChat/chat" + userChat.getCoordinationId();
- //发送用户的聊天记录
- this.template.convertAndSend(dest, userChat);
- //获取缓存,并将用户最新的聊天记录存储到缓存中
- Object[] cache = coordinationCache.get(Integer.parseInt(userChat.getCoordinationId()));
- try {
- userChat.setName(URLDecoder.decode(userChat.getName(), "utf-8"));
- userChat.setChatContent(URLDecoder.decode(userChat.getChatContent(), "utf-8"));
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- ((LimitQueue<UserChatCommand>) cache[1]).offer(userChat);
- }
已经有缓存了,只要在页面上取出缓存就能显示聊天记录了,可以通过ajax或者jsp等方法,不过,WebSocket也有方法可以实现,因为Spring WebSocket提供了一个叫SubscribeMapping的annotation,这个annotation标记的方法,是在订阅的时候调用的,也就是说,基本是只执行一次的方法,很适合我们来初始化聊天记录。所以,在订阅聊天信息的代码下面,可以增加一个初始化聊天记录的方法。我们先写好web端的代码:
- //初始化
- stompClient.subscribe('/app/init/' + coordinationId, function (initData) {
- console.log(initData);
- var body = JSON.parse(initData.body);
- var chat = body.chat;
- chat.forEach(function(item) {
- showChat(item);
- });
- });
这次订阅的地址是init,还是加上coordinationId来区分空间,发送过来的数据是一个聊天记录的数组,循环显示在对话框中。有了web端代码的约束,后台代码也基本出来了,只要使用SubscribeMapping,再组装一下数据就完成了,后台代码如下:
- /**
- * 初始化,初始化聊天记录
- *
- * @param coordinationId 协同空间的id
- */
- @SubscribeMapping("/init/{coordinationId}")
- public Map<String,Object> init(@DestinationVariable("coordinationId") int coordinationId) {
- System.out.println("------------新用户进入,空间初始化---------");
- Map<String, Object> document = new HashMap<String, Object>();
- document.put("chat",coordinationCache.get(coordinationId)[1]);
- return document;
- }
就这样,缓存聊天记录也实现了。
结语

Spring WebSocket教程(二)的更多相关文章
- Spring Security教程(二):自定义数据库查询
Spring Security教程(二):自定义数据库查询 Spring Security自带的默认数据库存储用户和权限的数据,但是Spring Security默认提供的表结构太过简单了,其实就 ...
- Spring Security教程(二):通过数据库获得用户权限信息
上一篇博客中,Spring Security教程(一):初识Spring Security,我把用户信息和权限信息放到了xml文件中,这是为了演示如何使用最小的配置就可以使用Spring Securi ...
- Spring Security教程(二)
上一篇博客中,Spring Security教程(一),我把用户信息和权限信息放到了xml文件中,这是为了演示如何使用最小的配置就可以使用Spring Security,而实际开发中,用户信息和权限信 ...
- Spring WebSocket教程(一)
学习背景 很久以前就知道WebSocket,但那时不论是浏览器还是开发技术对它的支持都还很少.但是,Spring4突然发布,让我眼前一亮,Spring4直接支持WebSocket. 对于Spring我 ...
- WebSocket教程(二)
运行环境:jdk8 tomcat8 无须其他jar包. package com.reach.socketController; import java.io.IOException; import j ...
- Spring WebSocket入门(二) 转载
本文转载自:http://www.jianshu.com/p/8500ad65eb50 WebSocket前端准备 前端我们需要用到两个js文件:sockjs.js和stomp.js SockJS:S ...
- web即时通讯2--基于Spring websocket达到web聊天室
如本文所用,Spring4和websocket要构建web聊天室,根据框架SpringMVC+Spring+Hibernate的Maven项目,后台使用spring websocket进行消息转发和聊 ...
- Spring Security教程(三):自定义表结构
在上一篇博客中讲解了用Spring Security自带的默认数据库存储用户和权限的数据,但是Spring Security默认提供的表结构太过简单了,其实就算默认提供的表结构很复杂,也不一定能满足项 ...
- Spring Security教程(三)
在上一篇博客中讲解了用Spring Security自带的默认数据库存储用户和权限的数据,但是Spring Security默认提供的表结构太过简单了,其实就算默认提供的表结构很复杂,也不一定能满足项 ...
随机推荐
- Nginx启动/重启脚本详解
Nginx手动启动 停止操作 停止操作是通过向nginx进程发送信号(什么是信号请参阅linux文 章)来进行的步骤1:查询nginx主进程号ps -ef | grep nginx在进程列表里 面找m ...
- vue 关于deep watch / computed 监听不到 vuex state 对象变化的的问题
简而言之,如果vuex state 中是一个对象 {},那么监听就会有问题.先给出解决方案: // 超简易拷贝(如果是深拷贝还多此一举把get/set拷贝进去了,所以用简易拷贝即可) let __VA ...
- C#基础课程之三循环语句
for循环: ; i < ; i++) { Console.WriteLine("执行"+i+"次"); } while循环: while (true) ...
- MySQL通过Explain查看select语句的执行计划结果触发写操作
[背景] 某某同学执行了一下Explain结果结果发现数据库有了一条写入操作,恭喜这位同学你的锅到货了,你签收一下: 对! 你没有听错,在一种场景下就算是Explain也会引发数据的写操作,就这是外层 ...
- 已安装 SQL Server 2005,安装 SQL Server 2008 时提示需要删除 SQL Server 2005 Express 工具
错误提示:已安装 SQL Server 2005 Express 工具.若要继续,请删除 SQL Server 2005 Express 工具. 解决方案: 修改注册表:HKLM\Software ...
- React Native在开发过程中遇到的一些问题(俗称:坑)
4900 服务器地址错误 运行时产生以下错误:Could not connect to development server. 1.URL地址设置 问题: Could not connect to d ...
- 安装 xcode 5.1.1
https://developer.apple.com/downloads/ 切换路径xcode 路径.然并卵,不好用 http://cms.35g.tw/coding/xcode-select-%E ...
- Binlog中最容易踩到的坑
MySQL高可用架构中,主库复制是非常常见的一种. 当主库宕机后,可以提升一个从库作为新的主库,保证服务可用性:同时可以通过扩展从库,提高整个集群的QPS. 在主从复制架构下,MySQL通过binlo ...
- chorme插件 ,在浏览器上模拟手机,pad 查看网页|前端技术开发必备插件
网址:http://lab.maltewassermann.com/viewport-resizer/使用方法:1在chrome浏览器上方右击,显示书签栏 2 打开插件网址,将<ignore_j ...
- Python爬取猫眼top100排行榜数据【含多线程】
# -*- coding: utf-8 -*- import requests from multiprocessing import Pool from requests.exceptions im ...