I am trying to listen to topic using the below snippet. However its listening to queue by default. There is no xml config in this case. I am completely relying on annotations. Moreover I have relied completely on the AutoConfiguration provided by Spring boot. I am not sure how to set the destination type as topic, In JmsListener. Spring JMS gurus please help.
@Component
public class MyTopicListener {
@JmsListener(destination = "${trans.alert.topic}")
public void receiveMessage(TransactionAlert alert) {
logger.info("AlertSubscriberEmail :: Sending Email => <" + alert + ">");
}
}
I just took the complete Spring boot example from : https://github.com/spring-guides/gs-messaging-jms/
In this it is created for sending and receipt of messages from a queue. To Change this to a topic , you have to set the Pub-Sub property in the Factory instance.
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jms.DefaultJmsListenerContainerFactoryConfigurer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.support.converter.MappingJackson2MessageConverter;
import org.springframework.jms.support.converter.MessageConverter;
import org.springframework.jms.support.converter.MessageType;
import javax.jms.ConnectionFactory;
@SpringBootApplication
@EnableJms
public class JmsSampleApplication {
public void registerBeans(ConfigurableApplicationContext context ){
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(JmsTemplate.class);
CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory();
builder.addPropertyValue("connectionFactory", cachingConnectionFactory); // set property value
DefaultListableBeanFactory factory = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();
factory.registerBeanDefinition("jmsTemplateName", builder.getBeanDefinition());
}
@Bean
public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setPubSubDomain(true);
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
// You could still override some of Boot's default if necessary.
return factory;
}
@Bean
public JmsListenerContainerFactory<?> queueListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
//factory.setPubSubDomain(true);
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
return factory;
}
@Bean // Serialize message content to json using TextMessage
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(JmsSampleApplication.class, args);
JmsTemplate jmsTemplate = context.getBean(JmsTemplate.class);
// Send a message with a POJO - the template reuse the message converter
System.out.println("Sending an email message.");
jmsTemplate.convertAndSend("mailbox.topic", new Email("info@example.com", "Hello"));
jmsTemplate.convertAndSend("mailbox.queue", new Email("info@example.com", "Hello"));
}
}
The listener
package org.springboot.jms;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
/**
* Created by RGOVIND on 10/20/2016.
*/
@Component
public class HelloTopicListener {
@JmsListener(destination = "mailbox.topic", containerFactory = "topicListenerFactory")
public void receiveTopicMessage(Email email) {
System.out.println("Received <" + email + ">");
}
@JmsListener(destination = "mailbox.queue", containerFactory = "queueListenerFactory")
public void receiveQueueMessage(Email email) {
System.out.println("Received <" + email + ">");
}
}
Once this is done , you are all set to subscribe to the topic of choice.
There are multiple approaches to this of course , you can have a map of beans for different jmsTemplates , each of which can be used when you need them based on queue or topic. The template & beans can be instantiated in a method you choose to like discussed in this SO Question. Hope it helps
The answer marked correct is ALMOST correct. It still wont work because:
factory.setPubSubDomain(true)
must come AFTER:
configurer.configure(factory, connectionFactory);
Otherwise the pubSubDomain flag being set to true is lost when configuring the defaults and that factory instance will still work with queues and not topics.
In Spring Boot's Application.properties, try setting the following property:
spring.jms.pub-sub-domain=true
Then, use this property for the container factory that you are using to listen to the topic.