Spring Springboot實現websocket通訊-1

特別說明:1. 本文基於Springboot spring-boot-starter-parent 1.5.1.RELEASE編碼,在不同的版本中部分方法有區別。

第一篇地址:Spring Springboot實現websocket通訊-1

第二篇地址:Spring Springboot實現websocket通訊-2

在spring和spring boot中配置websocket的代碼基本一樣的,只是pom引入的包不一樣,需要注意的是不同的tomcat版本對websocket的支持可能有區別,造成了代碼的區別,這裡本文沒有特別深究,有興趣的朋友可以去看一下。

spring 中需要引入的包

<code> org.springframework spring-websocket ${spring.version} org.springframework spring-messaging ${spring.version} /<code>

spring boot中引入的包

<code> org.springframework.boot spring-boot-starter-websocket /<code>


配置websocket的方式

配置websocket首先是需要運行的容器支持,這個是前提,我們常用的容器,tomcat,jetty,undertow都支持websocket,spring boot 對內嵌的tomcat(7,8),jetty9,undertow提供了支持,源碼在spring-websocket-4.3.6.RELEASE.jar包中。websocket是通過一個socket來實現雙向異步通訊的,websocket屬於(sockJs:websocket協議的模擬,用作瀏覽器使用,增加了當瀏覽器不支持websocket的時候的兼容性支持,也屬於底層協議)。底層協議配置有兩種方式,使用javax.websocket包中的配置,屬於JavaEE 7中出了JSR-356:Java API for WebSocket規範。還有一種是使用spring websocket api中提供的底層協議使用@EnableWebSocket註解,實現org.springframework.web.socket.config.annotation.WebSocketConfigurer;。使用底層協議比較繁瑣,需要自己寫大量的代碼進行支持,不過更加靈活,當然我們也可以websocket的子協議STOMP來,它是一個更高級的協議,STOMP是基於幀(frame)格式來定義消息,與http的request和response類似(具有類似@RequestMapping的@MessageMapping),使用@EnableWebSocketMessageBroker 源碼也在org.springframework.web.socket下。


配置websocket

<code>package com.wzh.demo.domain; import javax.websocket.Session; /** * * * @author wzh * @version 2018-07-08 18:49 * @see [相關類/方法] (可選) **/ public class WebSocketBean { /** * 連接session對象 */ private Session session; /** * 連接錯誤次數 */ private AtomicInteger erroerLinkCount = new AtomicInteger(0); public int getErroerLinkCount() { // 線程安全,以原子方式將當前值加1,注意:這裡返回的是自增前的值 return erroerLinkCount.getAndIncrement(); } public void cleanErrorNum() { // 清空計數 erroerLinkCount = new AtomicInteger(0); } //...... 省略get set toSting方法 } /<code>

1.javax.websocket 擴展協議配置

基於Spring搭建,一個公用的websocket配置,使用@ServerEndpoint創立websocket endpoint

<code>@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }/<code>

因為websocket的session和我們常用的httpsession不一樣,所有我們要轉換一下,部分場景會用到httpsession

<code>package com.wzh.config.utils; import javax.servlet.http.HttpSession; import javax.websocket.HandshakeResponse; import javax.websocket.server.HandshakeRequest; import javax.websocket.server.ServerEndpointConfig; import javax.websocket.server.ServerEndpointConfig.Configurator; /** * * * @author wzh * @version 2018-07-10 01:02 * @see [相關類/方法] (可選) **/ public class GetHttpSessionConfigurator extends Configurator{ @Override public void modifyHandshake(ServerEndpointConfig sec,HandshakeRequest request, HandshakeResponse response) { HttpSession httpSession=(HttpSession) request.getHttpSession(); sec.getUserProperties().put(HttpSession.class.getName(),httpSession); } } /<code>

websocket業務接口,抽一些共用的方法出來

<code>package com.wzh.demo.websocket.service; import javax.websocket.EndpointConfig; import javax.websocket.Session; /** * * * @author wzh * @version 2018-07-08 17:11 * @see [相關類/方法] (可選) **/ public interface WebSocketServer { /** * 連接建立成功調用的方法 * @param session session 對象 */ public void onOpen(Session session,EndpointConfig config); /** * 斷開連接方法 */ public void onClose(Session session); /** * 收到客戶端消息後調用的方法 * @param session session 對象 * @param message 返回客戶端的消息 */ public void onMessage(Session session, String message); /** * 發生異常時觸發的方法 * @param session session 對象 * @param throwable 拋出的異常 */ public void onError(Session session,Throwable throwable); /** * 向單個客戶端發送消息 * @param session session 對象 * @param message 發送給客戶端的消息 */ public void sendMessage(Session session, String message); /** * 向所有在線用戶群發消息 * @param message 發送給客戶端的消息 */ public void batchSendMessage(String message); } /<code>

方法的實現類

<code>package com.wzh.demo.websocket.service.impl; import com.wzh.config.utils.GetHttpSessionConfigurator; import com.wzh.demo.domain.WebSocketBean; import com.wzh.demo.websocket.service.WebSocketServer; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import javax.servlet.http.HttpSession; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; /** * * * @author wzh * @version 2018-07-08 17:11 * @see [相關類/方法] (可選) **/ @ServerEndpoint(value = "/javax/websocket",configurator=GetHttpSessionConfigurator.class) @Component("webSocketService") public class WebSocketServiceImpl implements WebSocketServer{ private Logger log = Logger.getLogger(WebSocketServiceImpl.class); /** * 錯誤最大重試次數 */ private static final int MAX_ERROR_NUM = 10; /** * 用來存放每個客戶端對應的webSocket對象。 */ private static Map webSocketInfo; static { // concurrent包的線程安全map webSocketInfo = new ConcurrentHashMap(); } @OnOpen @Override public void onOpen(Session session,EndpointConfig config) { // 如果是session沒有激活的情況,就是沒有請求獲取或session,這裡可能會取出空,需要實際業務處理 HttpSession httpSession= (HttpSession) config.getUserProperties().get(HttpSession.class.getName()); if(httpSession != null) { log.info("獲取到httpsession" + httpSession.getId()); }else { log.error("未獲取到httpsession"); } // 連接成功當前對象放入websocket對象集合 WebSocketBean bean = new WebSocketBean(); bean.setSession(session); webSocketInfo.put(session.getId(),bean); log.info("客戶端連接服務器session id :"+session.getId()+",當前連接數:" + webSocketInfo.size()); } @OnClose @Override public void onClose(Session session) { // 客戶端斷開連接移除websocket對象 webSocketInfo.remove(session.getId()); log.info("客戶端斷開連接,當前連接數:" + webSocketInfo.size()); } @OnMessage @Override public void onMessage(Session session, String message) { log.info("客戶端 session id: "+session.getId()+",消息:" + message); // 此方法為客戶端給服務器發送消息後進行的處理,可以根據業務自己處理,這裡返回頁面 sendMessage(session, "服務端返回" + message); } @OnError @Override public void onError(Session session, Throwable throwable) { log.error("發生錯誤"+ throwable.getMessage(),throwable); } @Override public void sendMessage(Session session, String message) { try { // 發送消息 session.getBasicRemote().sendText(message); // 清空錯誤計數 webSocketInfo.get(session.getId()).cleanErrorNum(); } catch (Exception e) { log.error("發送消息失敗"+ e.getMessage(),e); int errorNum = webSocketInfo.get(session.getId()).getErroerLinkCount(); // 小於最大重試次數重發 if(errorNum

<= MAX_ERROR_NUM) { sendMessage(session, message); } else{ log.error("發送消息失敗超過最大次數"); // 清空錯誤計數 webSocketInfo.get(session.getId()).cleanErrorNum(); } } } @Override public void batchSendMessage(String message) { Set> set = webSocketInfo.entrySet(); for (Map.Entry map : set) { sendMessage(map.getValue().getSession(),message); } } } /<code>

觸發websocket通訊推送的方式很多,這裡做個最簡單的按鈕,寫個簡單的controller和一個html

<code>package com.wzh.demo.controller; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import javax.servlet.http.HttpSession; /** * * * @author wzh * @version 2018-07-09 22:53 * @see [相關類/方法] (可選) **/ @Controller @RequestMapping("/websocket") public class WebSocketController { @RequestMapping(value = "socket.do",method = RequestMethod.GET) public String toWebSocket(HttpSession session, Model model) { model.addAttribute("address","/javax/websocket"); return "/test/webSocket"; } } /<code>

html,主要是socketjs觸發

<code> Title /<code>

測試一下,瀏覽器訪問controller
瀏覽器控制檯輸出

IDEA控制檯

頁面點擊按鈕

通過測試可以看到我們使用底層協議創建的websocket通訊就完成了,當然這只是最簡單的通訊,實際開發中還要保證心跳等其他因素。

上面的例子是通過JAVA的擴展JAR實現的,既然是Spring項目,可定也能用框架提供的方法進行websocket通訊。

Spring websocket api 地址:

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/socket/package-summary.html


@EnableWebSocket配置

在 spring 中 使用較低層級的 API 來處理消息,可以通過以下幾個步驟

一個HandshakeInterceptor攔截器,實現org.springframework.web.socket.server.HandshakeInterceptor,在次攔截器中可以做以下握手前後的處理,此步驟可以省略,此攔截器可以在springMVC中的websocket配置類中註冊使用,做一下前置或者後置操作

<code>@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new WebSocketHander(),"/xxxx").addInterceptors(new HandshakeInterceptor()); } }/<code>WebSocketHandler 一個消息處理中心,用於處理websocket的通訊具體服務,可以繼承AbstractWebSocketHandler,也可以實現WebSocketHandler

<code>public interface WebSocketHandler { void afterConnectionEstablished(WebSocketSession session) throws Exception; void handleMessage(WebSocketSession session, WebSocketMessage> message) throws Exception; void handleTransportError(WebSocketSession session, Throwable exception) throws Exception; void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception; boolean supportsPartialMessages(); }/<code>

<code>public class ChatTextHandler extends AbstractWebSocketHandler { @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { session.sendMessage(new TextMessage("xxxx")); } }/<code>一個SpringMVC的配置,其中registerWebSocketHandlers註冊消息處理器,此方法可以完成websocket路徑的註冊,消息處理器的註冊,攔截器的註冊

<code>@Configuration @EnableWebSocket//開啟websocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { // WebSocketHander 為消息處理器,HandshakeInterceptor為攔截器 registry.addHandler(new WebSocketHander(),"/xxx").addInterceptors(new HandshakeInterceptor()); } }/<code>

下面我們來開始正式配置基於Spring底層API的websocket通訊

WebSocketHandler 攔截器

在這一步可以做一些初始化操作,例如獲取httpSession,此步驟不是開啟websocket的必要步驟,根據自身的業務邏輯決定是否添加攔截器。攔截器我們可以直接使用HttpSessionHandshakeInterceptor這個Spring提供的攔截器,也可以實現HandshakeInterceptor 這個接口進行自定義。
攔截器HttpSessionHandshakeInterceptor將HttpSession中的值保存到了一個Map裡面,在後期的WebSocketHandler消息處理類中可以獲取存入httpsession中的信息,通過WebSocketSession的getAttributes()下提供get方法獲取。

HttpSessionHandshakeInterceptor源碼

<code>// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.web.socket.server.support; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.Map; import javax.servlet.http.HttpSession; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; public class HttpSessionHandshakeInterceptor implements HandshakeInterceptor { public static final String HTTP_SESSION_ID_ATTR_NAME = "HTTP.SESSION.ID"; private final Collection attributeNames; private boolean copyAllAttributes; private boolean copyHttpSessionId = true; private boolean createSession; public HttpSessionHandshakeInterceptor() { this.attributeNames = Collections.emptyList(); this.copyAllAttributes = true; } public HttpSessionHandshakeInterceptor(Collection attributeNames) { this.attributeNames = Collections.unmodifiableCollection(attributeNames); this.copyAllAttributes = false; } public Collection getAttributeNames() { return this.attributeNames; } public void setCopyAllAttributes(boolean copyAllAttributes) { this.copyAllAttributes = copyAllAttributes; } public boolean isCopyAllAttributes() { return this.copyAllAttributes; } public void setCopyHttpSessionId(boolean copyHttpSessionId) { this.copyHttpSessionId = copyHttpSessionId; } public boolean isCopyHttpSessionId() { return this.copyHttpSessionId; } public void setCreateSession(boolean createSession) { this.createSession = createSession; } public boolean isCreateSession() { return this.createSession; } // 在握手完成前(連接建立階段) public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { HttpSession session = this.getSession(request); if (session != null) { if (this.isCopyHttpSessionId()) { // 保存 sessionid attributes.put("HTTP.SESSION.ID", session.getId()); } Enumeration names = session.getAttributeNames(); while(true) { String name; do { if (!names.hasMoreElements()) { return true; } name = (String)names.nextElement(); } while(!this.isCopyAllAttributes() && !this.getAttributeNames().contains(name)); // 保存HttpSession中的信息 attributes.put(name, session.getAttribute(name)); } } else { return true; } } // 獲取HttpSession private HttpSession getSession(ServerHttpRequest request) { if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest serverRequest = (ServletServerHttpRequest)request; return serverRequest.getServletRequest().getSession(this.isCreateSession()); } else { return null; } } // 完成握手後業務 public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) { } } /<code>

spring 框架提供的攔截器在org.springframework.web.socket.server.support下,如果不能滿足業務需求,我們也可以直接去實現接口

實現HandshakeInterceptor接口,這裡因為是操作httpsession,就演示繼承 HttpSessionHandshakeInterceptor 並重寫beforeHandshake 方法

<code>package com.wzh.demo.websocket.interceptor; import org.apache.log4j.Logger; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; import javax.servlet.http.HttpSession; import java.util.Map; /** * * * @author wzh * @version 2018-07-21 20:05 * @see [相關類/方法] (可選) */ public class WebSocketHandshakeInterceptor extends HttpSessionHandshakeInterceptor { private Logger log = Logger.getLogger(WebSocketHandshakeInterceptor.class); @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map map) throws Exception { // websocket握手建立前調用,獲取httpsession if(request instanceof ServletServerHttpRequest) { ServletServerHttpRequest servletRequset = (ServletServerHttpRequest) request; // 這裡從request中獲取session,獲取不到不創建,可以根據業務處理此段 HttpSession httpSession = servletRequset.getServletRequest().getSession(false); if (httpSession != null) { // 這裡打印一下session id 方便等下對比和springMVC獲取到httpsession是不是同一個 log.info("httpSession key:" + httpSession.getId()); // 獲取到httpsession後,可以根據自身業務,操作其中的信息,這裡只是單純的和websocket進行關聯 map.put("HTTP_SESSION",httpSession); } else { log.warn("httpSession is null"); } } // 調用父類方法 return super.beforeHandshake(request,response,webSocketHandler,map); } @Override public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) { // websocket握手建立後調用 log.info("websocket連接握手成功"); } } /<code>

WebSocketHandler 消息處理中心

建立一個websocket消息處理中心,我們可以編寫一個類實現WebSocketHandler接口,此接口提供5個方法,用於處理websocket的消息。

<code>// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.web.socket; public interface WebSocketHandler { // 在WebSocket協商成功並且WebSocket連接打開並準備好使用後調用。 void afterConnectionEstablished(WebSocketSession var1) throws Exception; // 在新的WebSocket消息到達時調用,也就是接受客戶端信息併發發送 void handleMessage(WebSocketSession var1, WebSocketMessage> var2) throws Exception; // 處理底層WebSocket消息傳輸中的錯誤,連接出現異常時觸發 void handleTransportError(WebSocketSession var1, Throwable var2) throws Exception; // 在任何一方關閉WebSocket連接之後或在發生傳輸錯誤之後調用。 void afterConnectionClosed(WebSocketSession var1, CloseStatus var2) throws Exception; // WebSocketHandler是否處理部分消息,API文檔描述說是拆分消息,多次處理,沒有實際使用過 boolean supportsPartialMessages(); } /<code>

API路徑:

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/socket/WebSocketHandler.html

除了實現接口外,我們也可以繼承Spring已經給我提供的實現類,簡化操作,因為有的時候我們只需要此接口中的一個或幾個方法,並不需要全部關注,spring提供的handler都在org.springframework.web.socket.handler這個路徑下

這裡我們繼承一個抽象類AbstractWebSocketHandler,重寫我們關注的方法,並擴展我們自己的業務方法

一個公用的websocket類,存一些連接用到的基本信息,可以根據業務添加刪除屬性

<code>package com.wzh.demo.domain; import org.springframework.web.socket.WebSocketSession; import java.util.concurrent.atomic.AtomicInteger; /** * * * @author wzh * @version 2018-07-29 18:24 * @see [相關類/方法] (可選) **/ public class WebSocketBeanSpring { private WebSocketSession session; /** * 連接錯誤次數 */ private AtomicInteger erroerLinkCount = new AtomicInteger(0); public int getErroerLinkCount() { // 線程安全,以原子方式將當前值加1,注意:這裡返回的是自增前的值 return erroerLinkCount.getAndIncrement(); } public void cleanErrorNum() { // 清空計數 erroerLinkCount = new AtomicInteger(0); } // 省略get set 方法 } /<code>

一個簡單的消息處理中心,繼承AbstractWebSocketHandler

<code>package com.wzh.demo.websocket.handler; import com.wzh.demo.domain.WebSocketBeanSpring; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import org.springframework.web.socket.*; import org.springframework.web.socket.handler.AbstractWebSocketHandler; import java.io.IOException; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * * * @author wzh * @version 2018-07-24 23:11 * @see [相關類/方法] (可選) **/ @Component("webSocketHander") public class WebSocketHander extends AbstractWebSocketHandler{ private Logger log = Logger.getLogger(WebSocketHander.class); /** * 用來存放每個客戶端對應的webSocket對象。 */ private static Map webSocketInfo; static { // concurrent包的線程安全map webSocketInfo = new ConcurrentHashMap(); } // 服務器與客戶端初次websocket連接成功執行 @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { log.debug("websocket 連接成功......"); // 連接成功當前對象放入websocket對象集合 WebSocketBeanSpring bean = new WebSocketBeanSpring(); bean.setSession(session); webSocketInfo.put(session.getId(),bean); log.info("客戶端連接服務器session id :"+session.getId()+",當前連接數:" + webSocketInfo.size()); } // 接受消息處理消息 @Override public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage> webSocketMessage) throws Exception { /* 獲取客戶端發送的消息,這裡使用文件消息,也就是字符串進行接收 消息可以通過字符串,或者字節流進行接收 TextMessage String/byte[]接收均可以 BinaryMessage byte[]接收 */ log.info("客戶端發送消息" + webSocketMessage.getPayload().toString()); TextMessage message = new TextMessage(webSocketMessage.getPayload().toString()); /* 這裡直接是字符串,做群發,如果要指定發送,可以在前臺平均ID,後臺拆分後獲取需要發送的人。 也可以做一個單獨的controller,前臺把ID傳遞過來,調用方法發送,在登錄的時候把所有好友的標識符傳遞到前臺, 然後通過標識符發送私信消息 */ this.batchSendMessage(message); } // 連接錯誤時觸發 @Override public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception { if(webSocketSession.isOpen()){ webSocketSession.close(); } log.debug("鏈接出錯,關閉鏈接......"); webSocketInfo.remove(webSocketSession.getId()); } // 關閉websocket時觸發 @Override public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception { log.debug("鏈接關閉......" + closeStatus.toString()); webSocketInfo.remove(webSocketSession.getId()); } /** * 給所有在線用戶發送消息(這裡用的文本消息) * @param message */ public void batchSendMessage(TextMessage message) { Set> setInfo = webSocketInfo.entrySet(); for (Map.Entry entry : setInfo) { WebSocketBeanSpring bean = entry.getValue(); try { bean.getSession().sendMessage(message); } catch (IOException e) { log.error(e.getMessage(),e); } } } /** * 給指定用戶發送消息 * @param userId * @param message */ public void sendMessage(String userId, TextMessage message) { WebSocketBeanSpring bean = webSocketInfo.get(userId); try { bean.getSession().sendMessage(message); } catch (IOException e) { log.error(e.getMessage(), e); } } } /<code>

WebSocketConfig 配置

此步驟是在SpringMVC中註冊消息處理中心,因為基於SpringBoot搭建,這裡使用@Configuration註解配置,當然也可以xml配置,這個根據自身項目風格進行配置,這裡我們實現WebSocketConfigurer接口

<code>package com.wzh.demo.websocket.config; import com.wzh.demo.websocket.handler.WebSocketHander; import com.wzh.demo.websocket.interceptor.WebSocketHandshakeInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; /** * * * @author wzh * @version 2018-08-05 22:59 * @see [相關類/方法] (可選) **/ @Configuration //標記為spring 配置類 @EnableWebSocket //開啟websocket支持 public class WebSocketConfig implements WebSocketConfigurer{ // 註冊消息處理器,並映射連接地址 @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { // 註冊消息處理器,並添加自定義攔截器,支持websocket的連接訪問 registry.addHandler(new WebSocketHander(), "/spring/websocket") .addInterceptors(new WebSocketHandshakeInterceptor()); /* 註冊消息處理器,並添加自定義攔截器,添加不支持websocket的連接訪問 SockJs是一個WebSocket的通信js庫,Spring對這個js庫進行了後臺的自動支持, 也就是說,如果使用SockJs,那麼我們就不需要對後臺進行更多的配置,只需要加上withSockJS()這一句就可以了 */ registry.addHandler(new WebSocketHander(), "/spring/sockjs/websocket") .addInterceptors(new WebSocketHandshakeInterceptor()).withSockJS(); } } /<code>

代碼都準備好了,下面進行測試,這裡測試就做兩個簡單的方法,一個通過消息處理中心公告,一個通過controller進行私信

一個簡單的controller 用於跳轉到html頁面

<code>// 跳轉websocket界面 @RequestMapping(value = "/spring/socket.do",method = RequestMethod.GET) public String toSpringWebSocket(HttpSession session, Model model) { model.addAttribute("address","/spring/websocket"); System.out.println("進入websocket"); return "/test/springWebSocket"; }/<code>

html頁面就用上面寫的界面,按鈕用於觸發websocket的公告方法

登錄可以看見後臺控制檯打印

點擊頁面按鈕,瀏覽器控制檯輸出

在寫個測試私信的controller,這裡為了簡單就不寫頁面了,直接get請求訪問控制器,攜帶websession id

<code>package com.wzh.demo.controller; import com.wzh.demo.websocket.handler.WebSocketHander; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.socket.TextMessage; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; /** * * * @author wzh * @version 2018-07-09 22:53 * @see [相關類/方法] (可選) **/ @Controller @RequestMapping("/websocket") public class WebSocketController { @Resource private WebSocketHander webSocketHander; // 測試私信發送 @RequestMapping(value = "/spring/socketById.do",method = RequestMethod.GET) public void toSpringWebSocketByid(HttpSession session, HttpServletRequest request, Model model) { String id = request.getParameter("id"); webSocketHander.sendMessage(id,new TextMessage("測試指定人員發送")); } } /<code>

瀏覽器直接訪問http://localhost:8080/SpringBootDemo/websocket/spring/socketById.do?id=1
查看第一個session id 的界面成功收到消息,其他界面沒有消息