DEV Community

loading...

Mixing pub/sub and point-to-point in Spring Boot

adzubla profile image Eduardo Issao Ito ・2 min read

Using JMS point-to-point model (queues)

Using JMS queues in Spring Boot is easy.

Just create a JmsTemplate object and use it to send a message to a queue:

    jmsTemplate.convertAndSend("MY.QUEUE", message);

To receive messages from queue, create a message listener:

    @JmsListener(destination = "MY.QUEUE")
    public void receive(String message) {
        ...
    }

Using JMS pub/sub model (topics)

To use pub/sub model the code is the same as above, you just need to change MY.QUEUE to MY.TOPIC and add the following to application.properties file:

    spring.jms.pub-sub-domain=true

But there is a drawback with this solution. As the pub-sub-domain property is global, all JmsTemplates and JmsListeners will operate with the pub/sub model. You can't use queues and topics in the same application...

Mixing queues and topics

One solution to use both queues and topics in the same Spring Boot application follows.

Subscriber

On the subscriber side you need to create distinct connection factories for queues and topics, setting pubSubDomain property accordingly:

@Configuration
public class JmsConfig {

    @Bean
    public JmsListenerContainerFactory<?> queueConnectionFactory(ConnectionFactory connectionFactory,
                                                                 DefaultJmsListenerContainerFactoryConfigurer configurer) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        configurer.configure(factory, connectionFactory);
        factory.setPubSubDomain(false);
        return factory;
    }

    @Bean
    public JmsListenerContainerFactory<?> topicConnectionFactory(ConnectionFactory connectionFactory,
                                                                 DefaultJmsListenerContainerFactoryConfigurer configurer) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        configurer.configure(factory, connectionFactory);
        factory.setPubSubDomain(true);
        return factory;
    }

}

In JmsListener annotations you need to specify which factory will be used:

    @JmsListener(destination = "MY.QUEUE", containerFactory = "queueConnectionFactory")
    public void receiveFromQueue(String text) {
        ...
    }

    @JmsListener(destination = "MY.TOPIC", containerFactory = "topicConnectionFactory")
    public void receiveFromTopic(String text) {
        ...
    }

Publisher

On the publisher side, the solution is a bit more trickier. For this to work, we assume all topics must have a name ending with ".TOPIC" and we configure a DynamicDestinationResolver that sets the pubSubDomain on the destination according to this convention.

@Configuration
public class JmsConfig {

    @Bean
    public DynamicDestinationResolver destinationResolver() {
        return new DynamicDestinationResolver() {
            @Override
            public Destination resolveDestinationName(Session session, String destinationName, boolean pubSubDomain) throws JMSException {
                if (destinationName.endsWith(".TOPIC")) {
                    pubSubDomain = true;
                }
                return super.resolveDestinationName(session, destinationName, pubSubDomain);
            }
        };
    }

}

The same JmsTemplate can be used for both queues and topics because the destination resolver will configure the destination appropriately based on destination name:

jmsTemplate.convertAndSend("MY.QUEUE", message);

jmsTemplate.convertAndSend("MY.TOPIC", message);

Source code

The source code of the complete example is at https://github.com/adzubla/ibm-mq-pubsub

That repository contains a branch named "pubsub-only" with a simpler code that works in pub/sub mode only.

Discussion (0)

pic
Editor guide