Spring Boot 中整合WebSocket

引入依赖

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-websocket</artifactId>

</dependency>

配置文件

package cn.jt.config;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.socket.server.standard.ServerEndpointExporter;

*/

@Configuration

publicclass WebSocketConfig {

/**

* 初始化Bean,它会自动注册使用了 @ServerEndpoint 注解声明的 WebSocket endpoint

*

* @return

*/

@Bean

public ServerEndpointExporter serverEndpointExporter() {

returnnew ServerEndpointExporter();

}

}

在项目中的使用

复制代码
package com.unistrong.monitor.socket;

import com.alibaba.fastjson.JSONObject;
import com.unistrong.common.utils.JsonUtil;
import com.unistrong.common.utils.SpringUtils;
import com.unistrong.monitor.constant.RedisConstant;
import com.unistrong.monitor.service.RawNavService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

@ServerEndpoint("/socket/rawNav/Field")
@Slf4j
@Component
public class RawNavFieldWebSocket {

    /**
     * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的
     */
    private static int onlineCount = 0;

    /**
     * concurrent 包的线程安全Set,用来存放每个客户端对应的 Socket对象
     */
    public static ConcurrentHashMap<Session, Map<String, Object>> webSocketMap = new ConcurrentHashMap<>();

    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;

    /**
     * 连接建立成功调用的方法
     *
     * @param session
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        this.session = session;
        addOnlineCount(); // 在线数 +1
        log.info("有新窗口开始监听,当前在线人数为{}", getOnlineCount());

    }

    /**
     * 关闭连接
     */

    @OnClose
    public void onClose() {
        subOnlineCount(); // 人数 -1
        webSocketMap.remove(session);
        log.info("有一连接关闭,当前在线人数为:{}", getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自窗口的信息:{}", message);

        if (StringUtils.isNotBlank(message)) {
            Map<String, Object> map = JSONObject.parseObject(message, Map.class);
            //获取前端传递的参数
            String stationCode = MapUtils.getString(map, "stationCode");
            String mode = MapUtils.getString(map, "mode");
            String equ = MapUtils.getString(map, "equ");
            String navSystem = MapUtils.getString(map, "navSystem");
            String navFrequency = MapUtils.getString(map, "navFrequency");
            String serviceType = MapUtils.getString(map, "serviceType");
            String prn = MapUtils.getString(map, "prn");


            RawNavService service = SpringUtils.getBean(RawNavService.class);
            Map<String, Object> initData = null;
            String commRecordKey = null;

            switch (serviceType) {
                case "rawnav_clk_err":
                    commRecordKey = String.format(RedisConstant.KEY_RAWNAV_COMM + RedisConstant.KEY_RAWNAV_CLKERR
                            + "%s:%s:%s:%s:%s", serviceType, mode, navSystem, navFrequency, prn);
                    initData = service.getInitDataClkErrField(commRecordKey);
                    break;
                case "rawnav_ephemeris":
                    commRecordKey = String.format(RedisConstant.KEY_RAWNAV_COMM + RedisConstant.KEY_RAWNAV_EPHEMERIS
                            + "%s:%s:%s:%s:%s", serviceType, mode, navSystem, navFrequency, prn);
                    initData = service.getInitDataEphemerisField(commRecordKey);
                    break;
                case "rawnav_iono":
                    commRecordKey = String.format(RedisConstant.KEY_RAWNAV_COMM + RedisConstant.KEY_RAWNAV_INNO
                            + "%s:%s:%s:%s:%s", serviceType, mode, navSystem, navFrequency, prn);
                    initData = service.getInitDataIonoField(commRecordKey);
                    break;
                case "rawnav_utc_param":
                    commRecordKey = String.format(RedisConstant.KEY_RAWNAV_COMM + RedisConstant.KEY_RAWNAV_TIMESYNCPARAM
                            + "%s:%s:%s:%s:%s", serviceType, mode, navSystem, navFrequency, prn);
                    initData = service.getInitDataUtcParamField(commRecordKey);
                    break;
                case "rawnav_almanace":
                    commRecordKey = String.format(RedisConstant.KEY_RAWNAV_COMM + RedisConstant.KEY_RAWNAV_ALMANACE
                            + "%s:%s:%s:%s:%s", serviceType, mode, navSystem, navFrequency, prn);
                    initData = service.getInitDataAlmanaceField(commRecordKey);
                    break;
            }

            //构建redisKey
            map.put("commRecordKey", commRecordKey);

            if (MapUtils.isNotEmpty(initData)) {
                sendMessage(session, JsonUtil.toJson(initData));

                webSocketMap.put(session, map);
            }
        }

    }


    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("Error:{}", session.getId(), error.getMessage());
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     *
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }


    public static void sendMessage(Session session, String message) {
        try {
            session.getBasicRemote().sendText(message);
            log.info(message);
        } catch (IOException e) {
            log.error("ServerDataWebSocket.消息发送失败,失败原因:{}", e.getMessage());
        }
    }


    private static synchronized int getOnlineCount() {
        return onlineCount;
    }

    private static synchronized void addOnlineCount() {
        RawNavFieldWebSocket.onlineCount++;
    }

    private static synchronized void subOnlineCount() {
        RawNavFieldWebSocket.onlineCount--;
    }


}

1、代码很简单,大家一看就知道逻辑了,这里就解释一下各个注解的含义

  • @ServerEndpoint: 将目前的类定义成一个websocket服务器端,注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端

  • @OnOpen: 当WebSocket建立连接成功后会触发这个注解修饰的方法。

  • @OnClose: 当WebSocket建立的连接断开后会触发这个注解修饰的方法。

  • @OnMessage: 当客户端发送消息到服务端时,会触发这个注解修改的方法。

  • @OnError: 当WebSocket建立连接时出现异常会触发这个注解修饰的方法。

  • 若无法自动注入,可使用

  • public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

    if (SpringUtil.applicationContext == null) {

    SpringUtil.applicationContext = applicationContext; // 设置全局应用上下文

    }

    }

    // 获取应用上下文

    public static ApplicationContext getApplicationContext() {

    return applicationContext;

    }

    // 通过Bean名称获取Bean实例

    public static Object getBean(String name) {

    return getApplicationContext().getBean(name);

    }

    // 通过Bean类型获取Bean实例

    public static <T> T getBean(Class<T> clazz) {

    return getApplicationContext().getBean(clazz);

    }

    // 通过Bean名称和类型获取Bean实例

    public static <T> T getBean(String name, Class<T> clazz) {

    return getApplicationContext().getBean(name, clazz);

    }

    }

    该类在Spring启动时自动注入ApplicationContext(通过setApplicationContext方法),后续所有getBean调用都基于此上下文2。

SpringBoot 开启 WebSocket

在启动类加上 @EnableWebSocket

相关推荐
程序猿DD1 小时前
OctaFuse Gateway 2.15.0:MCP 聚合、向量 Embeddings 与官方目录同步
后端·agent
余槐i3 小时前
PostgreSQL 函数索引报错 must be immutable:3种绕法与代价
数据库·后端·sql·postgresql·性能优化
需要8264 小时前
Spring Bean 生命周期:从实例化到销毁的完整钩子
java·后端·spring
凤山老林5 小时前
Spring Boot 接入国密 SM2/SM3/SM4 的一些实践记录
spring boot·后端·python
伯恩bourne6 小时前
Spring Boot Bean 注册的两种方式:@Component 家族 vs @Configuration + @Bean
java·spring boot·后端
张彦峰ZYF6 小时前
停顿短不等于延迟低:G1、ZGC 与低延迟服务的目标分野、代价账本与选型标定
后端·回收暂停、应用停止总时长·到达安全点、分配停滞·屏障吃吞吐、并发线程吃核心·并发窗口吃堆余量·分区式靠挑回收集兑现目标·染色指针式不支持压缩引用
pride.li7 小时前
第03章 Python 教程-基础语法:注释与变量
服务器·前端·后端·python·算法·计算机视觉
省钱兄--zs7 小时前
深圳24小时自助健身房软硬件解决方案实战指南与开发流程详解
java·数据仓库·spring boot·小程序·架构
upala7 小时前
【仓颉语言入门 · 第二篇 第 6 课】
后端
罗丹丹Luna7 小时前
移动端显示“已提交”以后:SAP 与低代码集成该怎样暴露真实状态
后端·架构