前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >踩坑篇之WebSocket实现类中无法使用@Autowired注入对象

踩坑篇之WebSocket实现类中无法使用@Autowired注入对象

作者头像
JanYork_小简
发布2023-03-18 16:23:57
1.1K0
发布2023-03-18 16:23:57
举报

大家好,我是小简,今天我又大意了,在WebSocket这个类上踩坑了

接下来我讲讲我踩坑的经历吧!

package cn.donglifeng.shop.socket.endpoin;

import cn.donglifeng.shop.common.context.SpringBeanContext;
import cn.donglifeng.shop.common.redis.RedisUtil;
import cn.donglifeng.shop.socket.config.WebSocketConfiguration;
import cn.donglifeng.shop.socket.util.WebSocketEndpointTool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author JanYork
 * @date 2023/3/14 11:36
 * @description WebSocket服务端点
 */
@ServerEndpoint(value = "/websocket/{uid}",configurator = WebSocketConfiguration.class)
@Component
@Slf4j
public class WebSocketEndpoint {
    @Resource
    public RedisUtil redisUtil;

    /**
     * 连接建立成功调用的方法
     *
     * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     * @param uid     用户id
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("uid") String uid) {
        try {
            redisUtil.socketOnline(Long.parseLong(uid));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 收到客户端消息后调用的方法
     */
    @OnMessage
    public void onMessage(String message) {
        if (StringUtils.hasLength(message)) {
            //TODO 业务逻辑
        } else {
        }
    }

    /**
     * 连接错误调用的方法
     *
     * @param error 错误信息
     */
    @OnError
    public void onError(Throwable error) {
        error.printStackTrace();
    }

    /**
     * 连接关闭调用的方法
     *
     * @param session 会话
     * @param uid     用户id
     */
    @OnClose
    public void onClose(Session session, @PathParam("uid") String uid) {

    }

    /**
     * @return 在线人数
     */
    public AtomicInteger getOnlineCount() {
        return new AtomicInteger(redisUtil.countSocketOnline().intValue());
    }
}

上面是一个很简单的WebSocket端点服务类。

我打算使用RedisBitmap来做连接人数统计。

空指针?

@Resource
public RedisUtil redisUtil;

我直接注入我封装的Redis工具类,然后自信满满的开始测试。

结果.....

???

居然空指针???什么情况?

我是百思难得其解呀,因为这个类本身也是一个Bean,使用了@Component注解。

寻找答案

我开始使用万能的浏览器搜索。

于是在一番搜寻后,在CSDN东拼西凑,综合找到以下答案:

首先,使用了@ServerEndpoint注解的类中使用@Resource@Autowired注入都会失败,并且报出空指针异常。

原因是WebSocket服务是线程安全的,那么当我们去发起一个ws连接时,就会创建一个端点对象。

那么问题就在这了,根据CSDN上的说明,WebSocket服务是多对象的,不是单例的。

而我们的SpringBean默认就是单例的,在非单例类中注入一个单例的Bean是冲突的。

而且我虽然使用@Component注解了这个类,但是WebSocket的端点仍然不是单例的,这个是必须的,端点服务不可能单例。

来自CSDN@Autowired注解注入对象是在启动的时候就把对象注入,而不是在使用A对象时才把A需要的B对象注入到A中。 而WebSocket在刚刚有说到,有连接时才实例化对象,而且有多个连接就有多个。

如何解决?

知道原因还不好解决吗?我们开发的适合,基本上很常见的遇到要在非Bean的类中使用Bean,因为不被Spring容器所管理的类中是无法注入Bean对象的,所以我们需要去使用一个上下文类,在一开始就将Spring中所有的Bean静态化到上下文类中。

如何实现?

定义一个类,实现ApplicationContextAware接口:

public class SpringBeanContext implements ApplicationContextAware

不过需要注意的是!这个类也必须要是Bean,不如无法获取到SpringApplicationContext

@Component
public class SpringBeanContext implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }
}

重写他的setApplicationContext方法,将ApplicationContext赋值给本类静态的属性。

此时,当我们启动程序,Spring中的Bean对象就全部会被context获取到。

然后我们还需要写从上下文中获取Bean的方法,我就直接丢代码了:

package cn.donglifeng.shop.common.context;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

/**
 * @author JanYork
 * @date 2023/3/8 9:33
 * @description SpringBean上下文
 */
@Component
public class SpringBeanContext implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    /**
     * 获取上下文
     *
     * @return 上下文对象
     */
    public static ApplicationContext getContext() {
        return context;
    }

    /**
     * 根据beanName获取bean
     *
     * @param beanName bean名称
     * @return bean对象
     */
    public Object getBean(String beanName) {
        return context.getBean(beanName);
    }

    /**
     * 根据beanName和类型获取bean
     *
     * @param beanName bean名称
     * @param clazz    bean类型
     * @param <T>      bean类型
     * @return bean对象
     */
    public <T> T getBean(String beanName, Class<T> clazz) {
        return context.getBean(beanName, clazz);
    }

    /**
     * 根据类型获取bean
     *
     * @param clazz bean类型
     * @param <T>   bean类型
     * @return bean对象
     */
    public <T> T getBean(Class<T> clazz) {
        return context.getBean(clazz);
    }
}

解决效果

    /**
     * 连接建立成功调用的方法
     *
     * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     * @param uid     用户id
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("uid") String uid) {
        try {
            RedisUtil bean = SpringBeanContext.getContext().getBean(RedisUtil.class);
            bean.socketCache(uid, session);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这里我通过上下文类去获取到Bean对象,然后测试连接成功了。

扩展知识

注意!我这里有坑,别踩着了,我测试的适合数据还是写入失败了,我这里是想将SocketSession丢到Redis里面实现分布式环境对象共享(小小的尝试)。

 bean.socketCache(uid, session);

显然是不行的,序列化会报错,因为:

看他的源码,他没有去实现Serializable接口,是不能被序列化的!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023 年 03 月,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 空指针?
  • 寻找答案
  • 如何解决?
    • 如何实现?
    • 解决效果
    • 扩展知识
    相关产品与服务
    云数据库 Redis
    腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档