前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot集成WebSocket的基本实现

SpringBoot集成WebSocket的基本实现

原创
作者头像
用户10714507
修改2023-10-08 17:05:16
7060
修改2023-10-08 17:05:16
举报
文章被收录于专栏:Java后端进阶之路

前言

WebSocket的用途是什么?

想象一个场景,有一些数据实时变化,前端需要在数据变化时刷新界面

此时我们第一反应,前端定时使用HTTP协议调用后端接口,刷新界面。OK,需求实现,下班回家!

然后我们就被前端套麻袋打了一顿。

那么如何优雅的让前端知道数据发生了变化呢?就需要用到WebSocket由后端将数据推送给前端

正文

具体实现

一、引入依赖

代码语言:txt
复制
<dependency>

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

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

    <version>3.0.4</version>

</dependency>

二、配置WebSocket

创建一个config类,配置类代码为固定写法,主要用于告诉SpringBoot我有使用WebSocket的需求,

注意我加了@ServerEndpoint注解的类

代码语言:java
复制
//ServerEndpointExporter 是springBoot的用于自动注册和暴露 WebSocket 端点的类
 //暴露ServerEndpointExporter类后,所有使用@ServerEndpoint("/websocket")的注解都可以用来发送和接收WebSocket请求
@Component
public class WebSocketConfig {

    @Bean
    public  ServerEndpointExporter serverEndpointExporter() {

        return new ServerEndpointExporter();

    }

}

三、WebSocket逻辑实现

话不多说,直接上代码

代码语言:java
复制
@Component // 交给Spring管理
@ServerEndpoint("/websocket") // 告知SpringBoot,这是WebSocket的实现类
@Slf4j
public class WebSocketServer {

    //静态变量,用来记录当前在线连接数

    private static AtomicInteger onlineCount = new AtomicInteger(0);

    //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。

    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();



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

    private Session session;

    

    private List<String> ids = new ArrayList<>();


     //连接建立成功调用的方法
    @OnOpen
    public void onOpen(Session session) {

        this.session = session;

        webSocketSet.add(this);

        

        // ps:后端接参示例代码

        // 这样接参,前端对应传参方式为

        //    var client = new window.WebSocket(this.address + "?tunnelId=" + tunnelId);

        Map<String, List<String>> map = session.getRequestParameterMap();

        String id = map.get("tunnelId").get(0);

        ids = Arrays.asList(id.split(","));

        

        addOnlineCount();           //在线数加1

        try {

            sendMessage("连接成功");

        } catch (IOException e) {

            log.error("websocket IO异常");

        }

    }



     // 连接关闭调用的方法
    @OnClose
    public void onClose() {

        webSocketSet.remove(this);  //从set中删除

        subOnlineCount();           //在线数减1

        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());

    }


     // 收到客户端消息后调用的方法
    @OnMessage

    public void onMessage(String message, Session session) {

        // 心跳检测,看连接是否意外断开

        // ps:现在uniapp等前端好像自动带有心跳包,但是web端一般还需要心跳包确保连接一直未断开

        if ("heart".equals(message)) {

            try {

                sendMessage("heartOk");

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

    }


    @OnError

    public void onError(Session session, Throwable error) {

        log.error("发生错误");

        error.printStackTrace();

    }
     // 实现服务器主动推送

    public void sendMessage(String message) throws IOException {

        this.session.getBasicRemote().sendText(message);

    }
     // 群发自定义消息

    public static void sendInfo(String message, String id) {



        for (WebSocketServer item : webSocketSet) {

            try {

                if (id == null) {

                    item.sendMessage(message);

                } else if (item.ids.contains(id)) {

                    item.sendMessage(message);

                }

            } catch (IOException e) {

                e.printStackTrace();

            }

        }

    }



    public static int getOnlineCount() {

        return onlineCount.get();

    }



    public static void addOnlineCount() {

        WebSocketServer.onlineCount.incrementAndGet();

    }



    public static void subOnlineCount() {

        if (getOnlineCount() > 0) {

            WebSocketServer.onlineCount.decrementAndGet();

        }



    }

}

ok,到这里,一个基本的WebSocket服务端就搭建完成了

下面是前端测试代码(前端就是一个html的demo)

代码语言:html
复制
<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="utf-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width,initial-scale=1.0">

    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"

          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

    <title>websocket测试页面</title>

</head>

<body>

<div class="panel panel-default">

    <div class="panel-body">

        <div class="row">

            <div class="col-md-6">

                <div class="input-group">

                    <span class="input-group-addon">ws地址</span>

                    <input type="text" id="address" class="form-control" placeholder="ws地址"

                           aria-describedby="basic-addon1" value="ws://localhost:9700/tunnel/websocket">

                    <div class="input-group-btn">

                        <button class="btn btn-default" type="submit" id="connect">连接</button>

                    </div>

                </div>

            </div>

        </div>

        <div class="row" style="margin-top: 10px;display: none;" id="msg-panel">

            <div class="col-md-6">

                <div class="input-group">

                    <span class="input-group-addon">消息</span>

                    <input type="text" id="msg" class="form-control" placeholder="消息内容" aria-describedby="basic-addon1">

                    <div class="input-group-btn">

                        <button class="btn btn-default" type="submit" id="send">发送</button>

                    </div>

                </div>

            </div>

        </div>

        <div class="row" style="margin-top: 10px; padding: 10px;">

            <div class="panel panel-default">

                <div class="panel-body" id="log" style="height: 450px;overflow-y: auto;">

                </div>

            </div>

        </div>

    </div>

</div>



<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"

        integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"

        crossorigin="anonymous"></script>



<script type="text/javascript">

    $(function () {

        var \_socket;



        $("#connect").click(function () {

            var tunnelId = "2"; // 设置需要传递的参数

            \_socket = new \_websocket($("#address").val(), tunnelId);

            \_socket.init();

        });



        $("#send").click(function () {

            var \_msg = $("#msg").val();

            output("发送消息:" + \_msg);

            \_socket.client.send(\_msg);

        });

    });



    function output(e) {

        var \_text = $("#log").html();

        $("#log").html(\_text + "<br>" + e);

    }



    function \_websocket(address, tunnelId) {

        this.address = address;

        this.tunnelId = tunnelId;

        console.log(address)

        console.log(tunnelId)

        this.client;



        this.init = function () {

            if (!window.WebSocket) {

                this.websocket = null;

                return;

            }



            var \_this = this;

            // var \_client = new window.WebSocket(\_this.address + "/" + \_this.tunnelId);// 路径传参(没跑通)

            // 注意这里的名字要和后端接参数的名字对应上

            var \_client = new window.WebSocket(\_this.address + "?tunnelId=" + tunnelId);



            \_client.onopen = function () {

                output("websocket打开");

                $("#msg-panel").show();

            };



            \_client.onclose = function () {

                \_this.client = null;

                output("websocket关闭");

                $("#msg-panel").hide();

            };



            \_client.onmessage = function (evt) {

                output(evt.data);

            };



            \_this.client = \_client;

        };



        return this;

    }

</script>

</body>

</html>

进阶

以上内容实现了基本的推送消息到前端,也是网上大部分文章讲解的深度,但是实际开发中,笔者不可能不进行Spring的依赖注入,然后查询数据库拿到一些数据。此时我们就会发现,为什么空指针啊???为什么啊?

下面是笔者当时的排查思路

第一步:空指针?bean没被Spring管理呗。

看我三下五除二,要不就是@Component注解没加,要不就是SpringBoot启动类的扫描路径有问题,根本难不倒我

?都加了啊,为什么还是不行啊?开始怀疑人生

后来,因为我同时和小程序端还有web端对接,突然反应过来会不会是因为Spring默认单例,只会创造一个对象,但是WebSocket大概率都会有多个客户端,按照这个方向去尝试的话,直接手动获取bean对象是不是就不会空指针了呢?

我写了一个工具类获取bean对象

代码语言:java
复制
@Component

public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware 

{

    private static ConfigurableListableBeanFactory beanFactory;



    private static ApplicationContext applicationContext;



    @Override

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 

    {

        SpringUtils.beanFactory = beanFactory;

    }



    @Override

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 

    {

        SpringUtils.applicationContext = applicationContext;

    }


    public static <T> T getBean(Class<T> clz) throws BeansException

    {

        T result = (T) beanFactory.getBean(clz);

        return result;

    }

}

在我们的WebSocket类中使用以下代码进行依赖注入

代码语言:java
复制
EmergencyTypeService emergencyTypeService = SpringUtils.getBean(EmergencyTypeService.class);

ok,到此,我们就解决了空指针的问题,真是泪目。

我正在参与2023腾讯技术创作特训营第二期有奖征文,瓜分万元奖池和键盘手表

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • WebSocket的用途是什么?
    • 正文
      • 具体实现
        • 一、引入依赖
        • 二、配置WebSocket
        • 三、WebSocket逻辑实现
    • 进阶
    相关产品与服务
    云开发 CloudBase
    云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档