springboot3 集成websocket报错记录
一、pom依赖版本:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--添加以下排除方法-->
<exclusions>
<!-- 排除所有 Tomcat 相关 -->
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--websocket服务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 确保使用正确的 WebSocket 依赖 -->
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-api</artifactId>
<version>2.1.1</version>
</dependency>
<!-- Undertow 容器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
二、websocket配置类
@Configuration
@EnableWebSocket
public class WebSocketWarningConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler(), "/websocket")
.setAllowedOrigins("*"); // 允许跨域访问
}
@Bean
public WebSocketHandler webSocketHandler() {
// 使用自定义的WebSocket处理器
return new CustomWebSocketHandler();
}
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
三、WebSocketHandler
@Slf4j
@Component
public class CustomWebSocketHandler extends TextWebSocketHandler {
// 用于存储WebSocket会话
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
String sessionId = session.getId();
sessions.put(sessionId, session);
log.info("WebSocket连接建立成功:{}", sessionId);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
String payload = message.getPayload();
log.info("收到消息:{}", payload);
if (payload.equals("ping")) {
session.sendMessage(new TextMessage("pong"));
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
String sessionId = session.getId();
sessions.remove(sessionId);
log.info("WebSocket连接关闭:{}", sessionId);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
log.error("WebSocket传输错误", exception);
}
@Scheduled(fixedRate = 30000) // 每30秒发送一次心跳,需要启动类或配置类上添加@EnableScheduling
public void sendHeartbeat() {
String heartbeat = "heartbeat";
sessions.entrySet().removeIf(entry -> {
WebSocketSession session = entry.getValue();
try {
session.sendMessage(new TextMessage(heartbeat));
return false; // 会话仍然有效,不删除
} catch (IOException e) {
log.error("发送心跳消息失败,移除会话: {}", entry.getKey(), e);
try {
session.close(); // 尝试关闭会话
} catch (IOException closeException) {
log.error("关闭会话失败: {}", entry.getKey(), closeException);
}
return true; // 标记会话为无效,需要删除
}
});
}
public void sendMessageWithRetry(WebSocketSession session, String message, int maxRetries) {
int retryCount = 0;
while (retryCount < maxRetries) {
try {
session.sendMessage(new TextMessage(message));
return;
} catch (IOException e) {
retryCount++;
log.error("消息发送失败,尝试重试 {}/{}", retryCount, maxRetries);
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
log.error("消息发送失败,达到最大重试次数");
}
四、启动报错
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serverEndpointExporter' defined in class path resource [com/surfilter/fuxi/data/config/WebSocketWarningConfig.class]: jakarta.websocket.server.ServerContainer not available
五、问题排查
排除掉了jetty和tomcat容器,项目依旧使用jetty启动,未使用undertow启动,serverContainer获取null值报错
protected void initServletContext(ServletContext servletContext) {
if (this.serverContainer == null) {
this.serverContainer = (ServerContainer)servletContext.getAttribute("jakarta.websocket.server.ServerContainer");
}
}
Jetty 使用不同的属性名存储 ServerContainer,进而无法获取,导致ServerEnpointExporter实例化失败。采用undertow处理
六、问题处理
6.1 添加依赖
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-websockets-jsr</artifactId>
<version>2.3.10.Final</version> <!-- Spring Boot 3.0.6 对应的版本 -->
</dependency>
6.2 添加undertow配置类
@Configuration
public class UndertowWebSocketConfig {
@Bean
public UndertowServletWebServerFactory undertowWebSocketFactory() {
UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
// 配置 WebSocket
WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
// 添加到部署信息
deploymentInfo.addServletContextAttribute(
WebSocketDeploymentInfo.ATTRIBUTE_NAME,
webSocketDeploymentInfo
);
// 设置 Jakarta WebSocket 容器属性
deploymentInfo.addServletContextAttribute(
"jakarta.websocket.server.ServerContainer",
webSocketDeploymentInfo
);
});
return factory;
}
}
服务器工厂配置:创建 UndertowServletWebServerFactory Bean 来定制 Undertow 服务器
WebSocket 支持:通过 WebSocketDeploymentInfo 配置 WebSocket 部署信息
容器属性设置:设置 Jakarta WebSocket 容器属性,确保 WebSocket 能够正常工作(可不配置)
七、最终效果



魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)