preface
Recently, there is a private message function in the project, which needs to use websocket, so I looked for information on the Internet and summarized some experience in practice to share with you.
problem
Before I do this, let me ask you a question:
The SpringBoot project integrates webSocket. When the client establishes a connection with the server, it is found that the server object is not injected but null.
Cause: spring manages singleton s, which conflicts with websocket.
Detailed explanation: when the project is initialized at startup, websocket (not connected by the user) will be initialized, and spring will inject service for it. The service of the object is not null and is successfully injected. However, since spring manages singletons by default, it will inject services only once. When the client connects with the server, the server will create a new websocket object. At this time, the problem arises: Spring manages single instances and does not inject services into the second websocket object. Therefore, as long as the websocket object created by the user connection can no longer be injected.
For example, there is a service in the controller and a dao in the service. Because controller, service and dao are singletons, null will not be reported during injection. However, websocket is not a singleton, so after spring injection, the following objects will not be injected again, and NullException will be reported.
The solution will be discussed below.
operation
1. Introducing websocket dependency package
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
2. Configure websocket
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) { // webSocket channel // Specify the processor and path, such as: http://www.baidu.com/service-name/websocket?uid=xxxx webSocketHandlerRegistry.addHandler(new WebSocketHandler(), "/websocket") // //Specify custom interceptors .addInterceptors(new WebSocketInterceptor()) // Allow cross domain .setAllowedOrigins("*"); } }
3. Add a parameter class to get the websocket address
public class WebSocketInterceptor implements HandshakeInterceptor { /** * handler Before processing, the attributes attribute is finally in WebSocketSession, possibly through webSocketSession.. getAttributes(). Get (key value) */ @Override public boolean beforeHandshake(org.springframework.http.server.ServerHttpRequest request, ServerHttpResponse serverHttpResponse, org.springframework.web.socket.WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception { if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request; // Get the parameters carried by the request path String uid = serverHttpRequest.getServletRequest().getParameter("uid"); map.put("uid", uid); return true; } else { return false; } } @Override public void afterHandshake(org.springframework.http.server.ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, org.springframework.web.socket.WebSocketHandler webSocketHandler, Exception e) { } }
4. Add a class that resolves the null injection of the server object @ Autowired
@Component public class SpringContext implements ApplicationContextAware { /** * Print log */ private Logger logger = LoggerFactory.getLogger(getClass()); /** * Get context object */ private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringContext.applicationContext = applicationContext; logger.info("set applicationContext"); } /** * Get applicationContext * * @return */ public static ApplicationContext getApplicationContext() { return applicationContext; } /** * Get bean object by name * * @param name * @return */ public static Object getBean(String name) { return getApplicationContext().getBean(name); } /** * Get bean object through class * * @param clazz * @param <T> * @return */ public static <T> T getBean(Class<T> clazz) { return getApplicationContext().getBean(clazz); } /** * Get the specified bean object through name and clazz * * @param name * @param clazz * @param <T> * @return */ public static <T> T getBean(String name, Class<T> clazz) { return getApplicationContext().getBean(name, clazz); } }
5. Add websocket receive send message class
@Component public class WebSocketHandler extends AbstractWebSocketHandler { private static Logger log = LoggerFactory.getLogger(WebSocketHandler.class); public AccountFeignClient getAccountFeignClient() { return SpringContext.getBean(AccountFeignClient.class); } public NotifyMailboxService getNotifyMailboxService() { return SpringContext.getBean(NotifyMailboxService.class); } public NotifyMailboxMessageService getNotifyMailboxMessageService() { return SpringContext.getBean(NotifyMailboxMessageService.class); } /** * Store sessionId and webSocketSession * It should be noted that webSocketSession does not provide a parameterless structure and cannot be serialized or stored through redis * In the distributed system, we should find another way to realize webSocketSession sharing */ private static Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>(); private static Map<String, String> userMap = new ConcurrentHashMap<>(); /** * webSocket Call after connection creation */ @Override public void afterConnectionEstablished(WebSocketSession session) { // Get parameters String uid = String.valueOf(session.getAttributes().get("uid")); String sessionId = session.getId(); log.info("init websocket uid={},sessionId={}", uid, sessionId); userMap.put(uid, sessionId); sessionMap.put(sessionId, session); } /** * The front end sends messages to the background * Called when a message is received */ @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { //User A sends the front-end message to the background. The background should save the A message and push the message to user B if (message instanceof TextMessage) { log.info("message={}", message); } else if (message instanceof BinaryMessage) { } else if (message instanceof PongMessage) { } else { log.info("Unexpected WebSocket message type: " + message); } String uid = String.valueOf(session.getAttributes().get("uid")); String messages = (String) message.getPayload(); ObjectMapper mapper = new ObjectMapper(); HashMap<String, Object> map = mapper.readValue(messages, HashMap.class); String _uid = (String) map.get("uid"); // String _dialogId = (String) map.get("dialogId"); String _friendId = (String) map.get("friendId"); String _message = (String) map.get("message"); String sessionId = session.getId(); log.info("sessionId={},uid={},_uid={},_friendId={},_message={}", sessionId, uid, _uid, _friendId, _message); if (!StringUtils.hasLength(sessionId) || !StringUtils.hasLength(_uid) || !StringUtils.hasLength(_friendId)) { log.info("sessionId&_uid&_friendId Cannot be empty"); session.sendMessage(new TextMessage("error:sessionId&_uid&_friendId Cannot be empty")); return; } String dialogId = pushMessage(_uid, _friendId, _message); if (dialogId != null) { TextMessage textMessage = new TextMessage("dialogId:" + dialogId); // Push messages to your ws session.sendMessage(textMessage); String sessionIdForFriend = userMap.get(_friendId); log.info("sessionIdForFriend={}", sessionIdForFriend); if (StringUtils.hasLength(sessionIdForFriend)) { WebSocketSession friendSession = sessionMap.get(sessionIdForFriend); if (friendSession != null && friendSession.isOpen()) // Push messages to friends friendSession.sendMessage(textMessage); } } } /** * Connection error will call */ @Override public void handleTransportError(WebSocketSession session, Throwable exception) { String uid = String.valueOf(session.getAttributes().get("uid")); String sessionId = session.getId(); log.info("CLOSED uid= ={},sessionId={}", uid, sessionId); sessionMap.remove(sessionId); } /** * Called when the connection is closed */ @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { String uid = String.valueOf(session.getAttributes().get("uid")); String sessionId = session.getId(); log.info("CLOSED uid= ={},sessionId={}", uid, sessionId); sessionMap.remove(sessionId); } @Override public boolean supportsPartialMessages() { return false; } /** * Send messages from the background to the front end * Encapsulation method sends a message to the client */ public static void sendMessage(String uid, String dialogId) { log.info("Send message to:"); } /** * @param uid * @param friendId * @param message * @return */ private String pushMessage(String uid, String friendId, String message) { log.info("uid={},friendId={},message={}", uid, friendId, message); NotifyMailboxService notifyMailboxService = getNotifyMailboxService(); NotifyMailboxMessageService notifyMailboxMessageService = getNotifyMailboxMessageService(); try { NotifyMailbox notifyMailbox = notifyMailboxService.queryBy(uid, friendId); } catch (Exception e) { log.info("exception msg={}", e.getMessage()); return null; } } }
websocket front end status code readyState
0 CONNECTING The connection has not been established 1 OPEN WebSocket Your link has been established 2 CLOSING Connection closing 3 CLOSED The connection is closed or unavailable
summary
1. The server object is not injected but null, so you should add the above SpringContext class and reference it in the following way
public AccountFeignClient getAccountFeignClient() { return SpringContext.getBean(AccountFeignClient.class); } public NotifyMailboxService getNotifyMailboxService() { return SpringContext.getBean(NotifyMailboxService.class); } public NotifyMailboxMessageService getNotifyMailboxMessageService() { return SpringContext.getBean(NotifyMailboxMessageService.class); }
2. The connection of websocket corresponds to the closed session. There is no problem using the above code, otherwise there will be problems with the connection.
3. WebSocketInterceptor will get https://www.baidu.com/service-name/websocket?uid=xxx And inject the uid into the session, so the WebSocketHandler class can get the uid parameter in the session.
quote
WebSocket tutorial
The @ Autowired injection in webSocket corresponds to null