diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java index 0feca7f3cdc3..a227d45bdca4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -26,7 +26,6 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.flywaydb.core.Flyway; @@ -34,7 +33,6 @@ import org.flywaydb.core.api.callback.Callback; import org.flywaydb.core.api.configuration.FluentConfiguration; import org.flywaydb.core.api.migration.JavaMigration; -import org.jooq.DSLContext; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -45,22 +43,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDataSourceCondition; -import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDslContextDependsOnPostProcessor; -import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayEntityManagerFactoryDependsOnPostProcessor; -import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayJdbcOperationsDependsOnPostProcessor; -import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayNamedParameterJdbcOperationsDependencyConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.boot.autoconfigure.jdbc.JdbcOperationsDependsOnPostProcessor; import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.NamedParameterJdbcOperationsDependsOnPostProcessor; -import org.springframework.boot.autoconfigure.jooq.DslContextDependsOnPostProcessor; -import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.boot.jdbc.init.DataSourceInitializationDependencyConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; @@ -68,12 +59,8 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.io.ResourceLoader; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.support.JdbcUtils; import org.springframework.jdbc.support.MetaDataAccessException; -import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -100,8 +87,7 @@ @ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true) @AutoConfigureAfter({ DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) -@Import({ FlywayEntityManagerFactoryDependsOnPostProcessor.class, FlywayJdbcOperationsDependsOnPostProcessor.class, - FlywayNamedParameterJdbcOperationsDependencyConfiguration.class, FlywayDslContextDependsOnPostProcessor.class }) +@Import(DataSourceInitializationDependencyConfigurer.class) public class FlywayAutoConfiguration { @Bean @@ -118,10 +104,6 @@ public FlywaySchemaManagementProvider flywayDefaultDdlModeProvider(ObjectProvide @Configuration(proxyBeanMethods = false) @ConditionalOnMissingBean(Flyway.class) @EnableConfigurationProperties({ DataSourceProperties.class, FlywayProperties.class }) - @Import({ FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor.class, - FlywayMigrationInitializerJdbcOperationsDependsOnPostProcessor.class, - FlywayMigrationInitializerNamedParameterJdbcOperationsDependsOnPostProcessor.class, - FlywayMigrationInitializerDslContextDependsOnPostProcessor.class }) public static class FlywayConfiguration { @Bean @@ -325,122 +307,6 @@ public FlywayMigrationInitializer flywayInitializer(Flyway flyway, } - /** - * Post processor to ensure that {@link EntityManagerFactory} beans depend on any - * {@link FlywayMigrationInitializer} beans. - */ - @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class) - @ConditionalOnBean(AbstractEntityManagerFactoryBean.class) - static class FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor - extends EntityManagerFactoryDependsOnPostProcessor { - - FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor() { - super(FlywayMigrationInitializer.class); - } - - } - - /** - * Post processor to ensure that {@link JdbcOperations} beans depend on any - * {@link FlywayMigrationInitializer} beans. - */ - @ConditionalOnClass(JdbcOperations.class) - @ConditionalOnBean(JdbcOperations.class) - static class FlywayMigrationInitializerJdbcOperationsDependsOnPostProcessor - extends JdbcOperationsDependsOnPostProcessor { - - FlywayMigrationInitializerJdbcOperationsDependsOnPostProcessor() { - super(FlywayMigrationInitializer.class); - } - - } - - /** - * Post processor to ensure that {@link NamedParameterJdbcOperations} beans depend on - * any {@link FlywayMigrationInitializer} beans. - */ - @ConditionalOnClass(NamedParameterJdbcOperations.class) - @ConditionalOnBean(NamedParameterJdbcOperations.class) - static class FlywayMigrationInitializerNamedParameterJdbcOperationsDependsOnPostProcessor - extends NamedParameterJdbcOperationsDependsOnPostProcessor { - - FlywayMigrationInitializerNamedParameterJdbcOperationsDependsOnPostProcessor() { - super(FlywayMigrationInitializer.class); - } - - } - - /** - * Post processor to ensure that {@link DSLContext} beans depend on any - * {@link FlywayMigrationInitializer} beans. - */ - @ConditionalOnClass(DSLContext.class) - @ConditionalOnBean(DSLContext.class) - static class FlywayMigrationInitializerDslContextDependsOnPostProcessor extends DslContextDependsOnPostProcessor { - - FlywayMigrationInitializerDslContextDependsOnPostProcessor() { - super(FlywayMigrationInitializer.class); - } - - } - - /** - * Post processor to ensure that {@link EntityManagerFactory} beans depend on any - * {@link Flyway} beans. - */ - @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class) - @ConditionalOnBean(AbstractEntityManagerFactoryBean.class) - static class FlywayEntityManagerFactoryDependsOnPostProcessor extends EntityManagerFactoryDependsOnPostProcessor { - - FlywayEntityManagerFactoryDependsOnPostProcessor() { - super(Flyway.class); - } - - } - - /** - * Post processor to ensure that {@link JdbcOperations} beans depend on any - * {@link Flyway} beans. - */ - @ConditionalOnClass(JdbcOperations.class) - @ConditionalOnBean(JdbcOperations.class) - static class FlywayJdbcOperationsDependsOnPostProcessor extends JdbcOperationsDependsOnPostProcessor { - - FlywayJdbcOperationsDependsOnPostProcessor() { - super(Flyway.class); - } - - } - - /** - * Post processor to ensure that {@link NamedParameterJdbcOperations} beans depend on - * any {@link Flyway} beans. - */ - @ConditionalOnClass(NamedParameterJdbcOperations.class) - @ConditionalOnBean(NamedParameterJdbcOperations.class) - protected static class FlywayNamedParameterJdbcOperationsDependencyConfiguration - extends NamedParameterJdbcOperationsDependsOnPostProcessor { - - public FlywayNamedParameterJdbcOperationsDependencyConfiguration() { - super(Flyway.class); - } - - } - - /** - * Post processor to ensure that {@link DSLContext} beans depend on any {@link Flyway} - * beans. - */ - @ConditionalOnClass(DSLContext.class) - @ConditionalOnBean(DSLContext.class) - protected static class FlywayDslContextDependsOnPostProcessor extends DslContextDependsOnPostProcessor { - - public FlywayDslContextDependsOnPostProcessor() { - super(Flyway.class); - } - - } - private static class LocationResolver { private static final String VENDOR_PLACEHOLDER = "{vendor}"; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayMigrationInitializerDataSourceInitializerDetector.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayMigrationInitializerDataSourceInitializerDetector.java new file mode 100644 index 000000000000..eab9c9da5462 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayMigrationInitializerDataSourceInitializerDetector.java @@ -0,0 +1,37 @@ +/* + * 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.flyway; + +import java.util.Collections; +import java.util.Set; + +import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDataSourceInitializerDetector; +import org.springframework.boot.jdbc.init.DataSourceInitializerDetector; + +/** + * A {@link DataSourceInitializerDetector} for {@link FlywayMigrationInitializer}. + * + * @author Andy Wilkinson + */ +class FlywayMigrationInitializerDataSourceInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector { + + @Override + protected Set> getDataSourceInitializerBeanTypes() { + return Collections.singleton(FlywayMigrationInitializer.class); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java index 2273d76a345f..2b9db06ba257 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java @@ -16,19 +16,13 @@ package org.springframework.boot.autoconfigure.jdbc; -import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; -import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor; +import org.springframework.boot.jdbc.init.DataSourceInitializationDependencyConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; /** * Configuration for {@link DataSource} initialization using DDL and DML scripts. @@ -37,63 +31,12 @@ */ @Configuration(proxyBeanMethods = false) @ConditionalOnSingleCandidate(DataSource.class) +@Import(DataSourceInitializationDependencyConfigurer.class) class DataSourceInitializationConfiguration { - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(prefix = "spring.datasource", name = "initialization-order", havingValue = "before-jpa", - matchIfMissing = true) - @Import({ DataSourceInitializationJdbcOperationsDependsOnPostProcessor.class, - DataSourceInitializationNamedParameterJdbcOperationsDependsOnPostProcessor.class, - DataSourceInitializationEntityManagerFactoryDependsOnPostProcessor.class }) - static class BeforeJpaDataSourceInitializationConfiguration { - - @Bean - DataSourceInitialization dataSourceInitialization(DataSource dataSource, DataSourceProperties properties) { - return new DataSourceInitialization(dataSource, properties); - } - - } - - /** - * Post processor to ensure that {@link EntityManagerFactory} beans depend on any - * {@link DataSourceInitialization} beans. - */ - @ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class }) - static class DataSourceInitializationEntityManagerFactoryDependsOnPostProcessor - extends EntityManagerFactoryDependsOnPostProcessor { - - DataSourceInitializationEntityManagerFactoryDependsOnPostProcessor() { - super(DataSourceInitialization.class); - } - - } - - /** - * Post processor to ensure that {@link JdbcOperations} beans depend on any - * {@link DataSourceInitialization} beans. - */ - @ConditionalOnClass(JdbcOperations.class) - static class DataSourceInitializationJdbcOperationsDependsOnPostProcessor - extends JdbcOperationsDependsOnPostProcessor { - - DataSourceInitializationJdbcOperationsDependsOnPostProcessor() { - super(DataSourceInitialization.class); - } - - } - - /** - * Post processor to ensure that {@link NamedParameterJdbcOperations} beans depend on - * any {@link DataSourceInitialization} beans. - */ - @ConditionalOnClass(NamedParameterJdbcOperations.class) - protected static class DataSourceInitializationNamedParameterJdbcOperationsDependsOnPostProcessor - extends NamedParameterJdbcOperationsDependsOnPostProcessor { - - public DataSourceInitializationNamedParameterJdbcOperationsDependsOnPostProcessor() { - super(DataSourceInitialization.class); - } - + @Bean + DataSourceInitialization dataSourceInitialization(DataSource dataSource, DataSourceProperties properties) { + return new DataSourceInitialization(dataSource, properties); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationDataSourceInitializerDetector.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationDataSourceInitializerDetector.java new file mode 100644 index 000000000000..90e48ff9a8bd --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationDataSourceInitializerDetector.java @@ -0,0 +1,37 @@ +/* + * 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.jdbc; + +import java.util.Collections; +import java.util.Set; + +import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDataSourceInitializerDetector; +import org.springframework.boot.jdbc.init.DataSourceInitializerDetector; + +/** + * A {@link DataSourceInitializerDetector} for {@link DataSourceInitialization}. + * + * @author Andy Wilkinson + */ +class DataSourceInitializationDataSourceInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector { + + @Override + protected Set> getDataSourceInitializerBeanTypes() { + return Collections.singleton(DataSourceInitialization.class); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcOperationsDependsOnPostProcessor.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcOperationsDependsOnPostProcessor.java index ef375dec4db9..947c2f4a8769 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcOperationsDependsOnPostProcessor.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcOperationsDependsOnPostProcessor.java @@ -19,6 +19,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor; +import org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector; import org.springframework.jdbc.core.JdbcOperations; /** @@ -32,7 +33,9 @@ * @author Andrii Hrytsiuk * @since 2.0.4 * @see BeanDefinition#setDependsOn(String[]) + * @deprecated since 2.5.0 in favor of {@link DependsOnDataSourceInitializationDetector} */ +@Deprecated public class JdbcOperationsDependsOnPostProcessor extends AbstractDependsOnBeanFactoryPostProcessor { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfiguration.java index 2fdb9af54256..57c36538c903 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfiguration.java @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.jdbc.init.DataSourceInitializationDependencyConfigurer; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.jdbc.core.JdbcTemplate; @@ -43,7 +44,8 @@ @ConditionalOnSingleCandidate(DataSource.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) @EnableConfigurationProperties(JdbcProperties.class) -@Import({ JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class }) +@Import({ DataSourceInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class, + NamedParameterJdbcTemplateConfiguration.class }) public class JdbcTemplateAutoConfiguration { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/NamedParameterJdbcOperationsDependsOnPostProcessor.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/NamedParameterJdbcOperationsDependsOnPostProcessor.java index d7174a61a20a..44d766c9e6bb 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/NamedParameterJdbcOperationsDependsOnPostProcessor.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/NamedParameterJdbcOperationsDependsOnPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. @@ -19,6 +19,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor; +import org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; /** @@ -30,7 +31,9 @@ * @author Andrii Hrytsiuk * @since 2.1.4 * @see BeanDefinition#setDependsOn(String[]) + * @deprecated since 2.5.0 in favor of {@link DependsOnDataSourceInitializationDetector} */ +@Deprecated public class NamedParameterJdbcOperationsDependsOnPostProcessor extends AbstractDependsOnBeanFactoryPostProcessor { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/DslContextDependsOnPostProcessor.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/DslContextDependsOnPostProcessor.java index d951fba6105a..2fee541a69e2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/DslContextDependsOnPostProcessor.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/DslContextDependsOnPostProcessor.java @@ -21,6 +21,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor; +import org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector; /** * {@link BeanFactoryPostProcessor} that can be used to dynamically declare that all @@ -29,7 +30,9 @@ * @author EddĂș MelĂ©ndez * @since 2.3.9 * @see BeanDefinition#setDependsOn(String[]) + * @deprecated since 2.5.0 in favor of {@link DependsOnDataSourceInitializationDetector} */ +@Deprecated public class DslContextDependsOnPostProcessor extends AbstractDependsOnBeanFactoryPostProcessor { /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java index fbaeeaa207d2..79a75930ee7e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java @@ -18,12 +18,10 @@ import java.util.function.Supplier; -import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import liquibase.change.DatabaseChange; import liquibase.integration.spring.SpringLiquibase; -import org.jooq.DSLContext; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; @@ -35,28 +33,17 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; -import org.springframework.boot.autoconfigure.jdbc.JdbcOperationsDependsOnPostProcessor; -import org.springframework.boot.autoconfigure.jdbc.NamedParameterJdbcOperationsDependsOnPostProcessor; -import org.springframework.boot.autoconfigure.jooq.DslContextDependsOnPostProcessor; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseDataSourceCondition; -import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseDslContextDependsOnPostProcessor; -import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseEntityManagerFactoryDependsOnPostProcessor; -import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseJdbcOperationsDependsOnPostProcessor; -import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseNamedParameterJdbcOperationsDependsOnPostProcessor; -import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jdbc.DatabaseDriver; +import org.springframework.boot.jdbc.init.DataSourceInitializationDependencyConfigurer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.datasource.SimpleDriverDataSource; -import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.util.StringUtils; /** @@ -79,10 +66,7 @@ @ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled", matchIfMissing = true) @Conditional(LiquibaseDataSourceCondition.class) @AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) -@Import({ LiquibaseEntityManagerFactoryDependsOnPostProcessor.class, - LiquibaseJdbcOperationsDependsOnPostProcessor.class, - LiquibaseNamedParameterJdbcOperationsDependsOnPostProcessor.class, - LiquibaseDslContextDependsOnPostProcessor.class }) +@Import(DataSourceInitializationDependencyConfigurer.class) public class LiquibaseAutoConfiguration { @Bean @@ -175,64 +159,6 @@ private String getProperty(Supplier property, Supplier defaultVa } - /** - * Post processor to ensure that {@link EntityManagerFactory} beans depend on any - * {@link SpringLiquibase} beans. - */ - @ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class) - @ConditionalOnBean(AbstractEntityManagerFactoryBean.class) - static class LiquibaseEntityManagerFactoryDependsOnPostProcessor - extends EntityManagerFactoryDependsOnPostProcessor { - - LiquibaseEntityManagerFactoryDependsOnPostProcessor() { - super(SpringLiquibase.class); - } - - } - - /** - * Additional configuration to ensure that {@link JdbcOperations} beans depend on any - * {@link SpringLiquibase} beans. - */ - @ConditionalOnClass(JdbcOperations.class) - @ConditionalOnBean(JdbcOperations.class) - static class LiquibaseJdbcOperationsDependsOnPostProcessor extends JdbcOperationsDependsOnPostProcessor { - - LiquibaseJdbcOperationsDependsOnPostProcessor() { - super(SpringLiquibase.class); - } - - } - - /** - * Post processor to ensure that {@link NamedParameterJdbcOperations} beans depend on - * any {@link SpringLiquibase} beans. - */ - @ConditionalOnClass(NamedParameterJdbcOperations.class) - @ConditionalOnBean(NamedParameterJdbcOperations.class) - static class LiquibaseNamedParameterJdbcOperationsDependsOnPostProcessor - extends NamedParameterJdbcOperationsDependsOnPostProcessor { - - LiquibaseNamedParameterJdbcOperationsDependsOnPostProcessor() { - super(SpringLiquibase.class); - } - - } - - /** - * Post processor to ensure that {@link DSLContext} beans depend on any - * {@link SpringLiquibase} beans. - */ - @ConditionalOnClass(DSLContext.class) - @ConditionalOnBean(DSLContext.class) - static class LiquibaseDslContextDependsOnPostProcessor extends DslContextDependsOnPostProcessor { - - LiquibaseDslContextDependsOnPostProcessor() { - super(SpringLiquibase.class); - } - - } - static final class LiquibaseDataSourceCondition extends AnyNestedCondition { LiquibaseDataSourceCondition() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java index e36a1b4fc650..d6532819e1b8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.java @@ -17,23 +17,16 @@ package org.springframework.boot.autoconfigure.orm.jpa; import javax.persistence.EntityManager; -import javax.sql.DataSource; -import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.spi.SessionImplementor; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.core.io.ResourceLoader; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; /** @@ -52,18 +45,4 @@ @Import(HibernateJpaConfiguration.class) public class HibernateJpaAutoConfiguration { - @ConditionalOnProperty(prefix = "spring.datasource", name = "initialization-order", havingValue = "after-jpa", - matchIfMissing = false) - static class AfterJpaDataSourceInitializationConfiguration { - - @Bean - HibernatePropertiesCustomizer dataSourceInitializationCustomizer(DataSource dataSource, - DataSourceProperties properties, ResourceLoader resourceLoader) { - return (hibernateProperties) -> hibernateProperties.put(AvailableSettings.SCHEMA_MANAGEMENT_TOOL, - new SpringBootSchemaManagementTool( - new DataSourceInitializer(dataSource, properties, resourceLoader))); - } - - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/SpringBootSchemaCreator.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/SpringBootSchemaCreator.java deleted file mode 100644 index 29bef57c39f9..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/SpringBootSchemaCreator.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.orm.jpa; - -import org.hibernate.boot.Metadata; -import org.hibernate.internal.CoreLogging; -import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.resource.transaction.spi.DdlTransactionIsolator; -import org.hibernate.tool.schema.TargetType; -import org.hibernate.tool.schema.internal.HibernateSchemaManagementTool; -import org.hibernate.tool.schema.internal.exec.GenerationTarget; -import org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase; -import org.hibernate.tool.schema.internal.exec.JdbcContext; -import org.hibernate.tool.schema.spi.ExecutionOptions; -import org.hibernate.tool.schema.spi.SchemaCreator; -import org.hibernate.tool.schema.spi.SourceDescriptor; -import org.hibernate.tool.schema.spi.TargetDescriptor; - -import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer; - -/** - * Spring Boot {@link SchemaCreator}. - * - * @author Phillip Webb - * @author Andy Wilkinson - */ -class SpringBootSchemaCreator implements SchemaCreator { - - private static final CoreMessageLogger log = CoreLogging.messageLogger(SpringBootSchemaCreator.class); - - private final HibernateSchemaManagementTool tool; - - private final DataSourceInitializer dataSourceInitializer; - - private final SchemaCreator creator; - - SpringBootSchemaCreator(HibernateSchemaManagementTool tool, SchemaCreator creator, - DataSourceInitializer dataSourceInitializer) { - this.tool = tool; - this.creator = creator; - this.dataSourceInitializer = dataSourceInitializer; - } - - @Override - public void doCreation(Metadata metadata, ExecutionOptions options, SourceDescriptor sourceDescriptor, - TargetDescriptor targetDescriptor) { - if (!targetDescriptor.getTargetTypes().contains(TargetType.DATABASE)) { - this.creator.doCreation(metadata, options, sourceDescriptor, targetDescriptor); - return; - } - GenerationTarget databaseTarget = getDatabaseTarget(options, targetDescriptor); - databaseTarget.prepare(); - try { - this.creator.doCreation(metadata, options, sourceDescriptor, targetDescriptor); - this.dataSourceInitializer.initializeDataSource(); - } - finally { - try { - databaseTarget.release(); - } - catch (Exception ex) { - log.debugf("Problem releasing GenerationTarget [%s] : %s", databaseTarget, ex.getMessage()); - } - } - } - - private GenerationTarget getDatabaseTarget(ExecutionOptions options, TargetDescriptor targetDescriptor) { - JdbcContext jdbcContext = this.tool.resolveJdbcContext(options.getConfigurationValues()); - DdlTransactionIsolator ddlTransactionIsolator = this.tool.getDdlTransactionIsolator(jdbcContext); - return new GenerationTargetToDatabase(ddlTransactionIsolator); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/SpringBootSchemaManagementTool.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/SpringBootSchemaManagementTool.java deleted file mode 100644 index 4da61af3d109..000000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/orm/jpa/SpringBootSchemaManagementTool.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.orm.jpa; - -import java.util.Map; - -import org.hibernate.tool.schema.internal.HibernateSchemaManagementTool; -import org.hibernate.tool.schema.spi.SchemaCreator; -import org.hibernate.tool.schema.spi.SchemaManagementTool; - -import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer; - -/** - * Spring Boot {@link SchemaManagementTool}. - * - * @author Phillip Webb - * @author Andy Wilkinson - */ -class SpringBootSchemaManagementTool extends HibernateSchemaManagementTool { - - private final DataSourceInitializer dataSourceInitializer; - - SpringBootSchemaManagementTool(DataSourceInitializer dataSourceInitializer) { - this.dataSourceInitializer = dataSourceInitializer; - } - - @Override - @SuppressWarnings("rawtypes") - public SchemaCreator getSchemaCreator(Map options) { - SchemaCreator creator = super.getSchemaCreator(options); - return new SpringBootSchemaCreator(this, creator, this.dataSourceInitializer); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index ba8527ebbae5..cb6fa6771e87 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -756,10 +756,6 @@ "name": "spring.datasource.initialization-mode", "defaultValue": "embedded" }, - { - "name": "spring.datasource.initialization-order", - "defaultValue": "before-jpa" - }, { "name": "spring.datasource.jmx-enabled", "type": "java.lang.Boolean", @@ -1905,17 +1901,6 @@ } ] }, - { - "name": "spring.datasource.initialization-order", - "values": [ - { - "value": "before-jpa" - }, - { - "value": "after-jpa" - } - ] - }, { "name": "spring.datasource.schema", "providers": [ 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 ed0fa2ed8547..53be75a815b1 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 @@ -166,3 +166,9 @@ org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProv org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\ org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider + +# DataSource initializer detectors +org.springframework.boot.jdbc.init.DataSourceInitializerDetector=\ +org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializerDataSourceInitializerDetector,\ +org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationDataSourceInitializerDetector + diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index fcd4dfe1a128..682e34cc07b1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -577,7 +577,7 @@ void whenFlywayIsAutoConfiguredThenJooqDslContextDependsOnFlywayBeans() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JooqConfiguration.class) .run((context) -> { BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("dslContext"); - assertThat(beanDefinition.getDependsOn()).containsExactly("flywayInitializer", "flyway"); + assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("flywayInitializer", "flyway"); }); } @@ -586,7 +586,8 @@ void whenCustomMigrationInitializerIsDefinedThenJooqDslContextDependsOnIt() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JooqConfiguration.class, CustomFlywayMigrationInitializer.class).run((context) -> { BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("dslContext"); - assertThat(beanDefinition.getDependsOn()).containsExactly("flywayMigrationInitializer", "flyway"); + assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("flywayMigrationInitializer", + "flyway"); }); } @@ -595,7 +596,7 @@ void whenCustomFlywayIsDefinedThenJooqDslContextDependsOnIt() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JooqConfiguration.class, CustomFlyway.class).run((context) -> { BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("dslContext"); - assertThat(beanDefinition.getDependsOn()).containsExactly("customFlyway"); + assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("customFlyway"); }); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java index f1612febef0a..e0688ed1ab73 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java @@ -43,6 +43,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; +import org.springframework.boot.jdbc.init.DependsOnDataSourceInitialization; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index 09be7865e77d..0416b8b64c66 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -60,6 +60,7 @@ import org.springframework.boot.autoconfigure.orm.jpa.mapping.NonAnnotatedEntity; import org.springframework.boot.autoconfigure.orm.jpa.test.City; import org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration; +import org.springframework.boot.jdbc.init.DependsOnDataSourceInitialization; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy; import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform; @@ -123,8 +124,7 @@ void testDataScriptRunsEarly() { contextRunner().withUserConfiguration(TestInitializedJpaConfiguration.class) .withClassLoader(new HideDataScriptClassLoader()) .withPropertyValues("spring.jpa.show-sql=true", "spring.jpa.hibernate.ddl-auto:create-drop", - "spring.datasource.data:classpath:/city.sql", - "spring.datasource.initialization-order=after-jpa") + "spring.datasource.data:classpath:/city.sql", "spring.jpa.defer-datasource-initialization=true") .run((context) -> assertThat(context.getBean(TestInitializedJpaConfiguration.class).called).isTrue()); } @@ -287,7 +287,7 @@ void customResourceMapping() { contextRunner().withClassLoader(new HideDataScriptClassLoader()) .withPropertyValues("spring.datasource.data:classpath:/db/non-annotated-data.sql", "spring.jpa.mapping-resources=META-INF/mappings/non-annotated.xml", - "spring.datasource.initialization-order=after-jpa") + "spring.jpa.defer-datasource-initialization=true") .run((context) -> { EntityManager em = context.getBean(EntityManagerFactory.class).createEntityManager(); NonAnnotatedEntity found = em.find(NonAnnotatedEntity.class, 2000L); @@ -360,8 +360,7 @@ void eventListenerCanBeRegisteredAsBeans() { .withUserConfiguration(TestInitializedJpaConfiguration.class) .withClassLoader(new HideDataScriptClassLoader()) .withPropertyValues("spring.jpa.show-sql=true", "spring.jpa.hibernate.ddl-auto:create-drop", - "spring.datasource.data:classpath:/city.sql", - "spring.datasource.initialization-order=after-jpa") + "spring.datasource.data:classpath:/city.sql", "spring.jpa.defer-datasource-initialization=true") .run((context) -> { // See CityListener assertThat(context).hasSingleBean(City.class); @@ -418,6 +417,7 @@ void whenLocalContainerEntityManagerFactoryBeanHasNoJpaVendorAdapterAutoConfigur @Configuration(proxyBeanMethods = false) @TestAutoConfigurationPackage(City.class) + @DependsOnDataSourceInitialization static class TestInitializedJpaConfiguration { private boolean called; diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc index 1f666d0cf7a6..3fb0fc50997d 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc @@ -2030,11 +2030,6 @@ In addition, Spring Boot processes the `schema-$\{platform}.sql` and `data-$\{pl This allows you to switch to database-specific scripts if necessary. For example, you might choose to set it to the vendor name of the database (`hsqldb`, `h2`, `oracle`, `mysql`, `postgresql`, and so on). -By default, script-based `DataSource` initialization is performed before any JPA `EntityManagerFactory` beans are created. -`schema.sql` can be used to create the schema for JPA-managed entities and `data.sql` can be used to populate it. -If you want script-based `DataSource` initialization to be performed after the `EntityManagerFactory`, set `spring.datasource.initialization-order` to `after-jpa`. -`schema.sql` can then be used to make additions to any schema creation performed by Hibernate and `data.sql` can be used to populate it. - [NOTE] ==== When only basic SQL scripts are used, Spring Boot automatically creates the schema of an embedded `DataSource`. @@ -2046,13 +2041,12 @@ For instance, if you want to always initialize the `DataSource` regardless of it spring.datasource.initialization-mode=always ---- -In a JPA-based app, you can choose to let Hibernate create the schema or use `schema.sql`, but you cannot do both. -Make sure to disable `spring.jpa.hibernate.ddl-auto` if you use `schema.sql`. - -[indent=0,subs="verbatim,quotes,attributes"] ----- - spring.jpa.hibernate.ddl-auto=none ----- +By default, script-based `DataSource` initialization is performed before any JPA `EntityManagerFactory` beans are created. +`schema.sql` can be used to create the schema for JPA-managed entities and `data.sql` can be used to populate it. +We do not recommend using multiple data source initialization technologies. +However, if you want script-based `DataSource` initialization to be able to build upon the schema creation performed by Hibernate, set configprop:spring.jpa.defer-datasource-initialization[] to `true`. +This will defer data source initialization until after any `EntityManagerFactory` beans have been created and initialized. +`schema.sql` can then be used to make additions to any schema creation performed by Hibernate and `data.sql` can be used to populate it. If you are using a <>, like Flyway or Liquibase, you should use them alone to create and initialize the schema. Using the basic `schema.sql` and `data.sql` scripts alongside Flyway or Liquibase is not recommended and support will be removed in a future release. @@ -2200,6 +2194,47 @@ See {spring-boot-autoconfigure-module-code}/liquibase/LiquibaseProperties.java[` +[[howto-initialize-a-database-configuring-dependencies]] +=== Depend Upon an Initialized DataSource +DataSource initialization is performed while the application is starting up as part of application context refresh. +To allow an initialized database to be accessed during startup, beans that act as DataSource initializers and beans that +require that DataSource to have been initialized are detected automatically. +Beans whose initialization depends upon the DataSource having been initialized are configured to depend upon those that initialize it. +If, during startup, your application tries to access the database and it has not been initialized, you can configure additional detection of beans that initialize the DataSource and require the DataSource to have been initialized. + + + +[[howto-initialize-a-database-configuring-dependencies-initializer-detection]] +==== Detect a DataSource Initializer +Spring Boot will automatically detect beans of the following types that initialize a `DataSource`: + +- `DataSourceInitialization` +- `EntityManagerFactory` +- `Flyway` +- `FlywayMigrationInitializer` +- `SpringLiquibase` + +If you are using a third-party starter for a DataSource initialization library, it may provide a detector such that beans of other types are also detected automatically. +To have other beans be detected, register an implementation of `DataSourceInitializerDetector` in `META-INF/spring-factories`. + + + +[[howto-initialize-a-database-configuring-dependencies-depends-on-initialization-detection]] +==== Detect a Bean That Depends On DataSource Initialization +Spring Boot will automatically detect beans of the following types that depends upon `DataSource` initialization: + +- `AbstractEntityManagerFactoryBean` (unless configprop:spring.jpa.defer-datasource-initialization[] is set to `true`) +- `DSLContext` (jOOQ) +- `EntityManagerFactory` (unless configprop:spring.jpa.defer-datasource-initialization[] is set to `true`) +- `JdbcOperations` +- `NamedParameterJdbcOperations` + +If you are using a third-party starter data access library, it may provide a detector such that beans of other types are also detected automatically. +To have other beans be detected, register an implementation of `DependsOnDataSourceInitializationDetector` in `META-INF/spring-factories`. +Alternatively, annotate the bean's class or its `@Bean` method with `@DependsOnDataSourceInitializationDetector`. + + + [[howto-messaging]] == Messaging Spring Boot offers a number of starters that include messaging. diff --git a/spring-boot-project/spring-boot/build.gradle b/spring-boot-project/spring-boot/build.gradle index 9a644b6823d4..4ef5f337e56d 100644 --- a/spring-boot-project/spring-boot/build.gradle +++ b/spring-boot-project/spring-boot/build.gradle @@ -51,10 +51,12 @@ dependencies { optional("org.eclipse.jetty:jetty-webapp") optional("org.eclipse.jetty:jetty-alpn-conscrypt-server") optional("org.eclipse.jetty.http2:http2-server") + optional("org.flywaydb:flyway-core") optional("org.hamcrest:hamcrest-library") optional("org.hibernate:hibernate-core") optional("org.hibernate.validator:hibernate-validator") optional("org.jboss:jboss-transaction-spi") + optional("org.jooq:jooq") optional("org.liquibase:liquibase-core") optional("org.slf4j:jul-to-slf4j") optional("org.slf4j:slf4j-api") diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/flyway/FlywayDataSourceInitializerDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/flyway/FlywayDataSourceInitializerDetector.java new file mode 100644 index 000000000000..90f4d343a5d7 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/flyway/FlywayDataSourceInitializerDetector.java @@ -0,0 +1,39 @@ +/* + * 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.flyway; + +import java.util.Collections; +import java.util.Set; + +import org.flywaydb.core.Flyway; + +import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDataSourceInitializerDetector; +import org.springframework.boot.jdbc.init.DataSourceInitializerDetector; + +/** + * A {@link DataSourceInitializerDetector} for {@link Flyway}. + * + * @author Andy Wilkinson + */ +class FlywayDataSourceInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector { + + @Override + protected Set> getDataSourceInitializerBeanTypes() { + return Collections.singleton(Flyway.class); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/flyway/package-info.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/flyway/package-info.java new file mode 100644 index 000000000000..956b62a6e5cf --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/flyway/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Custom support for Flyway database migration. + */ +package org.springframework.boot.flyway; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/SpringJdbcDependsOnDataSourceInitializationDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/SpringJdbcDependsOnDataSourceInitializationDetector.java new file mode 100644 index 000000000000..c4a73d28cdb3 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/SpringJdbcDependsOnDataSourceInitializationDetector.java @@ -0,0 +1,41 @@ +/* + * 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.jdbc; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDependsOnDataSourceInitializationDetector; +import org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; + +/** + * {@link DependsOnDataSourceInitializationDetector} for Spring Framework's JDBC support. + * + * @author Andy Wilkinson + */ +class SpringJdbcDependsOnDataSourceInitializationDetector + extends AbstractBeansOfTypeDependsOnDataSourceInitializationDetector { + + @Override + protected Set> getDependsOnDataSourceInitializationBeanTypes() { + return new HashSet<>(Arrays.asList(JdbcOperations.class, NamedParameterJdbcOperations.class)); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/AbstractBeansOfTypeDataSourceInitializerDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/AbstractBeansOfTypeDataSourceInitializerDetector.java new file mode 100644 index 000000000000..09cf5da88ef8 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/AbstractBeansOfTypeDataSourceInitializerDetector.java @@ -0,0 +1,52 @@ +/* + * 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.jdbc.init; + +import java.util.Collections; +import java.util.Set; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * Base class for {@link DataSourceInitializerDetector DataSourceInitializerDetectors} + * that detect {@link DataSource} beans by type. + * + * @author Andy Wilkinson + * @since 2.5.0 + */ +public abstract class AbstractBeansOfTypeDataSourceInitializerDetector implements DataSourceInitializerDetector { + + /** + * Returns the bean types that should be detected as being data source initializers. + * @return the data source initializer bean types + */ + protected abstract Set> getDataSourceInitializerBeanTypes(); + + @Override + public Set detect(ConfigurableListableBeanFactory beanFactory) { + try { + Set> types = getDataSourceInitializerBeanTypes(); + return new BeansOfTypeDetector(types).detect(beanFactory); + } + catch (Throwable ex) { + return Collections.emptySet(); + } + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/AbstractBeansOfTypeDependsOnDataSourceInitializationDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/AbstractBeansOfTypeDependsOnDataSourceInitializationDetector.java new file mode 100644 index 000000000000..2e6c2dee1eba --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/AbstractBeansOfTypeDependsOnDataSourceInitializationDetector.java @@ -0,0 +1,53 @@ +/* + * 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.jdbc.init; + +import java.util.Collections; +import java.util.Set; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * Base class for {@link DependsOnDataSourceInitializationDetector + * InitializedDataSourceDependentDetector} that detect by type beans that depend upon data + * source initialization. + * + * @author Andy Wilkinson + * @since 2.5.0 + */ +public abstract class AbstractBeansOfTypeDependsOnDataSourceInitializationDetector + implements DependsOnDataSourceInitializationDetector { + + /** + * Returns the bean types that should be detected as depending on data source + * initialization. + * @return the data source initialization dependent bean types + */ + protected abstract Set> getDependsOnDataSourceInitializationBeanTypes(); + + @Override + public Set detect(ConfigurableListableBeanFactory beanFactory) { + try { + Set> types = getDependsOnDataSourceInitializationBeanTypes(); + return new BeansOfTypeDetector(types).detect(beanFactory); + } + catch (Throwable ex) { + return Collections.emptySet(); + } + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/AnnotationDependsOnDataSourceInitializationDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/AnnotationDependsOnDataSourceInitializationDetector.java new file mode 100644 index 000000000000..4b6ed26a49c5 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/AnnotationDependsOnDataSourceInitializationDetector.java @@ -0,0 +1,43 @@ +/* + * 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.jdbc.init; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * {@link DependsOnDataSourceInitializationDetector} that detects beans annotated with + * {@link DependsOnDataSourceInitialization}. + * + * @author Andy Wilkinson + */ +class AnnotationDependsOnDataSourceInitializationDetector implements DependsOnDataSourceInitializationDetector { + + @Override + public Set detect(ConfigurableListableBeanFactory beanFactory) { + Set dependentBeans = new HashSet<>(); + for (String beanName : beanFactory.getBeanDefinitionNames()) { + if (beanFactory.findAnnotationOnBean(beanName, DependsOnDataSourceInitialization.class) != null) { + dependentBeans.add(beanName); + } + } + return dependentBeans; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/BeansOfTypeDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/BeansOfTypeDetector.java new file mode 100644 index 000000000000..5fb442f9fe6c --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/BeansOfTypeDetector.java @@ -0,0 +1,53 @@ +/* + * 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.jdbc.init; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.ListableBeanFactory; + +/** + * Helper class for detecting beans of particular types in a bean factory. + * + * @author Andy Wilkinson + */ +class BeansOfTypeDetector { + + private final Set> types; + + BeansOfTypeDetector(Set> types) { + this.types = types; + } + + Set detect(ListableBeanFactory beanFactory) { + Set beanNames = new HashSet<>(); + for (Class type : this.types) { + try { + String[] names = beanFactory.getBeanNamesForType(type, true, false); + Arrays.stream(names).map(BeanFactoryUtils::transformedBeanName).forEach(beanNames::add); + } + catch (Throwable ex) { + // Continue + } + } + return beanNames; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceInitializationDependencyConfigurer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceInitializationDependencyConfigurer.java new file mode 100644 index 000000000000..444c0b6dcf69 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceInitializationDependencyConfigurer.java @@ -0,0 +1,157 @@ +/* + * 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.jdbc.init; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.boot.util.Instantiator; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.env.Environment; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.util.StringUtils; + +/** + * Configures beans that depend upon DataSource initialization with + * {@link BeanDefinition#getDependsOn()} dependencies upon beans that perform + * {@link DataSource} initialization. Intended for {@link Import import} in configuration + * classes that define {@code DataSource} initialization beans or that define beans that + * require DataSource initialization to have completed before they are initialized. + *

+ * Beans that initialize a {@link DataSource} are identified by + * {@link DataSourceInitializerDetector DataSourceInitializerDetectors}. Beans that depend + * upon DataSource initialization are identified by + * {@link DependsOnDataSourceInitializationDetector + * DependsOnDataSourceInitializationDetectors}. + * + * @author Andy Wilkinson + * @since 2.5.0 + * @see DataSourceInitializerDetector + * @see DependsOnDataSourceInitializationDetector + * @see DependsOnDataSourceInitialization + */ +public class DataSourceInitializationDependencyConfigurer implements ImportBeanDefinitionRegistrar { + + private final Environment environment; + + DataSourceInitializationDependencyConfigurer(Environment environment) { + this.environment = environment; + } + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + if (registry.containsBeanDefinition(DependsOnDataSourceInitializationPostProcessor.class.getName())) { + return; + } + registry.registerBeanDefinition(DependsOnDataSourceInitializationPostProcessor.class.getName(), + BeanDefinitionBuilder + .genericBeanDefinition(DependsOnDataSourceInitializationPostProcessor.class, + () -> new DependsOnDataSourceInitializationPostProcessor(this.environment)) + .getBeanDefinition()); + } + + static class DependsOnDataSourceInitializationPostProcessor implements BeanFactoryPostProcessor { + + private final Environment environment; + + DependsOnDataSourceInitializationPostProcessor(Environment environment) { + this.environment = environment; + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + Set detectedDataSourceInitializers = detectDataSourceInitializers(beanFactory); + for (String dependentDefinitionName : detectDependsOnDataSourceInitialization(beanFactory, + this.environment)) { + BeanDefinition definition = getBeanDefinition(dependentDefinitionName, beanFactory); + String[] dependencies = definition.getDependsOn(); + for (String dependencyName : detectedDataSourceInitializers) { + dependencies = StringUtils.addStringToArray(dependencies, dependencyName); + } + definition.setDependsOn(dependencies); + } + } + + private Set detectDataSourceInitializers(ConfigurableListableBeanFactory beanFactory) { + List detectors = instantiateDetectors(beanFactory, this.environment, + DataSourceInitializerDetector.class); + Set detected = new HashSet<>(); + for (DataSourceInitializerDetector detector : detectors) { + for (String initializerName : detector.detect(beanFactory)) { + detected.add(initializerName); + beanFactory.getBeanDefinition(initializerName) + .setAttribute(DataSourceInitializerDetector.class.getName(), detector.getClass().getName()); + } + } + detected = Collections.unmodifiableSet(detected); + for (DataSourceInitializerDetector detector : detectors) { + detector.detectionComplete(beanFactory, detected); + } + return detected; + } + + private Collection detectDependsOnDataSourceInitialization(ConfigurableListableBeanFactory beanFactory, + Environment environment) { + List detectors = instantiateDetectors(beanFactory, environment, + DependsOnDataSourceInitializationDetector.class); + Set dependentUponDataSourceInitialization = new HashSet<>(); + for (DependsOnDataSourceInitializationDetector detector : detectors) { + dependentUponDataSourceInitialization.addAll(detector.detect(beanFactory)); + } + return dependentUponDataSourceInitialization; + } + + private List instantiateDetectors(ConfigurableListableBeanFactory beanFactory, Environment environment, + Class detectorType) { + List detectorNames = SpringFactoriesLoader.loadFactoryNames(detectorType, + beanFactory.getBeanClassLoader()); + Instantiator instantiator = new Instantiator<>(detectorType, + (availableParameters) -> availableParameters.add(Environment.class, environment)); + List detectors = instantiator.instantiate(detectorNames); + return detectors; + } + + private static BeanDefinition getBeanDefinition(String beanName, ConfigurableListableBeanFactory beanFactory) { + try { + return beanFactory.getBeanDefinition(beanName); + } + catch (NoSuchBeanDefinitionException ex) { + BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory(); + if (parentBeanFactory instanceof ConfigurableListableBeanFactory) { + return getBeanDefinition(beanName, (ConfigurableListableBeanFactory) parentBeanFactory); + } + throw ex; + } + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceInitializerDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceInitializerDetector.java new file mode 100644 index 000000000000..6c6b781e041e --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceInitializerDetector.java @@ -0,0 +1,55 @@ +/* + * 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.jdbc.init; + +import java.util.Set; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * Detects beans that initialize a {@link DataSource}. Implementations should be + * registered in {@code META-INF/spring.factories} under the key + * {@code org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector}. + * + * @author Andy Wilkinson + * @since 2.5.0 + */ +public interface DataSourceInitializerDetector { + + /** + * Detect beans defined in the given {@code beanFactory} that initialize a + * {@link DataSource}. + * @param beanFactory bean factory to examine + * @return names of the detected {@code DataSource} initializer beans, or an empty set + * if none were detected. + */ + Set detect(ConfigurableListableBeanFactory beanFactory); + + /** + * Callback indicating that all known {@code DataSourceInitializerDetectors} have been + * called and detection of beans that initialize a {@link DataSource} is complete. + * @param beanFactory bean factory that was examined + * @param dataSourceInitializerNames names of the {@code DataSource} initializer beans + * detected by all known detectors + */ + default void detectionComplete(ConfigurableListableBeanFactory beanFactory, + Set dataSourceInitializerNames) { + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DependsOnDataSourceInitialization.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DependsOnDataSourceInitialization.java similarity index 70% rename from spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DependsOnDataSourceInitialization.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DependsOnDataSourceInitialization.java index 81f7a7157bbb..34c1dda720d9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DependsOnDataSourceInitialization.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DependsOnDataSourceInitialization.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.jdbc; +package org.springframework.boot.jdbc.init; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -22,14 +22,12 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import javax.sql.DataSource; - -import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.Bean; /** - * Annotation used to indicate that a bean depends upon the auto-configured DataSource - * initialization having completed before the {@link DataSource} is injected. If the - * DataSource is not used during startup, no such dependency is required. + * Indicate that a bean's creation and initialization depends upon data source + * initialization having completed. May be used on a bean's class or its + * {@link Bean @Bean} definition. * * @author Andy Wilkinson * @since 2.5.0 @@ -37,7 +35,6 @@ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented -@DependsOn("dataSourceInitialization") public @interface DependsOnDataSourceInitialization { } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DependsOnDataSourceInitializationDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DependsOnDataSourceInitializationDetector.java new file mode 100644 index 000000000000..3102297f6fe2 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DependsOnDataSourceInitializationDetector.java @@ -0,0 +1,44 @@ +/* + * 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.jdbc.init; + +import java.util.Set; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +/** + * Detects beans that depend on {@link DataSource} initialization. Implementations should + * be registered in {@code META-INF/spring.factories} under the key + * {@code org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector}. + * + * @author Andy Wilkinson + * @since 2.5.0 + */ +public interface DependsOnDataSourceInitializationDetector { + + /** + * Detect beans defined in the given {@code beanFactory} that depend on + * {@link DataSource} initialization. If no beans are detected, an empty set is + * returned. + * @param beanFactory bean factory to examine + * @return names of any beans that depend upon {@code DataSource} initialization + */ + Set detect(ConfigurableListableBeanFactory beanFactory); + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/package-info.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/package-info.java new file mode 100644 index 000000000000..adb42378d483 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * General infrastructure for {@code DataSource} initialization. + */ +package org.springframework.boot.jdbc.init; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jooq/JooqDependsOnDataSourceInitializationDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jooq/JooqDependsOnDataSourceInitializationDetector.java new file mode 100644 index 000000000000..9b6e80112e8f --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jooq/JooqDependsOnDataSourceInitializationDetector.java @@ -0,0 +1,40 @@ +/* + * 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.jooq; + +import java.util.Collections; +import java.util.Set; + +import org.jooq.DSLContext; + +import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDependsOnDataSourceInitializationDetector; +import org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector; + +/** + * {@link DependsOnDataSourceInitializationDetector} for jOOQ. + * + * @author Andy Wilkinson + */ +class JooqDependsOnDataSourceInitializationDetector + extends AbstractBeansOfTypeDependsOnDataSourceInitializationDetector { + + @Override + protected Set> getDependsOnDataSourceInitializationBeanTypes() { + return Collections.singleton(DSLContext.class); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jooq/package-info.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jooq/package-info.java new file mode 100644 index 000000000000..21ef27f91c7e --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jooq/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * Support for jOOQ. + * + * @see org.springframework.boot.json.JsonParser + */ +package org.springframework.boot.jooq; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseDataSourceInitializerDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseDataSourceInitializerDetector.java new file mode 100644 index 000000000000..6db132e70105 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/liquibase/LiquibaseDataSourceInitializerDetector.java @@ -0,0 +1,39 @@ +/* + * 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.liquibase; + +import java.util.Collections; +import java.util.Set; + +import liquibase.integration.spring.SpringLiquibase; + +import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDataSourceInitializerDetector; +import org.springframework.boot.jdbc.init.DataSourceInitializerDetector; + +/** + * A {@link DataSourceInitializerDetector} for Liquibase. + * + * @author Andy Wilkinson + */ +class LiquibaseDataSourceInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector { + + @Override + protected Set> getDataSourceInitializerBeanTypes() { + return Collections.singleton(SpringLiquibase.class); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/orm/jpa/JpaDataSourceInitializerDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/orm/jpa/JpaDataSourceInitializerDetector.java new file mode 100644 index 000000000000..b444b1905812 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/orm/jpa/JpaDataSourceInitializerDetector.java @@ -0,0 +1,82 @@ +/* + * 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.orm.jpa; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import javax.persistence.EntityManagerFactory; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDataSourceInitializerDetector; +import org.springframework.boot.jdbc.init.DataSourceInitializerDetector; +import org.springframework.core.env.Environment; +import org.springframework.util.StringUtils; + +/** + * A {@link DataSourceInitializerDetector} for JPA. + * + * @author Andy Wilkinson + */ +class JpaDataSourceInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector { + + private final Environment environment; + + JpaDataSourceInitializerDetector(Environment environment) { + this.environment = environment; + } + + @Override + protected Set> getDataSourceInitializerBeanTypes() { + boolean deferred = this.environment.getProperty("spring.jpa.defer-datasource-initialization", boolean.class, + false); + return deferred ? Collections.singleton(EntityManagerFactory.class) : Collections.emptySet(); + } + + @Override + public void detectionComplete(ConfigurableListableBeanFactory beanFactory, Set dataSourceInitializerNames) { + configureOtherInitializersToDependOnJpaInitializers(beanFactory, dataSourceInitializerNames); + } + + private void configureOtherInitializersToDependOnJpaInitializers(ConfigurableListableBeanFactory beanFactory, + Set dataSourceInitializerNames) { + Set jpaInitializers = new HashSet<>(); + Set otherInitializers = new HashSet<>(dataSourceInitializerNames); + Iterator iterator = otherInitializers.iterator(); + while (iterator.hasNext()) { + String initializerName = iterator.next(); + BeanDefinition initializerDefinition = beanFactory.getBeanDefinition(initializerName); + if (JpaDataSourceInitializerDetector.class.getName() + .equals(initializerDefinition.getAttribute(DataSourceInitializerDetector.class.getName()))) { + iterator.remove(); + jpaInitializers.add(initializerName); + } + } + for (String otherInitializerName : otherInitializers) { + BeanDefinition definition = beanFactory.getBeanDefinition(otherInitializerName); + String[] dependencies = definition.getDependsOn(); + for (String dependencyName : jpaInitializers) { + dependencies = StringUtils.addStringToArray(dependencies, dependencyName); + } + definition.setDependsOn(dependencies); + } + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/orm/jpa/JpaDependsOnDataSourceInitializationDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/orm/jpa/JpaDependsOnDataSourceInitializationDetector.java new file mode 100644 index 000000000000..192c1164b076 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/orm/jpa/JpaDependsOnDataSourceInitializationDetector.java @@ -0,0 +1,53 @@ +/* + * 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.orm.jpa; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.EntityManagerFactory; + +import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDependsOnDataSourceInitializationDetector; +import org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector; +import org.springframework.core.env.Environment; +import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; + +/** + * {@link DependsOnDataSourceInitializationDetector} for JPA. + * + * @author Andy Wilkinson + */ +class JpaDependsOnDataSourceInitializationDetector + extends AbstractBeansOfTypeDependsOnDataSourceInitializationDetector { + + private final Environment environment; + + JpaDependsOnDataSourceInitializationDetector(Environment environment) { + this.environment = environment; + } + + @Override + protected Set> getDependsOnDataSourceInitializationBeanTypes() { + boolean postpone = this.environment.getProperty("spring.jpa.defer-datasource-initialization", boolean.class, + false); + return postpone ? Collections.emptySet() + : new HashSet<>(Arrays.asList(EntityManagerFactory.class, AbstractEntityManagerFactoryBean.class)); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 4a1219c97c21..09a36f9a7b57 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -316,6 +316,11 @@ "description": "Whether to enable configuration data processing legacy mode.", "defaultValue": false }, + { + "name": "spring.jpa.defer-datasource-initialization", + "type": "java.lang.Boolean", + "defaultValue": false + }, { "name": "spring.jta.atomikos.connectionfactory.borrow-connection-timeout", "type": "java.lang.Integer", diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories index 50265de54d7a..318b977b2b6d 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories @@ -78,3 +78,16 @@ org.springframework.boot.liquibase.LiquibaseChangelogMissingFailureAnalyzer # Failure Analysis Reporters org.springframework.boot.diagnostics.FailureAnalysisReporter=\ org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter + +# DataSource Initializer Detectors +org.springframework.boot.jdbc.init.DataSourceInitializerDetector=\ +org.springframework.boot.flyway.FlywayDataSourceInitializerDetector,\ +org.springframework.boot.liquibase.LiquibaseDataSourceInitializerDetector,\ +org.springframework.boot.orm.jpa.JpaDataSourceInitializerDetector + +# Depends On DataSource Initialization Detectors +org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector=\ +org.springframework.boot.jdbc.init.AnnotationDependsOnDataSourceInitializationDetector,\ +org.springframework.boot.jdbc.SpringJdbcDependsOnDataSourceInitializationDetector,\ +org.springframework.boot.jooq.JooqDependsOnDataSourceInitializationDetector,\ +org.springframework.boot.orm.jpa.JpaDependsOnDataSourceInitializationDetector \ No newline at end of file diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceInitializationDependencyConfigurerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceInitializationDependencyConfigurerTests.java new file mode 100644 index 000000000000..d9697544d275 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceInitializationDependencyConfigurerTests.java @@ -0,0 +1,232 @@ +/* + * 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.jdbc.init; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link DataSourceInitializationDependencyConfigurer}. + * + * @author Andy Wilkinson + */ +class DataSourceInitializationDependencyConfigurerTests { + + private final ConfigurableEnvironment environment = new MockEnvironment(); + + DataSourceInitializerDetector dataSourceInitializerDetector = MockedDataSourceInitializerDetector.mock; + + DependsOnDataSourceInitializationDetector dependsOnDataSourceInitializationDetector = MockedDependsOnDataSourceInitializationDetector.mock; + + @TempDir + File temp; + + @BeforeEach + void resetMocks() { + reset(MockedDataSourceInitializerDetector.mock, MockedDependsOnDataSourceInitializationDetector.mock); + } + + @Test + void whenDetectorsAreCreatedThenTheEnvironmentCanBeInjected() { + performDetection(Arrays.asList(ConstructorInjectionDataSourceInitializerDetector.class, + ConstructorInjectionDependsOnDataSourceInitializationDetector.class), (context) -> { + context.refresh(); + assertThat(ConstructorInjectionDataSourceInitializerDetector.environment) + .isEqualTo(this.environment); + assertThat(ConstructorInjectionDependsOnDataSourceInitializationDetector.environment) + .isEqualTo(this.environment); + }); + } + + @Test + void whenDependenciesAreConfiguredThenBeansThatDependUponDataSourceInitializationDependUponDetectedDataSourceInitializers() { + BeanDefinition alpha = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition(); + BeanDefinition bravo = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition(); + performDetection(Arrays.asList(MockedDataSourceInitializerDetector.class, + MockedDependsOnDataSourceInitializationDetector.class), (context) -> { + context.registerBeanDefinition("alpha", alpha); + context.registerBeanDefinition("bravo", bravo); + given(this.dataSourceInitializerDetector.detect(context.getBeanFactory())) + .willReturn(Collections.singleton("alpha")); + given(this.dependsOnDataSourceInitializationDetector.detect(context.getBeanFactory())) + .willReturn(Collections.singleton("bravo")); + context.refresh(); + assertThat(alpha.getAttribute(DataSourceInitializerDetector.class.getName())) + .isEqualTo(MockedDataSourceInitializerDetector.class.getName()); + assertThat(bravo.getAttribute(DataSourceInitializerDetector.class.getName())).isNull(); + verify(this.dataSourceInitializerDetector).detectionComplete(context.getBeanFactory(), + Collections.singleton("alpha")); + assertThat(bravo.getDependsOn()).containsExactly("alpha"); + }); + } + + private void performDetection(Collection> detectors, + Consumer contextCallback) { + DetectorSpringFactoriesClassLoader detectorSpringFactories = new DetectorSpringFactoriesClassLoader(this.temp); + detectors.forEach(detectorSpringFactories::register); + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { + context.setEnvironment(this.environment); + context.setClassLoader(detectorSpringFactories); + context.register(DependencyConfigurerConfiguration.class); + contextCallback.accept(context); + } + } + + @Configuration(proxyBeanMethods = false) + @Import(DataSourceInitializationDependencyConfigurer.class) + static class DependencyConfigurerConfiguration { + + } + + static class ConstructorInjectionDataSourceInitializerDetector implements DataSourceInitializerDetector { + + private static Environment environment; + + ConstructorInjectionDataSourceInitializerDetector(Environment environment) { + ConstructorInjectionDataSourceInitializerDetector.environment = environment; + } + + @Override + public Set detect(ConfigurableListableBeanFactory beanFactory) { + return Collections.emptySet(); + } + + } + + static class ConstructorInjectionDependsOnDataSourceInitializationDetector + implements DependsOnDataSourceInitializationDetector { + + private static Environment environment; + + ConstructorInjectionDependsOnDataSourceInitializationDetector(Environment environment) { + ConstructorInjectionDependsOnDataSourceInitializationDetector.environment = environment; + } + + @Override + public Set detect(ConfigurableListableBeanFactory beanFactory) { + return Collections.emptySet(); + } + + } + + static class MockedDataSourceInitializerDetector implements DataSourceInitializerDetector { + + private static DataSourceInitializerDetector mock = Mockito.mock(DataSourceInitializerDetector.class); + + @Override + public Set detect(ConfigurableListableBeanFactory beanFactory) { + return MockedDataSourceInitializerDetector.mock.detect(beanFactory); + } + + @Override + public void detectionComplete(ConfigurableListableBeanFactory beanFactory, + Set dataSourceInitializerNames) { + mock.detectionComplete(beanFactory, dataSourceInitializerNames); + } + + } + + static class MockedDependsOnDataSourceInitializationDetector implements DependsOnDataSourceInitializationDetector { + + private static DependsOnDataSourceInitializationDetector mock = Mockito + .mock(DependsOnDataSourceInitializationDetector.class); + + @Override + public Set detect(ConfigurableListableBeanFactory beanFactory) { + return MockedDependsOnDataSourceInitializationDetector.mock.detect(beanFactory); + } + + } + + static class DetectorSpringFactoriesClassLoader extends ClassLoader { + + private final Set> dataSourceInitializerDetectors = new HashSet<>(); + + private final Set> dependsOnDataSourceInitializationDetectors = new HashSet<>(); + + private final File temp; + + DetectorSpringFactoriesClassLoader(File temp) { + this.temp = temp; + } + + @SuppressWarnings("unchecked") + void register(Class detector) { + if (DataSourceInitializerDetector.class.isAssignableFrom(detector)) { + this.dataSourceInitializerDetectors.add((Class) detector); + } + else if (DependsOnDataSourceInitializationDetector.class.isAssignableFrom(detector)) { + this.dependsOnDataSourceInitializationDetectors + .add((Class) detector); + } + else { + throw new IllegalArgumentException("Unsupported detector type '" + detector.getName() + "'"); + } + } + + @Override + public Enumeration getResources(String name) throws IOException { + if (!"META-INF/spring.factories".equals(name)) { + return super.findResources(name); + } + Properties properties = new Properties(); + properties.put(DataSourceInitializerDetector.class.getName(), String.join(",", + this.dataSourceInitializerDetectors.stream().map(Class::getName).collect(Collectors.toList()))); + properties.put(DependsOnDataSourceInitializationDetector.class.getName(), + String.join(",", this.dependsOnDataSourceInitializationDetectors.stream().map(Class::getName) + .collect(Collectors.toList()))); + File springFactories = new File(this.temp, "spring.factories"); + try (FileWriter writer = new FileWriter(springFactories)) { + properties.store(writer, ""); + } + return Collections.enumeration(Collections.singleton(springFactories.toURI().toURL())); + } + + } + +} diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-test/src/test/resources/application.properties b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-test/src/test/resources/application.properties index 357931f55092..86c5c9beef14 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-test/src/test/resources/application.properties +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-test/src/test/resources/application.properties @@ -1,2 +1,2 @@ spring.test.mockmvc.print=none -spring.datasource.initialization-order=after-jpa +spring.jpa.defer-datasource-initialization=true