因工作需要,需学习一下WebSocket的使用,结果发现了一个坑,具体如下:
1、首先,搭建工程,结构如下:
1.1配置类代码:
@Configuration
@EnableWebSocket
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
1.2处理请求代码:
@ServerEndpoint("/websocket/test")
@Service
public class WebSocketHandler {
private final Map<String, WebSocketHandler> sessionMap = new HashMap<>();
private Session session;
public void setSession(Session session) {
this.session = session;
}
@OnOpen
public void open(Session session){
setSession(session);
sessionMap.put(session.getId(), this);
System.out.println("size is " + sessionMap.size());
}
@OnClose
public void close(Session session){
System.out.println("关闭一个连接");
sessionMap.remove(session.getId());
}
@OnMessage
public void sendMessage(String message){
for (Map.Entry<String, WebSocketHandler> sessionEntry : sessionMap.entrySet()) {
try {
sessionEntry.getValue().session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、运行效果:
我使用的是一个在线网站模拟发送webSocket请求,网站地址点击这里,新开一个网页可以模拟一个新的session,模拟了两次,结果如图:
3、问题所在:
这个WebSocketHandler类应该由Spring进行管理,所以它应该是单例的,会有线程安全问题,也就是sessionMap的size应该会增长,看结果并没有。 从现象上看,每次它都是新生成一个实例,我验证的话也是多次请求该地址,判断this的hashcode是否相同,结果不一样。
4、解决办法:
sessionMap换成static修饰的concurrentHashMap。或者也可配置由Spring进行管理。
5、交由Spring管理的配置:
5.1、在目录结构中新增SpringWebSocketConfigurator.java文件,代码如下:
public class SpringWebSocketConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware {
private static volatile BeanFactory applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 重写该方法是为了将endpoint的实例从Spring的上下文中获取
* @param clazz
* @param <T>
* @return
* @throws InstantiationException
*/
@Override
public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {
return applicationContext.getBean(clazz);
}
}
5.2、修改WebSocketConfig.java配置如下:
@Configuration
@ConditionalOnWebApplication
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
@Bean
public SpringWebSocketConfigurator springWebSocketConfigurator(){
return new SpringWebSocketConfigurator();
}
}
5.3、修改WebSocketHandler.java中注解@ServerEndpoint如下:
@ServerEndpoint(value = "/websocket/test",configurator = SpringWebSocketConfigurator.class)
注意:不指定configurator = SpringWebSocketConfigurator.class会报错,Spring找不到配置类