Spring Boot WebSocket无法为新会话创建Prototype作用域Bean实例
如何为每个WebSocket会话创建独立的Handler实例(Spring Boot 非STOMP)
完全可行。你当前遇到的问题是原型Bean使用方式错误,以及自定义WebSocket作用域未注册导致报错。以下是具体解决方案:
为什么原型Bean没生效
你的WebSocketConfig是单例Bean,构造函数注入WebSocketHandler时,Spring仅创建一个原型实例并注入到配置类中。后续所有WebSocket连接都会复用这个已注入的Handler实例,因此日志中哈希码完全一致。
方案一:借助RequestScope快速实现
WebSocket连接始于HTTP握手请求,利用request作用域可间接实现每个会话对应一个Handler实例(每个握手请求生成一个Handler,后续会话全程复用该实例):
- 调整WebSocketHandler的作用域:
@Component @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) class WebSocketHandler : TextWebSocketHandler() { private val log = LoggerFactory.getLogger(this.javaClass) override fun handleTextMessage(session: WebSocketSession, message: TextMessage) { log.info("handleTextMessage with Message {}", message.payload) } override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) { log.info("afterConnectionClosed, sessionId: {} ", session.id) } override fun afterConnectionEstablished(session: WebSocketSession) { log.info("afterConnectionEstablished. hashcode: {}", this.hashCode()) } }
- 修改WebSocketConfig,通过ApplicationContext动态获取Handler:
@Configuration @EnableWebSocket class WebSocketConfig( private val applicationContext: ApplicationContext ) : WebSocketConfigurer { override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) { // 代理Handler,每次操作时获取当前请求作用域的Handler实例 val proxyHandler = object : TextWebSocketHandler() { override fun handleTextMessage(session: WebSocketSession, message: TextMessage) { getHandler().handleTextMessage(session, message) } override fun afterConnectionEstablished(session: WebSocketSession) { getHandler().afterConnectionEstablished(session) } override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) { getHandler().afterConnectionClosed(session, status) } override fun handleTransportError(session: WebSocketSession, exception: Throwable) { getHandler().handleTransportError(session, exception) } private fun getHandler(): WebSocketHandler { return applicationContext.getBean(WebSocketHandler::class.java) } } registry.addHandler(proxyHandler, "/messages") } }
方案二:自定义WebSocket会话作用域(精准控制)
如果需要完全与HTTP请求解耦的会话级作用域,可自定义并注册websocket作用域:
- 实现WebSocketScope类:
class WebSocketScope : Scope { private val sessionBeanMap = ConcurrentHashMap<String, MutableMap<String, Any>>() private val destructionCallbackMap = ConcurrentHashMap<String, MutableMap<String, Runnable>>() override fun get(name: String, objectFactory: ObjectFactory<*>): Any { val sessionId = getCurrentSessionId() ?: throw IllegalStateException("未找到当前WebSocket会话") val beanMap = sessionBeanMap.computeIfAbsent(sessionId) { mutableMapOf() } return beanMap.computeIfAbsent(name) { objectFactory.getObject() } } override fun remove(name: String): Any? { val sessionId = getCurrentSessionId() ?: return null return sessionBeanMap[sessionId]?.remove(name) } override fun registerDestructionCallback(name: String, callback: Runnable) { val sessionId = getCurrentSessionId() ?: return val callbackMap = destructionCallbackMap.computeIfAbsent(sessionId) { mutableMapOf() } callbackMap[name] = callback } override fun resolveContextualObject(key: String): Any? { return if (key == "websocketSessionId") getCurrentSessionId() else null } override fun getConversationId(): String? = getCurrentSessionId() private fun getCurrentSessionId(): String? { val requestAttrs = RequestContextHolder.getRequestAttributes() return requestAttrs?.getAttribute("WEBSOCKET_SESSION_ID", RequestAttributes.SCOPE_REQUEST) as? String } // 会话关闭时清理Bean资源 fun cleanupSession(sessionId: String) { sessionBeanMap.remove(sessionId) destructionCallbackMap.remove(sessionId)?.values?.forEach(Runnable::run) } }
- 注册自定义作用域:
@Configuration class WebSocketScopeConfiguration { @Bean fun webSocketScope(): WebSocketScope = WebSocketScope() @Bean fun scopeRegistrar(webSocketScope: WebSocketScope): BeanFactoryPostProcessor { return BeanFactoryPostProcessor { beanFactory -> beanFactory.registerScope("websocket", webSocketScope) } } }
- 调整WebSocketHandler使用自定义作用域:
@Component @Scope(value = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS) class WebSocketHandler : TextWebSocketHandler() { private val log = LoggerFactory.getLogger(this.javaClass) @Autowired private lateinit var webSocketScope: WebSocketScope override fun handleTextMessage(session: WebSocketSession, message: TextMessage) { log.info("handleTextMessage with Message {}", message.payload) } override fun afterConnectionClosed(session: WebSocketSession, status: CloseStatus) { log.info("afterConnectionClosed, sessionId: {} ", session.id) webSocketScope.cleanupSession(session.id) } override fun afterConnectionEstablished(session: WebSocketSession) { log.info("afterConnectionEstablished. hashcode: {}", this.hashCode()) } }
- 修改WebSocketConfig,在握手时传递会话ID:
@Configuration @EnableWebSocket class WebSocketConfig( private val webSocketHandler: WebSocketHandler ) : WebSocketConfigurer { override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) { registry.addHandler(webSocketHandler, "/messages") .addInterceptors(object : HandshakeInterceptor { override fun beforeHandshake( request: ServerHttpRequest, response: ServerHttpResponse, handler: WebSocketHandler, attributes: MutableMap<String, Any> ): Boolean { // 从握手属性中获取会话ID,或生成新ID val sessionId = attributes["sessionId"] as? String ?: UUID.randomUUID().toString() RequestContextHolder.getRequestAttributes()?.setAttribute( "WEBSOCKET_SESSION_ID", sessionId, RequestAttributes.SCOPE_REQUEST ) return true } override fun afterHandshake( request: ServerHttpRequest, response: ServerHttpResponse, handler: WebSocketHandler, exception: Exception? ) { // 无需额外操作 } }) } }
验证结果
完成配置后重启服务,建立多个WebSocket连接,日志中将显示不同的Handler哈希码,证明每个会话都使用了独立的Handler实例。
内容的提问来源于stack exchange,提问作者jihed zaoueli
相关产品推荐
相关产品推荐

