You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,后续会话全程复用该实例):

  1. 调整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())
    }
}
  1. 修改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作用域:

  1. 实现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)
    }
}
  1. 注册自定义作用域:
@Configuration
class WebSocketScopeConfiguration {
    @Bean
    fun webSocketScope(): WebSocketScope = WebSocketScope()

    @Bean
    fun scopeRegistrar(webSocketScope: WebSocketScope): BeanFactoryPostProcessor {
        return BeanFactoryPostProcessor { beanFactory ->
            beanFactory.registerScope("websocket", webSocketScope)
        }
    }
}
  1. 调整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())
    }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 13:48:13