From 91b904405c5602120069565ef6ea8a9a50bb6cd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Wed, 30 Jul 2025 18:56:05 -0600 Subject: [PATCH 1/9] Add support for Rabbitmq AMQP 1.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eddú Meléndez --- module/spring-boot-amqp/build.gradle | 1 + .../AmqpEnvironmentBuilderCustomizer.java | 39 ++++ .../RabbitAmqpAutoConfiguration.java | 156 ++++++++++++++ .../RabbitAmqpTemplateCustomizer.java | 36 ++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../RabbitAmqpAutoConfigurationTests.java | 204 ++++++++++++++++++ 6 files changed, 437 insertions(+) create mode 100644 module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpEnvironmentBuilderCustomizer.java create mode 100644 module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java create mode 100644 module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpTemplateCustomizer.java create mode 100644 module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfigurationTests.java diff --git a/module/spring-boot-amqp/build.gradle b/module/spring-boot-amqp/build.gradle index 38d9ce92c820..5ba4de34d294 100644 --- a/module/spring-boot-amqp/build.gradle +++ b/module/spring-boot-amqp/build.gradle @@ -41,6 +41,7 @@ dependencies { optional(project(":module:spring-boot-metrics")) optional("io.micrometer:micrometer-core") optional("org.springframework.amqp:spring-rabbit-stream") + optional("org.springframework.amqp:spring-rabbitmq-client") optional("org.testcontainers:rabbitmq") dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpEnvironmentBuilderCustomizer.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpEnvironmentBuilderCustomizer.java new file mode 100644 index 000000000000..3a8bbb77b443 --- /dev/null +++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/AmqpEnvironmentBuilderCustomizer.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-present 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.amqp.autoconfigure; + +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.impl.AmqpEnvironmentBuilder; + +/** + * Callback interface that can be implemented by beans wishing to customize the + * auto-configured {@link Environment} that is created by an + * {@link AmqpEnvironmentBuilder}. + * + * @author Eddú Meléndez + * @since 4.0.0 + */ +@FunctionalInterface +public interface AmqpEnvironmentBuilderCustomizer { + + /** + * Customize the {@code AmqpEnvironmentBuilder}. + * @param builder the builder to customize + */ + void customize(AmqpEnvironmentBuilder builder); + +} diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java new file mode 100644 index 000000000000..8663557d1a7c --- /dev/null +++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java @@ -0,0 +1,156 @@ +/* + * Copyright 2012-present 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.amqp.autoconfigure; + +import com.rabbitmq.client.amqp.Connection; +import com.rabbitmq.client.amqp.CredentialsProvider; +import com.rabbitmq.client.amqp.Environment; +import com.rabbitmq.client.amqp.impl.AmqpEnvironmentBuilder; +import com.rabbitmq.client.amqp.impl.AmqpEnvironmentBuilder.EnvironmentConnectionSettings; + +import org.springframework.amqp.rabbit.config.ContainerCustomizer; +import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder; +import org.springframework.amqp.rabbit.retry.MessageRecoverer; +import org.springframework.amqp.rabbit.retry.RejectAndDontRequeueRecoverer; +import org.springframework.amqp.rabbitmq.client.AmqpConnectionFactory; +import org.springframework.amqp.rabbitmq.client.RabbitAmqpAdmin; +import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; +import org.springframework.amqp.rabbitmq.client.SingleAmqpConnectionFactory; +import org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory; +import org.springframework.amqp.rabbitmq.client.listener.RabbitAmqpListenerContainer; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.amqp.autoconfigure.RabbitConnectionDetails.Address; +import org.springframework.boot.amqp.autoconfigure.RabbitProperties.ListenerRetry; +import org.springframework.boot.amqp.autoconfigure.RabbitRetryTemplateCustomizer.Target; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.ssl.SslBundles; +import org.springframework.context.annotation.Bean; +import org.springframework.retry.support.RetryTemplate; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link RabbitAmqpTemplate}. + * + * @author Eddú Meléndez + * @since 4.0.0 + */ +@AutoConfiguration +@ConditionalOnClass({ RabbitAmqpTemplate.class, Connection.class }) +@EnableConfigurationProperties(RabbitProperties.class) +public final class RabbitAmqpAutoConfiguration { + + private final RabbitProperties properties; + + RabbitAmqpAutoConfiguration(RabbitProperties properties) { + this.properties = properties; + } + + @Bean + @ConditionalOnMissingBean + RabbitConnectionDetails rabbitConnectionDetails(ObjectProvider sslBundles) { + return new PropertiesRabbitConnectionDetails(this.properties, sslBundles.getIfAvailable()); + } + + @Bean(name = "rabbitAmqpListenerContainerFactory") + @ConditionalOnMissingBean(name = "rabbitAmqpListenerContainerFactory") + public RabbitAmqpListenerContainerFactory rabbitAmqpListenerContainerFactory( + AmqpConnectionFactory connectionFactory, + ObjectProvider> amqpContainerCustomizer, + ObjectProvider retryTemplateCustomizers, + ObjectProvider messageRecoverer) { + RabbitAmqpListenerContainerFactory factory = new RabbitAmqpListenerContainerFactory(connectionFactory); + amqpContainerCustomizer.ifUnique(factory::setContainerCustomizer); + + RabbitProperties.AmqpContainer configuration = this.properties.getListener().getSimple(); + factory.setObservationEnabled(configuration.isObservationEnabled()); + ListenerRetry retryConfig = configuration.getRetry(); + if (retryConfig.isEnabled()) { + RetryInterceptorBuilder builder = (retryConfig.isStateless()) ? RetryInterceptorBuilder.stateless() + : RetryInterceptorBuilder.stateful(); + + RetryTemplate retryTemplate = new RetryTemplateFactory(retryTemplateCustomizers.orderedStream().toList()) + .createRetryTemplate(retryConfig, Target.LISTENER); + + builder.retryOperations(retryTemplate); + MessageRecoverer recoverer = (messageRecoverer.getIfAvailable() != null) ? messageRecoverer.getIfAvailable() + : new RejectAndDontRequeueRecoverer(); + builder.recoverer(recoverer); + factory.setAdviceChain(builder.build()); + } + return factory; + } + + @Bean + @ConditionalOnMissingBean + public Environment rabbitAmqpEnvironment(RabbitConnectionDetails connectionDetails, + ObjectProvider customizers, + ObjectProvider credentialsProvider) { + PropertyMapper map = PropertyMapper.get(); + EnvironmentConnectionSettings environmentConnectionSettings = new AmqpEnvironmentBuilder().connectionSettings(); + Address address = connectionDetails.getFirstAddress(); + map.from(address::host).whenNonNull().to(environmentConnectionSettings::host); + map.from(address::port).to(environmentConnectionSettings::port); + map.from(connectionDetails::getUsername).whenNonNull().to(environmentConnectionSettings::username); + map.from(connectionDetails::getPassword).whenNonNull().to(environmentConnectionSettings::password); + map.from(connectionDetails::getVirtualHost).whenNonNull().to(environmentConnectionSettings::virtualHost); + map.from(credentialsProvider::getIfAvailable) + .whenNonNull() + .to(environmentConnectionSettings::credentialsProvider); + + AmqpEnvironmentBuilder builder = environmentConnectionSettings.environmentBuilder(); + customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder.build(); + } + + @Bean + @ConditionalOnMissingBean + public AmqpConnectionFactory amqpConnection(Environment environment) { + return new SingleAmqpConnectionFactory(environment); + } + + @Bean + @ConditionalOnMissingBean + public RabbitAmqpTemplate rabbitAmqpTemplate(AmqpConnectionFactory connectionFactory, + ObjectProvider customizers, + ObjectProvider messageConverter) { + RabbitAmqpTemplate rabbitAmqpTemplate = new RabbitAmqpTemplate(connectionFactory); + if (messageConverter.getIfAvailable() != null) { + rabbitAmqpTemplate.setMessageConverter(messageConverter.getIfAvailable()); + } + RabbitProperties.Template templateProperties = this.properties.getTemplate(); + + PropertyMapper map = PropertyMapper.get(); + map.from(templateProperties::getDefaultReceiveQueue).whenNonNull().to(rabbitAmqpTemplate::setReceiveQueue); + map.from(templateProperties::getExchange).whenNonNull().to(rabbitAmqpTemplate::setExchange); + map.from(templateProperties::getRoutingKey).to(rabbitAmqpTemplate::setRoutingKey); + + customizers.orderedStream().forEach((customizer) -> customizer.customize(rabbitAmqpTemplate)); + return rabbitAmqpTemplate; + } + + @Bean + @ConditionalOnMissingBean + public RabbitAmqpAdmin rabbitAmqpAdmin(AmqpConnectionFactory connectionFactory) { + return new RabbitAmqpAdmin(connectionFactory); + } + +} diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpTemplateCustomizer.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpTemplateCustomizer.java new file mode 100644 index 000000000000..e1eb2404cbb2 --- /dev/null +++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpTemplateCustomizer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-present 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.amqp.autoconfigure; + +import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; + +/** + * Callback interface that can be used to customize a {@link RabbitAmqpTemplate}. + * + * @author Eddú Meléndez + * @since 4.0.0 + */ +@FunctionalInterface +public interface RabbitAmqpTemplateCustomizer { + + /** + * Callback to customize a {@link RabbitAmqpTemplate} instance. + * @param rabbitAmqpTemplate the rabbitAmqpTemplate to customize + */ + void customize(RabbitAmqpTemplate rabbitAmqpTemplate); + +} diff --git a/module/spring-boot-amqp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/module/spring-boot-amqp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index daf8ae3ba45f..c45ad36ad4d5 100644 --- a/module/spring-boot-amqp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/module/spring-boot-amqp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,3 +1,4 @@ org.springframework.boot.amqp.autoconfigure.RabbitAutoConfiguration +org.springframework.boot.amqp.autoconfigure.RabbitAmqpAutoConfiguration org.springframework.boot.amqp.autoconfigure.health.RabbitHealthContributorAutoConfiguration org.springframework.boot.amqp.autoconfigure.metrics.RabbitMetricsAutoConfiguration \ No newline at end of file diff --git a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfigurationTests.java b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfigurationTests.java new file mode 100644 index 000000000000..af91dbab6751 --- /dev/null +++ b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfigurationTests.java @@ -0,0 +1,204 @@ +/* + * Copyright 2012-present 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.amqp.autoconfigure; + +import org.aopalliance.aop.Advice; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbit.config.BaseRabbitListenerContainerFactory; +import org.springframework.amqp.rabbit.config.ContainerCustomizer; +import org.springframework.amqp.rabbitmq.client.AmqpConnectionFactory; +import org.springframework.amqp.rabbitmq.client.RabbitAmqpAdmin; +import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; +import org.springframework.amqp.rabbitmq.client.config.RabbitAmqpListenerContainerFactory; +import org.springframework.amqp.rabbitmq.client.listener.RabbitAmqpListenerContainer; +import org.springframework.amqp.support.converter.MessageConversionException; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.retry.RetryPolicy; +import org.springframework.retry.policy.NeverRetryPolicy; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link RabbitAmqpAutoConfiguration}. + * + * @author Eddú Meléndez + */ +class RabbitAmqpAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(RabbitAmqpAutoConfiguration.class)); + + @Test + void testDefaultRabbitAmqpConfiguration() { + this.contextRunner.run((context) -> { + RabbitAmqpListenerContainerFactory listenerContainerFactory = context + .getBean(RabbitAmqpListenerContainerFactory.class); + AmqpConnectionFactory connectionFactory = context.getBean(AmqpConnectionFactory.class); + RabbitAmqpTemplate rabbitAmqpTemplate = context.getBean(RabbitAmqpTemplate.class); + RabbitAmqpAdmin rabbitAmqpAdmin = context.getBean(RabbitAmqpAdmin.class); + + assertThat(listenerContainerFactory).isNotNull(); + assertThat(listenerContainerFactory).extracting("containerCustomizer").isNull(); + assertThat(connectionFactory).isNotNull(); + assertThat(rabbitAmqpTemplate).isNotNull(); + assertThat(rabbitAmqpAdmin).isNotNull(); + }); + } + + @Test + void whenMultipleRabbitAmqpTemplateCustomizersAreDefinedThenTheyAreCalledInOrder() { + this.contextRunner.withUserConfiguration(MultipleRabbitAmqpTemplateCustomizersConfiguration.class) + .run((context) -> { + RabbitAmqpTemplateCustomizer firstCustomizer = context.getBean("firstCustomizer", + RabbitAmqpTemplateCustomizer.class); + RabbitAmqpTemplateCustomizer secondCustomizer = context.getBean("secondCustomizer", + RabbitAmqpTemplateCustomizer.class); + InOrder inOrder = inOrder(firstCustomizer, secondCustomizer); + RabbitAmqpTemplate template = context.getBean(RabbitAmqpTemplate.class); + then(firstCustomizer).should(inOrder).customize(template); + then(secondCustomizer).should(inOrder).customize(template); + inOrder.verifyNoMoreInteractions(); + }); + } + + @Test + void testSimpleRabbitListenerContainerFactoryRetryWithCustomizer() { + this.contextRunner.withUserConfiguration(RabbitRetryTemplateCustomizerConfiguration.class) + .withPropertyValues("spring.rabbitmq.listener.simple.retry.enabled:true") + .run((context) -> { + RabbitAmqpListenerContainerFactory rabbitListenerContainerFactory = context + .getBean("rabbitAmqpListenerContainerFactory", RabbitAmqpListenerContainerFactory.class); + assertListenerRetryTemplate(rabbitListenerContainerFactory, + context.getBean(RabbitRetryTemplateCustomizerConfiguration.class).retryPolicy); + }); + } + + private void assertListenerRetryTemplate(BaseRabbitListenerContainerFactory rabbitListenerContainerFactory, + RetryPolicy retryPolicy) { + Advice[] adviceChain = rabbitListenerContainerFactory.getAdviceChain(); + assertThat(adviceChain).isNotNull(); + assertThat(adviceChain).hasSize(1); + Advice advice = adviceChain[0]; + RetryTemplate retryTemplate = (RetryTemplate) ReflectionTestUtils.getField(advice, "retryOperations"); + assertThat(retryTemplate).hasFieldOrPropertyWithValue("retryPolicy", retryPolicy); + } + + @Test + void testListenerContainerFactoryWithContainerCustomizer() { + this.contextRunner.withUserConfiguration(AmqpContainerCustomizerConfiguration.class).run((context) -> { + RabbitAmqpListenerContainerFactory listenerContainerFactory = context + .getBean(RabbitAmqpListenerContainerFactory.class); + + assertThat(listenerContainerFactory).isNotNull(); + assertThat(listenerContainerFactory).extracting("containerCustomizer").isNotNull(); + }); + } + + @Configuration(proxyBeanMethods = false) + static class MultipleRabbitAmqpTemplateCustomizersConfiguration { + + @Bean + @Order(Ordered.LOWEST_PRECEDENCE) + RabbitAmqpTemplateCustomizer secondCustomizer() { + return mock(RabbitAmqpTemplateCustomizer.class); + } + + @Bean + @Order(0) + RabbitAmqpTemplateCustomizer firstCustomizer() { + return mock(RabbitAmqpTemplateCustomizer.class); + } + + } + + @Configuration(proxyBeanMethods = false) + static class RabbitRetryTemplateCustomizerConfiguration { + + private final RetryPolicy retryPolicy = new NeverRetryPolicy(); + + @Bean + RabbitRetryTemplateCustomizer rabbitListenerRetryTemplateCustomizer() { + return (target, template) -> { + if (target.equals(RabbitRetryTemplateCustomizer.Target.LISTENER)) { + template.setRetryPolicy(this.retryPolicy); + } + }; + } + + } + + @Import(TestListener.class) + @Configuration(proxyBeanMethods = false) + static class AmqpContainerCustomizerConfiguration { + + @Bean + @SuppressWarnings("unchecked") + ContainerCustomizer customizer() { + return mock(ContainerCustomizer.class); + } + + } + + @Configuration + static class CustomMessageConverterConfiguration { + + @Bean + MessageConverter messageConverter() { + return new MessageConverter() { + + @Override + public Message toMessage(Object object, MessageProperties messageProperties) + throws MessageConversionException { + return new Message(object.toString().getBytes()); + } + + @Override + public Object fromMessage(Message message) throws MessageConversionException { + return new String(message.getBody()); + } + + }; + } + + } + + static class TestListener { + + @RabbitListener(queues = "test", autoStartup = "false") + void listen(String in) { + } + + } + +} From 02ba8887fb977fdbee541f3a9906cc8f88971913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Wed, 30 Jul 2025 18:56:22 -0600 Subject: [PATCH 2/9] Add smoke test for RabbitMQ AMQP 1.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eddú Meléndez --- settings.gradle | 1 + .../build.gradle | 34 +++++++++++ ...ampleRabbitAmqpSimpleApplicationTests.java | 54 ++++++++++++++++ .../SampleRabbitAmqpSimpleApplication.java | 61 +++++++++++++++++++ .../src/main/java/smoketest/amqp/Sender.java | 31 ++++++++++ 5 files changed, 181 insertions(+) create mode 100644 smoke-test/spring-boot-smoke-test-rabbit-amqp/build.gradle create mode 100644 smoke-test/spring-boot-smoke-test-rabbit-amqp/src/dockerTest/java/smoketest/amqp/SampleRabbitAmqpSimpleApplicationTests.java create mode 100644 smoke-test/spring-boot-smoke-test-rabbit-amqp/src/main/java/smoketest/amqp/SampleRabbitAmqpSimpleApplication.java create mode 100644 smoke-test/spring-boot-smoke-test-rabbit-amqp/src/main/java/smoketest/amqp/Sender.java diff --git a/settings.gradle b/settings.gradle index 65e82571407d..ec7d0fefcb08 100644 --- a/settings.gradle +++ b/settings.gradle @@ -331,6 +331,7 @@ include ":smoke-test:spring-boot-smoke-test-prometheus" include ":smoke-test:spring-boot-smoke-test-property-validation" include ":smoke-test:spring-boot-smoke-test-pulsar" include ":smoke-test:spring-boot-smoke-test-quartz" +include ":smoke-test:spring-boot-smoke-test-rabbit-amqp" include ":smoke-test:spring-boot-smoke-test-reactive-oauth2-client" include ":smoke-test:spring-boot-smoke-test-reactive-oauth2-resource-server" include ":smoke-test:spring-boot-smoke-test-rsocket" diff --git a/smoke-test/spring-boot-smoke-test-rabbit-amqp/build.gradle b/smoke-test/spring-boot-smoke-test-rabbit-amqp/build.gradle new file mode 100644 index 000000000000..305f0a702e5c --- /dev/null +++ b/smoke-test/spring-boot-smoke-test-rabbit-amqp/build.gradle @@ -0,0 +1,34 @@ +/* + * Copyright 2012-present 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. + */ + +plugins { + id "java" + id "org.springframework.boot.docker-test" +} + +description = "Spring Boot RabbitMQ AMQP smoke test" + +dependencies { + implementation(project(":starter:spring-boot-starter-amqp")) + implementation("org.springframework.amqp:spring-rabbitmq-client") + + dockerTestImplementation(project(":starter:spring-boot-starter-test")) + dockerTestImplementation(project(":core:spring-boot-testcontainers")) + dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) + dockerTestImplementation("org.awaitility:awaitility") + dockerTestImplementation("org.testcontainers:junit-jupiter") + dockerTestImplementation("org.testcontainers:rabbitmq") +} \ No newline at end of file diff --git a/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/dockerTest/java/smoketest/amqp/SampleRabbitAmqpSimpleApplicationTests.java b/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/dockerTest/java/smoketest/amqp/SampleRabbitAmqpSimpleApplicationTests.java new file mode 100644 index 000000000000..d5ef51eff469 --- /dev/null +++ b/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/dockerTest/java/smoketest/amqp/SampleRabbitAmqpSimpleApplicationTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-present 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 smoketest.amqp; + +import java.time.Duration; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.containers.RabbitMQContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Testcontainers(disabledWithoutDocker = true) +@ExtendWith(OutputCaptureExtension.class) +class SampleRabbitAmqpSimpleApplicationTests { + + @Container + @ServiceConnection + static final RabbitMQContainer rabbit = new RabbitMQContainer("rabbitmq:4.0-management-alpine"); + + @Autowired + private Sender sender; + + @Test + void sendSimpleMessage(CapturedOutput output) { + this.sender.send("Test message"); + Awaitility.waitAtMost(Duration.ofMinutes(1)).untilAsserted(() -> assertThat(output).contains("Test message")); + } + +} diff --git a/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/main/java/smoketest/amqp/SampleRabbitAmqpSimpleApplication.java b/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/main/java/smoketest/amqp/SampleRabbitAmqpSimpleApplication.java new file mode 100644 index 000000000000..aff9bc342e3b --- /dev/null +++ b/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/main/java/smoketest/amqp/SampleRabbitAmqpSimpleApplication.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-present 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 smoketest.amqp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.messaging.handler.annotation.Payload; + +@SpringBootApplication +@RabbitListener(queues = "foo") +public class SampleRabbitAmqpSimpleApplication { + + private static final Log logger = LogFactory.getLog(SampleRabbitAmqpSimpleApplication.class); + + @Bean + public Sender mySender() { + return new Sender(); + } + + @Bean + public Queue fooQueue() { + return new Queue("foo"); + } + + @RabbitHandler + public void process(@Payload String foo) { + logger.info(foo); + } + + @Bean + public ApplicationRunner runner(Sender sender) { + return (args) -> sender.send("Hello"); + } + + public static void main(String[] args) { + SpringApplication.run(SampleRabbitAmqpSimpleApplication.class, args); + } + +} diff --git a/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/main/java/smoketest/amqp/Sender.java b/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/main/java/smoketest/amqp/Sender.java new file mode 100644 index 000000000000..6639bc0316ce --- /dev/null +++ b/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/main/java/smoketest/amqp/Sender.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-present 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 smoketest.amqp; + +import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; +import org.springframework.beans.factory.annotation.Autowired; + +public class Sender { + + @Autowired + private RabbitAmqpTemplate rabbitAmqpTemplate; + + public void send(String message) { + this.rabbitAmqpTemplate.convertAndSend("foo", message); + } + +} From 93d232d91059199ad2684bd0e1c0a7feba39cd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Wed, 30 Jul 2025 19:17:55 -0600 Subject: [PATCH 3/9] Fix checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eddú Meléndez --- .../autoconfigure/RabbitAmqpAutoConfiguration.java | 10 +++++----- ...mework.boot.autoconfigure.AutoConfiguration.imports | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java index 8663557d1a7c..d45179242597 100644 --- a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java +++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java @@ -72,7 +72,7 @@ RabbitConnectionDetails rabbitConnectionDetails(ObjectProvider sslBu @Bean(name = "rabbitAmqpListenerContainerFactory") @ConditionalOnMissingBean(name = "rabbitAmqpListenerContainerFactory") - public RabbitAmqpListenerContainerFactory rabbitAmqpListenerContainerFactory( + RabbitAmqpListenerContainerFactory rabbitAmqpListenerContainerFactory( AmqpConnectionFactory connectionFactory, ObjectProvider> amqpContainerCustomizer, ObjectProvider retryTemplateCustomizers, @@ -101,7 +101,7 @@ public RabbitAmqpListenerContainerFactory rabbitAmqpListenerContainerFactory( @Bean @ConditionalOnMissingBean - public Environment rabbitAmqpEnvironment(RabbitConnectionDetails connectionDetails, + Environment rabbitAmqpEnvironment(RabbitConnectionDetails connectionDetails, ObjectProvider customizers, ObjectProvider credentialsProvider) { PropertyMapper map = PropertyMapper.get(); @@ -123,13 +123,13 @@ public Environment rabbitAmqpEnvironment(RabbitConnectionDetails connectionDetai @Bean @ConditionalOnMissingBean - public AmqpConnectionFactory amqpConnection(Environment environment) { + AmqpConnectionFactory amqpConnection(Environment environment) { return new SingleAmqpConnectionFactory(environment); } @Bean @ConditionalOnMissingBean - public RabbitAmqpTemplate rabbitAmqpTemplate(AmqpConnectionFactory connectionFactory, + RabbitAmqpTemplate rabbitAmqpTemplate(AmqpConnectionFactory connectionFactory, ObjectProvider customizers, ObjectProvider messageConverter) { RabbitAmqpTemplate rabbitAmqpTemplate = new RabbitAmqpTemplate(connectionFactory); @@ -149,7 +149,7 @@ public RabbitAmqpTemplate rabbitAmqpTemplate(AmqpConnectionFactory connectionFac @Bean @ConditionalOnMissingBean - public RabbitAmqpAdmin rabbitAmqpAdmin(AmqpConnectionFactory connectionFactory) { + RabbitAmqpAdmin rabbitAmqpAdmin(AmqpConnectionFactory connectionFactory) { return new RabbitAmqpAdmin(connectionFactory); } diff --git a/module/spring-boot-amqp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/module/spring-boot-amqp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index c45ad36ad4d5..febc373b38a8 100644 --- a/module/spring-boot-amqp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/module/spring-boot-amqp/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,4 +1,4 @@ -org.springframework.boot.amqp.autoconfigure.RabbitAutoConfiguration org.springframework.boot.amqp.autoconfigure.RabbitAmqpAutoConfiguration +org.springframework.boot.amqp.autoconfigure.RabbitAutoConfiguration org.springframework.boot.amqp.autoconfigure.health.RabbitHealthContributorAutoConfiguration org.springframework.boot.amqp.autoconfigure.metrics.RabbitMetricsAutoConfiguration \ No newline at end of file From 6572c3635c576901d85a434c9eb3c7ad630e8f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Mon, 4 Aug 2025 21:53:51 -0600 Subject: [PATCH 4/9] Fix comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eddú Meléndez --- .../RabbitAmqpAutoConfiguration.java | 23 ++----------------- .../RabbitAmqpAutoConfigurationTests.java | 12 ---------- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java index d45179242597..e629f77c8db6 100644 --- a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java +++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java @@ -23,9 +23,7 @@ import com.rabbitmq.client.amqp.impl.AmqpEnvironmentBuilder.EnvironmentConnectionSettings; import org.springframework.amqp.rabbit.config.ContainerCustomizer; -import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder; import org.springframework.amqp.rabbit.retry.MessageRecoverer; -import org.springframework.amqp.rabbit.retry.RejectAndDontRequeueRecoverer; import org.springframework.amqp.rabbitmq.client.AmqpConnectionFactory; import org.springframework.amqp.rabbitmq.client.RabbitAmqpAdmin; import org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate; @@ -35,8 +33,6 @@ import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.amqp.autoconfigure.RabbitConnectionDetails.Address; -import org.springframework.boot.amqp.autoconfigure.RabbitProperties.ListenerRetry; -import org.springframework.boot.amqp.autoconfigure.RabbitRetryTemplateCustomizer.Target; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -45,7 +41,6 @@ import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.ssl.SslBundles; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; /** * {@link EnableAutoConfiguration Auto-configuration} for {@link RabbitAmqpTemplate}. @@ -53,7 +48,7 @@ * @author Eddú Meléndez * @since 4.0.0 */ -@AutoConfiguration +@AutoConfiguration(before = RabbitAutoConfiguration.class) @ConditionalOnClass({ RabbitAmqpTemplate.class, Connection.class }) @EnableConfigurationProperties(RabbitProperties.class) public final class RabbitAmqpAutoConfiguration { @@ -82,20 +77,6 @@ RabbitAmqpListenerContainerFactory rabbitAmqpListenerContainerFactory( RabbitProperties.AmqpContainer configuration = this.properties.getListener().getSimple(); factory.setObservationEnabled(configuration.isObservationEnabled()); - ListenerRetry retryConfig = configuration.getRetry(); - if (retryConfig.isEnabled()) { - RetryInterceptorBuilder builder = (retryConfig.isStateless()) ? RetryInterceptorBuilder.stateless() - : RetryInterceptorBuilder.stateful(); - - RetryTemplate retryTemplate = new RetryTemplateFactory(retryTemplateCustomizers.orderedStream().toList()) - .createRetryTemplate(retryConfig, Target.LISTENER); - - builder.retryOperations(retryTemplate); - MessageRecoverer recoverer = (messageRecoverer.getIfAvailable() != null) ? messageRecoverer.getIfAvailable() - : new RejectAndDontRequeueRecoverer(); - builder.recoverer(recoverer); - factory.setAdviceChain(builder.build()); - } return factory; } @@ -123,7 +104,7 @@ Environment rabbitAmqpEnvironment(RabbitConnectionDetails connectionDetails, @Bean @ConditionalOnMissingBean - AmqpConnectionFactory amqpConnection(Environment environment) { + AmqpConnectionFactory amqpConnectionFactory(Environment environment) { return new SingleAmqpConnectionFactory(environment); } diff --git a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfigurationTests.java b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfigurationTests.java index af91dbab6751..fbc7a2b75f2f 100644 --- a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfigurationTests.java +++ b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfigurationTests.java @@ -92,18 +92,6 @@ void whenMultipleRabbitAmqpTemplateCustomizersAreDefinedThenTheyAreCalledInOrder }); } - @Test - void testSimpleRabbitListenerContainerFactoryRetryWithCustomizer() { - this.contextRunner.withUserConfiguration(RabbitRetryTemplateCustomizerConfiguration.class) - .withPropertyValues("spring.rabbitmq.listener.simple.retry.enabled:true") - .run((context) -> { - RabbitAmqpListenerContainerFactory rabbitListenerContainerFactory = context - .getBean("rabbitAmqpListenerContainerFactory", RabbitAmqpListenerContainerFactory.class); - assertListenerRetryTemplate(rabbitListenerContainerFactory, - context.getBean(RabbitRetryTemplateCustomizerConfiguration.class).retryPolicy); - }); - } - private void assertListenerRetryTemplate(BaseRabbitListenerContainerFactory rabbitListenerContainerFactory, RetryPolicy retryPolicy) { Advice[] adviceChain = rabbitListenerContainerFactory.getAdviceChain(); From a3913a1d9530dc41978f9a1d3adc938f35ca702f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Tue, 5 Aug 2025 14:47:47 -0600 Subject: [PATCH 5/9] Fix format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eddú Meléndez --- .../boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java index e629f77c8db6..4515a902d0a1 100644 --- a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java +++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java @@ -67,8 +67,7 @@ RabbitConnectionDetails rabbitConnectionDetails(ObjectProvider sslBu @Bean(name = "rabbitAmqpListenerContainerFactory") @ConditionalOnMissingBean(name = "rabbitAmqpListenerContainerFactory") - RabbitAmqpListenerContainerFactory rabbitAmqpListenerContainerFactory( - AmqpConnectionFactory connectionFactory, + RabbitAmqpListenerContainerFactory rabbitAmqpListenerContainerFactory(AmqpConnectionFactory connectionFactory, ObjectProvider> amqpContainerCustomizer, ObjectProvider retryTemplateCustomizers, ObjectProvider messageRecoverer) { From f983ae2c75b192dc3b4f5027ddfc618870c3ceea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Wed, 6 Aug 2025 15:44:55 -0600 Subject: [PATCH 6/9] Autoconfigure only AMQP 1.0 or 0.9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eddú Meléndez --- module/spring-boot-amqp/build.gradle | 3 +-- .../amqp/autoconfigure/RabbitAmqpAutoConfiguration.java | 4 ++-- .../boot/amqp/autoconfigure/RabbitAutoConfiguration.java | 4 ++++ .../amqp/autoconfigure/RabbitAutoConfigurationTests.java | 4 +++- .../amqp/autoconfigure/RabbitStreamConfigurationTests.java | 5 ++++- .../RabbitHealthContributorAutoConfigurationTests.java | 6 +++++- ...csAutoConfigurationMeterBinderCycleIntegrationTests.java | 3 +++ .../metrics/RabbitMetricsAutoConfigurationTests.java | 6 +++++- 8 files changed, 27 insertions(+), 8 deletions(-) diff --git a/module/spring-boot-amqp/build.gradle b/module/spring-boot-amqp/build.gradle index 5ba4de34d294..9a1901534343 100644 --- a/module/spring-boot-amqp/build.gradle +++ b/module/spring-boot-amqp/build.gradle @@ -28,7 +28,7 @@ description = "Spring Boot AMQP" dependencies { api(project(":core:spring-boot")) api("org.springframework:spring-messaging") - api("org.springframework.amqp:spring-rabbit") + api("org.springframework.amqp:spring-rabbitmq-client") compileOnly("com.fasterxml.jackson.core:jackson-annotations") @@ -41,7 +41,6 @@ dependencies { optional(project(":module:spring-boot-metrics")) optional("io.micrometer:micrometer-core") optional("org.springframework.amqp:spring-rabbit-stream") - optional("org.springframework.amqp:spring-rabbitmq-client") optional("org.testcontainers:rabbitmq") dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java index 4515a902d0a1..5170d2ef872e 100644 --- a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java +++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAmqpAutoConfiguration.java @@ -65,8 +65,8 @@ RabbitConnectionDetails rabbitConnectionDetails(ObjectProvider sslBu return new PropertiesRabbitConnectionDetails(this.properties, sslBundles.getIfAvailable()); } - @Bean(name = "rabbitAmqpListenerContainerFactory") - @ConditionalOnMissingBean(name = "rabbitAmqpListenerContainerFactory") + @Bean(name = "rabbitListenerContainerFactory") + @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory") RabbitAmqpListenerContainerFactory rabbitAmqpListenerContainerFactory(AmqpConnectionFactory connectionFactory, ObjectProvider> amqpContainerCustomizer, ObjectProvider retryTemplateCustomizers, diff --git a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAutoConfiguration.java b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAutoConfiguration.java index 159245f42cfc..aebe4e54c080 100644 --- a/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAutoConfiguration.java +++ b/module/spring-boot-amqp/src/main/java/org/springframework/boot/amqp/autoconfigure/RabbitAutoConfiguration.java @@ -36,6 +36,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.ssl.SslBundles; @@ -71,10 +72,13 @@ * @author Moritz Halbritter * @author Andy Wilkinson * @author Scott Frederick + * @author Eddú Meléndez * @since 4.0.0 */ @AutoConfiguration @ConditionalOnClass({ RabbitTemplate.class, Channel.class }) +@ConditionalOnMissingClass({ "com.rabbitmq.client.amqp.Connection", + "org.springframework.amqp.rabbitmq.client.RabbitAmqpTemplate" }) @EnableConfigurationProperties(RabbitProperties.class) @Import({ RabbitAnnotationDrivenConfiguration.class, RabbitStreamConfiguration.class }) public final class RabbitAutoConfiguration { diff --git a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAutoConfigurationTests.java b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAutoConfigurationTests.java index 56f57061096e..e8f8843b9fbb 100644 --- a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAutoConfigurationTests.java +++ b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitAutoConfigurationTests.java @@ -115,13 +115,15 @@ * @author Phillip Webb * @author Scott Frederick * @author Yanming Zhou + * @author Eddú Meléndez */ @ExtendWith(OutputCaptureExtension.class) class RabbitAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class, SslAutoConfiguration.class)) - .withClassLoader(new FilteredClassLoader("org.springframework.rabbit.stream")); // gh-38750 + .withClassLoader(new FilteredClassLoader("org.springframework.rabbit.stream", "com.rabbitmq.client.amqp", + "org.springframework.amqp.rabbitmq.client")); // gh-38750 @Test void testDefaultRabbitConfiguration() { diff --git a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitStreamConfigurationTests.java b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitStreamConfigurationTests.java index 27bbc9918845..3c256a173fe3 100644 --- a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitStreamConfigurationTests.java +++ b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/RabbitStreamConfigurationTests.java @@ -33,6 +33,7 @@ import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -60,7 +61,9 @@ class RabbitStreamConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class)) + .withClassLoader( + new FilteredClassLoader("com.rabbitmq.client.amqp", "org.springframework.amqp.rabbitmq.client")); @Test @SuppressWarnings("unchecked") diff --git a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/health/RabbitHealthContributorAutoConfigurationTests.java b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/health/RabbitHealthContributorAutoConfigurationTests.java index 8739399d0aae..62636bdfd00d 100644 --- a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/health/RabbitHealthContributorAutoConfigurationTests.java +++ b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/health/RabbitHealthContributorAutoConfigurationTests.java @@ -22,6 +22,7 @@ import org.springframework.boot.amqp.health.RabbitHealthIndicator; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.health.autoconfigure.contributor.HealthContributorAutoConfiguration; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -30,12 +31,15 @@ * Tests for {@link RabbitHealthContributorAutoConfiguration}. * * @author Phillip Webb + * @author Eddú Meléndez */ class RabbitHealthContributorAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class, - RabbitHealthContributorAutoConfiguration.class, HealthContributorAutoConfiguration.class)); + RabbitHealthContributorAutoConfiguration.class, HealthContributorAutoConfiguration.class)) + .withClassLoader( + new FilteredClassLoader("com.rabbitmq.client.amqp", "org.springframework.amqp.rabbitmq.client")); @Test void runShouldCreateIndicator() { diff --git a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/metrics/RabbitMetricsAutoConfigurationMeterBinderCycleIntegrationTests.java b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/metrics/RabbitMetricsAutoConfigurationMeterBinderCycleIntegrationTests.java index 76c0f82e5987..a65e3d31c815 100644 --- a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/metrics/RabbitMetricsAutoConfigurationMeterBinderCycleIntegrationTests.java +++ b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/metrics/RabbitMetricsAutoConfigurationMeterBinderCycleIntegrationTests.java @@ -24,6 +24,7 @@ import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.boot.amqp.autoconfigure.RabbitAutoConfiguration; import org.springframework.boot.metrics.autoconfigure.MetricsAutoConfiguration; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -36,8 +37,10 @@ * dependency cycle when used with {@link MeterBinder}. * * @author Phillip Webb + * @author Eddú Meléndez * @see gh-30636 */ +@ClassPathExclusions(files = "spring-rabbitmq-client-*.jar", packages = "com.rabbitmq.client.amqp") class RabbitMetricsAutoConfigurationMeterBinderCycleIntegrationTests { @Test diff --git a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/metrics/RabbitMetricsAutoConfigurationTests.java b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/metrics/RabbitMetricsAutoConfigurationTests.java index fea709700fc0..3d90d386bb29 100644 --- a/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/metrics/RabbitMetricsAutoConfigurationTests.java +++ b/module/spring-boot-amqp/src/test/java/org/springframework/boot/amqp/autoconfigure/metrics/RabbitMetricsAutoConfigurationTests.java @@ -25,6 +25,7 @@ import org.springframework.boot.amqp.autoconfigure.RabbitAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.metrics.autoconfigure.MetricsAutoConfiguration; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -35,6 +36,7 @@ * Tests for {@link RabbitMetricsAutoConfiguration}. * * @author Stephane Nicoll + * @author Eddú Meléndez */ class RabbitMetricsAutoConfigurationTests { @@ -42,7 +44,9 @@ class RabbitMetricsAutoConfigurationTests { .withBean(SimpleMeterRegistry.class) .withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class, RabbitMetricsAutoConfiguration.class, MetricsAutoConfiguration.class)) - .withPropertyValues("management.metrics.use-global-registry=false"); + .withPropertyValues("management.metrics.use-global-registry=false") + .withClassLoader( + new FilteredClassLoader("com.rabbitmq.client.amqp", "org.springframework.amqp.rabbitmq.client")); @Test void autoConfiguredConnectionFactoryIsInstrumented() { From db4589278ed10567568287ab6a46e45d2d29bcf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Wed, 6 Aug 2025 15:45:40 -0600 Subject: [PATCH 7/9] Add new starter for rabbitmq 0.9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eddú Meléndez --- settings.gradle | 1 + .../spring-boot-starter-rabbitmq/build.gradle | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 starter/spring-boot-starter-rabbitmq/build.gradle diff --git a/settings.gradle b/settings.gradle index ec7d0fefcb08..850d472783b0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -234,6 +234,7 @@ include "starter:spring-boot-starter-pulsar" include "starter:spring-boot-starter-pulsar-reactive" include "starter:spring-boot-starter-quartz" include "starter:spring-boot-starter-r2dbc" +include "starter:spring-boot-starter-rabbitmq" include "starter:spring-boot-starter-reactor" include "starter:spring-boot-starter-reactor-netty" include "starter:spring-boot-starter-restclient" diff --git a/starter/spring-boot-starter-rabbitmq/build.gradle b/starter/spring-boot-starter-rabbitmq/build.gradle new file mode 100644 index 000000000000..cd27bb185ec9 --- /dev/null +++ b/starter/spring-boot-starter-rabbitmq/build.gradle @@ -0,0 +1,30 @@ +/* + * Copyright 2012-present 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. + */ + +plugins { + id "org.springframework.boot.starter" +} + +description = "Starter for using Spring AMQP and Rabbit MQ" + +dependencies { + api(project(":starter:spring-boot-starter")) + + api(project(":module:spring-boot-amqp")) { + exclude group: "org.springframework.amqp", module: "spring-rabbitmq-client" + } + api("org.springframework.amqp:spring-rabbit") +} From b6ec014eca236fb5815936dcfa11a5bf69c27fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Wed, 6 Aug 2025 15:46:09 -0600 Subject: [PATCH 8/9] Use new starter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eddú Meléndez --- smoke-test/spring-boot-smoke-test-amqp/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smoke-test/spring-boot-smoke-test-amqp/build.gradle b/smoke-test/spring-boot-smoke-test-amqp/build.gradle index edf318600ce7..1616a33fa649 100644 --- a/smoke-test/spring-boot-smoke-test-amqp/build.gradle +++ b/smoke-test/spring-boot-smoke-test-amqp/build.gradle @@ -22,7 +22,7 @@ plugins { description = "Spring Boot AMQP smoke test" dependencies { - implementation(project(":starter:spring-boot-starter-amqp")) + implementation(project(":starter:spring-boot-starter-rabbitmq")) dockerTestImplementation(project(":starter:spring-boot-starter-test")) dockerTestImplementation(project(":core:spring-boot-testcontainers")) From 58692fb6247a2b33fe978dc1bc324e59963e6ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Wed, 6 Aug 2025 15:46:36 -0600 Subject: [PATCH 9/9] Update smoke test rabbit amqp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Eddú Meléndez --- smoke-test/spring-boot-smoke-test-rabbit-amqp/build.gradle | 1 - .../java/smoketest/amqp/SampleRabbitAmqpSimpleApplication.java | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/smoke-test/spring-boot-smoke-test-rabbit-amqp/build.gradle b/smoke-test/spring-boot-smoke-test-rabbit-amqp/build.gradle index 305f0a702e5c..d4d9a57a60a1 100644 --- a/smoke-test/spring-boot-smoke-test-rabbit-amqp/build.gradle +++ b/smoke-test/spring-boot-smoke-test-rabbit-amqp/build.gradle @@ -23,7 +23,6 @@ description = "Spring Boot RabbitMQ AMQP smoke test" dependencies { implementation(project(":starter:spring-boot-starter-amqp")) - implementation("org.springframework.amqp:spring-rabbitmq-client") dockerTestImplementation(project(":starter:spring-boot-starter-test")) dockerTestImplementation(project(":core:spring-boot-testcontainers")) diff --git a/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/main/java/smoketest/amqp/SampleRabbitAmqpSimpleApplication.java b/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/main/java/smoketest/amqp/SampleRabbitAmqpSimpleApplication.java index aff9bc342e3b..258131766e21 100644 --- a/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/main/java/smoketest/amqp/SampleRabbitAmqpSimpleApplication.java +++ b/smoke-test/spring-boot-smoke-test-rabbit-amqp/src/main/java/smoketest/amqp/SampleRabbitAmqpSimpleApplication.java @@ -20,6 +20,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.annotation.EnableRabbit; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.boot.ApplicationRunner; @@ -29,6 +30,7 @@ import org.springframework.messaging.handler.annotation.Payload; @SpringBootApplication +@EnableRabbit @RabbitListener(queues = "foo") public class SampleRabbitAmqpSimpleApplication {