使用WebSocket

我们知道Servlet3.0引入了WebSocket支持,在SpringMVC中我们可以直接使用这套API;除此之外,Spring还为我们进行了一些封装,提供了WebSocketHandler。这两种方式都能实现WebSocket功能的开发。

基于ServletAPI实现WebSocket

SpringMVC/Tomcat环境

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环境

如果使用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是无法加载的。

基于WebSocketHandler实现WebSocket

Spring提供了WebSocketHandler接口对ServletAPI进行了封装,实际开发中我们也可以采用WebSocketHandler替代ServletAPI的ServerEndpoint。具体代码里,我们可以继承抽象类AbstractWebSocketHandlerTextWebSocketHandler、或BinaryWebSocketHandler,后两个分别专用于处理文本消息和二进制消息。此外,Spring还封装了对于SockJS和STOMP协议的支持,这部分内容具体可以参考Spring官网文档。

使用WebSocketHandler例子

为了处理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>

HandshakeInterceptor握手拦截器

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就是在这一步起作用,我们可以看到参数中都是ServerHttpRequestServerHttpResponse等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>

代码非常简单,这里就不多解释了。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap