diff --git a/pom.xml b/pom.xml
index 67d87c78f..3fd2788a6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,6 +22,11 @@
pom
import
+
+ org.springframework.cloud
+ spring-cloud-stream-binder-rabbit
+ 1.1.0.BUILD-SNAPSHOT
+
@@ -29,6 +34,19 @@
spring-cloud-starter-stream-rabbit
spring-cloud-stream-binder-rabbit-test-support
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+
+
spring
diff --git a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitMessageChannelBinder.java b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitMessageChannelBinder.java
index a996de14d..a88249e89 100644
--- a/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitMessageChannelBinder.java
+++ b/spring-cloud-stream-binder-rabbit/src/main/java/org/springframework/cloud/stream/binder/rabbit/RabbitMessageChannelBinder.java
@@ -16,15 +16,11 @@
package org.springframework.cloud.stream.binder.rabbit;
-import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
import java.util.Map;
import com.rabbitmq.client.AMQP;
-import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Envelope;
import org.aopalliance.aop.Advice;
@@ -51,38 +47,25 @@ import org.springframework.amqp.rabbit.retry.RejectAndDontRequeueRecoverer;
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
import org.springframework.amqp.rabbit.support.DefaultMessagePropertiesConverter;
import org.springframework.amqp.rabbit.support.MessagePropertiesConverter;
-import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.amqp.support.postprocessor.DelegatingDecompressingPostProcessor;
import org.springframework.amqp.support.postprocessor.GZipPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
-import org.springframework.cloud.stream.binder.AbstractBinder;
-import org.springframework.cloud.stream.binder.Binding;
-import org.springframework.cloud.stream.binder.DefaultBinding;
+import org.springframework.cloud.stream.binder.AbstractMessageChannelBinder;
import org.springframework.cloud.stream.binder.ExtendedConsumerProperties;
import org.springframework.cloud.stream.binder.ExtendedProducerProperties;
import org.springframework.cloud.stream.binder.ExtendedPropertiesBinder;
-import org.springframework.cloud.stream.binder.MessageValues;
-import org.springframework.cloud.stream.binder.PartitionHandler;
-import org.springframework.context.Lifecycle;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
-import org.springframework.expression.ExpressionParser;
-import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter;
import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
import org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.context.IntegrationContextUtils;
-import org.springframework.integration.endpoint.EventDrivenConsumer;
-import org.springframework.integration.handler.AbstractMessageHandler;
-import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
-import org.springframework.messaging.Message;
+import org.springframework.integration.core.MessageProducer;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
-import org.springframework.messaging.MessageHeaders;
-import org.springframework.messaging.SubscribableChannel;
import org.springframework.retry.interceptor.RetryOperationsInterceptor;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.util.Assert;
@@ -90,7 +73,6 @@ import org.springframework.util.StringUtils;
/**
* A {@link org.springframework.cloud.stream.binder.Binder} implementation backed by RabbitMQ.
- *
* @author Mark Fisher
* @author Gary Russell
* @author Jennifer Hickey
@@ -99,10 +81,12 @@ import org.springframework.util.StringUtils;
* @author David Turanski
* @author Marius Bogoevici
*/
-public class RabbitMessageChannelBinder extends AbstractBinder,
- ExtendedProducerProperties> implements ExtendedPropertiesBinder {
+public class RabbitMessageChannelBinder
+ extends AbstractMessageChannelBinder,
+ ExtendedProducerProperties, Queue>
+ implements ExtendedPropertiesBinder {
- public static final AnonymousQueue.Base64UrlNamingStrategy ANONYMOUS_GROUP_NAME_GENERATOR
+ private static final AnonymousQueue.Base64UrlNamingStrategy ANONYMOUS_GROUP_NAME_GENERATOR
= new AnonymousQueue.Base64UrlNamingStrategy("anonymous.");
private static final String DEAD_LETTER_EXCHANGE = "DLX";
@@ -119,8 +103,6 @@ public class RabbitMessageChannelBinder extends AbstractBinder doBindConsumer(String name, String group, MessageChannel inputChannel,
+ protected MessageProducer createConsumerEndpoint(String name, String group, Queue destination,
ExtendedConsumerProperties properties) {
- boolean anonymousConsumer = !StringUtils.hasText(group);
- String baseQueueName = anonymousConsumer ? groupedName(name, ANONYMOUS_GROUP_NAME_GENERATOR.generateName())
- : groupedName(name, group);
- if (this.logger.isInfoEnabled()) {
- this.logger.info("declaring queue for inbound: " + baseQueueName + ", bound to: " + name);
- }
- String prefix = properties.getExtension().getPrefix();
- String exchangeName = applyPrefix(prefix, name);
- TopicExchange exchange = new TopicExchange(exchangeName);
- declareExchange(exchangeName, exchange);
- String queueName = applyPrefix(prefix, baseQueueName);
- boolean partitioned = !anonymousConsumer && properties.isPartitioned();
- boolean durable = !anonymousConsumer && properties.getExtension().isDurableSubscription();
- Queue queue;
+ DirectChannel convertingBridgeChannel = new DirectChannel();
+ convertingBridgeChannel.setBeanFactory(this.getBeanFactory());
+ final String baseQueueName = baseQueueName(name, group);
+ convertingBridgeChannel.setBeanName(baseQueueName + ".bridge");
- if (anonymousConsumer) {
- queue = new Queue(queueName, false, true, true);
- }
- else {
- if (partitioned) {
- String partitionSuffix = "-" + properties.getInstanceIndex();
- queueName += partitionSuffix;
- }
- if (durable) {
- queue = new Queue(queueName, true, false, false,
- queueArgs(queueName, properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq()));
- }
- else {
- queue = new Queue(queueName, false, false, true);
- }
- }
-
- declareQueue(queueName, queue);
-
- if (partitioned) {
- String bindingKey = String.format("%s-%d", name, properties.getInstanceIndex());
- declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(bindingKey));
- }
- else {
- declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with("#"));
- }
- Binding binding = doRegisterConsumer(baseQueueName, group, inputChannel, queue, properties);
- if (durable) {
- autoBindDLQ(applyPrefix(prefix, baseQueueName), queueName, properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq());
- }
- return binding;
- }
-
- private Map queueArgs(String queueName, String prefix, boolean bindDlq) {
- Map args = new HashMap<>();
- if (bindDlq) {
- args.put("x-dead-letter-exchange", applyPrefix(prefix, "DLX"));
- args.put("x-dead-letter-routing-key", queueName);
- }
- return args;
- }
-
- private Binding doRegisterConsumer(final String name, String group, MessageChannel moduleInputChannel, Queue queue,
- final ExtendedConsumerProperties properties) {
- DefaultBinding consumerBinding;
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(
this.connectionFactory);
listenerContainer.setAcknowledgeMode(properties.getExtension().getAcknowledgeMode());
@@ -290,84 +219,149 @@ public class RabbitMessageChannelBinder extends AbstractBinder 1 || properties.getExtension().isRepublishToDlq()) {
+ listenerContainer.setTaskExecutor(new SimpleAsyncTaskExecutor(destination.getName() + "-"));
+ listenerContainer.setQueues((Queue) destination);
+ if (properties.getMaxAttempts() > 1 || properties.getExtension().isRepublishToDlq()) {
RetryOperationsInterceptor retryInterceptor = RetryInterceptorBuilder.stateless()
- .maxAttempts(maxAttempts)
- .backOffOptions(properties.getBackOffInitialInterval(),
- properties.getBackOffMultiplier(),
- properties.getBackOffMaxInterval())
- .recoverer(determineRecoverer(name, properties.getExtension().getPrefix(), properties.getExtension().isRepublishToDlq()))
+ .retryOperations(buildRetryTemplate(properties))
+ .recoverer(determineRecoverer(baseQueueName, properties.getExtension().getPrefix(),
+ properties.getExtension().isRepublishToDlq()))
.build();
- listenerContainer.setAdviceChain(new Advice[] { retryInterceptor });
+ listenerContainer.setAdviceChain(new Advice[] {retryInterceptor});
}
listenerContainer.setAfterReceivePostProcessors(this.decompressingPostProcessor);
listenerContainer.setMessagePropertiesConverter(RabbitMessageChannelBinder.inboundMessagePropertiesConverter);
listenerContainer.afterPropertiesSet();
+
AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(listenerContainer);
adapter.setBeanFactory(this.getBeanFactory());
- DirectChannel bridgeToModuleChannel = new DirectChannel();
- bridgeToModuleChannel.setBeanFactory(this.getBeanFactory());
- bridgeToModuleChannel.setBeanName(name + ".bridge");
- adapter.setOutputChannel(bridgeToModuleChannel);
- adapter.setBeanName("inbound." + name);
- DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
+ adapter.setBeanName("inbound." + baseQueueName);
+ DefaultAmqpHeaderMapper mapper = DefaultAmqpHeaderMapper.inboundMapper();
mapper.setRequestHeaderNames(properties.getExtension().getRequestHeaderPatterns());
mapper.setReplyHeaderNames(properties.getExtension().getReplyHeaderPatterns());
adapter.setHeaderMapper(mapper);
adapter.afterPropertiesSet();
- consumerBinding = new DefaultBinding(name, group, moduleInputChannel, adapter) {
- @Override
- protected void afterUnbind() {
- cleanAutoDeclareContext(properties.getExtension().getPrefix(), name);
+ return adapter;
+ }
+
+ @Override
+ protected void afterUnbindConsumer(String name, String group,
+ ExtendedConsumerProperties consumerProperties) {
+ cleanAutoDeclareContext(consumerProperties.getExtension().getPrefix(), baseQueueName(name, group));
+ }
+
+ @Override
+ protected Queue createConsumerDestinationIfNecessary(String name, String group,
+ ExtendedConsumerProperties properties) {
+ boolean anonymous = !StringUtils.hasText(group);
+ String baseQueueName = anonymous ? groupedName(name, ANONYMOUS_GROUP_NAME_GENERATOR.generateName())
+ : groupedName(name, group);
+ if (this.logger.isInfoEnabled()) {
+ this.logger.info("declaring queue for inbound: " + baseQueueName + ", bound to: " + name);
+ }
+ String prefix = properties.getExtension().getPrefix();
+ String exchangeName = applyPrefix(prefix, name);
+ TopicExchange exchange = new TopicExchange(exchangeName);
+ declareExchange(exchangeName, exchange);
+
+ String queueName = applyPrefix(prefix, baseQueueName);
+ boolean partitioned = !anonymous && properties.isPartitioned();
+ boolean durable = !anonymous && properties.getExtension().isDurableSubscription();
+ Queue queue;
+
+ if (anonymous) {
+ queue = new Queue(queueName, false, true, true);
+ }
+ else {
+ if (partitioned) {
+ String partitionSuffix = "-" + properties.getInstanceIndex();
+ queueName += partitionSuffix;
}
- };
- ReceivingHandler convertingBridge = new ReceivingHandler();
- convertingBridge.setOutputChannel(moduleInputChannel);
- convertingBridge.setBeanName(name + ".convert.bridge");
- convertingBridge.afterPropertiesSet();
- bridgeToModuleChannel.subscribe(convertingBridge);
- adapter.start();
- return consumerBinding;
+ if (durable) {
+ queue = new Queue(queueName, true, false, false,
+ queueArgs(queueName, properties.getExtension().getPrefix(),
+ properties.getExtension().isAutoBindDlq()));
+ }
+ else {
+ queue = new Queue(queueName, false, false, true);
+ }
+ }
+ declareQueue(queueName, queue);
+ if (partitioned) {
+ String bindingKey = String.format("%s-%d", name, properties.getInstanceIndex());
+ declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(bindingKey));
+ }
+ else {
+ declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with("#"));
+ }
+ if (durable) {
+ autoBindDLQ(applyPrefix(properties.getExtension().getPrefix(), baseQueueName), queueName,
+ properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq());
+ }
+ return queue;
+ }
+
+ private String baseQueueName(String name, String group) {
+ return !StringUtils.hasText(group) ? groupedName(name, ANONYMOUS_GROUP_NAME_GENERATOR.generateName())
+ : groupedName(name, group);
+ }
+
+ private Map queueArgs(String queueName, String prefix, boolean bindDlq) {
+ Map args = new HashMap<>();
+ if (bindDlq) {
+ args.put("x-dead-letter-exchange", applyPrefix(prefix, "DLX"));
+ args.put("x-dead-letter-routing-key", queueName);
+ }
+ return args;
}
private MessageRecoverer determineRecoverer(String name, String prefix, boolean republish) {
if (republish) {
RabbitTemplate errorTemplate = new RabbitTemplate(this.connectionFactory);
- RepublishMessageRecoverer republishMessageRecoverer = new RepublishMessageRecoverer(errorTemplate,
+ return new RepublishMessageRecoverer(errorTemplate,
deadLetterExchangeName(prefix),
applyPrefix(prefix, name));
- return republishMessageRecoverer;
}
else {
return new RejectAndDontRequeueRecoverer();
}
}
- private AmqpOutboundEndpoint buildOutboundEndpoint(final String name,
- ExtendedProducerProperties properties,
- RabbitTemplate rabbitTemplate) {
- String prefix = properties.getExtension().getPrefix();
- String exchangeName = applyPrefix(prefix, name);
+ @Override
+ protected void createProducerDestinationIfNecessary(String name,
+ ExtendedProducerProperties producerProperties) {
+ String exchangeName = applyPrefix(producerProperties.getExtension().getPrefix(), name);
TopicExchange exchange = new TopicExchange(exchangeName);
declareExchange(exchangeName, exchange);
- AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(rabbitTemplate);
+ }
+
+ @Override
+ protected MessageHandler createProducerMessageHandler(final String destination,
+ ExtendedProducerProperties properties)
+ throws Exception {
+ String prefix = properties.getExtension().getPrefix();
+ String exchangeName = applyPrefix(prefix, destination);
+ TopicExchange exchange = new TopicExchange(exchangeName);
+ declareExchange(exchangeName, exchange);
+ final AmqpOutboundEndpoint endpoint = new AmqpOutboundEndpoint(buildRabbitTemplate(properties.getExtension()));
endpoint.setExchangeName(exchange.getName());
if (!properties.isPartitioned()) {
- endpoint.setRoutingKey(name);
+ endpoint.setRoutingKey(destination);
}
else {
- endpoint.setExpressionRoutingKey(EXPRESSION_PARSER.parseExpression(buildPartitionRoutingExpression(name)));
+ endpoint.setRoutingKeyExpression(EXPRESSION_PARSER.parseExpression(buildPartitionRoutingExpression(
+ destination)));
}
for (String requiredGroupName : properties.getRequiredGroups()) {
String baseQueueName = exchangeName + "." + requiredGroupName;
if (!properties.isPartitioned()) {
- Queue queue = new Queue(baseQueueName, true, false, false, queueArgs(baseQueueName, prefix, properties.getExtension().isAutoBindDlq()));
+ Queue queue = new Queue(baseQueueName, true, false, false,
+ queueArgs(baseQueueName, prefix, properties.getExtension().isAutoBindDlq()));
declareQueue(baseQueueName, queue);
- autoBindDLQ(baseQueueName, baseQueueName, properties.getExtension().getPrefix(), properties.getExtension().isAutoBindDlq());
- org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange).with(name);
+ autoBindDLQ(baseQueueName, baseQueueName, properties.getExtension().getPrefix(),
+ properties.getExtension().isAutoBindDlq());
+ org.springframework.amqp.core.Binding binding = BindingBuilder.bind(queue).to(exchange).with(
+ destination);
declareBinding(baseQueueName, binding);
}
else {
@@ -376,41 +370,29 @@ public class RabbitMessageChannelBinder extends AbstractBinder producerProperties) {
- DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
- mapper.setRequestHeaderNames(producerProperties.getExtension().getRequestHeaderPatterns());
- mapper.setReplyHeaderNames(producerProperties.getExtension().getReplyHeaderPatterns());
- handler.setHeaderMapper(mapper);
- handler.setDefaultDeliveryMode(producerProperties.getExtension().getDeliveryMode());
- handler.setBeanFactory(this.getBeanFactory());
- handler.afterPropertiesSet();
- }
-
- @Override
- public Binding doBindProducer(String name, MessageChannel outputChannel,
- ExtendedProducerProperties producerProperties) {
- String exchangeName = applyPrefix(producerProperties.getExtension().getPrefix(), name);
- TopicExchange exchange = new TopicExchange(exchangeName);
- declareExchange(exchangeName, exchange);
- AmqpOutboundEndpoint endpoint = this.buildOutboundEndpoint(name, producerProperties,
- buildRabbitTemplate(producerProperties.getExtension()));
- return doRegisterProducer(name, outputChannel, endpoint, producerProperties);
- }
private RabbitTemplate buildRabbitTemplate(RabbitProducerProperties properties) {
- RabbitTemplate rabbitTemplate = null;
+ RabbitTemplate rabbitTemplate;
if (properties.isBatchingEnabled()) {
BatchingStrategy batchingStrategy = new SimpleBatchingStrategy(
properties.getBatchSize(),
@@ -432,30 +414,11 @@ public class RabbitMessageChannelBinder extends AbstractBinder doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
- AmqpOutboundEndpoint delegate, ExtendedProducerProperties properties) {
- return this.doRegisterProducer(name, moduleOutputChannel, delegate, null, properties);
- }
-
- private Binding doRegisterProducer(final String name, MessageChannel moduleOutputChannel,
- AmqpOutboundEndpoint delegate, String replyTo,
- ExtendedProducerProperties properties) {
- Assert.isInstanceOf(SubscribableChannel.class, moduleOutputChannel);
- MessageHandler handler = new SendingHandler(delegate, replyTo, properties);
- EventDrivenConsumer consumer = new EventDrivenConsumer((SubscribableChannel) moduleOutputChannel, handler);
- consumer.setBeanFactory(getBeanFactory());
- consumer.setBeanName("outbound." + name);
- consumer.afterPropertiesSet();
- DefaultBinding producerBinding = new DefaultBinding<>(name, null, moduleOutputChannel, consumer);
- consumer.start();
- return producerBinding;
- }
-
/**
* If so requested, declare the DLX/DLQ and bind it. The DLQ is bound to the DLX with a routing key of the original
* queue name because we use default exchange routing by queue name for the original message.
- * @param queueName The base name for the queue (including the binder prefix, if any).
- * @param routingKey The routing key for the queue.
+ * @param queueName The base name for the queue (including the binder prefix, if any).
+ * @param routingKey The routing key for the queue.
* @param autoBindDlq true if the DLQ should be bound.
*/
private void autoBindDLQ(final String queueName, String routingKey, String prefix, boolean autoBindDlq) {
@@ -474,37 +437,39 @@ public class RabbitMessageChannelBinder extends AbstractBinder messageHeadersList) {
- Iterator iterator = messageHeadersList.iterator();
- Map