1. Introduction
In this article, we’ll explore the concept of fanout and topic exchanges with Spring AMQP and RabbitMQ.
At a high level, fanout exchanges will broadcast the same message to all bound queues, while topic exchanges use a routing key for passing messages to a particular bound queue or queues.
Prior reading of Messaging With Spring AMQP is recommended for this article.
2. Setting Up a Fanout Exchange
Let’s set up one fanout exchange with two bound queues.
Spring AMQP allows us to aggregate all the declarations of queues, exchanges, and bindings from Java configurations in a collection and returned as a List<Declarable>:
@Bean public List<Declarable> fanoutBindings() { Queue fanoutQueue1 = new Queue("fanout.queue1", false); Queue fanoutQueue2 = new Queue("fanout.queue2", false); FanoutExchange fanoutExchange = new FanoutExchange("fanout.exchange"); return Arrays.asList( fanoutQueue1, fanoutQueue2, fanoutExchange, bind(fanoutQueue1).to(fanoutExchange), BindingBuilder.bind(fanoutQueue2).to(fanoutExchange)); }
With this configuration, we expect that both queues will get all the messages sent to this exchange.
3. Setting Up a Topic Exchange
We’ll also set up a topic exchange with two bound queues each with specific routing keys:
@Bean public List<Declarable> topicBindings() { Queue topicQueue1 = new Queue(topicQueue1Name, false); Queue topicQueue2 = new Queue(topicQueue2Name, false); TopicExchange topicExchange = new TopicExchange(topicExchangeName); return Arrays.asList( topicQueue1, topicQueue2, topicExchange, BindingBuilder .bind(topicQueue1) .to(topicExchange).with("*.important.*"), BindingBuilder .bind(topicQueue2) .to(topicExchange).with("#.error")); }
With this configuration, we expect topicQueue1 to get all messages with routing keys having a three-word pattern with the middle word being “important” – for example: “user.important.error” or “blog.important.notification”.
The second queue will get all messages with routing keys ending in error; matching examples are “user.important.error” or “blog.post.save.error”.
4. Setting Up a Producer
We’ll need to create a message producer to use the exchanges we configured. The BroadcastMessageProducer class will use a RabbitTemplate and its method convertAndSend to send messages:
@Component public class BroadcastMessageProducer { @Autowired private RabbitTemplate rabbitTemplate; public void sendMessages(String message) { rabbitTemplate.convertAndSend( SpringAmqpConfig.fanoutExchangeName, "", message); rabbitTemplate.convertAndSend( SpringAmqpConfig.topicExchangeName, "user.not-important.info", message); rabbitTemplate.convertAndSend( SpringAmqpConfig.topicExchangeName, "user.important.error", message); } }
The RabbitTemplate provides many overloaded convertAndSend() methods for different exchange types.
When we send a message to fanout exchange, the routing key is ignored, and the message is passed to all bound queues.
When we send a message to the topic exchange, we need to pass a routing key. Based on this routing key the message will be delivered to specific queues.
5. Configuring Consumers
Finally, let’s set up four consumers – one for each queue – to pick up the messages produced:
@Component public class BroadcastMessageConsumers { @RabbitListener(queues = {SpringAmqpConfig.fanoutQueue1Name}) public void receiveMessageFromFanout1(String message) { } @RabbitListener(queues = {SpringAmqpConfig.fanoutQueue2Name}) public void receiveMessageFromFanout2(String message) { } @RabbitListener(queues = {SpringAmqpConfig.topicQueue1Name}) public void receiveMessageFromTopic1(String message) { } @RabbitListener(queues = {SpringAmqpConfig.topicQueue2Name}) public void receiveMessageFromTopic2(String message) { } }
We configure consumers using the @RabbitListener annotation. The only argument passed here is the queues’ name. Consumers are not aware here of exchanges or routing keys.
6. Running the Example
Our sample project is a Spring Boot application, and so it will initialize the application together with a connection to RabbitMQ and set up all queues, exchanges, and bindings.
By default, our application expects a RabbitMQ instance running on the localhost on port 5672. You can modify the defaults in application.yaml.
Our project exposes HTTP endpoint on the URI: “/broadcast”, that accepts POST calls with a message in the request body.
When we send a request to this URI with body “Test” we should see something similar to this in log output:
2017-04-14 12:27:59.611 INFO 4534 --- [cTaskExecutor-1] c.b.springamqpsimple.MessageConsumers : Received fanout 1 message:Test 2017-04-14 12:27:59.611 INFO 4534 --- [cTaskExecutor-1] c.b.springamqpsimple.MessageConsumers : Received topic 2 message: Test 2017-04-14 12:27:59.611 INFO 4534 --- [cTaskExecutor-1] c.b.springamqpsimple.MessageConsumers : Received fanout 2 message: Test 2017-04-14 12:27:59.611 INFO 4534 --- [cTaskExecutor-1] c.b.springamqpsimple.MessageConsumers : Received topic 1 message: Test 2017-04-14 12:27:59.612 INFO 4534 --- [cTaskExecutor-1] c.b.springamqpsimple.MessageConsumers : Received topic 2 message: Test
The order in which you will see these messages is, of course, not guaranteed.
7. Conclusion
In this quick tutorial, we covered fanout and topic exchanges with Spring AMQP and RabbitMQ.
The complete source code and all code snippets for this article are available on GitHub repository.