2
votes

Spring Boot JMS Topic not working properly it behave like queue.

Sample code

Application

@SpringBootApplication
public class Application extends SpringBootServletInitializer {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

JMSConfig

@Configuration
@EnableJms
public class JMSConfig {

private static final String JMS_BROKER_URL = "vm://embedded?broker.persistent=false,useShutdownHook=false";
public static final String JMS_TOPIC_MAIL = "mailbox.topic";

@Bean
// Serialize message content to json using TextMessage
public MessageConverter jacksonJmsMessageConverter() {
    MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
    converter.setTargetType(MessageType.TEXT);
    converter.setTypeIdPropertyName("_type");
    return converter;
}

@Bean
public ActiveMQConnectionFactory amqConnectionFactory() {

    return new ActiveMQConnectionFactory(JMS_BROKER_URL);

}

@Bean
public CachingConnectionFactory connectionFactory() {

    return new CachingConnectionFactory(amqConnectionFactory());

}

@Bean
public ActiveMQTopic destinationTopic() {

    return new ActiveMQTopic(JMS_TOPIC_MAIL);
}

@Bean
public JmsTemplate jmsTemplate() {

    JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory());
    // jmsTemplate.setDefaultDestination(destinationTopic());;
    jmsTemplate.setDefaultDestinationName(JMS_TOPIC_MAIL);
    jmsTemplate.setConnectionFactory(connectionFactory());
    jmsTemplate.setMessageConverter(jacksonJmsMessageConverter());
    return jmsTemplate;
}

@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory(
        ConnectionFactory connectionFactory,
        DefaultJmsListenerContainerFactoryConfigurer configurer) {
    DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
    factory.setPubSubDomain(true);
    factory.setConnectionFactory(connectionFactory());
    // 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;
}
}

Here listeners are

First listener

@Component
public class JMSTopicListener1 {

@JmsListener(destination = JMSConfig.JMS_TOPIC_MAIL, containerFactory = "jmsListenerContainerFactory")
public void receiveTopicMessage(Email email) {
    System.out.println("JMSTopicListener#1 Received <" + email + ">");
}
}

Second listener

@Component
public class JMSTopicListener2 {

@JmsListener(destination = JMSConfig.JMS_TOPIC_MAIL, containerFactory = "jmsListenerContainerFactory")
public void receiveTopicMessage(Email email) {
    System.out.println("JMSTopicListener#2 Received <" + email + ">");
}
}

Access through web

@RestController
public class WebController {

@Autowired
JmsTemplate jmsTemplate;


@RequestMapping("/sendEmail")
public String sendEmail() {

    System.out.println("Sending ************************ .");

    jmsTemplate.convertAndSend(JMSConfig.JMS_TOPIC_MAIL, new Email(
            "[email protected]", "Hello"));


    return "Email send success!!!";
}
}

Ouput is

Sending ************************ . JMSTopicListener#2 Received

But message should be subscribed by both listener because it attached with Topic

1

1 Answers

7
votes

add to your aplication.properties

spring.jms.pub-sub-domain=true

or change bean definiton code to :

@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory(
        ConnectionFactory connectionFactory,
        DefaultJmsListenerContainerFactoryConfigurer configurer) {
    DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory());
    // 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.
    // As you said if you want to override Boot's defaults or 
    // values from aplication.properties you have to do it after configurer.configure()
    factory.setPubSubDomain(true);
    return factory;
}

UPDATE

you need to update the jmsTemplate too to use topic

@Bean
public JmsTemplate jmsTemplate() {

    JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory());
    // jmsTemplate.setDefaultDestination(destinationTopic());;
    jmsTemplate.setDefaultDestinationName(JMS_TOPIC_MAIL);
    jmsTemplate.setConnectionFactory(connectionFactory());
    jmsTemplate.setMessageConverter(jacksonJmsMessageConverter());
    jmsTemplate.setPubSubDomain(true);
    return jmsTemplate;
}

OR

@RestController
public class WebController {

@Autowired
JmsTemplate jmsTemplate;

@Autowired
ActiveMQTopic  destinationTopic;

@RequestMapping("/sendEmail")
public String sendEmail() {

    System.out.println("Sending ************************ .");

    jmsTemplate.convertAndSend(destinationTopic, new Email(
            "[email protected]", "Hello"));


    return "Email send success!!!";
}
}