惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
About on SuperTechFans
Y
Y Combinator Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Security Blog
Microsoft Security Blog
aimingoo的专栏
aimingoo的专栏
I
InfoQ
C
Check Point Blog
IT之家
IT之家
MyScale Blog
MyScale Blog
Apple Machine Learning Research
Apple Machine Learning Research
Vercel News
Vercel News
Last Week in AI
Last Week in AI
GbyAI
GbyAI
P
Proofpoint News Feed
量子位
Stack Overflow Blog
Stack Overflow Blog
Microsoft Azure Blog
Microsoft Azure Blog
月光博客
月光博客
阮一峰的网络日志
阮一峰的网络日志
人人都是产品经理
人人都是产品经理
B
Blog
T
The Blog of Author Tim Ferriss
H
Help Net Security
云风的 BLOG
云风的 BLOG

钟意博客

国家统计局数据查询 MCP 照见·硅·肆 涌现之前 照见·硅·叁 换行重启 照见·硅·贰 钢筋水泥 照见·硅·壹 你好世界 照见·硅·零 序言 大语言模型的不确定性 科艺知识库 ARM64 胶东半岛观察 海光 K100 DCU VLLM 推理环境构建 博客多平台负载均衡方案 浅谈RAG Web技术构建桌面应用方案 SpringBoot WebSocket 代理模式、客户端模式 Spring AOP 调用自身失效 window 端口占用但是查不到 Coze同插件不同工具之间代码复用 Pachelbel's Greatest Hit: The Ultimate Canon - 纪念帕海贝尔:终极卡农 圣诞快乐,劳伦斯先生 Merry Christmas Mr. Lawrence
Spring WebSocket 错误
钟意 · 2025-06-07 · via 钟意博客

1. JSR-356 容器握手失败

现象

  • 控制台没有任何 afterConnectionEstablished 日志
  • 客户端卡在握手阶段或报错超时

原因

  • Spring MVC 默认用 StandardWebSocketClient(),但未指定底层 WebSocketContainer
  • 嵌入式 Tomcat(JSR-356 实现)与默认容器不匹配,导致握手不发起

排查 & 解决

  1. 显式注入 JSR-356 容器:

    1
    2
    3
    4
    5
    @Bean
    fun webSocketClient(): WebSocketClient {
    val container = ContainerProvider.getWebSocketContainer()
    return StandardWebSocketClient(container)
    }
  2. 或干脆切换到 Reactor Netty 客户端:

    1
    2
    3
    4
    @Bean
    fun webSocketClient(): WebSocketClient {
    return ReactorNettyWebSocketClient()
    }
  3. 启动时应看到类似日志:

    1
    Downstream connection established for session: <id>
  4. 实在不行web等依赖都排除tomcat,自行在web依赖加上exclusions:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <!-- 1. Spring Boot Web,但排除默认 Tomcat -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
    <!-- 排除 Tomcat 嵌入式容器 -->
    <exclusion>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    </exclusion>
    </exclusions>
    </dependency>

    <!-- 2. 专门引入 Undertow 及其 JSR-356 支持 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>
    <!-- Undertow 对 JSR-356 WebSocket 的实现 -->
    <dependency>
    <groupId>io.undertow</groupId>
    <artifactId>undertow-websockets-jsr</artifactId>
    <!-- 可根据 Spring Boot 版本选择合适版本,通常与 Spring Boot 兼容即可 -->
    </dependency>

    <!-- 3. Spring WebSocket 模块,用于控制握手与消息处理 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <!-- 注意:Spring Boot Starter WebSocket 本身会带 Tomcat 的 WebSocket 支持,
    但由于我们已经排除了 Tomcat Starter,这里只会引入 spring-websocket 相关依赖,不会再带 tomcat-embed-websocket -->
    </dependency>

2. 路由没生效

现象

  • 配置了 WebSocketConfigurer,但 afterConnectionEstablished 并未触发
  • 日志中不见任何 /your/path 映射信息

原因

  • 构造函数注入 List<ISocketHandler> 导致循环依赖
  • Spring Boot 2.6+ 默认禁用循环引用,Bean 未注册
  • 或者忘记 @EnableWebSocket

排查 & 解决

  1. 确保配置类加上:

    1
    2
    3
    4
    5
    @Configuration
    @EnableWebSocket
    class WebSocketConfig(
    @Lazy private val handlers: List<ISocketHandler>
    ) : WebSocketConfigurer { … }
  2. 日志应包含:

    1
    Mapping “[/{registerPath}]” to WebSocketHandler

3. API 弃用与签名变化

3.1 Unresolved reference: handshake / execute

提示

1
2
Unresolved reference 'handshake'
Unresolved reference 'execute'

解读

  • StandardWebSocketClient 使用 doHandshake(...) 而非 execute
  • execute() 属于 WebFlux Reactor 客户端,与 MVC 客户端不通

修正

  • MVC 客户端:

    1
    2
    client.doHandshake(handler, uri)
    .addCallback({ sess -> … }, { ex -> … })
  • Reactor 客户端:

    1
    2
    3
    client.execute(URI.create(uri)) { wsSession ->

    }.subscribe()

3.2 doHandshake(...) 自 6.0 起弃用

提示

1
'doHandshake(WebSocketHandler, String, Object...)' 自版本 6.0 起已弃用并标记为移除

说明

  • Spring Framework 6 推荐注入 WebSocketContainer 或切换到 Reactor Netty
  • 暂可忽略警告,或升级为新版推荐 API

4. 依赖冲突 & 类加载

典型报错

1
2
ClassNotFoundException: javax.websocket.ContainerProvider
NoSuchMethodError: jakarta.websocket.ContainerProvider.getWebSocketContainer()

原因

  • Spring Boot 3.x 使用 jakarta.websocket,2.x 使用 javax.websocket
  • 嵌入式 Tomcat/WebSocket API 版本与项目引入冲突

解决

  • 统一依赖至一个版本,且与 Spring Boot 主版本匹配

  • 必要时在依赖中排除冲突项:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-websocket</artifactId>
    <exclusions>
    <exclusion>
    <groupId>jakarta.websocket</groupId>
    <artifactId>jakarta.websocket-api</artifactId>
    </exclusion>
    </exclusions>
    </dependency>

5. 其他常见错误

错误类型 典型现象 / 异常 排查要点
循环依赖 BeanCreationException: circular reference 使用 @Lazy 或拆分配置
无效 JSON JsonParseException: Unexpected character (‘“’) 前端必须用英文双引号;捕获异常并 friendly 返回
ConcurrentModificationException java.util.ConcurrentModificationException 迭代时修改集合;先转成列表再遍历
NullPointerException Cannot invoke "JsonNode.asText()" ... get(...) 判空或使用 ?.asText(default)