springboot使用websocket (1)添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
(2)配置一下
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration public class WebSocketConfig { /** * ServerEndpointExporter 作用 * * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint * * @return */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
(3)写一个服务
package com.example.demo.service; import org.springframework.stereotype.Component;
import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger;
@ServerEndpoint("/webSocket/{sid}") @Component public class WebSocketServer { //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static AtomicInteger onlineNum = new AtomicInteger();
//concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。 private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>();
//发送消息 public void sendMessage(Session session, String message) throws IOException { if(session != null){ synchronized (session) { // System.out.println("发送数据:" + message); session.getBasicRemote().sendText(message); } } } //给指定用户发送信息 public void sendInfo(String userName, String message){ Session session = sessionPools.get(userName); try { sendMessage(session, message); }catch (Exception e){ e.printStackTrace(); } }
//建立连接成功调用 @OnOpen public void onOpen(Session session, @PathParam(value = "sid") String userName){ sessionPools.put(userName, session); addOnlineCount(); System.out.println(userName + "加入webSocket!当前人数为" + onlineNum); try { sendMessage(session, "欢迎" + userName + "加入连接!"); } catch (IOException e) { e.printStackTrace(); } }
//关闭连接时调用 @OnClose public void onClose(@PathParam(value = "sid") String userName){ sessionPools.remove(userName); subOnlineCount(); System.out.println(userName + "断开webSocket连接!当前人数为" + onlineNum); }
//收到客户端信息 @OnMessage public void onMessage(String message) throws IOException{ message = "客户端:" + message + ",已收到"; System.out.println(message); for (Session session: sessionPools.values()) { try { sendMessage(session, message); } catch(Exception e){ e.printStackTrace(); continue; } } }
//错误时调用 @OnError public void onError(Session session, Throwable throwable){ System.out.println("发生错误"); throwable.printStackTrace(); }
public static void addOnlineCount(){ onlineNum.incrementAndGet(); }
public static void subOnlineCount() { onlineNum.decrementAndGet(); }
}
启动,页面就可以通过websocket与服务端进行交互了。
(4)同样的代码,弄到另外的工程里就不好使,感觉没有扫描配置一样。 研究半天,发现是懒加载的问题。
懒加载,@Configuration注解的类,启动时都没执行。 比如WebMvcConfigurer,访问时才会执行。 而我的websocket访问时,都不执行了,伤不起。 去掉懒加载就会走配置类,就行了。
或者 @Lazy(false) @Configuration 指定某个配置不懒加载,应该也行吧,我没试验。 ----------------------------------------------------- 如果你这样写,@ServerEndpoint(value = "/xxx", configurator = SpringConfigurator.class) 多了configurator = SpringConfigurator.class就会报错 java.lang.IllegalStateException: Failed to find the root WebApplicationContext. Was ContextLoaderListener not used? at org.springframework.web.socket.server.standard.SpringConfigurator.getEndpointInstance(SpringConfigurator.java:70) ~[spring-websocket-5.3.4.jar:5.3.4] at org.apache.tomcat.websocket.pojo.PojoEndpointServer.onOpen(PojoEndpointServer.java:44) ~[tomcat-embed-websocket-9.0.43.jar:9.0.43]
搜了半天,原因是这个: SpringConfigurator uses ContextLoader to obtain spring context. Spring Boot does set up the ServletContext but it never uses ContextLoaderListener which initializes ContextLoader to hold static state of spring context. You can try to add ContextLoaderListener or as a workaround you can write your own context holder and configurator. SpringConfigurator依赖ContextLoader获取spring上下文,但是Spring Boot没用ContextLoaderListener,你得自己搞。
简单的办法,就是把configurator = SpringConfigurator.class去掉。
复杂的办法:
import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware;
import javax.websocket.server.ServerEndpointConfig;
public class CustomSpringConfigurator extends ServerEndpointConfig.Configurator implements ApplicationContextAware {
/** * Spring application context. */ private static volatile BeanFactory context;
@Override public <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException { return context.getBean(clazz); }
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { CustomSpringConfigurator.context = applicationContext; } }
@ConditionalOnWebApplication @Configuration public class WebSocketConfigurator {
...
@Bean public CustomSpringConfigurator customSpringConfigurator() { return new CustomSpringConfigurator(); // This is just to get context } }
@ServerEndpoint(value = "/", decoders = MessageDecoder.class, encoders = MessageEncoder.class, configurator = CustomSpringConfigurator.class) public class ServerEndPoint { ... }
好费劲啊。
|