Spring整合WebSocket應(yīng)用示例(上)
以下教程是小編在參與開發(fā)公司的一個crm系統(tǒng),整理些相關(guān)資料,在該系統(tǒng)中有很多消息推送功能,在其中用到了websocket技術(shù)。下面小編整理分享到腳本之家平臺供大家參考
1. maven依賴
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>4.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>4.0.1.RELEASE</version> </dependency>
2. spring-servlet的配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:websocket="http://www.springframework.org/schema/websocket" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd"> ...... <!-- websocket --> <bean id="websocket" class="cn.bridgeli.websocket.WebsocketEndPoint"/> <websocket:handlers> <websocket:mapping path="/websocket" handler="websocket"/> <websocket:handshake-interceptors> <bean class="cn.bridgeli.websocket.HandshakeInterceptor"/> </websocket:handshake-interceptors> </websocket:handlers> </beans>
其中,path對應(yīng)的路徑就是前段通過ws協(xié)議調(diào)的接口路徑
3. HandshakeInterceptor的實現(xiàn)
package cn.bridgeli.websocket; import cn.bridgeli.utils.UserManager; import cn.bridgeli.util.DateUtil; import cn.bridgeli.sharesession.UserInfo; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; import java.util.Date; import java.util.Map; /** * @Description :創(chuàng)建握手(handshake)接口 * @Date : 16-3-3 */ public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor{ private static final Logger logger = LoggerFactory.getLogger(HandshakeInterceptor.class); @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { logger.info("建立握手前..."); ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); UserInfo currUser = UserManager.getSessionUser(attrs.getRequest()); UserSocketVo userSocketVo = new UserSocketVo(); String email= ""; if(null != currUser){ email = currUser.getEmail(); } if(StringUtils.isBlank(email)){ email = DateUtil.date2String(new Date()); } userSocketVo.setUserEmail(email); attributes.put("SESSION_USER", userSocketVo); return super.beforeHandshake(request, response, wsHandler, attributes); } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) { logger.info("建立握手后..."); super.afterHandshake(request, response, wsHandler, ex); } }
因為老夫不是很懂,所以最大限度的保留原代碼,這其實就是從單點登錄中取出當前登錄用戶,轉(zhuǎn)成UserSocketVo對象,放到Map中。所以接下來我們看看UserSocketVo對象的定義
4. UserSocketVo的定義
package cn.bridgeli.websocket; import org.springframework.web.socket.WebSocketSession; import java.util.Date; /** * @Description : 用戶socket連接實體 * @Date : 16-3-7 */ public class UserSocketVo { private String userEmail; //用戶郵箱 private Date connectionTime; //成功連接時間 private Date preRequestTime; //上次請求時間 private Date newRequestTime; //新請求時間 private Date lastSendTime = new Date(); //下架消息最近一次發(fā)送時間 private Date lastTaskSendTime = new Date(); //待處理任務(wù)最近一次發(fā)送時間 private WebSocketSession webSocketSession; //用戶對應(yīng)的wsSession 默認僅緩存一個 // getXX and setXX }
其中最重要的就是這個WebSocketSession這個屬性了,后面我們要用到
5. WebsocketEndPoint的實現(xiàn)
package cn.bridgeli.websocket; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; /** * @Description : websocket處理類 * @Date : 16-3-3 */ public class WebsocketEndPoint extends TextWebSocketHandler{ private static final Logger logger = LoggerFactory.getLogger(WebsocketEndPoint.class); @Autowired private NewsListenerImpl newsListener; @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { super.handleTextMessage(session, message); TextMessage returnMessage = new TextMessage(message.getPayload()+" received at server"); session.sendMessage(returnMessage); } /** * @Description : 建立連接后 * @param session * @throws Exception */ @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception{ UserSocketVo userSocketVo = (UserSocketVo)session.getAttributes().get("SESSION_USER"); if(null != userSocketVo){ userSocketVo.setWebSocketSession(session); if(WSSessionLocalCache.exists(userSocketVo.getUserEmail())){ WSSessionLocalCache.remove(userSocketVo.getUserEmail()); } WSSessionLocalCache.put(userSocketVo.getUserEmail(), userSocketVo); newsListener.afterConnectionEstablished(userSocketVo.getUserEmail()); } logger.info("socket成功建立連接..."); super.afterConnectionEstablished(session); } @Override public void afterConnectionClosed(WebSocketSession session,CloseStatus status) throws Exception{ UserSocketVo userSocketVo = (UserSocketVo)session.getAttributes().get("SESSION_USER"); if(null != userSocketVo){ WSSessionLocalCache.remove(userSocketVo.getUserEmail()); } logger.info("socket成功關(guān)閉連接..."); super.afterConnectionClosed(session, status); } }
6. WSSessionLocalCache的實現(xiàn)
package cn.bridgeli.websocket; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @Description :本地緩存WebSocketSession實例 * @Date : 16-3-7 */ public class WSSessionLocalCache implements Serializable { private static Map<String, UserSocketVo> wsSessionCache = new HashMap<>(); public static boolean exists(String userEmail){ if(!wsSessionCache.containsKey(userEmail)){ return false; }else{ return true; } } public static void put(String userEmail, UserSocketVo UserSocketVo){ wsSessionCache.put(userEmail, UserSocketVo); } public static UserSocketVo get(String userEmail){ return wsSessionCache.get(userEmail); } public static void remove(String userEmail){ wsSessionCache.remove(userEmail); } public static List<UserSocketVo> getAllSessions(){ return new ArrayList<>(wsSessionCache.values()); } }
看了其實現(xiàn),作用就比較明顯了吧,存放每個UserSocketVo的最新數(shù)據(jù),其實到這里我們websocket的實現(xiàn)已經(jīng)算完了,但還有一個核心類(關(guān)于業(yè)務(wù)邏輯查理的類)沒有實現(xiàn),下篇Spring整合WebSocket應(yīng)用示例(下),我們就看怎么實現(xiàn)這個類。
WebSocket協(xié)議介紹
WebSocket協(xié)議是RFC-6455規(guī)范定義的一個Web領(lǐng)域的重要的功能:全雙工,即客戶端和服務(wù)器之間的雙向通信。它是一個令人興奮的功能,業(yè)界在此領(lǐng)域上已經(jīng)探索很久,使用的技術(shù)包括Java Applet、XMLHttpRequest、Adobe Flash、ActiveXObject、各種Comet技術(shù)、服務(wù)器端的發(fā)送事件等。
需要理解一點,在使用WebSocket協(xié)議前,需要先使用HTTP協(xié)議用于構(gòu)建最初的握手。這依賴于一個機制——建立HTTP,請求協(xié)議升級(或叫協(xié)議轉(zhuǎn)換)。當服務(wù)器同意后,它會響應(yīng)HTTP狀態(tài)碼101,表示同意切換協(xié)議。假設(shè)通過TCP套接字成功握手,HTTP協(xié)議升級請求通過,那么客戶端和服務(wù)器端都可以彼此互發(fā)消息。
Spring框架4.0以上版本引入了一個新模塊,即spring-websocket模塊。它對WebSocket通信提供了支持。它兼容Java WebSocket API規(guī)范JSR-356,同時提供了額外的功能。
什么場景下該使用WebSocket
在Web應(yīng)用中,客戶端和服務(wù)器端需要以較高頻率和較低延遲來交換事件時,適合用WebSocket。因此WebSocket適合財經(jīng)、游戲、協(xié)作等應(yīng)用場景。
對于其他應(yīng)用場景則未必適合。例如,某個新聞訂閱需要顯示突發(fā)新聞,使用間隔幾分鐘的長輪詢也是可以的,這里的延遲可以接受。
即使在要求低延遲的應(yīng)用場景,如果傳輸?shù)南?shù)很低(比如監(jiān)測網(wǎng)絡(luò)故障的場景),那么應(yīng)該考慮使用長輪詢技術(shù)。
而只有在低延遲和高頻消息通信的場景下,選用WebSocket協(xié)議才是非常適合的。即使是這樣的應(yīng)用場景,仍然存在是選擇WebSocket通信呢?又或者是選擇REST HTTP通信呢?
答案是會根據(jù)應(yīng)用程序的需求而定。但是,也可能同時使用這兩種技術(shù),把需要頻繁交換的數(shù)據(jù)放到WebSocket中實現(xiàn),而把REST API作為過程性的業(yè)務(wù)的實現(xiàn)技術(shù)。另外,當REST API的調(diào)用中需要把某個信息廣播給多個客戶端是,也可以通過WebSocket連接來實現(xiàn)。
Spring框架提供了@Controller注釋和@RestController注釋,兩者都可以用于HTTP請求的處理以及WebSocket消息的處理。另外,Spring MVC的請求處理方法,或其它應(yīng)用程序的請求處理方法,都可以很容易地使用WebSocket協(xié)議來廣播消息到所有感興趣的客戶端或指定用戶。
- Spring WebSocket 404錯誤的解決方法
- Spring和Websocket相結(jié)合實現(xiàn)消息的推送
- 完美解決spring websocket自動斷開連接再創(chuàng)建引發(fā)的問題
- java中實現(xiàn)兼容ie6 7 8 9的spring4+websocket
- 詳解WebSocket+spring示例demo(已使用sockJs庫)
- 詳解在Spring Boot框架下使用WebSocket實現(xiàn)消息推送
- SpringBoot webSocket實現(xiàn)發(fā)送廣播、點對點消息和Android接收
- 詳解spring boot Websocket使用筆記
- Spring整合websocket整合應(yīng)用示例(下)
- Spring集成webSocket頁面訪問404問題的解決方法
相關(guān)文章
深入探究Bean生命周期的擴展點Bean Post Processor
在Spring框架中,Bean生命周期的管理是非常重要的一部分,在Bean的創(chuàng)建、初始化和銷毀過程中,Spring提供了一系列的擴展點,其中,Bean Post Processor(后處理器)是一個重要的擴展點,它能夠在Bean的初始化前后做一些額外的處理,本文就和大家一起深入探究2023-07-07mybatis之調(diào)用帶輸出參數(shù)的存儲過程(Oracle)
這篇文章主要介紹了mybatis調(diào)用帶輸出參數(shù)的存儲過程(Oracle),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11Spring Security跳轉(zhuǎn)頁面失敗問題解決
這篇文章主要介紹了Spring Security跳轉(zhuǎn)頁面失敗問題解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-01-01springboot項目如何在linux服務(wù)器上啟動、停止腳本
這篇文章主要介紹了springboot項目如何在linux服務(wù)器上啟動、停止腳本問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05