Search code examples
javaspringspring-mvcwebsocketspring-websocket

Send Message to all clients via SimpMessagingTemplate in ServletContextListener


I'm using the Spring framework and I have a working websocket controller that looks like this:

@Controller
public class GreetingController {

    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Greeting greeting(HelloMessage message) throws InterruptedException {
        return new Greeting("Hello, " + message.getName() + "!");
    }
}

I also have this configuration:

@Configuration
@EnableWebSocketMessageBroker
public class HelloWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/hello").withSockJS();
    }
}

That part works great! I can successfully send and receive messages between two or more browsers using Stomp.js. Here's the part that doesn't work. I have implemented a ServletContextListener that contains a custom object that, for simplicity sake, I have called 'notifier'. The notifier listens for certain events to happen on the server-side. It then calls the 'notify' method, which is supposed to send details about the event to all clients. It doesn't work, though.

@WebListener
public class MessageListener implements ServletContextListener, Notifiable {

    private Notifier notifier;

    @Autowired
    private SimpMessagingTemplate messageSender;


    public MessageListener() {
        notifier = new Notifier(this);
    }

    public void contextInitialized(ServletContextEvent contextEvent) {
        WebApplicationContextUtils
        .getRequiredWebApplicationContext(contextEvent.getServletContext())
        .getAutowireCapableBeanFactory()
        .autowireBean(this);

        notifier.start();
    }

    public void contextDestroyed(ServletContextEvent contextEvent) {
        notifier.stop();
    }

    public void notify(NotifyEvent event) {
        messageSender.convertAndSend("/topic/greetings", new Greeting("Hello, " + event.subject + "!"));
    }
}

I do not get an exception. The SimpMessagingTemplate has been successfully injected by Spring, so it's not null. I have been able to step into the Spring code and have figured out that the SimpleBrokerMessageHandler's subscriptionRegistry is empty when using the SimpMessagingTemplate. So it must be a separate instance from the one that the controllers are using. How can I get the same subscriptionRegistry that is used by the controllers?


Solution

  • The solution was to use Spring's ApplicationListener class instead of a ServletContextListener, and to specifically listen for the ContextRefreshedEvent.

    This is my working example:

    @Component
    public class MessagingApplicationListener implements ApplicationListener<ContextRefreshedEvent>, Notifiable {
        private final NotifierFactor notifierFactory;
        private final MessageSendingOperations<String> messagingTemplate;
        private Notifier notifier;
    
        @Autowired
        public MessagingApplicationListener(NotifierFactor notifierFactory, MessageSendingOperations<String> messagingTemplate) {
            this.notifierFactory = notifierFactory;
            this.messagingTemplate = messagingTemplate;
        }
    
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            if (notifier == null) {
                notifier = notifierFactory.create(this);
                notifier.start();
            }
        }
    
        public void notify(NotifyEvent event) {
            messagingTemplate.convertAndSend("/topic/greetings", new Greeting("Hello, " + event.subject + "!"));
        }
    
        @PreDestroy
        private void stopNotifier() {
            if (notifier != null) {
                notifier.stop();
            }
        }
    }