SpringBoot Connect Multiple RabbitMQ Sources

Keywords: Programming RabbitMQ Spring JSON SpringBoot

In practical development, many scenarios require asynchronous processing, where RabbitMQ is required, and as the number of scenarios increases, programs may need to connect multiple RabbitMQs.SpringBoot itself provides a default configuration to quickly configure the connection to RabbitMQ, but only one RabbitMQ can be connected. When multiple RabbitMQs need to be connected, the default configuration is not applicable and each connection needs to be written separately.

In the SpringBoot framework, two common classes are:

  • RabbitTemplate: Used as production and consumption messages;
  • RabbitAdmin: Used as an assertion, deletion, and binding relationship between queues and switches.

So if we connect multiple RabbitMQ s, we need to re-establish the connection and re-implement the two classes. The code is as follows:

To configure

The application.properties configuration file requires two connections to be configured:

server.port=8080

# rabbitmq
v2.spring.rabbitmq.host=host
v2.spring.rabbitmq.port=5672
v2.spring.rabbitmq.username=username
v2.spring.rabbitmq.password=password
v2.spring.rabbitmq.virtual-host=virtual-host
#consume manual ack
v2.spring.rabbitmq.listener.simple.acknowledge-mode=manual
#1. When the mandatory flag bit is set to true,
#   If exchange cannot find a suitable queue to store messages based on its type and message routingKey,
#   The broker then calls the basic.return method to return the message to the producer;
#2. When mandatory is set to false, broker discards the message directly; in general,
#   The mandatory flag tells the broker proxy server to route messages to at least one queue.
#   Otherwise, the message is return ed to the sender;
v2.spring.rabbitmq.template.mandatory=true
#publisher confirms send confirmation
v2.spring.rabbitmq.publisher-confirms=true
#returns callback : 
#   1. exchange not delivered
#   2. Message callback returnCallback that delivers exchange without queue. (Note) When 2 occurs, publisher-confirms calls back true
v2.spring.rabbitmq.publisher-returns=true
v2.spring.rabbitmq.listener.simple.prefetch=5

# rabbitmq
v1.spring.rabbitmq.host=host
v1.spring.rabbitmq.port=5672
v1.spring.rabbitmq.username=username
v1.spring.rabbitmq.password=password
v1.spring.rabbitmq.virtual-host=virtual-host
#consume manual ack
v1.spring.rabbitmq.listener.simple.acknowledge-mode=manual
#1. When the mandatory flag bit is set to true,
#   If exchange cannot find a suitable queue to store messages based on its type and message routingKey,
#   The broker then calls the basic.return method to return the message to the producer;
#2. When mandatory is set to false, broker discards the message directly; in general,
#   The mandatory flag tells the broker proxy server to route messages to at least one queue.
#   Otherwise, the message is return ed to the sender;
v1.spring.rabbitmq.template.mandatory=true
#publisher confirms send confirmation
v1.spring.rabbitmq.publisher-confirms=true
#returns callback : 
#   1. exchange not delivered
#   2. Message callback returnCallback that delivers exchange without queue. (Note) When 2 occurs, publisher-confirms calls back true
v1.spring.rabbitmq.publisher-returns=true
v1.spring.rabbitmq.listener.simple.prefetch=5

Rewrite Connection Factory

It is important to note that in the case of multiple sources, a connection needs to be annotated with @Primary to indicate the primary connection, which is used by default

package com.example.config.rabbitmq;

import com.alibaba.fastjson.JSON;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * Created by shuai on 2019/4/23.
 */
@Configuration
public class MultipleRabbitMQConfig {

    @Bean(name = "v2ConnectionFactory")
    public CachingConnectionFactory hospSyncConnectionFactory(
            @Value("${v2.spring.rabbitmq.host}") String host,
            @Value("${v2.spring.rabbitmq.port}") int port,
            @Value("${v2.spring.rabbitmq.username}") String username,
            @Value("${v2.spring.rabbitmq.password}") String password,
            @Value("${v2.spring.rabbitmq.virtual-host}") String virtualHost,
            @Value("${v2.spring.rabbitmq.publisher-confirms}") Boolean publisherConfirms,
            @Value("${v2.spring.rabbitmq.publisher-returns}") Boolean publisherReturns) {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setHost(host);
        connectionFactory.setPort(port);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost(virtualHost);
        connectionFactory.setPublisherConfirms(publisherConfirms);
        connectionFactory.setPublisherReturns(publisherReturns);
        return connectionFactory;
    }

    @Bean(name = "v2RabbitTemplate")
    public RabbitTemplate firstRabbitTemplate(
            @Qualifier("v2ConnectionFactory") ConnectionFactory connectionFactory,
            @Value("${v2.spring.rabbitmq.template.mandatory}") Boolean mandatory) {
        RabbitTemplate v2RabbitTemplate = new RabbitTemplate(connectionFactory);
        v2RabbitTemplate.setMandatory(mandatory);
        v2RabbitTemplate.setConfirmCallback((correlationData, ack, s) -> {
            if (!ack) {
//                    LOGGER.info('{} failed to send RabbitMQ message ack confirmation: [{}], this.name, JSON.toJSONString(object)));
            } else {
//                    LOGGER.info('{} send RabbitMQ message ack confirmation success: [{}], this.name, JSON.toJSONString(object)));
            }
        });
        v2RabbitTemplate.setReturnCallback((message, code, s, exchange, routingKey) -> {
//                LOGGER.error('{}} sent RabbitMQ message returnedMessage sent by RabbitMQ message returnedMessage, exception, Exchange does not exist or sent to Exchange but did not send to Queue, message:[{}], code [{}], s[{}], exchange[{}]], routingKey [{}], new Object []{this.name, JSON.toJSON.toJSONSNString (message), JSON.toJSON JNSSONString (code), JSON.toJNSSON (toJSON) tring (code), JSON.toJNString (exchange), JNSSON.JSON (JSON) toJNString (exchange), JNSSON tring (rou)TingKey)};
        });
        return v2RabbitTemplate;
    }

    @Bean(name = "v2ContainerFactory")
    public SimpleRabbitListenerContainerFactory hospSyncFactory(
            @Qualifier("v2ConnectionFactory") ConnectionFactory connectionFactory,
            @Value("${v2.spring.rabbitmq.listener.simple.acknowledge-mode}") String acknowledge,
            @Value("${v2.spring.rabbitmq.listener.simple.prefetch}") Integer prefetch
    ) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setAcknowledgeMode(AcknowledgeMode.valueOf(acknowledge.toUpperCase()));
        factory.setPrefetchCount(prefetch);
        return factory;
    }

    @Bean(name = "v2RabbitAdmin")
    public RabbitAdmin iqianzhanRabbitAdmin(
            @Qualifier("v2ConnectionFactory") ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        rabbitAdmin.setAutoStartup(true);
        return rabbitAdmin;
    }


    // mq primary connection
    @Bean(name = "v1ConnectionFactory")
    @Primary
    public CachingConnectionFactory publicConnectionFactory(
            @Value("${v1.spring.rabbitmq.host}") String host,
            @Value("${v1.spring.rabbitmq.port}") int port,
            @Value("${v1.spring.rabbitmq.username}") String username,
            @Value("${v1.spring.rabbitmq.password}") String password,
            @Value("${v1.spring.rabbitmq.virtual-host}") String virtualHost,
            @Value("${v1.spring.rabbitmq.publisher-confirms}") Boolean publisherConfirms,
            @Value("${v1.spring.rabbitmq.publisher-returns}") Boolean publisherReturns) {
        CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
        connectionFactory.setHost(host);
        connectionFactory.setPort(port);
        connectionFactory.setUsername(username);
        connectionFactory.setPassword(password);
        connectionFactory.setVirtualHost(virtualHost);
        connectionFactory.setPublisherConfirms(publisherConfirms);
        connectionFactory.setPublisherReturns(publisherReturns);
        return connectionFactory;
    }

    @Bean(name = "v1RabbitTemplate")
    @Primary
    public RabbitTemplate publicRabbitTemplate(
            @Qualifier("v1ConnectionFactory") ConnectionFactory connectionFactory,
            @Value("${v1.spring.rabbitmq.template.mandatory}") Boolean mandatory) {
        RabbitTemplate v1RabbitTemplate = new RabbitTemplate(connectionFactory);
        v1RabbitTemplate.setMandatory(mandatory);
        v1RabbitTemplate.setConfirmCallback((correlationData, ack, s) -> {
            if (!ack) {
//                    LOGGER.info('{} failed to send RabbitMQ message ack confirmation: [{}], this.name, JSON.toJSONString(object)));
            } else {
//                    LOGGER.info('{} send RabbitMQ message ack confirmation success: [{}], this.name, JSON.toJSONString(object)));
            }
        });
        v1RabbitTemplate.setReturnCallback((message, code, s, exchange, routingKey) -> {
//                LOGGER.error('{}} sent RabbitMQ message returnedMessage sent by RabbitMQ message returnedMessage, exception, Exchange does not exist or sent to Exchange but did not send to Queue, message:[{}], code [{}], s[{}], exchange[{}]], routingKey [{}], new Object []{this.name, JSON.toJSON.toJSONSNString (message), JSON.toJSON JNSSONString (code), JSON.toJNSSON (toJSON) tring (code), JSON.toJNString (exchange), JNSSON.JSON (JSON) toJNString (exchange), JNSSON tring (rou)TingKey)};
        });
        return v1RabbitTemplate;
    }

    @Bean(name = "v1ContainerFactory")
    @Primary
    public SimpleRabbitListenerContainerFactory insMessageListenerContainer(
            @Qualifier("v1ConnectionFactory") ConnectionFactory connectionFactory,
            @Value("${v1.spring.rabbitmq.listener.simple.acknowledge-mode}") String acknowledge,
            @Value("${v1.spring.rabbitmq.listener.simple.prefetch}") Integer prefetch) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setAcknowledgeMode(AcknowledgeMode.valueOf(acknowledge.toUpperCase()));
        factory.setPrefetchCount(prefetch);
        return factory;
    }

    @Bean(name = "v1RabbitAdmin")
    @Primary
    public RabbitAdmin publicRabbitAdmin(
            @Qualifier("v1ConnectionFactory") ConnectionFactory connectionFactory) {
        RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
        rabbitAdmin.setAutoStartup(true);
        return rabbitAdmin;
    }
}

Create Exchange, Queue, and Bind

Once RabbitAdmin is implemented, we need to create switches and queues based on RabbitAdmin and establish binding relationships

package com.example.config.rabbitmq;

import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

/**
 * Create Queue, Exchange, and Bind Relationships
 * Created by shuai on 2019/5/16.
 */
@Configuration
public class MyRabbitMQCreateConfig {

    @Resource(name = "v2RabbitAdmin")
    private RabbitAdmin v2RabbitAdmin;

    @Resource(name = "v1RabbitAdmin")
    private RabbitAdmin v1RabbitAdmin;

    @PostConstruct
    public void RabbitInit() {
        v2RabbitAdmin.declareExchange(new TopicExchange("exchange.topic.example.new", true, false));
        v2RabbitAdmin.declareQueue(new Queue("queue.example.topic.new", true));
        v2RabbitAdmin.declareBinding(
                BindingBuilder
                        .bind(new Queue("queue.example.topic.new", true))        //Create Queue Directly
                        .to(new TopicExchange("exchange.topic.example.new", true, false))    //Create switches directly to establish associations
                        .with("routing.key.example.new"));    //Specify Routing Key
    }
}

Producer

In order to subsequently verify that each connection is established successfully and that messages can be produced, the producer sends a message using the newly generated RabbitTemplate here.

package com.example.topic;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class TopicProducer {

    @Resource(name = "v1RabbitTemplate")
    private RabbitTemplate v1RabbitTemplate;

    @Resource(name = "v2RabbitTemplate")
    private RabbitTemplate v2RabbitTemplate;

    public void sendMessageByTopic() {
        String content1 = "This is a topic type of the RabbitMQ message example from v1RabbitTemplate";
        v1RabbitTemplate.convertAndSend(
                "exchange.topic.example.new",
                "routing.key.example.new",
                content1);

        String content2 = "This is a topic type of the RabbitMQ message example from v2RabbitTemplate";
        v2RabbitTemplate.convertAndSend(
                "exchange.topic.example.new",
                "routing.key.example.new",
                content2);
    }
}

Consumer

Note here that the ContainerFactory needs to be identified when configuring the consumer queue

package com.example.topic;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(queues = "queue.example.topic.new", containerFactory = "v2ContainerFactory")
public class TopicConsumer {

    @RabbitHandler
    public void consumer(String message) {
        System.out.println(message);
    }
}

This completes the SpringBoot example of connecting multiple RabbitMQ sources, and then writes a test code to verify it.

Test Validation

package com.example.test;

import com.example.topic.TopicProducer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitMQMultipleTest {

    @Autowired
    private TopicProducer topicProducer;


    @Test
    public void topicProducerTest() {
        topicProducer.sendMessageByTopic();
    }
}

Execute the test code and verify that:

Verify that SpringBoot connects multiple RabbitMQ sources successfully!

Posted by epp_b on Tue, 07 Jan 2020 00:29:49 -0800