以下教程是小編在參與開發公司的一個crm系統,整理些相關資料,在該系統中有很多消息推送功能,在其中用到了websocket技術。下面小編整理分享到武林網平台供大家參考
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/beanshttp://www.springframework.org/schema/beans/spring-beans-3.1.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring -context-3.1.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc-3.1.xsdhttp://www.springframework.org/schema/txhttp://www.spr ingframework.org/schema/tx/spring-tx-3.1.xsdhttp://www.springframework.org/schema/websockethttp://www.springframework.org/schema/websocket/spring-websocket.xsd">......<!-- websocket --><bean id="websocket"/><websocket:handlers><websocket:mapping path="/websocket" handler="websocket"/><websocket:handshake-interceptors><bean/></websocket:handshake-interceptors></websocket:handlers></beans>
其中,path對應的路徑就是前段通過ws協議調的接口路徑
3. HandshakeInterceptor的實現
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 :創建握手(handshake)接口* @Date : 16-3-3*/public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor{private static final Logger logger = LoggerFactory.getLogger(HandshakeInterceptor.class);@Overridepublic 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);}@Overridepublic void afterHandshake(ServerHttpRequest request,ServerHttpResponse response, WebSocketHandler wsHandler,Exception ex) {logger.info("建立握手後...");super.afterHandshake(request, response, wsHandler, ex);}}因為老夫不是很懂,所以最大限度的保留原代碼,這其實就是從單點登錄中取出當前登錄用戶,轉成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(); //下架消息最近一次發送時間private Date lastTaskSendTime = new Date(); //待處理任務最近一次發送時間private WebSocketSession webSocketSession; //用戶對應的wsSession 默認僅緩存一個// getXX and setXX }其中最重要的就是這個WebSocketSession這個屬性了,後面我們要用到
5. WebsocketEndPoint的實現
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);@Autowiredprivate NewsListenerImpl newsListener;@Overrideprotected 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*/@Overridepublic 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);}@Overridepublic 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成功關閉連接...");super.afterConnectionClosed(session, status);}}6. WSSessionLocalCache的實現
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());}}看了其實現,作用就比較明顯了吧,存放每個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協議來廣播消息到所有感興趣的客戶端或指定用戶。