一、准备内容:

1、添加 WebSocket依赖及 相关js库的 webjars 依赖:


<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
   <groupId>org.webjars</groupId>
   <artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
   <groupId>org.webjars</groupId>
   <artifactId>sockjs-client</artifactId>
   <version>1.1.2</version>
</dependency>
<dependency>
   <groupId>org.webjars</groupId>
   <artifactId>stomp-websocket</artifactId>
   <version>2.3.3-1</version>
</dependency>
<dependency>
   <groupId>org.webjars</groupId>
   <artifactId>jquery</artifactId>
   <version>3.4.1</version>
</dependency>

2、准备好聊天室HTML页面JS代码和对应的ChatRoomController控制类

3、WebSocketConfig配置类,给Spring容器注入 ServerEndpointExporter 对象;

4、WebSocket服务处理类 WebSocketServer ,并使用 @ServerEndpoint 注解;


二、HTML页面与JS代码

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" /><title></title>
<link rel="stylesheet" th:href="@{/biz/chatroom2.css}" />
</head>
<body>
   <div class="box" id="box">
      <div class="item left"><img class="header-img" th:src="@{/toux/1.jpg}" /> <span class="message">爱你呦</span></div>
      <div class="chart-timer">2019-5-17</div>
      <div class="item right"><img class="header-img" th:src="@{/toux/2.jpg}" /> <span class="message">哈哈哈哈哈</span></div>
      <div class="item left"><img class="header-img" th:src="@{/toux/1.jpg}" /> <span class="message">干嘛呢</span></div>
      <div class="item right"><img class="header-img" th:src="@{/toux/2.jpg}" /> <span class="message">吃饭饭</span></div>
      <div class="item left"><img class="header-img" th:src="@{/toux/1.jpg}" /> <span class="message">爱你呦</span></div>
   </div>
   <div class="input-box">
      <input id="msg" type="text" /><button type="button" onclick="onSend()">发送</button>
   </div>
   <script type="text/javascript" th:src="@{/webjars/jquery/jquery.min.js}"></script>
   <script type="text/javascript" th:src="@{/webjars/sockjs-client/sockjs.min.js}"></script>
   <script type="text/javascript" th:src="@{/webjars/stomp-websocket/stomp.min.js}"></script>
   <script type="text/javascript" th:src="@{/bizjs/chatroom2.js}"></script>
</body>
</html>


let socket;
if (typeof (WebSocket) == "undefined") {
   console.log("遗憾:您的浏览器不支持WebSocket");
} else {
   console.log("恭喜:您的浏览器支持WebSocket");
   // 实现化WebSocket对象
   // 指定要连接的服务器地址与端口建立连接
   // 注意ws、wss使用不同的端口。我使用自签名的证书测试,
   // 无法使用wss,浏览器打开WebSocket时报错
   // ws对应http、wss对应https。
   socket = new WebSocket("ws://localhost:18080/ws/asset");
   // 连接打开事件
   socket.onopen = function() {
      console.log("Socket 已打开");
      socket.send("消息发送测试(From Client)");
   };
   // 收到消息事件
   socket.onmessage = function(msg) {
      onReceive(msg.data);
      console.log(msg.data);
   };
   // 连接关闭事件
   socket.onclose = function() {
      console.log("Socket已关闭");
   };
   // 发生了错误事件
   socket.onerror = function() {
      alert("Socket发生了错误");
   }
   // 窗口关闭时,关闭连接
   window.unload = function() {
      socket.close();
   };
}

function onSend() {
   let msg = $("#msg").val();
   socket.send(msg);
}

/**
 * 接收消息,显示在消息对话框中 */
function onReceive(msg) {
   var div = document.getElementById('box');
   div.innerHTML = div.innerHTML + msg + '<br />';
   div.scrollTop = div.scrollHeight;
}


三、ChatRoomController控制类


package com.mall.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ChatRoomController {
   @GetMapping("/chatroom")
   public String chatroom(Model m) {
      return "chatroom";
   }
   @GetMapping("/chatroom2")
   public String chatroom2(Model m) {
      return "chatroom2";
   }
}

四、WebSocketConfig配置类:


package com.mall.websocket2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
   /**
    * 给spring容器注入这个ServerEndpointExporter对象
    * <p>
    * 检测所有带有@serverEndpoint注解的bean并注册他们。
    */
   @Bean
   public ServerEndpointExporter serverEndpointExporter() {
      System.out.println("我被注入了");
      return new ServerEndpointExporter();
   }
}

五、服务处理类 WebSocketServer:


package com.mall.websocket2;
import static com.mall.websocket.SendMsgTool.BroadCastInfo;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@ServerEndpoint(value = "/ws/asset")
@Component
@Slf4j
public class WebSocketServer {
   private static final AtomicInteger OnlineCount = new AtomicInteger(0);
   /** concurrent包的线程安全Set,用来存放每个客户端对应的Session对象。 */
   private static CopyOnWriteArraySet<Session> SessionSet = new CopyOnWriteArraySet<Session>();

   @PostConstruct
   public void init() {
      log.info("websocket 加载");
   }

   /** 连接建立成功调用的方法 */
   @OnOpen
   public void onOpen(Session session) {
      SessionSet.add(session);
      int cnt = OnlineCount.incrementAndGet(); // 在线数加1
      log.info("有连接加入,当前连接数为:{}", cnt);
//      SendMessage(session, "连接成功");
      BroadCastInfo("连接成功", SessionSet);
   }

   /** 连接关闭调用的方法 */
   @OnClose
   public void onClose(Session session) {
      SessionSet.remove(session);
      int cnt = OnlineCount.decrementAndGet();
      log.info("有连接关闭,当前连接数为:{}", cnt);
   }

   /** 收到客户端消息后调用的方法 */
   @OnMessage
   public void onMessage(String message, Session session) {
      log.info("来自客户端的消息:{}", message);
//      SendMessage(session, "收到消息,消息内容:" + message);

      BroadCastInfo(message, SessionSet);

   }

   /** 出现错误 */
   @OnError
   public void onError(Session session, Throwable error) {
      log.error("发生错误:{},Session ID: {}", error.getMessage(), session.getId());
      error.printStackTrace();
   }
}

SendMsgTool类,用来发送消息:


package com.mall.websocket;
import lombok.extern.slf4j.Slf4j;
import javax.websocket.Session;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@Slf4j
public class SendMsgTool {
   /**
    * 发送消息,实践表明,每次浏览器刷新,session会发生变化。
    * 
    * @param session
    * @param message
    */
   public static void SendMessage(Session session, String message) {
      try {
//            session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId()));
         session.getBasicRemote().sendText(message);
      } catch (IOException e) {
         log.error("发送消息出错:{}", e.getMessage());
         e.printStackTrace();
      }
   }
   /**
    * 群发消息
    * 
    * @param message
    * @throws IOException
    */
   public static void BroadCastInfo(String message, CopyOnWriteArraySet<Session> SessionSet) {
      try {
         for (Session session : SessionSet) {
            if (session.isOpen()) {
               SendMessage(session, message);
            }
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
   /**
    * 指定Session发送消息
    * 
    * @param sessionId
    * @param message
    * @throws IOException
    */
   public static void SendMessage(String message, String sessionId, CopyOnWriteArraySet<Session> SessionSet) {
      try {
         Session session = null;
         for (Session s : SessionSet) {
            if (s.getId().equals(sessionId)) {
               session = s;
               break;
            }
         }
         if (session != null) {
            SendMessage(session, message);
         } else {
            log.warn("没有找到你指定ID的会话:{}", sessionId);
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}