WebSocketEndpoint注解不生效主因是类未被容器扫描:Servlet容器默认不自动扫描@ServerEndpoint类,需确认容器支持JSR-356、启用WebSocket模块,Spring Boot中应使用ServerEndpointExporter而非直接注解。

WebSocketEndpoint注解不生效?检查类是否被容器扫描到
Java里用@ServerEndpoint写完类,启动后连不上——大概率不是代码写错了,而是这个类压根没被WebSocket容器加载。Tomcat、Jetty这些Servlet容器默认只扫描META-INF/services/javax.websocket.server.ServerEndpointConfig$Builder或通过ServerApplicationConfig显式注册的类,不会自动扫你包里的任意@ServerEndpoint。
实操建议:
- 确认你用的是支持JSR-356的容器(如Tomcat 7.0.47+、Jetty 9.1+),且没禁用WebSocket模块
- 如果用Spring Boot,别直接用
@ServerEndpoint——它和Spring IoC不兼容;改用ServerEndpointExporterBean或迁移到Spring WebFlux的WebSocketHandler - 纯Servlet环境,确保类在
ServletContext初始化时被ServerContainer发现:要么放在WEB-INF/classes下并配置web.xml中的<servlet-container-initializer></servlet-container-initializer>,要么用ServerApplicationConfig手动返回Set<serverendpointconfig></serverendpointconfig>
onMessage方法接收String还是ByteBuffer?看客户端发什么
WebSocket协议本身不分“文本帧”和“二进制帧”,但Java API强制你声明处理方式:@OnMessage方法参数类型决定了容器如何解析入站帧。传String就走UTF-8解码,传ByteBuffer或byte[]就原样交付。一旦客户端发的是二进制帧,而你只写了String参数的方法,会直接抛DecodeException,连接中断。
常见错误现象:
立即学习“Java免费学习笔记(深入)”;
- 浏览器调
socket.send(new ArrayBuffer()),服务端报javax.websocket.DecodeException: Text message not supported - Android客户端用OkHttp发JSON字符串,但服务端用
ByteBuffer接收,结果前几个字节是乱码(因为没按UTF-8解释)
实操建议:
- 一个
@ServerEndpoint类里可以同时定义多个@OnMessage方法,分别处理String、ByteBuffer、PongMessage等,容器会按帧类型自动路由 - 不要在
String方法里再手动new String(bytes, StandardCharsets.UTF_8)——重复解码会出错 - 如果必须统一处理,优先选
ByteBuffer,再根据buffer.hasArray()和buffer.arrayOffset()安全提取原始字节
session.getBasicRemote().sendText()阻塞?注意I/O线程模型
调用session.getBasicRemote().sendText()看似简单,但它底层依赖容器的I/O线程池。Tomcat默认用org.apache.tomcat.util.net.NioEndpoint的共享线程池,如果某次发送触发了TCP重传、网络抖动或对方接收窗口满,这个调用就会同步阻塞当前线程——而你的@OnMessage方法很可能就在这个线程里跑着,导致后续消息积压甚至超时断连。
性能影响:
- 高并发场景下,几十个慢连接就能拖垮整个WebSocket I/O线程池
- Tomcat 8.5+开始默认启用异步发送(
sendText(String, boolean)第二个参数设true),但BasicRemote不支持回调,失败只能靠Session.addMessageHandler()捕获异常
实操建议:
- 改用
session.getAsyncRemote():它的sendText()是非阻塞的,失败时通过Future或SendHandler通知 - 不要在
@OnError里直接调session.close()——此时session可能已失效,会抛IllegalStateException - 如果必须同步发,加超时控制:
session.setMaxIdleTimeout(30_000)防长连接僵死
为什么@OnOpen里拿不到HttpSession?因为WebSocket握手已完成
很多人想在@OnOpen方法里获取用户登录态,顺手写httpSession = (HttpSession) session.getUserProperties().get("httpSession")——这行代码永远是null。因为JSR-356规范明确要求:WebSocket握手(HTTP Upgrade)完成后,原始HTTP请求生命周期就结束了,HttpSession对象不可再访问。
使用场景:
- 需要鉴权或绑定用户ID,不能等到
@OnMessage才做(防止恶意连接占资源) - 想把WebSocket
Session和已有的用户会话关联起来
实操建议:
- 在HTTP Upgrade请求的URL里带token或sid,比如
ws://host/chat?token=abc123,然后在@OnOpen里用session.getRequestParameterMap()取出来校验 - 更安全的做法是:先用普通HTTP接口校验token并返回临时ticket,再用ticket发起WebSocket连接,服务端在
@OnOpen里验证ticket有效性 - 避免在
@OnOpen里做耗时操作(如查DB),否则会卡住WebSocket握手,浏览器报net::ERR_CONNECTION_RESET
真正麻烦的从来不是写几个注解,而是搞清容器在哪一层接管了连接、HTTP上下文何时销毁、线程池怎么分配——这些边界不摸清,光靠复制粘贴示例代码,上线后准出问题。











