From bb5e09882a6400a7ad5ca0f8d9b0c38c4d2b838c Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Sat, 28 Dec 2019 10:56:04 +0100 Subject: [PATCH] Extract RabbitTemplate auto-configuration in a configurer This commit movers the auto-configuration of RabbitTemplate to a dedicated class that can be reused to create additional template with similar settings. CLoses gh-19440 --- .../amqp/RabbitAutoConfiguration.java | 43 +++----- .../amqp/RabbitTemplateConfigurer.java | 102 ++++++++++++++++++ .../amqp/RabbitAutoConfigurationTests.java | 24 +++++ .../main/asciidoc/spring-boot-features.adoc | 2 + 4 files changed, 144 insertions(+), 27 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitTemplateConfigurer.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java index e9f717f05fa..065c7617bd1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfiguration.java @@ -152,36 +152,25 @@ public class RabbitAutoConfiguration { protected static class RabbitTemplateConfiguration { @Bean - @ConditionalOnSingleCandidate(ConnectionFactory.class) - @ConditionalOnMissingBean(RabbitOperations.class) - public RabbitTemplate rabbitTemplate(RabbitProperties properties, + @ConditionalOnMissingBean + public RabbitTemplateConfigurer rabbitTemplateConfigurer(RabbitProperties properties, ObjectProvider messageConverter, - ObjectProvider retryTemplateCustomizers, - ConnectionFactory connectionFactory) { - PropertyMapper map = PropertyMapper.get(); - RabbitTemplate template = new RabbitTemplate(connectionFactory); - messageConverter.ifUnique(template::setMessageConverter); - template.setMandatory(determineMandatoryFlag(properties)); - RabbitProperties.Template templateProperties = properties.getTemplate(); - if (templateProperties.getRetry().isEnabled()) { - template.setRetryTemplate( - new RetryTemplateFactory(retryTemplateCustomizers.orderedStream().collect(Collectors.toList())) - .createRetryTemplate(templateProperties.getRetry(), - RabbitRetryTemplateCustomizer.Target.SENDER)); - } - map.from(templateProperties::getReceiveTimeout).whenNonNull().as(Duration::toMillis) - .to(template::setReceiveTimeout); - map.from(templateProperties::getReplyTimeout).whenNonNull().as(Duration::toMillis) - .to(template::setReplyTimeout); - map.from(templateProperties::getExchange).to(template::setExchange); - map.from(templateProperties::getRoutingKey).to(template::setRoutingKey); - map.from(templateProperties::getDefaultReceiveQueue).whenNonNull().to(template::setDefaultReceiveQueue); - return template; + ObjectProvider retryTemplateCustomizers) { + RabbitTemplateConfigurer configurer = new RabbitTemplateConfigurer(); + configurer.setMessageConverter(messageConverter.getIfUnique()); + configurer + .setRetryTemplateCustomizers(retryTemplateCustomizers.orderedStream().collect(Collectors.toList())); + configurer.setRabbitProperties(properties); + return configurer; } - private boolean determineMandatoryFlag(RabbitProperties properties) { - Boolean mandatory = properties.getTemplate().getMandatory(); - return (mandatory != null) ? mandatory : properties.isPublisherReturns(); + @Bean + @ConditionalOnSingleCandidate(ConnectionFactory.class) + @ConditionalOnMissingBean(RabbitOperations.class) + public RabbitTemplate rabbitTemplate(RabbitTemplateConfigurer configurer, ConnectionFactory connectionFactory) { + RabbitTemplate template = new RabbitTemplate(); + configurer.configure(template, connectionFactory); + return template; } @Bean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitTemplateConfigurer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitTemplateConfigurer.java new file mode 100644 index 00000000000..f7315d864b9 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/amqp/RabbitTemplateConfigurer.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.amqp; + +import java.time.Duration; +import java.util.List; + +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.boot.context.properties.PropertyMapper; + +/** + * Configure {@link RabbitTemplate} with sensible defaults. + * + * @author Stephane Nicoll + * @since 2.3.0 + */ +public class RabbitTemplateConfigurer { + + private MessageConverter messageConverter; + + private List retryTemplateCustomizers; + + private RabbitProperties rabbitProperties; + + /** + * Set the {@link MessageConverter} to use or {@code null} if the out-of-the-box + * converter should be used. + * @param messageConverter the {@link MessageConverter} + */ + protected void setMessageConverter(MessageConverter messageConverter) { + this.messageConverter = messageConverter; + } + + /** + * Set the {@link RabbitRetryTemplateCustomizer} instances to use. + * @param retryTemplateCustomizers the retry template customizers + */ + protected void setRetryTemplateCustomizers(List retryTemplateCustomizers) { + this.retryTemplateCustomizers = retryTemplateCustomizers; + } + + /** + * Set the {@link RabbitProperties} to use. + * @param rabbitProperties the {@link RabbitProperties} + */ + protected void setRabbitProperties(RabbitProperties rabbitProperties) { + this.rabbitProperties = rabbitProperties; + } + + protected final RabbitProperties getRabbitProperties() { + return this.rabbitProperties; + } + + /** + * Configure the specified {@link RabbitTemplate}. The template can be further tuned + * and default settings can be overridden. + * @param template the {@link RabbitTemplate} instance to configure + * @param connectionFactory the {@link ConnectionFactory} to use + */ + public void configure(RabbitTemplate template, ConnectionFactory connectionFactory) { + PropertyMapper map = PropertyMapper.get(); + template.setConnectionFactory(connectionFactory); + if (this.messageConverter != null) { + template.setMessageConverter(this.messageConverter); + } + template.setMandatory(determineMandatoryFlag()); + RabbitProperties.Template templateProperties = this.rabbitProperties.getTemplate(); + if (templateProperties.getRetry().isEnabled()) { + template.setRetryTemplate(new RetryTemplateFactory(this.retryTemplateCustomizers) + .createRetryTemplate(templateProperties.getRetry(), RabbitRetryTemplateCustomizer.Target.SENDER)); + } + map.from(templateProperties::getReceiveTimeout).whenNonNull().as(Duration::toMillis) + .to(template::setReceiveTimeout); + map.from(templateProperties::getReplyTimeout).whenNonNull().as(Duration::toMillis) + .to(template::setReplyTimeout); + map.from(templateProperties::getExchange).to(template::setExchange); + map.from(templateProperties::getRoutingKey).to(template::setRoutingKey); + map.from(templateProperties::getDefaultReceiveQueue).whenNonNull().to(template::setDefaultReceiveQueue); + } + + private boolean determineMandatoryFlag() { + Boolean mandatory = this.rabbitProperties.getTemplate().getMandatory(); + return (mandatory != null) ? mandatory : this.rabbitProperties.isPublisherReturns(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java index 1630f03c05a..ab80ca7500d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/amqp/RabbitAutoConfigurationTests.java @@ -333,6 +333,30 @@ class RabbitAutoConfigurationTests { }); } + @Test + void testRabbitTemplateConfigurersIsAvailable() { + this.contextRunner.withUserConfiguration(TestConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(RabbitTemplateConfigurer.class)); + } + + @Test + void testRabbitTemplateConfigurerUsesConfig() { + this.contextRunner.withUserConfiguration(MessageConvertersConfiguration.class) + .withPropertyValues("spring.rabbitmq.template.exchange:my-exchange", + "spring.rabbitmq.template.routing-key:my-routing-key", + "spring.rabbitmq.template.default-receive-queue:default-queue") + .run((context) -> { + RabbitTemplateConfigurer configurer = context.getBean(RabbitTemplateConfigurer.class); + RabbitTemplate template = mock(RabbitTemplate.class); + ConnectionFactory connectionFactory = mock(ConnectionFactory.class); + configurer.configure(template, connectionFactory); + verify(template).setMessageConverter(context.getBean("myMessageConverter", MessageConverter.class)); + verify(template).setExchange("my-exchange"); + verify(template).setRoutingKey("my-routing-key"); + verify(template).setDefaultReceiveQueue("default-queue"); + }); + } + @Test void testConnectionFactoryBackOff() { this.contextRunner.withUserConfiguration(TestConfiguration2.class).run((context) -> { diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 066580e3b91..80e2347983c 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -5131,6 +5131,8 @@ To retry operations, you can enable retries on the `AmqpTemplate` (for example, Retries are disabled by default. You can also customize the `RetryTemplate` programmatically by declaring a `RabbitRetryTemplateCustomizer` bean. +If you need to create more `RabbitTemplate` instances or if you want to override the default, Spring Boot provides a `RabbitTemplateConfigurer` bean that you can use to initialize a `RabbitTemplate` with the same settings as the factories used by the auto-configuration. + [[boot-features-using-amqp-receiving]]