大家好,我是小简,今天我又大意了,在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;
@ServerEndpoint(value = "/websocket/{uid}",configurator = WebSocketConfiguration.class)
@Component
@Slf4j
public class WebSocketEndpoint {@Resourcepublic RedisUtil redisUtil;@OnOpenpublic void onOpen(Session session, @PathParam("uid") String uid) {try {redisUtil.socketOnline(Long.parseLong(uid));} catch (Exception e) {e.printStackTrace();}}@OnMessagepublic void onMessage(String message) {if (StringUtils.hasLength(message)) {} else {}}@OnErrorpublic void onError(Throwable error) {error.printStackTrace();}@OnClosepublic void onClose(Session session, @PathParam("uid") String uid) {}public AtomicInteger getOnlineCount() {return new AtomicInteger(redisUtil.countSocketOnline().intValue());}
}
上面是一个很简单的WebSocket
端点服务类。
我打算使用Redis
的Bitmap
来做连接人数统计。
空指针?
@Resource
public RedisUtil redisUtil;
我直接注入我封装的Redis
工具类,然后自信满满的开始测试。
结果…
???
居然空指针???什么情况?
我是百思难得其解呀,因为这个类本身也是一个Bean
,使用了@Component
注解。
寻找答案
我开始使用万能的浏览器搜索。
于是在一番搜寻后,在CSDN
东拼西凑,综合找到以下答案:
首先,使用了@ServerEndpoint
注解的类中使用@Resource
或@Autowired
注入都会失败,并且报出空指针异常。
原因是WebSocket
服务是线程安全的,那么当我们去发起一个ws
连接时,就会创建一个端点对象。
那么问题就在这了,根据CSDN
上的说明,WebSocket
服务是多对象的,不是单例的。
而我们的Spring
的Bean
默认就是单例的,在非单例类中注入一个单例的Bean
是冲突的。
而且我虽然使用@Component
注解了这个类,但是WebSocket
的端点仍然不是单例的,这个是必须的,端点服务不可能单例。
来自CSDN
:
@Autowired
注解注入对象是在启动的时候就把对象注入,而不是在使用A对象时才把A需要的B对象注入到A中。
而WebSocket
在刚刚有说到,有连接时才实例化对象,而且有多个连接就有多个。
如何解决?
知道原因还不好解决吗?我们开发的适合,基本上很常见的遇到要在非Bean
的类中使用Bean
,因为不被Spring容器所管理的类中是无法注入Bean
对象的,所以我们需要去使用一个上下文类,在一开始就将Spring
中所有的Bean
静态化到上下文类中。
如何实现?
定义一个类,实现ApplicationContextAware
接口:
public class SpringBeanContext implements ApplicationContextAware
不过需要注意的是!这个类也必须要是Bean
,不如无法获取到Spring
的ApplicationContext
。
@Component
public class SpringBeanContext implements ApplicationContextAware {private static ApplicationContext context;@Overridepublic 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;
@Component
public class SpringBeanContext implements ApplicationContextAware {private static ApplicationContext context;&#64;Overridepublic void setApplicationContext(&#64;NonNull ApplicationContext applicationContext) throws BeansException {context &#61; applicationContext;}public static ApplicationContext getContext() {return context;}public Object getBean(String beanName) {return context.getBean(beanName);}public <T> T getBean(String beanName, Class<T> clazz) {return context.getBean(beanName, clazz);}public <T> T getBean(Class<T> clazz) {return context.getBean(clazz);}
}
解决效果
&#64;OnOpenpublic void onOpen(Session session, &#64;PathParam("uid") String uid) {try {RedisUtil bean &#61; SpringBeanContext.getContext().getBean(RedisUtil.class);bean.socketCache(uid, session);} catch (Exception e) {e.printStackTrace();}}
这里我通过上下文类去获取到Bean
对象&#xff0c;然后测试连接成功了。
扩展知识
注意&#xff01;我这里有坑&#xff0c;别踩着了&#xff0c;我测试的适合数据还是写入失败了&#xff0c;我这里是想将Socket
的Session
丢到Redis
里面实现分布式环境对象共享(小小的尝试)。
bean.socketCache(uid, session);
显然是不行的&#xff0c;序列化会报错&#xff0c;因为&#xff1a;
看他的源码&#xff0c;他没有去实现Serializable
接口&#xff0c;是不能被序列化的&#xff01;
好了&#xff0c;此文结束&#xff0c;下一篇小简来讲将分布式环境下WebSocket
的同步问题吧&#xff01;