@ServerEndpoint类不由Spring管理,无法直接注入或调用;正确做法是用静态线程安全集合(如CopyOnWriteArraySet)存储Session,再通过@Service暴露发送方法供Controller调用。

直接用 @ServerEndpoint 注解写的 WebSocket 服务,没法从 Spring 容器里直接调用(比如在 Controller 或 Service 里发消息),这是绝大多数人卡住的第一步。
为什么不能在 Controller 里 new WebSocket() 发消息
WebSocket 端点类(带 @ServerEndpoint)不是 Spring 管理的 Bean,它由容器(如 Tomcat)直接实例化,不走 Spring 生命周期。所以你在 @RestController 里 new WebSocket() 或注入它,都会失败或空指针。
-
WebSocket实例只在连接建立时由容器创建,每个连接一个实例,生命周期与 Session 绑定 - Spring 的
@Autowired对@ServerEndpoint类完全无效 - 试图在 Controller 里保存
WebSocket引用 → 多线程下会错乱、内存泄漏、并发异常
正确做法:用静态集合 + 同步广播
必须把所有活跃的 Session 存起来,用线程安全的容器,再提供一个 Spring Bean 方法供后台调用。这是最轻量、最可控、也最符合生产要求的方式。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 用
CopyOnWriteArraySet存活连接,避免遍历时并发修改异常 - 在
@OnOpen里webSocketSet.add(session),@OnClose和@OnError里移除 - 写一个
@Service类,暴露sendToAll(String message)方法,内部遍历集合调用session.getAsyncRemote().sendText() - 不要用
getBasicRemote()—— 它是同步阻塞的,高并发下会拖垮整个连接池
后台管理发送消息的实际调用链
用户在后台点击「发布公告」,触发的是普通 HTTP 请求,后端要做的只是拿到消息内容,然后调用上面那个 sendToAll() 方法。不需要任何前端 WebSocket 地址、Token 或鉴权逻辑介入。
- Controller 接收 POST 数据,校验权限(如
@PreAuthorize("hasRole('ADMIN')")) - 组装 JSON 字符串,例如:
{"type":"notice","title":"系统维护通知","content":"将于今晚23:00停机10分钟"} - 调用
webSocketService.sendToAll(jsonStr) - 前端 WebSocket
onmessage收到后解析event.data,渲染气泡或铃铛角标 - 注意:如果需要“已读/未读”状态,这个动作必须走独立 HTTP 接口(如
/api/notice/read),WebSocket 只负责推送,不负责状态变更
容易被忽略的三个细节
心跳、跨域、连接数限制,这三个问题不会报错,但上线后一定出事。
- 浏览器默认 60 秒无通信就断开 WebSocket 连接 —— 必须前后端约定心跳(如每 45 秒
ws.send("ping"),后端收到回"pong") - Spring Boot 的
WebSocketConfig里没配setAllowedOrigins("*")?Chrome 会静默拒绝连接,控制台只显示net::ERR_CONNECTION_REFUSED - Tomcat 默认最大 WebSocket 连接数是 1000,如果你的系统有 5000 个在线用户,得在
application.properties加:server.tomcat.max-connections=6000










