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?
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();
}
}
}
This solution worked great.
For anybody who wants a quick solution - strip out all the "Notifier" stuff and just inject MessagingApplicationListener into your class. Then call 'notify' with a String to send a message. Obviously, as @battmanz says you'll want some way to push events to the listener, but this class does the basics of what you need.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With