diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/NoConnectionFactoryBeanFailureAnalyzer.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/NoConnectionFactoryBeanFailureAnalyzer.java new file mode 100644 index 00000000000..48c94bf03ee --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/NoConnectionFactoryBeanFailureAnalyzer.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2021 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.spi.ConnectionFactory; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; +import org.springframework.boot.diagnostics.FailureAnalysis; +import org.springframework.core.Ordered; + +/** + * An {@link AbstractFailureAnalyzer} that produces failure analysis when a + * {@link NoSuchBeanDefinitionException} for a {@link ConnectionFactory} bean is thrown + * and there is no {@code META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider} + * resource on the classpath. + * + * @author Andy Wilkinson + */ +class NoConnectionFactoryBeanFailureAnalyzer extends AbstractFailureAnalyzer + implements Ordered { + + private final ClassLoader classLoader; + + NoConnectionFactoryBeanFailureAnalyzer() { + this(NoConnectionFactoryBeanFailureAnalyzer.class.getClassLoader()); + } + + NoConnectionFactoryBeanFailureAnalyzer(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + protected FailureAnalysis analyze(Throwable rootFailure, NoSuchBeanDefinitionException cause) { + if (ConnectionFactory.class.equals(cause.getBeanType()) + && this.classLoader.getResource("META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider") == null) { + return new FailureAnalysis("No R2DBC ConnectionFactory bean is available " + + "and no /META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider resource could be found.", + "Check that the R2DBC driver for your database is on the classpath.", cause); + } + return null; + } + + @Override + public int getOrder() { + return 0; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfiguration.java index 282655b1e88..1ef806e0dc5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfiguration.java @@ -21,6 +21,7 @@ import io.r2dbc.spi.ConnectionFactory; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnResource; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -36,6 +37,7 @@ import org.springframework.context.annotation.Import; */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(ConnectionFactory.class) +@ConditionalOnResource(resources = "classpath:META-INF/services/io.r2dbc.spi.ConnectionFactoryProvider") @AutoConfigureBefore({ DataSourceAutoConfiguration.class, SqlInitializationAutoConfiguration.class }) @EnableConfigurationProperties(R2dbcProperties.class) @Import({ ConnectionFactoryConfigurations.Pool.class, ConnectionFactoryConfigurations.Generic.class, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 7fba956a790..be659a21ec4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -165,6 +165,7 @@ org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyze org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\ org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\ org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\ +org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer,\ org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer # Template availability providers diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/NoConnectionFactoryBeanFailureAnalyzerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/NoConnectionFactoryBeanFailureAnalyzerTests.java new file mode 100644 index 00000000000..8407735a94a --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/NoConnectionFactoryBeanFailureAnalyzerTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2021 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.spi.ConnectionFactory; +import io.r2dbc.spi.ConnectionFactoryProvider; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.test.context.FilteredClassLoader; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link NoConnectionFactoryBeanFailureAnalyzer}. + * + * @author Andy Wilkinson + */ +class NoConnectionFactoryBeanFailureAnalyzerTests { + + @Test + void analyzeWhenNotNoSuchBeanDefinitionExceptionShouldReturnNull() { + assertThat(new NoConnectionFactoryBeanFailureAnalyzer().analyze(new Exception())).isNull(); + } + + @Test + void analyzeWhenNoSuchBeanDefinitionExceptionForDifferentTypeShouldReturnNull() { + assertThat( + new NoConnectionFactoryBeanFailureAnalyzer().analyze(new NoSuchBeanDefinitionException(String.class))) + .isNull(); + } + + @Test + void analyzeWhenNoSuchBeanDefinitionExceptionButProviderIsAvailableShouldReturnNull() { + assertThat(new NoConnectionFactoryBeanFailureAnalyzer() + .analyze(new NoSuchBeanDefinitionException(ConnectionFactory.class))).isNull(); + } + + @Test + void analyzeWhenNoSuchBeanDefinitionExceptionAndNoProviderShouldAnalyze() { + assertThat(new NoConnectionFactoryBeanFailureAnalyzer( + new FilteredClassLoader(("META-INF/services/" + ConnectionFactoryProvider.class.getName())::equals)) + .analyze(new NoSuchBeanDefinitionException(ConnectionFactory.class))).isNotNull(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationTests.java index f959a8e3aa0..2f3aa57be2b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/R2dbcAutoConfigurationTests.java @@ -28,6 +28,7 @@ import io.r2dbc.h2.H2ConnectionFactory; import io.r2dbc.pool.ConnectionPool; import io.r2dbc.pool.PoolMetrics; import io.r2dbc.spi.ConnectionFactory; +import io.r2dbc.spi.ConnectionFactoryProvider; import io.r2dbc.spi.Option; import io.r2dbc.spi.Wrapped; import org.assertj.core.api.InstanceOfAssertFactories; @@ -255,6 +256,14 @@ class R2dbcAutoConfigurationTests { }); } + @Test + void configureWithoutUrlAndNoConnectionFactoryProviderBacksOff() { + this.contextRunner + .withClassLoader(new FilteredClassLoader( + ("META-INF/services/" + ConnectionFactoryProvider.class.getName())::equals)) + .run((context) -> assertThat(context).doesNotHaveBean(R2dbcAutoConfiguration.class)); + } + @Test void configureWithDataSourceAutoConfigurationDoesNotCreateDataSource() { this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-jooq/build.gradle b/spring-boot-project/spring-boot-starters/spring-boot-starter-jooq/build.gradle index 734f55a8032..3282f290d34 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-jooq/build.gradle +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-jooq/build.gradle @@ -2,7 +2,7 @@ plugins { id "org.springframework.boot.starter" } -description = "Starter for using jOOQ to access SQL databases. An alternative to spring-boot-starter-data-jpa or spring-boot-starter-jdbc" +description = "Starter for using jOOQ to access SQL databases with JDBC. An alternative to spring-boot-starter-data-jpa or spring-boot-starter-jdbc" dependencies { api(project(":spring-boot-project:spring-boot-starters:spring-boot-starter-jdbc"))