推荐用@serverendpoint方式——它直连servlet容器、响应快、线程隔离,适合设备直连等场景;但需配serverendpointexporter且路径须绝对,禁与websockethandler混用。

Spring Boot集成WebSocket,**推荐用@ServerEndpoint方式**——它上手快、路径直、不依赖Spring MVC请求生命周期,特别适合设备直连、轻量通知、后台服务间通信等场景。但前提是别把它和WebSocketHandler混着配,否则会注册失败、连接404、甚至启动报错。
为什么@ServerEndpoint方式更推荐?
它直接走Servlet容器(如Tomcat)原生WebSocket运行时,绕过Spring的DispatcherServlet,响应更快、资源开销更低。尤其在物联网设备直连、高频心跳、低延迟推送等场景下,没有Spring拦截链路的干扰,稳定性更高。
关键点在于:@ServerEndpoint类是每个连接新建一个实例,天然线程隔离;而WebSocketHandler是单例,所有连接共用一个对象,共享状态必须手动加锁或用ConcurrentHashMap,容易出并发问题。
常见错误现象:
- 客户端连
ws://localhost:8080/ws返回404 → 没配ServerEndpointExporter或路径写错 - 连接成功但收不到消息 →
@OnMessage方法没加public修饰符,或参数类型不匹配(只支持String、byte[]、Session等有限类型) - 多客户端连接后互相干扰 → 误把会话集合(如
static Map)放在@ServerEndpoint类里,但每个连接都是新实例,静态变量其实能共享,但生命周期难控,建议改用外部Bean管理
ServerEndpointExporter必须配,且仅限内嵌容器
Spring Boot内嵌Tomcat/Jetty时,@ServerEndpoint注解不会被自动扫描——这是Java EE规范限制,不是Spring漏了。必须显式注册ServerEndpointExporter Bean来“桥接”Spring容器和Servlet容器。
配置示例:
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
注意:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 如果部署到独立Tomcat(war包),这个Bean**不能存在**,否则启动报
java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available - 路径必须用绝对路径,比如
@ServerEndpoint("/ws"),不能写成@ServerEndpoint("/api/ws")再靠Spring MVC路由转发——那根本不是WebSocket握手 - 不要在
@ServerEndpoint类上加@Component或@Scope("prototype"),它不由Spring管理实例,加了也没用
@ServerEndpoint类的写法要点
核心是四个生命周期注解:@OnOpen、@OnMessage、@OnClose、@OnError,它们方法必须是public,参数类型要严格匹配。
典型结构:
@ServerEndpoint("/ws")
public class DeviceWebSocketEndpoint {
// 存储所有活跃会话,用static + ConcurrentHashMap(因为每个连接是新实例)
private static final ConcurrentHashMap<string session> sessions = new ConcurrentHashMap();
@OnOpen
public void onOpen(Session session) {
sessions.put(session.getId(), session);
}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
// 处理文本消息,例如JSON格式的设备指令
sessions.values().forEach(s -> {
try { s.getBasicRemote().sendText("ACK:" + message); }
catch (IOException ignored) {}
});
}
@OnClose
public void onClose(Session session) {
sessions.remove(session.getId());
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
}</string>
容易踩的坑:
-
session.getBasicRemote().sendText()是阻塞调用,高并发下可能卡住线程 → 如需异步发,改用session.getAsyncRemote() - 不要在
@OnMessage里做耗时操作(如DB查询、HTTP调用)→ 会阻塞该连接的后续消息接收,应丢进线程池或发事件 - 路径中带参数(如
/ws/{deviceId})不被@ServerEndpoint原生支持 → 需在@OnOpen里解析session.getRequestParameterMap()或改用WebSocketHandler方式
跨域和生产环境注意事项
开发阶段设setAllowedOrigins("*")方便调试,但上线必须明确指定来源域名,否则有安全风险。不过@ServerEndpoint本身不提供跨域配置入口——它是Servlet容器层面的端点,跨域由前端发起的HTTP握手请求控制,实际生效的是浏览器对Origin头的校验。
真正要处理的只有两点:
- 确保Nginx或网关层不拦截
Upgrade和Connection头(常见错误:Nginx默认不透传这些头,需加proxy_set_header Upgrade $http_upgrade;等配置) - 如果用SSL,WebSocket必须用
wss://,且证书要有效;自签名证书在Chrome里会直接拒绝握手 - 连接空闲超时:Tomcat默认60秒关闭空闲连接,可在
application.properties加server.tomcat.connection-timeout=180000,或通过ServletServerContainerFactoryBean设置maxSessionIdleTimeout(仅对WebSocketHandler方式有效)
最常被忽略的一点:WebSocket连接建立后,**HTTP Session和WebSocket Session完全无关**。别指望在@OnOpen里拿到HttpSession,认证和用户绑定得靠Token传参、URL携带或握手时的HttpServletRequest(后者仅WebSocketHandler方式能拿到)。










