我们知道Servlet3.0引入了WebSocket支持,在SpringMVC中我们可以直接使用这套API;除此之外,Spring还为我们进行了一些封装,提供了WebSocketHandler
。这两种方式都能实现WebSocket功能的开发。
SpringMVC环境下,使用这套API我们需要引入javax.websocket-api
的依赖。
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
此时我们在工程中使用@ServerEndpoint
注解进行相关的开发即可,Tomcat会自动进行注解扫描找到需要的类并加载。这种方式实现WebSocket其实和Spring没有任何关系,我们不需要将其注册到IoC容器,也不需要任何额外配置。
基于ServletAPI进行WebSocket开发的内容可以参考Servlet相关章节,这里就不重复黏贴代码了。
如果使用SpringBoot的Embed Tomcat时情况就稍有区别了,我们还额外需要ServerEndpointExporter
类的支持。此时需要额外引入一个SpringBoot起步依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
然后需要将ServerEndpointExporter
对象注册到Ioc容器中:
WsConfig.java
package com.gacfox.demo.ws;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WsConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
此外,对于@ServerEndpoint
类,我们还需要对其添加@Component
注解将其注册到IoC容器,否则Tomcat是无法加载的。
Spring提供了WebSocketHandler
接口对ServletAPI进行了封装,实际开发中我们也可以采用WebSocketHandler
替代ServletAPI的ServerEndpoint
。具体代码里,我们可以继承抽象类AbstractWebSocketHandler
、TextWebSocketHandler
、或BinaryWebSocketHandler
,后两个分别专用于处理文本消息和二进制消息。此外,Spring还封装了对于SockJS和STOMP协议的支持,这部分内容具体可以参考Spring官网文档。
为了处理WebSocket消息,我们需要先编写我们自己的Handler类。
ChatHandler.java
package com.gacfox.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
@Slf4j
public class ChatHandler extends TextWebSocketHandler {
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
log.info(message.getPayload());
}
}
上面例子比较简单,我们直接继承了TextWebSocketHandler
来处理文本消息。其中WebSocketSession
包含连接的信息,例如需要实现消息广播等功能我们就需要保存所有的连接并统一发送消息;message
则是具体的消息体。
在SpringMVC的配置文件中,我们还需要进行Bean的注册。首先我们要在XML文件中引入以下命名空间和Schema。
xmlns:websocket="http://www.springframework.org/schema/websocket"
http://www.springframework.org/schema/websocket
https://www.springframework.org/schema/websocket/spring-websocket.xsd
然后注册我们的Handler即可,此时就可以正常处理WebSocket消息了。
demows-servlet.xml
<bean id="chatHandler" class="com.gacfox.controller.ChatHandler"/>
<websocket:handlers>
<websocket:mapping path="/chat" handler="chatHandler"/>
</websocket:handlers>
对于SpringBoot环境配置也是差不多的,只不过把XML换成了JavaConfig,这里就不赘述了。
注意:Spring提供的WebSocketHandler
默认不接收跨域连接,当试图跨域连接时会报403错误,这和ServerEndpoint
略有不同。
实现WebSocket跨域连接也很简单,我们进行类似如下配置即可,下面我们允许了全部的跨域连接,具体使用时也可以根据具体需求进行域名的配置。
demows-servlet.xml
<websocket:handlers allowed-origins="*">
<websocket:mapping path="/chat" handler="chatHandler"/>
</websocket:handlers>
Spring还提供了HandshakeInterceptor
接口,用于对WebSocket建立握手连接前和建立握手连接后进行一些拦截处理。HandshakeInterceptor
接口有两个方法:
package org.springframework.web.socket.server;
import java.util.Map;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.socket.WebSocketHandler;
public interface HandshakeInterceptor {
boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception;
void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, @Nullable Exception exception);
}
beforeHandshake:建立握手连接前处理,返回true
继续连接,返回false
则中断
afterHandshake:建立握手连接后处理
我们知道WebSocket建立连接使用过HTTPUpgrade
请求实现的,HandshakeInterceptor
就是在这一步起作用,我们可以看到参数中都是ServerHttpRequest
、ServerHttpResponse
等HTTP协议请求和响应的对象,我们可以借此进行建立WebSocket连接前的认证、鉴权等操作。
下面例子中我们编写了一个拦截器,并进行注册。
ChatInterceptor.java
package com.gacfox.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
@Slf4j
public class ChatInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
log.info("建立连接前");
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
log.info("建立连接后");
}
}
demows-servlet.xml
<bean id="chatHandler" class="com.gacfox.controller.ChatHandler"/>
<websocket:handlers allowed-origins="*">
<websocket:mapping path="/chat" handler="chatHandler"/>
<websocket:handshake-interceptors>
<bean class="com.gacfox.controller.ChatInterceptor"/>
</websocket:handshake-interceptors>
</websocket:handlers>
代码非常简单,这里就不多解释了。