multiple Rabbitmq queues with spring boot - spring-boot

From spring boot tutorial:
https://spring.io/guides/gs/messaging-rabbitmq/
They give an example of creating 1 queue and 1 queue only, but, what if I want to be able to create more then 1 queue? how would it be possible?
Obviously, I can't just create the same bean twice:
#Bean
Queue queue() {
return new Queue(queueNameAAA, false);
}
#Bean
Queue queue() {
return new Queue(queueNameBBB, false);
}
You can't create the same bean twice, it will make ambiguous.

Give the bean definition factory methods different names. Usually, by convention, you would name them the same as the queue, but that's not required...
#Bean
Queue queue1() {
return new Queue(queueNameAAA, false);
}
#Bean
Queue queue2() {
return new Queue(queueNameBBB, false);
}
The method name is the bean name.
EDIT
When using the queues in the binding beans, there are two options:
#Bean
Binding binding1(#Qualifier("queue1") Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(queueNameAAA);
}
#Bean
Binding binding2(#Qualifier("queue2") Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(queueNameBBB);
}
or
#Bean
Binding binding1(TopicExchange exchange) {
return BindingBuilder.bind(queue1()).to(exchange).with(queueNameAAA);
}
#Bean
Binding binding2(TopicExchange exchange) {
return BindingBuilder.bind(queue2()).to(exchange).with(queueNameBBB);
}
or even better...
#Bean
Binding binding1(TopicExchange exchange) {
return BindingBuilder.bind(queue1()).to(exchange).with(queue1().getName());
}
#Bean
Binding binding2(TopicExchange exchange) {
return BindingBuilder.bind(queue2()).to(exchange).with(queue2().getName());
}

Related

Spring RabbitMQ - add bindings during runtime

I've been following the guide https://spring.io/guides/gs/messaging-rabbitmq/#initial for setting up rabbitMQ communication via Spring. Queues, Exchanges, Bindings are declared and initialised on Application startup. The code, pretty much like the tutorial:
public static final String SPRINGQUEUE = "springqueue";
public static final String SPRINGEXCHANGE = "springexchange";
#Bean
Queue queue() {
return new Queue(SPRINGQUEUE, false);
}
#Bean
TopicExchange exchange() {
return new TopicExchange(SPRINGEXCHANGE);
}
#Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("routingkey1");
}
#Bean
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListener listener) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(SPRINGQUEUE);
container.setMessageListener(listener);
return container;
}
#Bean
MessageListener createListener(Queue queue, TopicExchange exchange) {
return new Listener(queue, exchange);
}
This listens to "routingkey1" just as expected, however, I would like to change the keys to listen for dynamically during runtime. I know this is possible using the RabbitMQ Java client, by simply binding a channel multiple times. Is there any way to do this through spring AMQP as well?
See RabbitAdmin API:
/**
* Declare a binding of a queue to an exchange.
* #param binding a description of the binding to declare.
*/
void declareBinding(Binding binding);
for example.
You don't listen to the routing key: you listen to the queue. The routing key is a part of publisher logic: you publish a message to the exchange with some routing key. The RabbitMQ broker already decides to what queue place this message to. Therefore see if your expectations with dynamic bindings and listening is what is possible with an AQMP protocol.

How to create JMS MessageListenerContainer on a list of ConnectionFactory

The following configuration creates a MessageListenerContainer on one ConnectionFactory.
#Configuration
public class MyConfig {
#Bean
public MessageListenerContainer myListenerContainer() {
DefaultMessageListenerContainer messageListenerContainer = new DefaultMessageListenerContainer();
messageListenerContainer.setConnectionFactory(myConnectionFactory1);
[...]
return messageListenerContainer;
}
}
I want to create the same configuration of MessageListenerContainer but with differents ConnectionFactory (pointing on differents queues managers).
I've tried to return a list of MessageListenerContainer (MessageListenerContainer[] or List<MessageListenerContainer> with or without a #Qualifier) but new messages are ignored.
How could I manage the MessageListenerContainer creation on a list of ConnectionFactory?
There's another way to create multiple MessageListenerContainer, instead of creating MessageListenerContainer you create multiple JmsListenerContainerFactory and set the bean name in JmsListener.
#EnableJms
class JmsConfiguration{
Bean
public JmsListenerContainerFactory jmsListenerContainerFactory(){
// create jms listener
}
#Bean
public JmsListenerContainerFactory jmsListenerContainerFactory2(){
// create jms listener
}
}
Two factory beans are created here, jmsListenerContainerFactory and jmsListenerContainerFactory2, now you can use these factory beans in JmsListener to denote which bean to be used.
#Component
class JmsListeners {
#JmsListener(containerFactory="jmsListenerContainerFactory")
public void onMessage(...){
}
#JmsListener(containerFactory="jmsListenerContainerFactory2")
public void onMessage2(...){
}
}
You can register the beans dynamically with the application context.
#Component
class Configurer {
Configurer (GenericApplicationContext context) {
for (i = 0; ...) {
ConnectionFactory cf = ...
context.registerBean("cf" + i, ConnectionFactory.class, () -> cf);
context.getBean("cf" + i); // to initialize it
DefaultMessageListenerContainer container = ...
context.registerBean("container" + i, ...);
context.getBean("container" + i, ...
}
}
}

Argument type error with Spring AMQP receiver

My Spring AMQP application has been logging the following exception on initiation:
org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Failed to invoke target method 'receiveMessage' with argument type = [class [B], value = [{[B#660cff44}]
From my searching I understand that this is because there is a class incompatibility with the message type? However, I am not able to see where this is.
The following are the relevant code segments:
#Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
#Bean
Queue queue() {
return new Queue(config.getAMQPResultsQueue(), false);
}
#Bean
TopicExchange exchange() {
return new TopicExchange(config.getAMQPResultsExchange());
}
#Bean
Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("#");
}
#Bean
SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(config.getAMQPResultsQueue());
container.setMessageListener(listenerAdapter);
container.setMessageConverter(jsonMessageConverter());
return container;
}
#Bean
MessageListenerAdapter listenerAdapter(Receiver receiver) {
return new MessageListenerAdapter(receiver, "receiveMessage");
}
and
#Component
public class Receiver {
public void receiveMessage(String message) {
System.out.println("Received <" + message + ">");
}
}
I have tried setting the class of message to Byte[] but the result is the same. I am sure I am missing something simple - just not sure what it is!
The Jackson2JsonMessageConverter will only perform conversion if the message has a content_type header that contains json.
Otherwise, it will return byte[].
byte[] will also not be converted to Byte[]. Set the header or use byte[].
I ran into this issue when I set the property in RabbitMQ interface to content-type (understandably, since that's how the http spec spells it). But RabbitMQ has it underscored. content_type is the name of the property you would have to set in RabbitMQ interface to publish a message with HTTP Header Content-Type

RabbitListener annotation queue name by ConfigurationProperties

I have configured my rabbit properties via application.yaml and spring configurationProperties.
Thus, when I configure exchanges, queues and bindings, I can use the getters of my properties
#Bean Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(properties.getQueue());
}
#Bean Queue queue() {
return new Queue(properties.getQueue(), true);
}
#Bean TopicExchange exchange() {
return new TopicExchange(properties.getExchange());
}
However, when I configure a #RabbitListener to log the messages on from the queue, I have to use the full properties name like
#RabbitListener(queues = "${some.long.path.to.the.queue.name}")
public void onMessage(
final Message message, final Channel channel) throws Exception {
log.info("receiving message: {}#{}", message, channel);
}
I want to avoid this error prone hard coded String and refer to the configurationProperties bean like:
#RabbitListener(queues = "${properties.getQueue()}")
I had a similar issue once with #EventListener where using a bean reference "#bean.method()" helped, but it does not work here, the bean expression is just interpreted as queue name, which fails because a queue namde "#bean...." does not exist.
Is it possible to use ConfigurationProperty-Beans for RabbitListener queue configuration?
Something like this worked for me where I just used the Bean and SpEL.
#Autowired
Queue queue;
#RabbitListener(queues = "#{queue.getName()}")
I was finally able to accomplish what we both desired to do by taking what #David Diehl suggested, using the bean and SpEL; however, using MyRabbitProperties itself instead. I removed the #EnableConfigurationProperties(MyRabbitProperties.class) in the config class, and registered the bean the standard way:
#Configuration
//#EnableConfigurationProperties(RabbitProperties.class)
#EnableRabbit
public class RabbitConfig {
//private final MyRabbitProperties myRabbitProperties;
//#Autowired
//public RabbitConfig(MyRabbitProperties myRabbitProperties) {
//this.myRabbitProperties = myRabbitProperties;
//}
#Bean
public TopicExchange myExchange(MyRabbitProperties myRabbitProperties) {
return new TopicExchange(myRabbitProperties.getExchange());
}
#Bean
public Queue myQueueBean(MyRabbitProperties myRabbitProperties) {
return new Queue(myRabbitProperties.getQueue(), true);
}
#Bean
public Binding binding(Queue myQueueBean, TopicExchange myExchange, MyRabbitProperties myRabbitProperties) {
return BindingBuilder.bind(myQueueBean).to(myExchange).with(myRabbitProperties.getRoutingKey());
}
#Bean
public MyRabbitProperties myRabbitProperties() {
return new MyRabbitProperties();
}
}
From there, you can access the get method for that field:
#Component
public class RabbitQueueListenerClass {
#RabbitListener(queues = "#{myRabbitProperties.getQueue()}")
public void processMessage(Message message) {
}
}
#RabbitListener(queues = "#{myQueue.name}")
Listener:
#RabbitListener(queues = "${queueName}")
application.properties:
queueName=myQueue

Spring + AMQP serialise object message

I have an application that publishes a message using Spring AMQP’s RabbitTemplate and subscribes to the message on a POJO using MessageListenerAdapter, pretty much as per the Getting Started - Messaging with RabbitMQ guide.
void handleMessage(final String message) {...}
rabbitTemplate.convertAndSend(EXCHANGE_NAME, QUEUE_NAME, "message");
However, this is sending messages as String's; surely there is a way to send and receive Object messages directly?
I have tried registering a JsonMessageConverter but to no avail.
Any help would be greatly appreciated - at present I'm manually de/serialising Strings on either side which seems messy and I'm surprised this isn't a supported feature.
I have tried registering a JsonMessageConverter but to no avail.
It would be better to see your attempt and figure out the issue on our side.
Right now I only can say that you should supply JsonMessageConverter for both sending and receiving parts.
I've just tested with the gs-messaging-rabbitmq:
#Autowired
RabbitTemplate rabbitTemplate;
#Autowired
MessageConverter messageConverter;
.....
#Bean
MessageListenerAdapter listenerAdapter(Receiver receiver, MessageConverter messageConverter) {
MessageListenerAdapter adapter = new MessageListenerAdapter(receiver, "receiveMessage");
adapter.setMessageConverter(messageConverter);
return adapter;
}
#Bean
MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
.....
System.out.println("Sending message...");
rabbitTemplate.setMessageConverter(messageConverter);
rabbitTemplate.convertAndSend(queueName, new Foo("Hello from RabbitMQ!"));
Where Receiver has been changed to this:
public void receiveMessage(Foo message) {
System.out.println("Received <" + message + ">");
latch.countDown();
}
So, the output is:
Waiting five seconds...
Sending message...
Received <Foo{foo='Hello from RabbitMQ!'}>
when we use Foo like this:
#Override
public String toString() {
return "Foo{" +
"foo='" + foo + '\'' +
'}';
}
with appropriate getter and setter.
Thanks #Artem for the hints. My code is pretty much as per the Getting Started Guide and I had already tried adding a Converter.
But as #Artem has pointed out, the trick is to register the converter with both the container, the listener adapter, and the rabbit template (which was auto-configured in the example).
So my #Configuration class now looks like so, in addition to whatever is mentioned in the Getting Started Guide:
#Bean
SimpleMessageListenerContainer container(final ConnectionFactory connectionFactory, final MessageListenerAdapter messageListenerAdapter,
final MessageConverter messageConverter)
{
final SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(QUEUE_NAME);
container.setMessageListener(messageListenerAdapter);
container.setMessageConverter(messageConverter);
return container;
}
#Bean
RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory, final MessageConverter messageConverter)
{
final RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
#Bean
MessageConverter messageConverter()
{
return new Jackson2JsonMessageConverter();
}
#Bean
Receiver receiver()
{
return new Receiver();
}
#Bean
MessageListenerAdapter listenerAdapter(final Receiver receiver, final MessageConverter messageConverter)
{
return new MessageListenerAdapter(receiver, messageConverter);
}
which means the Receiver can have an Object method signature such as:
void handleMessage(final CbeEvent message)

Resources