Spring WebSocket配置报错:如何正确调用WebSocketSession?
问题分析与解决方案
关键错误根源
WebSocketSession不能作为Spring Bean注入:它是每个WebSocket连接的临时实例,由Spring在连接建立时动态创建,并非容器管理的单例Bean,因此不能通过构造注入到配置类或拦截器中。- 手动调用
WebSocketHandler方法错误:Spring会自动管理WebSocketHandler的生命周期,连接建立时会自动触发afterConnectionEstablished方法,无需手动调用。 - STOMP与原生WebSocket配置冲突:你同时使用了
@EnableWebSocketMessageBroker(STOMP协议)和自定义WebSocketHandler,两者的消息处理机制不一致,导致逻辑混乱。
方案一:基于STOMP协议的正确配置(推荐)
如果你需要使用STOMP的消息代理能力,按以下步骤修改:
1. 修改WebsocketConfig
移除WebSocketSession注入,改用Spring管理的拦截器:
@Configuration @EnableWebSocketMessageBroker @RequiredArgsConstructor @Slf4j public class WebsocketConfig implements WebSocketMessageBrokerConfigurer { private final HandshakeInterceptor handshakeInterceptor; @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/queue/", "/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/connect-socket") .addInterceptors(handshakeInterceptor) .setAllowedOrigins("*"); registry.addEndpoint("/connect-socket") .addInterceptors(handshakeInterceptor) .setAllowedOrigins("*") .withSockJS(); } }
2. 修改HandshakeInterceptor
移除WebSocketSession注入,在握手阶段完成参数验证:
@Component @Slf4j public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { URI uri = request.getURI(); String query = uri.getQuery(); // 参数合法性校验 if (query == null || !query.contains("auth=") || !query.contains("serialNumber=")) { response.setStatusCode(HttpStatus.BAD_REQUEST); return false; } // 解析参数(修复原代码的截取错误) String auth = query.substring(0, query.indexOf("&")).replace("auth=", ""); String serialNumber = query.substring(query.lastIndexOf("=") + 1); // 执行连接验证逻辑 if (!validateConnection(auth, serialNumber)) { response.setStatusCode(HttpStatus.UNAUTHORIZED); return false; } // 将验证后的参数存入属性,后续STOMP控制器可获取 attributes.put("auth", auth); attributes.put("serialNumber", serialNumber); return super.beforeHandshake(request, response, wsHandler, attributes); } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) { super.afterHandshake(request, response, wsHandler, ex); } private boolean validateConnection(String auth, String serialNumber) { // 实现你的验证逻辑(比如调用权限服务) log.info("验证连接:auth={}, serialNumber={}", auth, serialNumber); return true; // 根据实际情况返回 } }
3. 移除自定义WebsocketHandler(STOMP模式下无需)
如果需要处理STOMP消息,创建控制器即可:
@Controller public class StompController { @MessageMapping("/hello") @SendTo("/topic/greetings") public Greeting greeting(HelloMessage message, @Header("simpSessionAttributes") Map<String, Object> attributes) { String auth = (String) attributes.get("auth"); String serialNumber = (String) attributes.get("serialNumber"); log.info("收到消息:{},来自auth={}, serialNumber={}", message.getName(), auth, serialNumber); return new Greeting("Hello, " + message.getName() + "!"); } }
方案二:基于原生WebSocket的配置(无需STOMP)
如果不需要STOMP协议,直接使用原生WebSocket:
1. 修改WebsocketConfig
实现WebSocketConfigurer,注册自定义处理器:
@Configuration @RequiredArgsConstructor @Slf4j public class WebsocketConfig implements WebSocketConfigurer { private final WebsocketHandler websocketHandler; private final HandshakeInterceptor handshakeInterceptor; @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(websocketHandler, "/connect-socket") .addInterceptors(handshakeInterceptor) .setAllowedOrigins("*"); // 支持SockJS registry.addHandler(websocketHandler, "/connect-socket") .addInterceptors(handshakeInterceptor) .setAllowedOrigins("*") .withSockJS(); } }
2. 修改HandshakeInterceptor(同方案一的拦截器代码)
移除WebSocketSession注入,仅做前置验证。
3. 保留并优化WebsocketHandler
Spring会自动触发生命周期方法:
@Component @Slf4j public class WebsocketHandler implements WebSocketHandler { @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { Map<String, Object> attributes = session.getAttributes(); String auth = (String) attributes.get("auth"); String serialNumber = (String) attributes.get("serialNumber"); log.info("============= 连接建立 ============="); log.debug("auth: {}", auth); log.info("serial number: {}", serialNumber); // 这里可做后续处理,比如将session存入全局Map管理 // sessionMap.put(serialNumber, session); } @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { log.info("收到消息:{}", message.getPayload()); // 处理消息逻辑 } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { log.error("传输错误:{}", exception.getMessage()); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { log.info("连接关闭:{}", closeStatus); } @Override public boolean supportsPartialMessages() { return false; } }
内容的提问来源于stack exchange,提问作者Evan Mahendra
相关产品推荐
相关产品推荐

