Spring WebSocket(XML 配置方式)

一、项目说明

1.1 项目结构

  1. 项目模拟一个简单的群聊功能,为区分不同的聊天客户端,登录时候将临时用户名存储在 session 当中;
  2. CustomHander 为自定义的消息处理器;
  3. CustomHandershakerInterceptor 为自定义的 WebSocket 的握手拦截器;
  4. 项目以 Web 的方式构建。

1.2 基本依赖

除了基本的 Spring 依赖外,还需要导入 WebSocket 的相关依赖:

<!--spring webSocket 的依赖包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>5.1.3.RELEASE</version>
</dependency>

二、实现 WebSocket

2.1 消息处理器

继承自TextWebSocketHandler,创建消息处理类:

/**
 * @description : 自定义消息处理类
 */
public class CustomHandler extends TextWebSocketHandler {
    private Map<String, WebSocketSession> nameAndSession = new ConcurrentHashMap<>();
    // 建立连接时候触发
    @Override
    public void afterConnectionEstablished(WebSocketSession session)  {
        String username = getNameFromSession(session);
        nameAndSession.putIfAbsent(username, session);
    }
    // 关闭连接时候触发
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        String username = getNameFromSession(session);
        nameAndSession.remove(username);
    }
    // 处理消息
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 防止中文乱码
        String msg = URLDecoder.decode(message.getPayload(), "utf-8");
        String username = getNameFromSession(session);
        // 简单模拟群发消息
        TextMessage reply = new TextMessage(username + " : " + msg);
        nameAndSession.forEach((s, webSocketSession)
                -> {
            try {
                webSocketSession.sendMessage(reply);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
    private String getNameFromSession(WebSocketSession session) {
        Map<String, Object> attributes = session.getAttributes();
        return (String) attributes.get(Constant.USER_NAME);
    }
}

2.2 握手拦截器

创建WebSocket 握手拦截器(如果没有权限拦截等需求,这一步不是必须的):

/**
 * @description : 可以按照需求实现权限拦截等功能
 */
public class CustomHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        InetAddress address = remoteAddress.getAddress();
        System.out.println(address);
        /*
         * 最后需要要显示调用父类方法,父类的 beforeHandshake 方法
         * 把 ServerHttpRequest 中 session 中对应的值拷贝到 WebSocketSession 中。
         * 如果我们没有实现这个方法,我们在最后的 handler 处理中 是拿不到 session 中的值
         * 作为测试 可以注释掉下面这一行 可以发现自定义处理器中 session 的 username 总是为空
         */
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }
}

2.3 配置 WebSocket

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:websocket="http://www.springframework.org/schema/websocket"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd">
    <!-- 开启注解包扫描-->
    <context:component-scan base-package="com.heibaiying.*"/>
    <!--使用默认的 Servlet 来响应静态文件 -->
    <mvc:default-servlet-handler/>
    <!-- 开启注解驱动 -->
    <mvc:annotation-driven/>
    <!-- 配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          id="internalResourceViewResolver">
        <!-- 前缀 -->
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <!-- 后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>
    <!--配置 webSocket-->
    <bean id="customHandler" class="com.heibaiying.websocket.CustomHandler"/>
    <websocket:handlers>
        <!--指定 webSocket 地址-->
        <websocket:mapping path="/socket" handler="customHandler"/>
        <!--webSocket 握手-->
        <websocket:handshake-interceptors>
            <bean class="com.heibaiying.websocket.CustomHandshakeInterceptor"/>
        </websocket:handshake-interceptors>
    </websocket:handlers>
</beans>

2.4 前端 WebSocket

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>${sessionScope.get("username")}您好!欢迎进入群聊大厅!</title>
</head>
<body>
<input id="message" type="text">
<button id="btn">发送消息</button>
<div id="show">
</div>
<script>
    let btn = document.getElementById("btn");
    let message = document.getElementById("message");
    let show = document.getElementById("show");
    let ws = new WebSocket("ws://localhost:8080/socket");
    ws.onmessage = function (evt) {
        let node = document.createElement("div");
        node.innerHTML = "<h5>" + evt.data + "</h5>";
        show.appendChild(node);
    };
    btn.addEventListener("click", function () {
        let data = message.value;
        console.log(data);
        if (data) {
            ws.send(encodeURI(data));
        } else {
            alert("请输入消息后发送");
        }
        message.value = "";
    });
    // 关闭页面时候关闭 ws
    window.addEventListener("beforeunload", function(event) {
        ws.close();
    });
</script>
</body>
</html>

2.5 实现简单登录

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/login" method="post">
    <input name="username" type="text">
    <button id="btn">输入临时用户名后登录!</button>
</form>
</body>
</html>
@Controller
public class LoginController {
    @PostMapping("login")
    public String login(String username, HttpSession session){
        session.setAttribute(Constant.USER_NAME,username);
        return "chat";
    }
}