From 75ae7c968aa0e6eaab1444c72de2e6953f3a8ab8 Mon Sep 17 00:00:00 2001 From: Tadaya Tsuyukubo Date: Sun, 28 Apr 2024 08:22:58 -0700 Subject: [PATCH 1/2] Add ProxyConnectionFactoryCustomizer See gh-40555 --- .../R2dbcObservationAutoConfiguration.java | 28 ++++++++++-- ...2dbcObservationAutoConfigurationTests.java | 43 ++++++++++++++++++- spring-boot-project/spring-boot/build.gradle | 1 + .../ProxyConnectionFactoryCustomizer.java | 36 ++++++++++++++++ 4 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ProxyConnectionFactoryCustomizer.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfiguration.java index 75f619c1bdf..bcd1cf69b26 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -33,12 +33,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.r2dbc.ConnectionFactoryDecorator; import org.springframework.boot.r2dbc.OptionsCapableConnectionFactory; +import org.springframework.boot.r2dbc.ProxyConnectionFactoryCustomizer; import org.springframework.context.annotation.Bean; +import org.springframework.core.annotation.Order; /** * {@link EnableAutoConfiguration Auto-configuration} for R2DBC observability support. * * @author Moritz Halbritter + * @author Tadaya Tsuyukubo * @since 3.2.0 */ @AutoConfiguration(after = ObservationAutoConfiguration.class) @@ -46,20 +49,37 @@ import org.springframework.context.annotation.Bean; @EnableConfigurationProperties(R2dbcObservationProperties.class) public class R2dbcObservationAutoConfiguration { + /** + * {@code @Order} value of observation customizer. + */ + public static final int R2DBC_PROXY_OBSERVATION_CUSTOMIZER_ORDER = 1000; + @Bean + ConnectionFactoryDecorator connectionFactoryDecorator( + ObjectProvider customizers) { + return (connectionFactory) -> { + ProxyConnectionFactory.Builder builder = ProxyConnectionFactory.builder(connectionFactory); + customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder.build(); + }; + } + + @Bean + @Order(R2DBC_PROXY_OBSERVATION_CUSTOMIZER_ORDER) @ConditionalOnBean(ObservationRegistry.class) - ConnectionFactoryDecorator connectionFactoryDecorator(R2dbcObservationProperties properties, + ProxyConnectionFactoryCustomizer proxyConnectionFactoryObservationCustomizer(R2dbcObservationProperties properties, ObservationRegistry observationRegistry, ObjectProvider queryObservationConvention, ObjectProvider queryParametersTagProvider) { - return (connectionFactory) -> { + return (builder) -> { + ConnectionFactory connectionFactory = builder.getConnectionFactory(); HostAndPort hostAndPort = extractHostAndPort(connectionFactory); ObservationProxyExecutionListener listener = new ObservationProxyExecutionListener(observationRegistry, connectionFactory, hostAndPort.host(), hostAndPort.port()); listener.setIncludeParameterValues(properties.isIncludeParameterValues()); queryObservationConvention.ifAvailable(listener::setQueryObservationConvention); queryParametersTagProvider.ifAvailable(listener::setQueryParametersTagProvider); - return ProxyConnectionFactory.builder(connectionFactory).listener(listener).build(); + builder.listener(listener); }; } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfigurationTests.java index e5ae366b49b..815819030ec 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -16,6 +16,8 @@ package org.springframework.boot.actuate.autoconfigure.r2dbc; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; @@ -25,6 +27,7 @@ import io.micrometer.observation.ObservationRegistry; import io.r2dbc.spi.ConnectionFactory; import org.awaitility.Awaitility; import org.hamcrest.Matchers; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; @@ -33,9 +36,12 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.context.annotation.ImportCandidates; import org.springframework.boot.r2dbc.ConnectionFactoryBuilder; import org.springframework.boot.r2dbc.ConnectionFactoryDecorator; +import org.springframework.boot.r2dbc.ProxyConnectionFactoryCustomizer; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; @@ -43,6 +49,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link R2dbcObservationAutoConfiguration}. * * @author Moritz Halbritter + * @author Tadaya Tsuyukubo */ class R2dbcObservationAutoConfigurationTests { @@ -78,7 +85,20 @@ class R2dbcObservationAutoConfigurationTests { @Test void shouldNotSupplyBeansIfObservationRegistryIsNotPresent() { this.runnerWithoutObservationRegistry - .run((context) -> assertThat(context).doesNotHaveBean(ConnectionFactoryDecorator.class)); + .run((context) -> assertThat(context).doesNotHaveBean(ProxyConnectionFactoryCustomizer.class)); + } + + @Test + void shouldApplyCustomizers() { + this.runner.withUserConfiguration(ProxyConnectionFactoryCustomizerConfig.class).run((context) -> { + ConnectionFactoryDecorator decorator = context.getBean(ConnectionFactoryDecorator.class); + ConnectionFactory connectionFactory = ConnectionFactoryBuilder + .withUrl("r2dbc:h2:mem:///" + UUID.randomUUID()) + .build(); + decorator.decorate(connectionFactory); + assertThat(context.getBean(ProxyConnectionFactoryCustomizerConfig.class).called).containsExactly("first", + "second"); + }); } @Test @@ -128,4 +148,23 @@ class R2dbcObservationAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + private static final class ProxyConnectionFactoryCustomizerConfig { + + private final List called = new ArrayList<>(); + + @Bean + @Order(R2dbcObservationAutoConfiguration.R2DBC_PROXY_OBSERVATION_CUSTOMIZER_ORDER - 1) + ProxyConnectionFactoryCustomizer first() { + return (builder) -> this.called.add("first"); + } + + @Bean + @Order(R2dbcObservationAutoConfiguration.R2DBC_PROXY_OBSERVATION_CUSTOMIZER_ORDER + 1) + ProxyConnectionFactoryCustomizer second() { + return (builder) -> this.called.add("second"); + } + + } + } diff --git a/spring-boot-project/spring-boot/build.gradle b/spring-boot-project/spring-boot/build.gradle index 0e2f235e55b..2de14ff476a 100644 --- a/spring-boot-project/spring-boot/build.gradle +++ b/spring-boot-project/spring-boot/build.gradle @@ -35,6 +35,7 @@ dependencies { optional("io.projectreactor:reactor-tools") optional("io.projectreactor.netty:reactor-netty-http") optional("io.r2dbc:r2dbc-pool") + optional("io.r2dbc:r2dbc-proxy") optional("io.rsocket:rsocket-core") optional("io.rsocket:rsocket-transport-netty") optional("io.undertow:undertow-servlet") diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ProxyConnectionFactoryCustomizer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ProxyConnectionFactoryCustomizer.java new file mode 100644 index 00000000000..b4ce1972bd9 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ProxyConnectionFactoryCustomizer.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012-2024 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.r2dbc; + +import io.r2dbc.proxy.ProxyConnectionFactory; + +/** + * Callback interface that can be used to customize a + * {@link ProxyConnectionFactory.Builder}. + * + * @author Tadaya Tsuyukubo + * @since 3.3 + */ +public interface ProxyConnectionFactoryCustomizer { + + /** + * Callback to customize a {@link ProxyConnectionFactory.Builder} instance. + * @param builder the builder to customize + */ + void customize(ProxyConnectionFactory.Builder builder); + +} From 7c3576bda80bbf53292e15fa21d0f390dedd8268 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Fri, 28 Jun 2024 14:07:40 +0200 Subject: [PATCH 2/2] Polish "Add ProxyConnectionFactoryCustomizer" See gh-40555 --- .../R2dbcObservationAutoConfiguration.java | 19 +--- ...2dbcObservationAutoConfigurationTests.java | 61 +----------- .../spring-boot-autoconfigure/build.gradle | 1 + .../ProxyConnectionFactoryCustomizer.java | 4 +- .../r2dbc/R2dbcProxyAutoConfiguration.java | 50 ++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../R2dbcProxyAutoConfigurationTests.java | 97 +++++++++++++++++++ spring-boot-project/spring-boot/build.gradle | 1 - 8 files changed, 159 insertions(+), 75 deletions(-) rename spring-boot-project/{spring-boot/src/main/java/org/springframework/boot => spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure}/r2dbc/ProxyConnectionFactoryCustomizer.java (93%) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfigurationTests.java diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfiguration.java index bcd1cf69b26..fca8478ced7 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfiguration.java @@ -30,10 +30,9 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.r2dbc.ProxyConnectionFactoryCustomizer; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.r2dbc.ConnectionFactoryDecorator; import org.springframework.boot.r2dbc.OptionsCapableConnectionFactory; -import org.springframework.boot.r2dbc.ProxyConnectionFactoryCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.core.annotation.Order; @@ -50,24 +49,14 @@ import org.springframework.core.annotation.Order; public class R2dbcObservationAutoConfiguration { /** - * {@code @Order} value of observation customizer. + * {@code @Order} value of the observation customizer. */ - public static final int R2DBC_PROXY_OBSERVATION_CUSTOMIZER_ORDER = 1000; - - @Bean - ConnectionFactoryDecorator connectionFactoryDecorator( - ObjectProvider customizers) { - return (connectionFactory) -> { - ProxyConnectionFactory.Builder builder = ProxyConnectionFactory.builder(connectionFactory); - customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); - return builder.build(); - }; - } + public static final int R2DBC_PROXY_OBSERVATION_CUSTOMIZER_ORDER = 0; @Bean @Order(R2DBC_PROXY_OBSERVATION_CUSTOMIZER_ORDER) @ConditionalOnBean(ObservationRegistry.class) - ProxyConnectionFactoryCustomizer proxyConnectionFactoryObservationCustomizer(R2dbcObservationProperties properties, + ProxyConnectionFactoryCustomizer observationProxyConnectionFactoryCustomizer(R2dbcObservationProperties properties, ObservationRegistry observationRegistry, ObjectProvider queryObservationConvention, ObjectProvider queryParametersTagProvider) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfigurationTests.java index 815819030ec..7f827a6343d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/r2dbc/R2dbcObservationAutoConfigurationTests.java @@ -16,8 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.r2dbc; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; @@ -27,21 +25,18 @@ import io.micrometer.observation.ObservationRegistry; import io.r2dbc.spi.ConnectionFactory; import org.awaitility.Awaitility; import org.hamcrest.Matchers; -import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.r2dbc.ProxyConnectionFactoryCustomizer; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcProxyAutoConfiguration; import org.springframework.boot.context.annotation.ImportCandidates; import org.springframework.boot.r2dbc.ConnectionFactoryBuilder; import org.springframework.boot.r2dbc.ConnectionFactoryDecorator; -import org.springframework.boot.r2dbc.ProxyConnectionFactoryCustomizer; -import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; @@ -54,7 +49,8 @@ import static org.assertj.core.api.Assertions.assertThat; class R2dbcObservationAutoConfigurationTests { private final ApplicationContextRunner runnerWithoutObservationRegistry = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(R2dbcObservationAutoConfiguration.class)); + .withConfiguration( + AutoConfigurations.of(R2dbcProxyAutoConfiguration.class, R2dbcObservationAutoConfiguration.class)); private final ApplicationContextRunner runner = this.runnerWithoutObservationRegistry .withBean(ObservationRegistry.class, ObservationRegistry::create); @@ -65,42 +61,12 @@ class R2dbcObservationAutoConfigurationTests { .contains(R2dbcObservationAutoConfiguration.class.getName()); } - @Test - void shouldSupplyConnectionFactoryDecorator() { - this.runner.run((context) -> assertThat(context).hasSingleBean(ConnectionFactoryDecorator.class)); - } - - @Test - void shouldNotSupplyBeansIfR2dbcSpiIsNotOnClasspath() { - this.runner.withClassLoader(new FilteredClassLoader("io.r2dbc.spi")) - .run((context) -> assertThat(context).doesNotHaveBean(ConnectionFactoryDecorator.class)); - } - - @Test - void shouldNotSupplyBeansIfR2dbcProxyIsNotOnClasspath() { - this.runner.withClassLoader(new FilteredClassLoader("io.r2dbc.proxy")) - .run((context) -> assertThat(context).doesNotHaveBean(ConnectionFactoryDecorator.class)); - } - @Test void shouldNotSupplyBeansIfObservationRegistryIsNotPresent() { this.runnerWithoutObservationRegistry .run((context) -> assertThat(context).doesNotHaveBean(ProxyConnectionFactoryCustomizer.class)); } - @Test - void shouldApplyCustomizers() { - this.runner.withUserConfiguration(ProxyConnectionFactoryCustomizerConfig.class).run((context) -> { - ConnectionFactoryDecorator decorator = context.getBean(ConnectionFactoryDecorator.class); - ConnectionFactory connectionFactory = ConnectionFactoryBuilder - .withUrl("r2dbc:h2:mem:///" + UUID.randomUUID()) - .build(); - decorator.decorate(connectionFactory); - assertThat(context.getBean(ProxyConnectionFactoryCustomizerConfig.class).called).containsExactly("first", - "second"); - }); - } - @Test void decoratorShouldReportObservations() { this.runner.run((context) -> { @@ -148,23 +114,4 @@ class R2dbcObservationAutoConfigurationTests { } - @Configuration(proxyBeanMethods = false) - private static final class ProxyConnectionFactoryCustomizerConfig { - - private final List called = new ArrayList<>(); - - @Bean - @Order(R2dbcObservationAutoConfiguration.R2DBC_PROXY_OBSERVATION_CUSTOMIZER_ORDER - 1) - ProxyConnectionFactoryCustomizer first() { - return (builder) -> this.called.add("first"); - } - - @Bean - @Order(R2dbcObservationAutoConfiguration.R2DBC_PROXY_OBSERVATION_CUSTOMIZER_ORDER + 1) - ProxyConnectionFactoryCustomizer second() { - return (builder) -> this.called.add("second"); - } - - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/build.gradle b/spring-boot-project/spring-boot-autoconfigure/build.gradle index d18e136eeb1..ffff44cabe3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/build.gradle +++ b/spring-boot-project/spring-boot-autoconfigure/build.gradle @@ -50,6 +50,7 @@ dependencies { optional("io.projectreactor.netty:reactor-netty-http") optional("io.r2dbc:r2dbc-spi") optional("io.r2dbc:r2dbc-pool") + optional("io.r2dbc:r2dbc-proxy") optional("io.rsocket:rsocket-core") optional("io.rsocket:rsocket-transport-netty") optional("io.undertow:undertow-servlet") diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ProxyConnectionFactoryCustomizer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ProxyConnectionFactoryCustomizer.java similarity index 93% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ProxyConnectionFactoryCustomizer.java rename to spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ProxyConnectionFactoryCustomizer.java index b4ce1972bd9..d969ae5aa7b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ProxyConnectionFactoryCustomizer.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ProxyConnectionFactoryCustomizer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.r2dbc; +package org.springframework.boot.autoconfigure.r2dbc; import io.r2dbc.proxy.ProxyConnectionFactory; @@ -23,7 +23,7 @@ import io.r2dbc.proxy.ProxyConnectionFactory; * {@link ProxyConnectionFactory.Builder}. * * @author Tadaya Tsuyukubo - * @since 3.3 + * @since 3.4.0 */ public interface ProxyConnectionFactoryCustomizer { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfiguration.java new file mode 100644 index 00000000000..e929be0d95a --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2024 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.r2dbc; + +import io.r2dbc.proxy.ProxyConnectionFactory; +import io.r2dbc.spi.ConnectionFactory; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.r2dbc.ConnectionFactoryDecorator; +import org.springframework.context.annotation.Bean; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for {@link ProxyConnectionFactory}. + * + * @author Tadaya Tsuyukubo + * @author Moritz Halbritter + * @since 3.4.0 + */ +@AutoConfiguration +@ConditionalOnClass({ ConnectionFactory.class, ProxyConnectionFactory.class }) +public class R2dbcProxyAutoConfiguration { + + @Bean + ConnectionFactoryDecorator connectionFactoryDecorator( + ObjectProvider customizers) { + return (connectionFactory) -> { + ProxyConnectionFactory.Builder builder = ProxyConnectionFactory.builder(connectionFactory); + customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); + return builder.build(); + }; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 38fe003d37f..1f95e7f316e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -98,6 +98,7 @@ org.springframework.boot.autoconfigure.pulsar.PulsarAutoConfiguration org.springframework.boot.autoconfigure.pulsar.PulsarReactiveAutoConfiguration org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration +org.springframework.boot.autoconfigure.r2dbc.R2dbcProxyAutoConfiguration org.springframework.boot.autoconfigure.r2dbc.R2dbcTransactionManagerAutoConfiguration org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfigurationTests.java new file mode 100644 index 00000000000..a6250d0e63e --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcProxyAutoConfigurationTests.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2024 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.r2dbc; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import io.r2dbc.spi.ConnectionFactory; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.r2dbc.ConnectionFactoryBuilder; +import org.springframework.boot.r2dbc.ConnectionFactoryDecorator; +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; +import org.springframework.core.annotation.Order; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link R2dbcProxyAutoConfiguration}. + * + * @author Tadaya Tsuyukubo + * @author Moritz Halbritter + */ +class R2dbcProxyAutoConfigurationTests { + + private final ApplicationContextRunner runner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(R2dbcProxyAutoConfiguration.class)); + + @Test + void shouldSupplyConnectionFactoryDecorator() { + this.runner.run((context) -> assertThat(context).hasSingleBean(ConnectionFactoryDecorator.class)); + } + + @Test + void shouldNotSupplyBeansIfR2dbcSpiIsNotOnClasspath() { + this.runner.withClassLoader(new FilteredClassLoader("io.r2dbc.spi")) + .run((context) -> assertThat(context).doesNotHaveBean(ConnectionFactoryDecorator.class)); + } + + @Test + void shouldNotSupplyBeansIfR2dbcProxyIsNotOnClasspath() { + this.runner.withClassLoader(new FilteredClassLoader("io.r2dbc.proxy")) + .run((context) -> assertThat(context).doesNotHaveBean(ConnectionFactoryDecorator.class)); + } + + @Test + void shouldApplyCustomizers() { + this.runner.withUserConfiguration(ProxyConnectionFactoryCustomizerConfig.class).run((context) -> { + ConnectionFactoryDecorator decorator = context.getBean(ConnectionFactoryDecorator.class); + ConnectionFactory connectionFactory = ConnectionFactoryBuilder + .withUrl("r2dbc:h2:mem:///" + UUID.randomUUID()) + .build(); + decorator.decorate(connectionFactory); + assertThat(context.getBean(ProxyConnectionFactoryCustomizerConfig.class).called).containsExactly("first", + "second"); + }); + } + + @Configuration(proxyBeanMethods = false) + private static final class ProxyConnectionFactoryCustomizerConfig { + + private final List called = new ArrayList<>(); + + @Bean + @Order(1) + ProxyConnectionFactoryCustomizer first() { + return (builder) -> this.called.add("first"); + } + + @Bean + @Order(2) + ProxyConnectionFactoryCustomizer second() { + return (builder) -> this.called.add("second"); + } + + } + +} diff --git a/spring-boot-project/spring-boot/build.gradle b/spring-boot-project/spring-boot/build.gradle index 2de14ff476a..0e2f235e55b 100644 --- a/spring-boot-project/spring-boot/build.gradle +++ b/spring-boot-project/spring-boot/build.gradle @@ -35,7 +35,6 @@ dependencies { optional("io.projectreactor:reactor-tools") optional("io.projectreactor.netty:reactor-netty-http") optional("io.r2dbc:r2dbc-pool") - optional("io.r2dbc:r2dbc-proxy") optional("io.rsocket:rsocket-core") optional("io.rsocket:rsocket-transport-netty") optional("io.undertow:undertow-servlet")