brief introduction
The so-called WebSocket is similar to Socket. Its function is to enable the client and server in Web applications to establish full duplex communication. There are three ways to use WebSocket in Spring based applications:
- Implemented using @ ServerEndpoint annotation provided by Java
- It is implemented using the low-level WebSocket API provided by Spring
- Implemented using STOMP messages
Next, I will give a brief introduction to these three implementation methods. In addition, for more information about the properties of WebSocket, please refer to the following article: WebSocket Quest
Note: for the complete source code of this article, please refer to: https://github.com/zifangsky/WebSocketDemo
Implemented using @ ServerEndpoint annotation provided by Java
(1) Listen for a WebSocket request path using the @ ServerEndpoint annotation:
Here, the connection port / reverse of the client is monitored, and how to handle messages sent by the client is defined
package cn.zifangsky.samplewebsocket.websocket; import javax.websocket.OnMessage; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import java.io.IOException; /** * ReverseWebSocketEndpoint * * @author zifangsky * @date 2018/9/30 * @since 1.0.0 */ @ServerEndpoint("/reverse") public class ReverseWebSocketEndpoint { @OnMessage public void handleMessage(Session session, String message) throws IOException { session.getBasicRemote().sendText("Reversed: " + new StringBuilder(message).reverse()); } }
(2) WebSocket related configuration:
package cn.zifangsky.samplewebsocket.config; import cn.zifangsky.samplewebsocket.websocket.ReverseWebSocketEndpoint; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * WebSocket Related configuration * * @author zifangsky * @date 2018/9/30 * @since 1.0.0 */ @Configuration @EnableWebSocket public class WebSocketConfig{ @Bean public ReverseWebSocketEndpoint reverseWebSocketEndpoint() { return new ReverseWebSocketEndpoint(); } @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
(3) Sample page:
slightly
It is implemented using the low-level WebSocket API provided by Spring
Spring 4.0 supports WebSocket communication, including:
-
Low level API for sending and receiving messages;
-
High level API for sending and receiving messages;
-
A template for sending messages;
-
It supports SockJS to solve the problem that the browser, server and agent do not support WebSocket.
Using the low-level API provided by Spring to implement WebSocket mainly requires the following steps:
(1) Add a WebSocketHandler:
Define a message processing class that inherits the AbstractWebSocketHandler class, and then customize the processing of "establish connection", "receive / send message", "exception", etc
package cn.zifangsky.samplewebsocket.websocket; import cn.zifangsky.samplewebsocket.service.EchoService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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; import javax.annotation.Resource; import java.text.MessageFormat; /** * Example by inheriting {@ link org.springframework.web.socket.handler.AbstractWebSocketHandler} * * @author zifangsky * @date 2018/10/9 * @since 1.0.0 */ public class EchoWebSocketHandler extends TextWebSocketHandler{ private final Logger logger = LoggerFactory.getLogger(getClass()); @Resource(name = "echoServiceImpl") private EchoService echoService; @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { logger.debug("Opened new session in instance " + this); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { //Echo information returned by assembly String echoMessage = this.echoService.echo(message.getPayload()); logger.debug(MessageFormat.format("Echo message \"{0}\"", echoMessage)); session.sendMessage(new TextMessage(echoMessage)); } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { session.close(CloseStatus.SERVER_ERROR); logger.debug("Info: WebSocket connection closed."); } }
(2) WebSocket related configuration:
package cn.zifangsky.samplewebsocket.config; import cn.zifangsky.samplewebsocket.websocket.EchoWebSocketHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; /** * WebSocket Related configuration * * @author zifangsky * @date 2018/9/30 * @since 1.0.0 */ @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer{ @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(echoWebSocketHandler(), "/echoMessage"); registry.addHandler(echoWebSocketHandler(), "/echoMessage_SockJS").withSockJS(); } /** * Example by inheriting {@ link org.springframework.web.socket.handler.AbstractWebSocketHandler} */ @Bean public WebSocketHandler echoWebSocketHandler(){ return new EchoWebSocketHandler(); } }
(3) Two sample pages: omitted
As can be seen from the above code, in addition to configuring the basic WebSocket (that is, the connection address of / echoMessage), SockJS is also used to configure the alternative scheme when the browser does not support WebSocket Technology (that is, the connection address of / echoMessage_SockJS).
Implemented using STOMP messages
The so-called STOMP(Simple Text Oriented Messaging Protocol), A frame based wire format layer is provided on the basis of WebSocket. It defines a set of standard formats for sending simple Text messages (stop messages are based on Text, and of course they also support the transmission of binary data). At present, many server message queues already support stop, such as RabbitMQ, ActiveMQ, etc.
(1) WebSocket related configuration:
package cn.zifangsky.stompwebsocket.config; import cn.zifangsky.stompwebsocket.interceptor.websocket.MyChannelInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; /** * WebSocket Related configuration * * @author zifangsky * @date 2018/9/30 * @since 1.0.0 */ @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{ @Autowired private MyChannelInterceptor myChannelInterceptor; @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/stomp-websocket").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { //The client needs to send the message to the / message/xxx address registry.setApplicationDestinationPrefixes("/message"); //The server broadcasts the path prefix of the message, and the client needs to subscribe to the message at / topic/yyy registry.enableSimpleBroker("/topic"); } @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(myChannelInterceptor); } }
As can be seen from the above code, several addresses are set here, which are briefly explained as follows:
-
First, an endpoint named / STOMP websocket is registered, that is, the address of the STOMP client connection.
-
In addition, it is defined that the prefix of WebSocket messages processed by the server is / message, which is used for the client to send messages to the server (for example, if the client sends messages to the address of / message/hello, the server receives and processes messages through the annotation @ MessageMapping("/ hello")
-
Finally, a simple message proxy is defined, that is, the path prefix of the server broadcast message (for example, if the client listens to the address / topic / meeting, the server can send a stop message to the client through the annotation @ SendTo("/ topic / meeting").
It should be noted that the above code also adds an interceptor named MyChannelInterceptor to print the log after the client disconnects. Relevant codes are as follows:
package cn.zifangsky.stompwebsocket.interceptor.websocket; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.stereotype.Component; import java.security.Principal; import java.text.MessageFormat; /** * Customize {@ link org.springframework.messaging.support.ChannelInterceptor} to handle disconnection * * @author zifangsky * @date 2018/10/10 * @since 1.0.0 */ @Component public class MyChannelInterceptor implements ChannelInterceptor{ private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, Exception ex) { StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); StompCommand command = accessor.getCommand(); //The user has disconnected if(StompCommand.DISCONNECT.equals(command)){ String user = ""; Principal principal = accessor.getUser(); if(principal != null && StringUtils.isNoneBlank(principal.getName())){ user = principal.getName(); }else{ user = accessor.getSessionId(); } logger.debug(MessageFormat.format("user{0}of WebSocket The connection has been disconnected", user)); } } }
(2) Process messages using @ MessageMapping and @ SendTo annotations:
@The MessageMapping annotation is used to listen to the client message of the specified path, while the @ SendTo annotation is used to send the server message to the client listening to the path.
package cn.zifangsky.stompwebsocket.controller; import cn.zifangsky.stompwebsocket.model.websocket.Greeting; import cn.zifangsky.stompwebsocket.model.websocket.HelloMessage; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller; /** * Greeting * @author zifangsky * @date 2018/9/30 * @since 1.0.0 */ @Controller public class GreetingController { @MessageMapping("/hello") @SendTo("/topic/greeting") public HelloMessage greeting(Greeting greeting) { return new HelloMessage("Hello," + greeting.getName() + "!"); } }
(3) Example page: omitted