Skip to content

Commit fdbab70

Browse files
committed
Improve the scope of DataSourceInitializer
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. See spring-projectsgh-9528 This leads to cycle in slice tests
1 parent bf945cc commit fdbab70

File tree

7 files changed

+304
-205
lines changed

7 files changed

+304
-205
lines changed

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java

+2-11
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,10 @@
2828
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2929
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3030
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
31-
import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor.Registrar;
3231
import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration;
3332
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3433
import org.springframework.boot.jdbc.DataSourceBuilder;
3534
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
36-
import org.springframework.context.ApplicationContext;
37-
import org.springframework.context.annotation.Bean;
3835
import org.springframework.context.annotation.Condition;
3936
import org.springframework.context.annotation.ConditionContext;
4037
import org.springframework.context.annotation.Conditional;
@@ -56,16 +53,10 @@
5653
@Configuration
5754
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
5855
@EnableConfigurationProperties(DataSourceProperties.class)
59-
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
56+
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
57+
DataSourceInitializationConfiguration.class })
6058
public class DataSourceAutoConfiguration {
6159

62-
@Bean
63-
@ConditionalOnMissingBean
64-
public DataSourceInitializer dataSourceInitializer(DataSourceProperties properties,
65-
ApplicationContext applicationContext) {
66-
return new DataSourceInitializer(properties, applicationContext);
67-
}
68-
6960
@Configuration
7061
@Conditional(EmbeddedDatabaseCondition.class)
7162
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jdbc;
18+
19+
import org.springframework.beans.factory.config.BeanDefinition;
20+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
21+
import org.springframework.beans.factory.support.GenericBeanDefinition;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.context.annotation.Import;
24+
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
25+
import org.springframework.core.type.AnnotationMetadata;
26+
27+
/**
28+
* Configures DataSource initialization.
29+
*
30+
* @author Stephane Nicoll
31+
*/
32+
@Configuration
33+
@Import({ DataSourceInitializerInvoker.class, DataSourceInitializationConfiguration.Registrar.class })
34+
class DataSourceInitializationConfiguration {
35+
36+
/**
37+
* {@link ImportBeanDefinitionRegistrar} to register the
38+
* {@link DataSourceInitializerPostProcessor} without causing early bean instantiation
39+
* issues.
40+
*/
41+
static class Registrar implements ImportBeanDefinitionRegistrar {
42+
43+
private static final String BEAN_NAME = "dataSourceInitializerPostProcessor";
44+
45+
@Override
46+
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
47+
BeanDefinitionRegistry registry) {
48+
if (!registry.containsBeanDefinition(BEAN_NAME)) {
49+
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
50+
beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class);
51+
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
52+
// We don't need this one to be post processed otherwise it can cause a
53+
// cascade of bean instantiation that we would rather avoid.
54+
beanDefinition.setSynthetic(true);
55+
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
56+
}
57+
}
58+
59+
}
60+
61+
}

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java

+54-59
Original file line numberDiff line numberDiff line change
@@ -20,112 +20,107 @@
2020
import java.util.Collections;
2121
import java.util.List;
2222

23-
import javax.annotation.PostConstruct;
2423
import javax.sql.DataSource;
2524

2625
import org.apache.commons.logging.Log;
2726
import org.apache.commons.logging.LogFactory;
2827

2928
import org.springframework.boot.context.config.ResourceNotFoundException;
3029
import org.springframework.boot.jdbc.DataSourceBuilder;
31-
import org.springframework.context.ApplicationContext;
32-
import org.springframework.context.ApplicationListener;
30+
import org.springframework.core.io.DefaultResourceLoader;
3331
import org.springframework.core.io.Resource;
32+
import org.springframework.core.io.ResourceLoader;
3433
import org.springframework.jdbc.config.SortedResourcesFactoryBean;
3534
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
3635
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
3736
import org.springframework.util.StringUtils;
3837

3938
/**
40-
* Bean to handle {@link DataSource} initialization by running {@literal schema-*.sql} on
41-
* {@link PostConstruct} and {@literal data-*.sql} SQL scripts on a
42-
* {@link DataSourceInitializedEvent}.
39+
* Initialize a {@link DataSource} based on a matching {@link DataSourceProperties}
40+
* config.
4341
*
4442
* @author Dave Syer
4543
* @author Phillip Webb
4644
* @author Eddú Meléndez
4745
* @author Stephane Nicoll
4846
* @author Kazuki Shimizu
49-
* @see DataSourceAutoConfiguration
5047
*/
51-
class DataSourceInitializer implements ApplicationListener<DataSourceInitializedEvent> {
48+
class DataSourceInitializer {
5249

5350
private static final Log logger = LogFactory.getLog(DataSourceInitializer.class);
5451

55-
private final DataSourceProperties properties;
52+
private final DataSource dataSource;
5653

57-
private final ApplicationContext applicationContext;
54+
private final DataSourceProperties properties;
5855

59-
private DataSource dataSource;
56+
private final ResourceLoader resourceLoader;
57+
58+
/**
59+
* Create a new instance with the {@link DataSource} to initialize and its matching
60+
* {@link DataSourceProperties configuration}.
61+
* @param dataSource the datasource to initialize
62+
* @param properties the matching configuration
63+
* @param resourceLoader the resource loader to use (can be null)
64+
*/
65+
DataSourceInitializer(DataSource dataSource, DataSourceProperties properties,
66+
ResourceLoader resourceLoader) {
67+
this.dataSource = dataSource;
68+
this.properties = properties;
69+
this.resourceLoader = (resourceLoader != null ? resourceLoader
70+
: new DefaultResourceLoader());
71+
}
6072

61-
private boolean initialized = false;
73+
/**
74+
* Create a new instance with the {@link DataSource} to initialize and its matching
75+
* {@link DataSourceProperties configuration}.
76+
* @param dataSource the datasource to initialize
77+
* @param properties the matching configuration
78+
*/
79+
DataSourceInitializer(DataSource dataSource, DataSourceProperties properties) {
80+
this(dataSource, properties, null);
81+
}
6282

63-
DataSourceInitializer(DataSourceProperties properties,
64-
ApplicationContext applicationContext) {
65-
this.properties = properties;
66-
this.applicationContext = applicationContext;
83+
public DataSource getDataSource() {
84+
return this.dataSource;
6785
}
6886

69-
@PostConstruct
70-
public void init() {
87+
/**
88+
* Create the schema if necessary.
89+
* @return {@code true} if the schema was created
90+
* @see DataSourceProperties#getSchema()
91+
*/
92+
public boolean createSchema() {
7193
if (!this.properties.isInitialize()) {
7294
logger.debug("Initialization disabled (not running DDL scripts)");
73-
return;
74-
}
75-
if (this.applicationContext.getBeanNamesForType(DataSource.class, false,
76-
false).length > 0) {
77-
this.dataSource = this.applicationContext.getBean(DataSource.class);
95+
return false;
7896
}
79-
if (this.dataSource == null) {
80-
logger.debug("No DataSource found so not initializing");
81-
return;
82-
}
83-
runSchemaScripts();
84-
}
85-
86-
private void runSchemaScripts() {
8797
List<Resource> scripts = getScripts("spring.datasource.schema",
8898
this.properties.getSchema(), "schema");
8999
if (!scripts.isEmpty()) {
90100
String username = this.properties.getSchemaUsername();
91101
String password = this.properties.getSchemaPassword();
92102
runScripts(scripts, username, password);
93-
try {
94-
this.applicationContext
95-
.publishEvent(new DataSourceInitializedEvent(this.dataSource));
96-
// The listener might not be registered yet, so don't rely on it.
97-
if (!this.initialized) {
98-
runDataScripts();
99-
this.initialized = true;
100-
}
101-
}
102-
catch (IllegalStateException ex) {
103-
logger.warn("Could not send event to complete DataSource initialization ("
104-
+ ex.getMessage() + ")");
105-
}
106103
}
104+
return !scripts.isEmpty();
107105
}
108106

109-
@Override
110-
public void onApplicationEvent(DataSourceInitializedEvent event) {
107+
/**
108+
* Initialize the schema if necessary.
109+
* @see DataSourceProperties#getData()
110+
*/
111+
public void initSchema() {
111112
if (!this.properties.isInitialize()) {
112113
logger.debug("Initialization disabled (not running data scripts)");
113114
return;
114115
}
115-
// NOTE the event can happen more than once and
116-
// the event datasource is not used here
117-
if (!this.initialized) {
118-
runDataScripts();
119-
this.initialized = true;
120-
}
121-
}
122-
123-
private void runDataScripts() {
124116
List<Resource> scripts = getScripts("spring.datasource.data",
125117
this.properties.getData(), "data");
126-
String username = this.properties.getDataUsername();
127-
String password = this.properties.getDataPassword();
128-
runScripts(scripts, username, password);
118+
if (!scripts.isEmpty()) {
119+
String username = this.properties.getDataUsername();
120+
String password = this.properties.getDataPassword();
121+
runScripts(scripts, username, password);
122+
123+
}
129124
}
130125

131126
private List<Resource> getScripts(String propertyName, List<String> resources,
@@ -159,7 +154,7 @@ else if (validate) {
159154
private Resource[] doGetResources(String location) {
160155
try {
161156
SortedResourcesFactoryBean factory = new SortedResourcesFactoryBean(
162-
this.applicationContext, Collections.singletonList(location));
157+
this.resourceLoader, Collections.singletonList(location));
163158
factory.afterPropertiesSet();
164159
return factory.getObject();
165160
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jdbc;
18+
19+
import javax.sql.DataSource;
20+
21+
import org.apache.commons.logging.Log;
22+
import org.apache.commons.logging.LogFactory;
23+
24+
import org.springframework.beans.factory.InitializingBean;
25+
import org.springframework.beans.factory.ObjectProvider;
26+
import org.springframework.context.ApplicationContext;
27+
import org.springframework.context.ApplicationListener;
28+
29+
/**
30+
* Bean to handle {@link DataSource} initialization by running {@literal schema-*.sql} on
31+
* {@link InitializingBean#afterPropertiesSet()} and {@literal data-*.sql} SQL scripts on
32+
* a {@link DataSourceInitializedEvent}.
33+
*
34+
* @author Stephane Nicoll
35+
* @see DataSourceAutoConfiguration
36+
*/
37+
class DataSourceInitializerInvoker
38+
implements ApplicationListener<DataSourceInitializedEvent>, InitializingBean {
39+
40+
private static final Log logger = LogFactory.getLog(DataSourceInitializerInvoker.class);
41+
42+
private final ObjectProvider<DataSource> dataSource;
43+
44+
private final DataSourceProperties properties;
45+
46+
private final ApplicationContext applicationContext;
47+
48+
private DataSourceInitializer dataSourceInitializer;
49+
50+
private boolean initialized;
51+
52+
DataSourceInitializerInvoker(ObjectProvider<DataSource> dataSource,
53+
DataSourceProperties properties,
54+
ApplicationContext applicationContext) {
55+
this.dataSource = dataSource;
56+
this.properties = properties;
57+
this.applicationContext = applicationContext;
58+
}
59+
60+
@Override
61+
public void afterPropertiesSet() {
62+
DataSourceInitializer initializer = getDataSourceInitializer();
63+
if (initializer != null) {
64+
boolean schemaCreated = this.dataSourceInitializer.createSchema();
65+
if (schemaCreated) {
66+
try {
67+
this.applicationContext
68+
.publishEvent(new DataSourceInitializedEvent(
69+
initializer.getDataSource()));
70+
// The listener might not be registered yet, so don't rely on it.
71+
if (!this.initialized) {
72+
this.dataSourceInitializer.initSchema();
73+
this.initialized = true;
74+
}
75+
}
76+
catch (IllegalStateException ex) {
77+
logger.warn("Could not send event to complete DataSource initialization ("
78+
+ ex.getMessage() + ")");
79+
}
80+
}
81+
}
82+
}
83+
84+
@Override
85+
public void onApplicationEvent(DataSourceInitializedEvent event) {
86+
// NOTE the event can happen more than once and
87+
// the event datasource is not used here
88+
DataSourceInitializer initializer = getDataSourceInitializer();
89+
if (!this.initialized && initializer != null) {
90+
initializer.initSchema();
91+
this.initialized = true;
92+
}
93+
}
94+
95+
private DataSourceInitializer getDataSourceInitializer() {
96+
if (this.dataSourceInitializer == null) {
97+
DataSource ds = this.dataSource.getIfUnique();
98+
if (ds != null) {
99+
this.dataSourceInitializer = new DataSourceInitializer(ds,
100+
this.properties, this.applicationContext);
101+
}
102+
}
103+
return this.dataSourceInitializer;
104+
}
105+
106+
}

0 commit comments

Comments
 (0)