大家好,我是小简,明天我又粗心了,在
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
端点服务类。
我打算应用 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;
@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
对象,而后测试连贯胜利了。
扩大常识
留神!我这里有坑,别踩着了,我测试的适宜数据还是写入失败了,我这里是想将 Socket
的Session
丢到 Redis
外面实现分布式环境对象共享(小小的尝试)。
bean.socketCache(uid, session);
显然是不行的,序列化会报错,因为:
看他的源码,他没有去实现 Serializable
接口,是不能被序列化的!
好了,此文完结,下一篇小简来讲将分布式环境下 WebSocket
的同步问题吧!