Search code examples
javaspringrabbitmqspring-amqpspring-rabbit

spring-amqp Multiple queues with different routingKey


I have recently started learning Spring and spring-amqp so this question might appear very basic so please excuse me for that.

I have multiple queues which are on different hosts and have different QueueName, RoutingKey, vhost, user, password. I am writing the publishing logic for these queues and was not able to decide if I should have one configuration class per queue or can it be done in XML.

The method of creating a class to have all the information about the queue (host, vhost, username etc) is working fine as described in this example. I created a @Configuration class and defined all the beans for that Queue. But then I need to do

ApplicationContext context = new AnnotationConfigApplicationContext(MyQueueConfiguration.class);
AmqpTemplate amqpTemplate  = context.getBean(AmqpTemplate.class);
amqpTemplate.convertAndSend("Hello World!!");

So my requirement is:

  1. Since I have many queues that need to be instantiated at the application startup, once tomcat starts the connections/channels to the queue/rabbit cluster should be established.
  2. Then as soon as there is POST request that comes to my application I need to publish the message to one of the queue based on a POST parameter.

So for each queue do I always need to do:

ApplicationContext context = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class);

Or is there a way to Spring load the all my Queue configuration classes and just use the object like:

    // calling code when I get a POST request
MyQueueConfigurationClass.publishMessage(payload, queueName);

// The implementation code
public boolean publishMessage(String payload, String queueName){

    // Get Bean for the **queueName** somehow
    AmqpTemplate amqpTemplate = context.getBean(AmqpTemplate.class);
    // Use the bean to send the message
    amqpTemplate.convertAndSend(payload);

}
  1. So how can I get the amqpTemplate for the exact queue without doing new AnnotationConfigApplicationContext() everytime?
  2. What is the harm of doing new AnnotationConfigApplicationContext every time a request comes to my service? [I am guessing that creating a new object for each request is not a good idea]

Solution

  • You should not create a new context each time; that is very wasteful.

    You can add multiple connection factories (one for each rabbit host) to the root (or web) context, and then use a Routing Connection Factory together with a sendConnectionFactorySelectorExpression to select the proper host based on the message you are sending.

    Or, you can simply wire up a different RabbitTemplate for each server.

    EDIT:

    To use the SimpleRoutingConnectionFactory, do something like...

    try {
        SimpleResourceHolder.bind(routingCF, keyForThisMessage);
        rabbitTemplate.convertAndSend(message);
    }
    finally {
        SimpleResourceHolder.unbind(routingCF);
    }
    

    (this will work with an unmodified RabbitTemplate) or...

    <rabbit:template id="routingTemplate"
        connection-factory="rcf"
        send-connection-factory-selector-expression="messageProperties.headers['cfKey']" />
    
    <bean id="rcf" class="org.springframework.amqp.rabbit.connection.SimpleRoutingConnectionFactory">
        <property name="targetConnectionFactories">
            <map>
                <entry key="foo" value-ref="cf1"/>
                <entry key="bar" value-ref="cf2"/>
            </map>
        </property>
        <property name="defaultTargetConnectionFactory" ref="defaultCF"/>
    </bean>
    

    ...and then...

    this.routingTemplate.convertAndSend("exchange", "routingKey", "xyz", new MessagePostProcessor() {
    
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setHeader("cfKey", "foo");
            return message;
        }
    
    });
    

    There's a complete test case here.