Spring AMQP: how to avoid manual declaration of many queues?

Amir Abiri picture Amir Abiri · Nov 24, 2014 · Viewed 10k times · Source

Having created a basic spring-amqp setup, I see that for every type of message I want to send (using message POJOs), I need to declare the queue in the spring config.

So for every pair of these:

amqpTemplate.convertAndSend("queue1", new MessageType1(...));

@RabbitListener(queues = "queue1")
public void handleMessage(MessageType1 msg) {...}

I need an entry in the spring config like this:

<rabbit:queue name="queue1"/>

This seems redundant to me. If I think of the queues like HTTP urls, than it's enough to declare a controller with @RequestMapping("/some-url"), it's not necessary to than also declare /some-url elsewhere in the config.

If eventually the application will have many types of messages going through the broker, that will just bloat the config.

I see two ways to avoid this:

  1. Automatically declaring the queues for each annotated listener endpoint. i.e if there is an annotated listener endpoint @RabbitListener(queues = "hello"), then it can be inferred that a queue hello should be declared. I thought this might already be the case, but when I removed the queue from the context config, I got the following error:

    WARN  BlockingQueueConsumer - Failed to declare queue:hello
    WARN  BlockingQueueConsumer - Queue declaration failed; retries left=2
    org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$DeclarationException: Failed to declare queue(s):[hello]
        at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.attemptPassiveDeclarations(BlockingQueueConsumer.java:479)
        at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.java:400)
        at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1022)
    
  2. Using different message POJOs on the same queue. So sending both MessageType1 and MessageType2 on queue1, each reaching a different endpoint, based on payload type. In effect this creates sort of a single "application queue", similar to a database schema:

    @RabbitListener(queues = "queue1")
    public void handleMessage1(MessageType1 msg) {...}
    
    @RabbitListener(queues = "queue1")
    public void handleMessage2(MessageType2 msg) {...}
    

    Trying this approach yielded an error.

Is any of the above currently possible with spring-amqp (1.4.0), and if so how?

Answer

Gary Russell picture Gary Russell · Nov 24, 2014

It's not currently possible to auto-declare @RabbitListener queues but it sounds like a reasonable request. Of course, declaring the queue alone might not be enough; while it will be bound to the default exchange "" with the queue name as a routing key, any more sophisticated binding would require additional configuration anyway.

Using the queue name as a routing key with the default exchange is not considered best practice because it couples the producer and consumer. But I do recognize it is used at times, so please enter a New Feature JIRA Issue and we'll consider it.

BTW, you can also use @Beans alongside the listeners to avoid XML config.

Your second request can be implemented by a delegating message listener:

@RabbitListener(queues = "queue1")
public void handleMessage(Object o) {
    if (o instanceof Type1) {
        delegate.type1((Type1) o);
    }
    else if (o instanceof Type2) {
        delegate.type2((Type2) o);
    } 
}

I have been thinking of providing such a listener (in a more generic way). Again, please open a new feature issue.

EDIT

Both of these features are now available in the 1.5 release. @QueueBinding and Class-level @RabbitListener.