SpringBoot集成WebSocket的坑之Bean不是单例的(不来源于上下文)

987 阅读1分钟

因工作需要,需学习一下WebSocket的使用,结果发现了一个坑,具体如下:

1、首先,搭建工程,结构如下:

project.png

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,模拟了两次,结果如图:

image.png

3、问题所在:

这个WebSocketHandler类应该由Spring进行管理,所以它应该是单例的,会有线程安全问题,也就是sessionMap的size应该会增长,看结果并没有。 从现象上看,每次它都是新生成一个实例,我验证的话也是多次请求该地址,判断this的hashcode是否相同,结果不一样。

4、解决办法:

sessionMap换成static修饰的concurrentHashMap。或者也可配置由Spring进行管理。

5、交由Spring管理的配置:

image.png

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找不到配置类

6、git仓库地址:

SpringBoot集成WebSocket