Move DataSourceInitializer lifecycle
This commit separates the lifecycle of the datasource initialization from DataSourceInitializer itself. It also makes sure that a @Primary data source is no longer required. Closes gh-10502
This commit is contained in:
parent
89992ea4cc
commit
8656512bbe
|
|
@ -28,13 +28,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor.Registrar;
|
|
||||||
import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration;
|
import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||||
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
|
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Condition;
|
import org.springframework.context.annotation.Condition;
|
||||||
import org.springframework.context.annotation.ConditionContext;
|
import org.springframework.context.annotation.ConditionContext;
|
||||||
import org.springframework.context.annotation.Conditional;
|
import org.springframework.context.annotation.Conditional;
|
||||||
|
|
@ -56,16 +53,10 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
|
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
|
||||||
@EnableConfigurationProperties(DataSourceProperties.class)
|
@EnableConfigurationProperties(DataSourceProperties.class)
|
||||||
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
|
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
|
||||||
|
DataSourceInitializationConfiguration.class })
|
||||||
public class DataSourceAutoConfiguration {
|
public class DataSourceAutoConfiguration {
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
public DataSourceInitializer dataSourceInitializer(DataSourceProperties properties,
|
|
||||||
ApplicationContext applicationContext) {
|
|
||||||
return new DataSourceInitializer(properties, applicationContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Conditional(EmbeddedDatabaseCondition.class)
|
@Conditional(EmbeddedDatabaseCondition.class)
|
||||||
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
|
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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
|
||||||
|
*
|
||||||
|
* http://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 org.springframework.beans.factory.config.BeanDefinition;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
|
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||||
|
import org.springframework.core.type.AnnotationMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures DataSource initialization.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@Import({ DataSourceInitializerInvoker.class, DataSourceInitializationConfiguration.Registrar.class })
|
||||||
|
class DataSourceInitializationConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ImportBeanDefinitionRegistrar} to register the
|
||||||
|
* {@link DataSourceInitializerPostProcessor} without causing early bean instantiation
|
||||||
|
* issues.
|
||||||
|
*/
|
||||||
|
static class Registrar implements ImportBeanDefinitionRegistrar {
|
||||||
|
|
||||||
|
private static final String BEAN_NAME = "dataSourceInitializerPostProcessor";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
|
||||||
|
BeanDefinitionRegistry registry) {
|
||||||
|
if (!registry.containsBeanDefinition(BEAN_NAME)) {
|
||||||
|
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
|
||||||
|
beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class);
|
||||||
|
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||||
|
// We don't need this one to be post processed otherwise it can cause a
|
||||||
|
// cascade of bean instantiation that we would rather avoid.
|
||||||
|
beanDefinition.setSynthetic(true);
|
||||||
|
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,6 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
|
|
@ -28,104 +27,100 @@ import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.boot.context.config.ResourceNotFoundException;
|
import org.springframework.boot.context.config.ResourceNotFoundException;
|
||||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.core.io.DefaultResourceLoader;
|
||||||
import org.springframework.context.ApplicationListener;
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
import org.springframework.jdbc.config.SortedResourcesFactoryBean;
|
import org.springframework.jdbc.config.SortedResourcesFactoryBean;
|
||||||
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
|
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
|
||||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bean to handle {@link DataSource} initialization by running {@literal schema-*.sql} on
|
* Initialize a {@link DataSource} based on a matching {@link DataSourceProperties}
|
||||||
* {@link PostConstruct} and {@literal data-*.sql} SQL scripts on a
|
* config.
|
||||||
* {@link DataSourceInitializedEvent}.
|
|
||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Eddú Meléndez
|
* @author Eddú Meléndez
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @author Kazuki Shimizu
|
* @author Kazuki Shimizu
|
||||||
* @see DataSourceAutoConfiguration
|
|
||||||
*/
|
*/
|
||||||
class DataSourceInitializer implements ApplicationListener<DataSourceInitializedEvent> {
|
class DataSourceInitializer {
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(DataSourceInitializer.class);
|
private static final Log logger = LogFactory.getLog(DataSourceInitializer.class);
|
||||||
|
|
||||||
|
private final DataSource dataSource;
|
||||||
|
|
||||||
private final DataSourceProperties properties;
|
private final DataSourceProperties properties;
|
||||||
|
|
||||||
private final ApplicationContext applicationContext;
|
private final ResourceLoader resourceLoader;
|
||||||
|
|
||||||
private DataSource dataSource;
|
/**
|
||||||
|
* Create a new instance with the {@link DataSource} to initialize and its matching
|
||||||
private boolean initialized = false;
|
* {@link DataSourceProperties configuration}.
|
||||||
|
* @param dataSource the datasource to initialize
|
||||||
DataSourceInitializer(DataSourceProperties properties,
|
* @param properties the matching configuration
|
||||||
ApplicationContext applicationContext) {
|
* @param resourceLoader the resource loader to use (can be null)
|
||||||
|
*/
|
||||||
|
DataSourceInitializer(DataSource dataSource, DataSourceProperties properties,
|
||||||
|
ResourceLoader resourceLoader) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
this.applicationContext = applicationContext;
|
this.resourceLoader = (resourceLoader != null ? resourceLoader
|
||||||
|
: new DefaultResourceLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
/**
|
||||||
public void init() {
|
* Create a new instance with the {@link DataSource} to initialize and its matching
|
||||||
|
* {@link DataSourceProperties configuration}.
|
||||||
|
* @param dataSource the datasource to initialize
|
||||||
|
* @param properties the matching configuration
|
||||||
|
*/
|
||||||
|
DataSourceInitializer(DataSource dataSource, DataSourceProperties properties) {
|
||||||
|
this(dataSource, properties, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataSource getDataSource() {
|
||||||
|
return this.dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the schema if necessary.
|
||||||
|
* @return {@code true} if the schema was created
|
||||||
|
* @see DataSourceProperties#getSchema()
|
||||||
|
*/
|
||||||
|
public boolean createSchema() {
|
||||||
if (!this.properties.isInitialize()) {
|
if (!this.properties.isInitialize()) {
|
||||||
logger.debug("Initialization disabled (not running DDL scripts)");
|
logger.debug("Initialization disabled (not running DDL scripts)");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.applicationContext.getBeanNamesForType(DataSource.class, false,
|
|
||||||
false).length > 0) {
|
|
||||||
this.dataSource = this.applicationContext.getBean(DataSource.class);
|
|
||||||
}
|
|
||||||
if (this.dataSource == null) {
|
|
||||||
logger.debug("No DataSource found so not initializing");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
runSchemaScripts();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runSchemaScripts() {
|
|
||||||
List<Resource> scripts = getScripts("spring.datasource.schema",
|
List<Resource> scripts = getScripts("spring.datasource.schema",
|
||||||
this.properties.getSchema(), "schema");
|
this.properties.getSchema(), "schema");
|
||||||
if (!scripts.isEmpty()) {
|
if (!scripts.isEmpty()) {
|
||||||
String username = this.properties.getSchemaUsername();
|
String username = this.properties.getSchemaUsername();
|
||||||
String password = this.properties.getSchemaPassword();
|
String password = this.properties.getSchemaPassword();
|
||||||
runScripts(scripts, username, password);
|
runScripts(scripts, username, password);
|
||||||
try {
|
|
||||||
this.applicationContext
|
|
||||||
.publishEvent(new DataSourceInitializedEvent(this.dataSource));
|
|
||||||
// The listener might not be registered yet, so don't rely on it.
|
|
||||||
if (!this.initialized) {
|
|
||||||
runDataScripts();
|
|
||||||
this.initialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IllegalStateException ex) {
|
|
||||||
logger.warn("Could not send event to complete DataSource initialization ("
|
|
||||||
+ ex.getMessage() + ")");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return !scripts.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void onApplicationEvent(DataSourceInitializedEvent event) {
|
* Initialize the schema if necessary.
|
||||||
|
* @see DataSourceProperties#getData()
|
||||||
|
*/
|
||||||
|
public void initSchema() {
|
||||||
if (!this.properties.isInitialize()) {
|
if (!this.properties.isInitialize()) {
|
||||||
logger.debug("Initialization disabled (not running data scripts)");
|
logger.debug("Initialization disabled (not running data scripts)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// NOTE the event can happen more than once and
|
|
||||||
// the event datasource is not used here
|
|
||||||
if (!this.initialized) {
|
|
||||||
runDataScripts();
|
|
||||||
this.initialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void runDataScripts() {
|
|
||||||
List<Resource> scripts = getScripts("spring.datasource.data",
|
List<Resource> scripts = getScripts("spring.datasource.data",
|
||||||
this.properties.getData(), "data");
|
this.properties.getData(), "data");
|
||||||
String username = this.properties.getDataUsername();
|
if (!scripts.isEmpty()) {
|
||||||
String password = this.properties.getDataPassword();
|
String username = this.properties.getDataUsername();
|
||||||
runScripts(scripts, username, password);
|
String password = this.properties.getDataPassword();
|
||||||
|
runScripts(scripts, username, password);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Resource> getScripts(String propertyName, List<String> resources,
|
private List<Resource> getScripts(String propertyName, List<String> resources,
|
||||||
|
|
@ -159,7 +154,7 @@ class DataSourceInitializer implements ApplicationListener<DataSourceInitialized
|
||||||
private Resource[] doGetResources(String location) {
|
private Resource[] doGetResources(String location) {
|
||||||
try {
|
try {
|
||||||
SortedResourcesFactoryBean factory = new SortedResourcesFactoryBean(
|
SortedResourcesFactoryBean factory = new SortedResourcesFactoryBean(
|
||||||
this.applicationContext, Collections.singletonList(location));
|
this.resourceLoader, Collections.singletonList(location));
|
||||||
factory.afterPropertiesSet();
|
factory.afterPropertiesSet();
|
||||||
return factory.getObject();
|
return factory.getObject();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2017 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
|
||||||
|
*
|
||||||
|
* http://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 javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bean to handle {@link DataSource} initialization by running {@literal schema-*.sql} on
|
||||||
|
* {@link InitializingBean#afterPropertiesSet()} and {@literal data-*.sql} SQL scripts on
|
||||||
|
* a {@link DataSourceInitializedEvent}.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @see DataSourceAutoConfiguration
|
||||||
|
*/
|
||||||
|
class DataSourceInitializerInvoker
|
||||||
|
implements ApplicationListener<DataSourceInitializedEvent>, InitializingBean {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(DataSourceInitializerInvoker.class);
|
||||||
|
|
||||||
|
private final ObjectProvider<DataSource> dataSource;
|
||||||
|
|
||||||
|
private final DataSourceProperties properties;
|
||||||
|
|
||||||
|
private final ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
private DataSourceInitializer dataSourceInitializer;
|
||||||
|
|
||||||
|
private boolean initialized;
|
||||||
|
|
||||||
|
DataSourceInitializerInvoker(ObjectProvider<DataSource> dataSource,
|
||||||
|
DataSourceProperties properties,
|
||||||
|
ApplicationContext applicationContext) {
|
||||||
|
this.dataSource = dataSource;
|
||||||
|
this.properties = properties;
|
||||||
|
this.applicationContext = applicationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterPropertiesSet() {
|
||||||
|
DataSourceInitializer initializer = getDataSourceInitializer();
|
||||||
|
if (initializer != null) {
|
||||||
|
boolean schemaCreated = this.dataSourceInitializer.createSchema();
|
||||||
|
if (schemaCreated) {
|
||||||
|
try {
|
||||||
|
this.applicationContext
|
||||||
|
.publishEvent(new DataSourceInitializedEvent(
|
||||||
|
initializer.getDataSource()));
|
||||||
|
// The listener might not be registered yet, so don't rely on it.
|
||||||
|
if (!this.initialized) {
|
||||||
|
this.dataSourceInitializer.initSchema();
|
||||||
|
this.initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalStateException ex) {
|
||||||
|
logger.warn("Could not send event to complete DataSource initialization ("
|
||||||
|
+ ex.getMessage() + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(DataSourceInitializedEvent event) {
|
||||||
|
// NOTE the event can happen more than once and
|
||||||
|
// the event datasource is not used here
|
||||||
|
DataSourceInitializer initializer = getDataSourceInitializer();
|
||||||
|
if (!this.initialized && initializer != null) {
|
||||||
|
initializer.initSchema();
|
||||||
|
this.initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DataSourceInitializer getDataSourceInitializer() {
|
||||||
|
if (this.dataSourceInitializer == null) {
|
||||||
|
DataSource ds = this.dataSource.getIfUnique();
|
||||||
|
if (ds != null) {
|
||||||
|
this.dataSourceInitializer = new DataSourceInitializer(ds,
|
||||||
|
this.properties, this.applicationContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.dataSourceInitializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2014 the original author or authors.
|
* Copyright 2012-2017 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -21,17 +21,12 @@ import javax.sql.DataSource;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
|
||||||
import org.springframework.beans.factory.support.GenericBeanDefinition;
|
|
||||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.type.AnnotationMetadata;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link BeanPostProcessor} used to fire {@link DataSourceInitializedEvent}s. Should only
|
* {@link BeanPostProcessor} used to ensure that {@link DataSourceInitializer} is
|
||||||
* be registered via the inner {@link Registrar} class.
|
* initialized as soon as a {@link DataSource} is.
|
||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
* @since 1.1.2
|
* @since 1.1.2
|
||||||
|
|
@ -59,34 +54,9 @@ class DataSourceInitializerPostProcessor implements BeanPostProcessor, Ordered {
|
||||||
throws BeansException {
|
throws BeansException {
|
||||||
if (bean instanceof DataSource) {
|
if (bean instanceof DataSource) {
|
||||||
// force initialization of this bean as soon as we see a DataSource
|
// force initialization of this bean as soon as we see a DataSource
|
||||||
this.beanFactory.getBean(DataSourceInitializer.class);
|
this.beanFactory.getBean(DataSourceInitializerInvoker.class);
|
||||||
}
|
}
|
||||||
return bean;
|
return bean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link ImportBeanDefinitionRegistrar} to register the
|
|
||||||
* {@link DataSourceInitializerPostProcessor} without causing early bean instantiation
|
|
||||||
* issues.
|
|
||||||
*/
|
|
||||||
static class Registrar implements ImportBeanDefinitionRegistrar {
|
|
||||||
|
|
||||||
private static final String BEAN_NAME = "dataSourceInitializerPostProcessor";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
|
|
||||||
BeanDefinitionRegistry registry) {
|
|
||||||
if (!registry.containsBeanDefinition(BEAN_NAME)) {
|
|
||||||
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
|
|
||||||
beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class);
|
|
||||||
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
|
||||||
// We don't need this one to be post processed otherwise it can cause a
|
|
||||||
// cascade of bean instantiation that we would rather avoid.
|
|
||||||
beanDefinition.setSynthetic(true);
|
|
||||||
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.sql.SQLException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
|
@ -29,15 +30,12 @@ import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.beans.factory.BeanCreationException;
|
import org.springframework.beans.factory.BeanCreationException;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
||||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
|
||||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||||
|
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
import org.springframework.context.annotation.Primary;
|
|
||||||
import org.springframework.core.io.DefaultResourceLoader;
|
import org.springframework.core.io.DefaultResourceLoader;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.ResourceLoader;
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
|
@ -57,50 +55,49 @@ import static org.junit.Assert.fail;
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
*/
|
*/
|
||||||
public class DataSourceInitializerTests {
|
public class DataSourceInitializerInvokerTests {
|
||||||
|
|
||||||
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||||
.withUserConfiguration(BasicConfiguration.class)
|
.withConfiguration(
|
||||||
|
AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
||||||
.withPropertyValues("spring.datasource.initialize=false",
|
.withPropertyValues("spring.datasource.initialize=false",
|
||||||
"spring.datasource.url:jdbc:hsqldb:mem:testdb-"
|
"spring.datasource.url:jdbc:hsqldb:mem:init-"
|
||||||
+ new Random().nextInt());
|
+ UUID.randomUUID().toString());
|
||||||
|
|
||||||
@Test
|
|
||||||
public void defaultDataSourceDoesNotExists() {
|
|
||||||
this.contextRunner
|
|
||||||
.run((context) -> assertThat(context).doesNotHaveBean(DataSource.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void twoDataSources() {
|
|
||||||
this.contextRunner.withUserConfiguration(TwoDataSources.class)
|
|
||||||
.withPropertyValues("datasource.one.url=jdbc:hsqldb:mem:/one",
|
|
||||||
"datasource.two.url=jdbc:hsqldb:mem:/two")
|
|
||||||
.run((context) -> assertThat(
|
|
||||||
context.getBeanNamesForType(DataSource.class)).hasSize(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dataSourceInitialized() {
|
public void dataSourceInitialized() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
.withConfiguration(
|
|
||||||
AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
|
||||||
.withPropertyValues("spring.datasource.initialize:true")
|
.withPropertyValues("spring.datasource.initialize:true")
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(DataSource.class);
|
||||||
DataSource dataSource = context.getBean(DataSource.class);
|
DataSource dataSource = context.getBean(DataSource.class);
|
||||||
assertThat(dataSource).isInstanceOf(HikariDataSource.class);
|
assertThat(dataSource).isInstanceOf(HikariDataSource.class);
|
||||||
assertThat(dataSource).isNotNull();
|
assertDataSourceIsInitialized(dataSource);
|
||||||
JdbcOperations template = new JdbcTemplate(dataSource);
|
|
||||||
assertThat(template.queryForObject("SELECT COUNT(*) from BAR",
|
|
||||||
Integer.class)).isEqualTo(1);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void initializationAppliesToCustomDataSource() {
|
||||||
|
this.contextRunner
|
||||||
|
.withUserConfiguration(OneDataSource.class)
|
||||||
|
.withPropertyValues("spring.datasource.initialize:true")
|
||||||
|
.run((context) -> {
|
||||||
|
assertThat(context).hasSingleBean(DataSource.class);
|
||||||
|
assertDataSourceIsInitialized(context.getBean(DataSource.class));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void assertDataSourceIsInitialized(DataSource dataSource) {
|
||||||
|
JdbcOperations template = new JdbcTemplate(dataSource);
|
||||||
|
assertThat(template.queryForObject("SELECT COUNT(*) from BAR",
|
||||||
|
Integer.class)).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dataSourceInitializedWithExplicitScript() {
|
public void dataSourceInitializedWithExplicitScript() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
.withConfiguration(
|
|
||||||
AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
|
||||||
.withPropertyValues("spring.datasource.initialize:true",
|
.withPropertyValues("spring.datasource.initialize:true",
|
||||||
"spring.datasource.schema:"
|
"spring.datasource.schema:"
|
||||||
+ getRelativeLocationFor("schema.sql"),
|
+ getRelativeLocationFor("schema.sql"),
|
||||||
|
|
@ -118,8 +115,6 @@ public class DataSourceInitializerTests {
|
||||||
@Test
|
@Test
|
||||||
public void dataSourceInitializedWithMultipleScripts() {
|
public void dataSourceInitializedWithMultipleScripts() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
.withConfiguration(
|
|
||||||
AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
|
||||||
.withPropertyValues("spring.datasource.initialize:true",
|
.withPropertyValues("spring.datasource.initialize:true",
|
||||||
"spring.datasource.schema:" + getRelativeLocationFor("schema.sql")
|
"spring.datasource.schema:" + getRelativeLocationFor("schema.sql")
|
||||||
+ "," + getRelativeLocationFor("another.sql"),
|
+ "," + getRelativeLocationFor("another.sql"),
|
||||||
|
|
@ -139,8 +134,6 @@ public class DataSourceInitializerTests {
|
||||||
@Test
|
@Test
|
||||||
public void dataSourceInitializedWithExplicitSqlScriptEncoding() {
|
public void dataSourceInitializedWithExplicitSqlScriptEncoding() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
.withConfiguration(
|
|
||||||
AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
|
||||||
.withPropertyValues("spring.datasource.initialize:true",
|
.withPropertyValues("spring.datasource.initialize:true",
|
||||||
"spring.datasource.sqlScriptEncoding:UTF-8",
|
"spring.datasource.sqlScriptEncoding:UTF-8",
|
||||||
"spring.datasource.schema:"
|
"spring.datasource.schema:"
|
||||||
|
|
@ -163,34 +156,50 @@ public class DataSourceInitializerTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void initializationDisabled() {
|
public void initializationDisabled() {
|
||||||
|
this.contextRunner.run(assertInitializationIsDisabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void initializationDoesNotApplyWithSeveralDataSources() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
.withConfiguration(
|
.withUserConfiguration(TwoDataSources.class)
|
||||||
AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
.withPropertyValues("spring.datasource.initialize:true")
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
DataSource dataSource = context.getBean(DataSource.class);
|
assertThat(context.getBeanNamesForType(DataSource.class)).hasSize(2);
|
||||||
context.publishEvent(new DataSourceInitializedEvent(dataSource));
|
assertDataSourceNotInitialized(context.getBean(
|
||||||
assertThat(dataSource).isInstanceOf(HikariDataSource.class);
|
"oneDataSource", DataSource.class));
|
||||||
assertThat(dataSource).isNotNull();
|
assertDataSourceNotInitialized(context.getBean(
|
||||||
JdbcOperations template = new JdbcTemplate(dataSource);
|
"twoDataSource", DataSource.class));
|
||||||
try {
|
|
||||||
template.queryForObject("SELECT COUNT(*) from BAR",
|
|
||||||
Integer.class);
|
|
||||||
fail("Query should have failed as BAR table does not exist");
|
|
||||||
}
|
|
||||||
catch (BadSqlGrammarException ex) {
|
|
||||||
SQLException sqlException = ex.getSQLException();
|
|
||||||
int expectedCode = -5501; // user lacks privilege or object not
|
|
||||||
// found
|
|
||||||
assertThat(sqlException.getErrorCode()).isEqualTo(expectedCode);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ContextConsumer<AssertableApplicationContext> assertInitializationIsDisabled() {
|
||||||
|
return context -> {
|
||||||
|
assertThat(context).hasSingleBean(DataSource.class);
|
||||||
|
DataSource dataSource = context.getBean(DataSource.class);
|
||||||
|
context.publishEvent(new DataSourceInitializedEvent(dataSource));
|
||||||
|
assertDataSourceNotInitialized(dataSource);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertDataSourceNotInitialized(DataSource dataSource) {
|
||||||
|
JdbcOperations template = new JdbcTemplate(dataSource);
|
||||||
|
try {
|
||||||
|
template.queryForObject("SELECT COUNT(*) from BAR",
|
||||||
|
Integer.class);
|
||||||
|
fail("Query should have failed as BAR table does not exist");
|
||||||
|
}
|
||||||
|
catch (BadSqlGrammarException ex) {
|
||||||
|
SQLException sqlException = ex.getSQLException();
|
||||||
|
int expectedCode = -5501; // user lacks privilege or object not
|
||||||
|
// found
|
||||||
|
assertThat(sqlException.getErrorCode()).isEqualTo(expectedCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void dataSourceInitializedWithSchemaCredentials() {
|
public void dataSourceInitializedWithSchemaCredentials() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
.withConfiguration(
|
|
||||||
AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
|
||||||
.withPropertyValues("spring.datasource.initialize:true",
|
.withPropertyValues("spring.datasource.initialize:true",
|
||||||
"spring.datasource.sqlScriptEncoding:UTF-8",
|
"spring.datasource.sqlScriptEncoding:UTF-8",
|
||||||
"spring.datasource.schema:"
|
"spring.datasource.schema:"
|
||||||
|
|
@ -209,8 +218,6 @@ public class DataSourceInitializerTests {
|
||||||
@Test
|
@Test
|
||||||
public void dataSourceInitializedWithDataCredentials() {
|
public void dataSourceInitializedWithDataCredentials() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
.withConfiguration(
|
|
||||||
AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
|
||||||
.withPropertyValues("spring.datasource.initialize:true",
|
.withPropertyValues("spring.datasource.initialize:true",
|
||||||
"spring.datasource.sqlScriptEncoding:UTF-8",
|
"spring.datasource.sqlScriptEncoding:UTF-8",
|
||||||
"spring.datasource.schema:"
|
"spring.datasource.schema:"
|
||||||
|
|
@ -233,9 +240,7 @@ public class DataSourceInitializerTests {
|
||||||
context.setResourceLoader(
|
context.setResourceLoader(
|
||||||
new ReverseOrderResourceLoader(new DefaultResourceLoader()));
|
new ReverseOrderResourceLoader(new DefaultResourceLoader()));
|
||||||
return context;
|
return context;
|
||||||
}).withUserConfiguration(BasicConfiguration.class)
|
}).withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
||||||
.withConfiguration(
|
|
||||||
AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
|
||||||
.withPropertyValues("spring.datasource.initialize=false",
|
.withPropertyValues("spring.datasource.initialize=false",
|
||||||
"spring.datasource.url:jdbc:hsqldb:mem:testdb-"
|
"spring.datasource.url:jdbc:hsqldb:mem:testdb-"
|
||||||
+ new Random().nextInt(),
|
+ new Random().nextInt(),
|
||||||
|
|
@ -256,8 +261,6 @@ public class DataSourceInitializerTests {
|
||||||
@Test
|
@Test
|
||||||
public void testDataSourceInitializedWithInvalidSchemaResource() {
|
public void testDataSourceInitializedWithInvalidSchemaResource() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
.withConfiguration(
|
|
||||||
AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
|
||||||
.withPropertyValues("spring.datasource.initialize:true",
|
.withPropertyValues("spring.datasource.initialize:true",
|
||||||
"spring.datasource.schema:classpath:does/not/exist.sql")
|
"spring.datasource.schema:classpath:does/not/exist.sql")
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
|
|
@ -274,8 +277,6 @@ public class DataSourceInitializerTests {
|
||||||
@Test
|
@Test
|
||||||
public void dataSourceInitializedWithInvalidDataResource() {
|
public void dataSourceInitializedWithInvalidDataResource() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
.withConfiguration(
|
|
||||||
AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
|
||||||
.withPropertyValues("spring.datasource.initialize:true",
|
.withPropertyValues("spring.datasource.initialize:true",
|
||||||
"spring.datasource.schema:"
|
"spring.datasource.schema:"
|
||||||
+ getRelativeLocationFor("schema.sql"),
|
+ getRelativeLocationFor("schema.sql"),
|
||||||
|
|
@ -296,27 +297,21 @@ public class DataSourceInitializerTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableConfigurationProperties(DataSourceProperties.class)
|
protected static class OneDataSource {
|
||||||
@Import(DataSourceInitializer.class)
|
|
||||||
static class BasicConfiguration {
|
@Bean
|
||||||
|
public DataSource oneDataSource() {
|
||||||
|
return new TestDataSource();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableConfigurationProperties
|
protected static class TwoDataSources extends OneDataSource {
|
||||||
protected static class TwoDataSources {
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Primary
|
|
||||||
@ConfigurationProperties(prefix = "datasource.one")
|
|
||||||
public DataSource oneDataSource() {
|
|
||||||
return DataSourceBuilder.create().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConfigurationProperties(prefix = "datasource.two")
|
|
||||||
public DataSource twoDataSource() {
|
public DataSource twoDataSource() {
|
||||||
return DataSourceBuilder.create().build();
|
return new TestDataSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +28,6 @@ import com.zaxxer.hikari.HikariDataSource;
|
||||||
import org.apache.tomcat.jdbc.pool.DataSource;
|
import org.apache.tomcat.jdbc.pool.DataSource;
|
||||||
import org.apache.tomcat.jdbc.pool.DataSourceProxy;
|
import org.apache.tomcat.jdbc.pool.DataSourceProxy;
|
||||||
import org.apache.tomcat.jdbc.pool.jmx.ConnectionPool;
|
import org.apache.tomcat.jdbc.pool.jmx.ConnectionPool;
|
||||||
import org.hsqldb.jdbc.JDBCDriver;
|
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
@ -38,9 +37,6 @@ import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
|
||||||
import org.springframework.boot.test.util.TestPropertyValues;
|
import org.springframework.boot.test.util.TestPropertyValues;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.jmx.export.UnableToRegisterMBeanException;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
|
@ -63,13 +59,6 @@ public class DataSourceJmxConfigurationTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void hikariCustomCannotUseRegisterMBeansByDefault() {
|
|
||||||
this.thrown.expect(UnableToRegisterMBeanException.class);
|
|
||||||
this.thrown.expectMessage("myDataSource");
|
|
||||||
load(CustomHikariConfiguration.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hikariAutoConfiguredCanUseRegisterMBeans()
|
public void hikariAutoConfiguredCanUseRegisterMBeans()
|
||||||
throws MalformedObjectNameException {
|
throws MalformedObjectNameException {
|
||||||
|
|
@ -140,18 +129,4 @@ public class DataSourceJmxConfigurationTests {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
|
||||||
static class CustomHikariConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public HikariDataSource myDataSource() {
|
|
||||||
HikariDataSource dataSource = new HikariDataSource();
|
|
||||||
dataSource.setJdbcUrl("jdbc:hsqldb:mem:custom");
|
|
||||||
dataSource.setDriverClassName(JDBCDriver.class.getName());
|
|
||||||
dataSource.setRegisterMbeans(true);
|
|
||||||
return dataSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue