Spring整合WebSocket应用示例(上)

发表于2017-08-01
评论0 1.6k浏览

以下是在参与开发公司的一个crm系统,整理了相关资料,在该系统中有很多消息推送功能,在其中用到了websocket技术。特地将Spring整合WebSocket应用示例整理出来分享给给大家参考。


1. maven依赖

  1. javax.servlet
  2. javax.servlet-api
  3. 3.1.0
  4. com.fasterxml.jackson.core
  5. jackson-core
  6. 2.3.0
  7. com.fasterxml.jackson.core
  8. jackson-databind
  9. 2.3.0
  10. org.springframework
  11. spring-websocket
  12. 4.0.1.RELEASE
  13. org.springframework
  14. spring-messaging
  15. 4.0.1.RELEASE
复制代码

2. spring-servlet的配置

  1. xmlns:context="http://www.springframework.org/schema/context"
  2. xmlns:mvc="http://www.springframework.org/schema/mvc"
  3. xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:websocket="http://www.springframework.org/schema/websocket"
  5. xsi:schemaLocation="
  6. http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
  8. http://www.springframework.org/schema/context
  9. http://www.springframework.org/schema/context/spring-context-3.1.xsd
  10. http://www.springframework.org/schema/mvc
  11. http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
  12. http://www.springframework.org/schema/tx
  13. http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
  14. http://www.springframework.org/schema/websocket
  15. http://www.springframework.org/schema/websocket/spring-websocket.xsd">
  16. ......
  17. </websocket:handshake-interceptors>
复制代码

其中,path对应的路径就是前段通过ws协议调的接口路径

3. HandshakeInterceptor的实现

  1. package cn.bridgeli.websocket;
  2. import cn.bridgeli.utils.UserManager;
  3. import cn.bridgeli.util.DateUtil;
  4. import cn.bridgeli.sharesession.UserInfo;
  5. import org.apache.commons.lang.StringUtils;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8. import org.springframework.http.server.ServerHttpRequest;
  9. import org.springframework.http.server.ServerHttpResponse;
  10. import org.springframework.web.context.request.RequestContextHolder;
  11. import org.springframework.web.context.request.ServletRequestAttributes;
  12. import org.springframework.web.socket.WebSocketHandler;
  13. import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
  14. import java.util.Date;
  15. import java.util.Map;
  16. /**
  17. * @Description :创建握手(handshake)接口
  18. * @Date : 16-3-3
  19. */
  20. public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor{
  21. private static final Logger logger = LoggerFactory.getLogger(HandshakeInterceptor.class);
  22. @Override
  23. public boolean beforeHandshake(ServerHttpRequest request,
  24. ServerHttpResponse response, WebSocketHandler wsHandler,
  25. Map attributes) throws Exception {
  26. logger.info("建立握手前...");
  27. ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
  28. UserInfo currUser = UserManager.getSessionUser(attrs.getRequest());
  29. UserSocketVo userSocketVo = new UserSocketVo();
  30. String email= "";
  31. if(null != currUser){
  32. email = currUser.getEmail();
  33. }
  34. if(StringUtils.isBlank(email)){
  35. email = DateUtil.date2String(new Date());
  36. }
  37. userSocketVo.setUserEmail(email);
  38. attributes.put("SESSION_USER", userSocketVo);
  39. return super.beforeHandshake(request, response, wsHandler, attributes);
  40. }
  41. @Override
  42. public void afterHandshake(ServerHttpRequest request,
  43. ServerHttpResponse response, WebSocketHandler wsHandler,
  44. Exception ex) {
  45. logger.info("建立握手后...");
  46. super.afterHandshake(request, response, wsHandler, ex);
  47. }
  48. }
复制代码

因为老夫不是很懂,所以最大限度的保留原代码,这其实就是从单点登录中取出当前登录用户,转成UserSocketVo对象,放到Map中。所以接下来我们看看UserSocketVo对象的定义

4. UserSocketVo的定义

  1. package cn.bridgeli.websocket;
  2. import org.springframework.web.socket.WebSocketSession;
  3. import java.util.Date;
  4. /**
  5. * @Description : 用户socket连接实体
  6. * @Date : 16-3-7
  7. */
  8. public class UserSocketVo {
  9. private String userEmail; //用户邮箱
  10. private Date connectionTime; //成功连接时间
  11. private Date preRequestTime; //上次请求时间
  12. private Date newRequestTime; //新请求时间
  13. private Date lastSendTime = new Date(); //下架消息最近一次发送时间
  14. private Date lastTaskSendTime = new Date(); //待处理任务最近一次发送时间
  15. private WebSocketSession webSocketSession; //用户对应的wsSession 默认仅缓存一个
  16. // getXX and setXX
  17. }
复制代码

其中最重要的就是这个WebSocketSession这个属性了,后面我们要用到

5. WebsocketEndPoint的实现

  1. package cn.bridgeli.websocket;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.socket.CloseStatus;
  6. import org.springframework.web.socket.TextMessage;
  7. import org.springframework.web.socket.WebSocketSession;
  8. import org.springframework.web.socket.handler.TextWebSocketHandler;
  9. /**
  10. * @Description : websocket处理类
  11. * @Date : 16-3-3
  12. */
  13. public class WebsocketEndPoint extends TextWebSocketHandler{
  14. private static final Logger logger = LoggerFactory.getLogger(WebsocketEndPoint.class);
  15. @Autowired
  16. private NewsListenerImpl newsListener;
  17. @Override
  18. protected void handleTextMessage(WebSocketSession session,
  19. TextMessage message) throws Exception {
  20. super.handleTextMessage(session, message);
  21. TextMessage returnMessage = new TextMessage(message.getPayload() " received at server");
  22. session.sendMessage(returnMessage);
  23. }
  24. /**
  25. * @Description : 建立连接后
  26. * @param session
  27. * @throws Exception
  28. */
  29. @Override
  30. public void afterConnectionEstablished(WebSocketSession session) throws Exception{
  31. UserSocketVo userSocketVo = (UserSocketVo)session.getAttributes().get("SESSION_USER");
  32. if(null != userSocketVo){
  33. userSocketVo.setWebSocketSession(session);
  34. if(WSSessionLocalCache.exists(userSocketVo.getUserEmail())){
  35. WSSessionLocalCache.remove(userSocketVo.getUserEmail());
  36. }
  37. WSSessionLocalCache.put(userSocketVo.getUserEmail(), userSocketVo);
  38. newsListener.afterConnectionEstablished(userSocketVo.getUserEmail());
  39. }
  40. logger.info("socket成功建立连接...");
  41. super.afterConnectionEstablished(session);
  42. }
  43. @Override
  44. public void afterConnectionClosed(WebSocketSession session,CloseStatus status) throws Exception{
  45. UserSocketVo userSocketVo = (UserSocketVo)session.getAttributes().get("SESSION_USER");
  46. if(null != userSocketVo){
  47. WSSessionLocalCache.remove(userSocketVo.getUserEmail());
  48. }
  49. logger.info("socket成功关闭连接...");
  50. super.afterConnectionClosed(session, status);
  51. }
  52. }
复制代码

6. WSSessionLocalCache的实现

  1. package cn.bridgeli.websocket;
  2. import java.io.Serializable;
  3. import java.util.ArrayList;
  4. import java.util.HashMap;
  5. import java.util.List;
  6. import java.util.Map;
  7. /**
  8. * @Description :本地缓存WebSocketSession实例
  9. * @Date : 16-3-7
  10. */
  11. public class WSSessionLocalCache implements Serializable {
  12. private static Map wsSessionCache = new HashMap<>();
  13. public static boolean exists(String userEmail){
  14. if(!wsSessionCache.containsKey(userEmail)){
  15. return false;
  16. }else{
  17. return true;
  18. }
  19. }
  20. public static void put(String userEmail, UserSocketVo UserSocketVo){
  21. wsSessionCache.put(userEmail, UserSocketVo);
  22. }
  23. public static UserSocketVo get(String userEmail){
  24. return wsSessionCache.get(userEmail);
  25. }
  26. public static void remove(String userEmail){
  27. wsSessionCache.remove(userEmail);
  28. }
  29. public static List getAllSessions(){
  30. return new ArrayList<>(wsSessionCache.values());
  31. }
  32. }
复制代码

看了其实现,作用就比较明显了吧,存放每个UserSocketVo的最新数据,其实到这里我们websocket的实现已经算完了,但还有一个核心类(关于业务逻辑查理的类)没有实现,下篇Spring整合WebSocket应用示例(下),我们就看怎么实现这个类。

WebSocket协议介绍

WebSocket协议是RFC-6455规范定义的一个Web领域的重要的功能:全双工,即客户端和服务器之间的双向通信。它是一个令人兴奋的功能,业界在此领域上已经探索很久,使用的技术包括Java Applet、XMLHttpRequest、Adobe Flash、ActiveXObject、各种Comet技术、服务器端的发送事件等。

需要理解一点,在使用WebSocket协议前,需要先使用HTTP协议用于构建最初的握手。这依赖于一个机制——建立HTTP,请求协议升级(或叫协议转换)。当服务器同意后,它会响应HTTP状态码101,表示同意切换协议。假设通过TCP套接字成功握手,HTTP协议升级请求通过,那么客户端和服务器端都可以彼此互发消息。

Spring框架4.0以上版本引入了一个新模块,即spring-websocket模块。它对WebSocket通信提供了支持。它兼容Java WebSocket API规范JSR-356,同时提供了额外的功能。

什么场景下该使用WebSocket

在Web应用中,客户端和服务器端需要以较高频率和较低延迟来交换事件时,适合用WebSocket。因此WebSocket适合财经、游戏、协作等应用场景。
对于其他应用场景则未必适合。例如,某个新闻订阅需要显示突发新闻,使用间隔几分钟的长轮询也是可以的,这里的延迟可以接受。
即使在要求低延迟的应用场景,如果传输的消息数很低(比如监测网络故障的场景),那么应该考虑使用长轮询技术。

而只有在低延迟和高频消息通信的场景下,选用WebSocket协议才是非常适合的。即使是这样的应用场景,仍然存在是选择WebSocket通信呢?又或者是选择REST HTTP通信呢?
答案是会根据应用程序的需求而定。但是,也可能同时使用这两种技术,把需要频繁交换的数据放到WebSocket中实现,而把REST API作为过程性的业务的实现技术。另外,当REST API的调用中需要把某个信息广播给多个客户端是,也可以通过WebSocket连接来实现。

Spring框架提供了@Controller注释和@RestController注释,两者都可以用于HTTP请求的处理以及WebSocket消息的处理。另外,Spring MVC的请求处理方法,或其它应用程序的请求处理方法,都可以很容易地使用WebSocket协议来广播消息到所有感兴趣的客户端或指定用户。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引