大家好,我是小简,明天我又粗心了,在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@Slf4jpublic 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来做连贯人数统计。

空指针?

@Resourcepublic 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

@Componentpublic 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上下文 */@Componentpublic 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接口,是不能被序列化的!

好了,此文完结,下一篇小简来讲将分布式环境下WebSocket的同步问题吧!