Skip to content

Commit 11d4282

Browse files
garyrussellartembilan
authored andcommitted
GH-1533: Template Receive with Consumer Args
Resolves #1533 Allow setting consumer arguments when using non-zero receive timeouts using the `RabbitTemplate`. **cherry-pick to 2.4.x** # Conflicts: # spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java
1 parent 0ff3eb9 commit 11d4282

File tree

4 files changed

+73
-5
lines changed

4 files changed

+73
-5
lines changed

spring-rabbit/src/main/java/org/springframework/amqp/rabbit/core/RabbitTemplate.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ public class RabbitTemplate extends RabbitAccessor // NOSONAR type line count
198198

199199
private final AtomicInteger containerInstance = new AtomicInteger();
200200

201+
private final Map<String, Object> consumerArgs = new HashMap<>();
202+
203+
201204
private String exchange = DEFAULT_EXCHANGE;
202205

203206
private String routingKey = DEFAULT_ROUTING_KEY;
@@ -936,6 +939,33 @@ public int getUnconfirmedCount() {
936939
.sum();
937940
}
938941

942+
/**
943+
* When using receive methods with a non-zero timeout, a
944+
* {@link com.rabbitmq.client.Consumer} is created to receive the message. Use this
945+
* property to add arguments to the consumer (e.g. {@code x-priority}).
946+
* @param arg the argument name to pass into the {@code basicConsume} operation.
947+
* @param value the argument value to pass into the {@code basicConsume} operation.
948+
* @since 2.4.8
949+
* @see #removeConsumerArg(String)
950+
*/
951+
public void addConsumerArg(String arg, Object value) {
952+
this.consumerArgs.put(arg, value);
953+
}
954+
955+
/**
956+
* When using receive methods with a non-zero timeout, a
957+
* {@link com.rabbitmq.client.Consumer} is created to receive the message. Use this
958+
* method to remove an argument from those passed into the {@code basicConsume}
959+
* operation.
960+
* @param arg the argument name.
961+
* @return the previous value.
962+
* @since 2.4.8
963+
* @see #addConsumerArg(String, Object)
964+
*/
965+
public Object removeConsumerArg(String arg) {
966+
return this.consumerArgs.remove(arg);
967+
}
968+
939969
@Override
940970
public void start() {
941971
doStart();
@@ -2743,7 +2773,7 @@ public void handleDelivery(String consumerTag, Envelope envelope, BasicPropertie
27432773
}
27442774

27452775
};
2746-
channel.basicConsume(queueName, consumer);
2776+
channel.basicConsume(queueName, false, this.consumerArgs, consumer);
27472777
if (!latch.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
27482778
if (channel instanceof ChannelProxy) {
27492779
((ChannelProxy) channel).getTargetChannel().close();

spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateIntegrationTests.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -335,8 +335,10 @@ class MockChannel extends PublisherCallbackChannelImpl {
335335
}
336336

337337
@Override
338-
public String basicConsume(String queue, Consumer callback) throws IOException {
339-
return super.basicConsume(queue, new MockConsumer(callback));
338+
public String basicConsume(String queue, boolean autoAck, Map<String, Object> args, Consumer callback)
339+
throws IOException {
340+
341+
return super.basicConsume(queue, autoAck, args, new MockConsumer(callback));
340342
}
341343

342344
}

spring-rabbit/src/test/java/org/springframework/amqp/rabbit/core/RabbitTemplateTests.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import static org.mockito.ArgumentMatchers.anyBoolean;
2626
import static org.mockito.ArgumentMatchers.anyMap;
2727
import static org.mockito.ArgumentMatchers.anyString;
28+
import static org.mockito.ArgumentMatchers.eq;
2829
import static org.mockito.ArgumentMatchers.isNull;
2930
import static org.mockito.BDDMockito.given;
3031
import static org.mockito.BDDMockito.willAnswer;
@@ -49,6 +50,7 @@
4950
import java.util.concurrent.atomic.AtomicReference;
5051

5152
import org.junit.jupiter.api.Test;
53+
import org.mockito.ArgumentCaptor;
5254
import org.mockito.Mockito;
5355

5456
import org.springframework.amqp.AmqpAuthenticationException;
@@ -658,6 +660,37 @@ void resourcesClearedAfterTxFailsWithSync() throws IOException, TimeoutException
658660
ConnectionFactoryUtils.enableAfterCompletionFailureCapture(false);
659661
}
660662

663+
@Test
664+
void consumerArgs() throws Exception {
665+
ConnectionFactory mockConnectionFactory = mock(ConnectionFactory.class);
666+
Connection mockConnection = mock(Connection.class);
667+
Channel mockChannel = mock(Channel.class);
668+
669+
given(mockConnectionFactory.newConnection(any(ExecutorService.class), anyString())).willReturn(mockConnection);
670+
given(mockConnection.isOpen()).willReturn(true);
671+
given(mockConnection.createChannel()).willReturn(mockChannel);
672+
willAnswer(inv -> {
673+
Consumer consumer = inv.getArgument(3);
674+
consumer.handleConsumeOk("tag");
675+
return null;
676+
}).given(mockChannel).basicConsume(any(), anyBoolean(), anyMap(), any());
677+
678+
SingleConnectionFactory connectionFactory = new SingleConnectionFactory(mockConnectionFactory);
679+
connectionFactory.setExecutor(mock(ExecutorService.class));
680+
RabbitTemplate template = new RabbitTemplate(connectionFactory);
681+
assertThat(template.receive("foo", 1)).isNull();
682+
@SuppressWarnings("unchecked")
683+
ArgumentCaptor<Map<String, Object>> argsCaptor = ArgumentCaptor.forClass(Map.class);
684+
verify(mockChannel).basicConsume(eq("foo"), eq(false), argsCaptor.capture(), any());
685+
assertThat(argsCaptor.getValue()).isEmpty();
686+
template.addConsumerArg("x-priority", 10);
687+
assertThat(template.receive("foo", 1)).isNull();
688+
assertThat(argsCaptor.getValue()).containsEntry("x-priority", 10);
689+
assertThat(template.removeConsumerArg("x-priority")).isEqualTo(10);
690+
assertThat(template.receive("foo", 1)).isNull();
691+
assertThat(argsCaptor.getValue()).isEmpty();
692+
}
693+
661694
@SuppressWarnings("serial")
662695
private class TestTransactionManager extends AbstractPlatformTransactionManager {
663696

src/reference/asciidoc/amqp.adoc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1885,11 +1885,14 @@ By default, if no message is available, `null` is returned immediately.
18851885
There is no blocking.
18861886
Starting with version 1.5, you can set a `receiveTimeout`, in milliseconds, and the receive methods block for up to that long, waiting for a message.
18871887
A value less than zero means block indefinitely (or at least until the connection to the broker is lost).
1888-
Version 1.6 introduced variants of the `receive` methods that let the timeout be passed in on each call.
1888+
Version 1.6 introduced variants of the `receive` methods that allows the timeout be passed in on each call.
18891889

18901890
CAUTION: Since the receive operation creates a new `QueueingConsumer` for each message, this technique is not really appropriate for high-volume environments.
18911891
Consider using an asynchronous consumer or a `receiveTimeout` of zero for those use cases.
18921892

1893+
Starting with version 2.4.8, when using a non-zero timeout, you can specify arguments passed into the `basicConsume` method used to associate the consumer with the channel.
1894+
For example: `template.addConsumerArg("x-priority", 10)`.
1895+
18931896
There are four simple `receive` methods available.
18941897
As with the `Exchange` on the sending side, there is a method that requires that a default queue property has been set
18951898
directly on the template itself, and there is a method that accepts a queue parameter at runtime.

0 commit comments

Comments
 (0)