Search code examples
javaspringspring-bootstompspring-websocket

Spring WebSocket: Use SimpUserRegistry inside of ChannelInterceptor without creating a dependency cycle


I aim to use SimpUserRegistry to check how many users are subscribed to a topic to limit the number of subscriptions. To test my code I used the basic https://spring.io/guides/gs/messaging-stomp-websocket/ project.

Here are my only changes to this project:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Autowired
    private TopicSubscriptionInterceptor topicSubscriptionInterceptor
    ...
    
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(topicSubscriptionInterceptor);
    }
}

//--------------------------------------------------------------------------------------

@Component
public class TopicSubscriptionInterceptor implements ChannelInterceptor {
    
    @Autowired 
    private SimpUserRegistry userRegistry;
    
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel){              
        // ... use SimpUserRegistry to check amount of users subscribed to a topic and whether 
        // or not to allow a subscription (throw exception/null)

    }

//-----------------------------------------------------------------------------------------

I've tried to just pass a new TopicSubscriptionInterceptor() instead of adding it as a dependency using @Autowired but that obviously made all dependencies in TopicSubscriptionInterceptor null.

//---------------------------------------------------

Dependency Cycle:

┌─────┐
|  webSocketConfig (field private com.xxx.yyy.interceptor.TopicSubscriptionInterceptor com.xxx.yyy.config.WebSocketConfig.topicSubscriptionInterceptor)
↑     ↓
|  topicSubscriptionInterceptor (field private org.springframework.messaging.simp.user.SimpUserRegistry com.xxx.yyy.interceptor.TopicSubscriptionInterceptor.userRegistry)
↑     ↓
|  org.springframework.web.socket.config.annotation.DelegatingWebSocketMessageBrokerConfiguration
└─────┘

Here's what I tried Edit(1):

    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
        @Bean
        public TopicSubscriptionInterceptor 
            myTopicSubscriptionInterceptor(){
                return new TopicSubscriptionInterceptor();
        }

        ...
        
        @Override
        public void configureClientInboundChannel(ChannelRegistration registration) {
            registration.interceptors(myTopicSubscriptionInterceptor());
        }
    }

Solution

  • Using Lazy Initialization fixed the issue although it feels like a bandaid solution. I'm not sure if there is another better way since I'm new to Spring.

    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
        private TopicSubscriptionInterceptor topicSubscriptionInterceptor;
        
        @Autowired
        public WebSocketConfig(@Lazy TopicSubscriptionInterceptor topicSubscriptionInterceptor) {
            this.topicSubscriptionInterceptor = topicSubscriptionInterceptor;
        }
    
        ...
        
        @Override
        public void configureClientInboundChannel(ChannelRegistration registration) {
            registration.interceptors(topicSubscriptionInterceptor);
        }
    }