From c7fbf7809fbbbd408be8cd668f2eab0b9111b733 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sat, 26 Jul 2025 11:38:02 +0200 Subject: [PATCH] Provide @WebSocketScope annotation and public SCOPE_WEBSOCKET constant Closes gh-35235 --- .../ROOT/pages/web/websocket/stomp/scope.adoc | 10 ++-- ...cketMessageBrokerConfigurationSupport.java | 10 +++- .../config/annotation/WebSocketScope.java | 60 +++++++++++++++++++ 3 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketScope.java diff --git a/framework-docs/modules/ROOT/pages/web/websocket/stomp/scope.adoc b/framework-docs/modules/ROOT/pages/web/websocket/stomp/scope.adoc index b066e00e56a..0be9eecb94a 100644 --- a/framework-docs/modules/ROOT/pages/web/websocket/stomp/scope.adoc +++ b/framework-docs/modules/ROOT/pages/web/websocket/stomp/scope.adoc @@ -1,8 +1,8 @@ [[websocket-stomp-websocket-scope]] = WebSocket Scope -Each WebSocket session has a map of attributes. The map is attached as a header to -inbound client messages and may be accessed from a controller method, as the following example shows: +Each WebSocket session has a map of attributes. The map is attached as a header to inbound +client messages and may be accessed from a controller method, as the following example shows: [source,java,indent=0,subs="verbatim,quotes"] ---- @@ -20,13 +20,13 @@ public class MyController { You can declare a Spring-managed bean in the `websocket` scope. You can inject WebSocket-scoped beans into controllers and any channel interceptors registered on the `clientInboundChannel`. Those are typically singletons and live -longer than any individual WebSocket session. Therefore, you need to use a -scope proxy mode for WebSocket-scoped beans, as the following example shows: +longer than any individual WebSocket session. Therefore, you need to use +WebSocket-scoped beans in proxy mode, conveniently defined with `@WebSocketScope`: [source,java,indent=0,subs="verbatim,quotes"] ---- @Component - @Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS) + @WebSocketScope public class MyBean { @PostConstruct diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurationSupport.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurationSupport.java index 31a65520dcc..004ccc5768a 100644 --- a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurationSupport.java +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketMessageBrokerConfigurationSupport.java @@ -54,10 +54,18 @@ import org.springframework.web.socket.server.support.WebSocketHandlerMapping; * @author Rossen Stoyanchev * @author Artem Bilan * @author Sebastien Deleuze + * @author Juergen Hoeller * @since 4.0 */ public abstract class WebSocketMessageBrokerConfigurationSupport extends AbstractMessageBrokerConfiguration { + /** + * Scope identifier for WebSocket scope: "websocket". + * @since 7.0 + */ + public static final String SCOPE_WEBSOCKET = "websocket"; + + private @Nullable WebSocketTransportRegistration transportRegistration; @@ -137,7 +145,7 @@ public abstract class WebSocketMessageBrokerConfigurationSupport extends Abstrac @Bean public static CustomScopeConfigurer webSocketScopeConfigurer() { CustomScopeConfigurer configurer = new CustomScopeConfigurer(); - configurer.addScope("websocket", new SimpSessionScope()); + configurer.addScope(SCOPE_WEBSOCKET, new SimpSessionScope()); return configurer; } diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketScope.java b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketScope.java new file mode 100644 index 00000000000..1b9fce7bffe --- /dev/null +++ b/spring-websocket/src/main/java/org/springframework/web/socket/config/annotation/WebSocketScope.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.socket.config.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.core.annotation.AliasFor; + +/** + * {@code @WebSocketScope} is a specialization of {@link Scope @Scope} for a + * component whose lifecycle is bound to the current WebSocket lifecycle. + * + *

Specifically, {@code @WebSocketScope} is a composed annotation that + * acts as a shortcut for {@code @Scope("websocket")} with the default + * {@link #proxyMode} set to {@link ScopedProxyMode#TARGET_CLASS TARGET_CLASS}. + * + *

{@code @WebSocketScope} may be used as a meta-annotation to create custom + * composed annotations. + * + * @author Juergen Hoeller + * @since 7.0 + * @see org.springframework.context.annotation.Scope + * @see WebSocketMessageBrokerConfigurationSupport#SCOPE_WEBSOCKET + * @see org.springframework.stereotype.Component + * @see org.springframework.context.annotation.Bean + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Scope(WebSocketMessageBrokerConfigurationSupport.SCOPE_WEBSOCKET) +public @interface WebSocketScope { + + /** + * Alias for {@link Scope#proxyMode}. + *

Defaults to {@link ScopedProxyMode#TARGET_CLASS}. + */ + @AliasFor(annotation = Scope.class) + ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS; + +}