Skip to content

Commit 1ea07ee

Browse files
qnnnphilwebb
authored andcommitted
Fix inconsistent ordering of config imports
Update `ConfigDataEnvironment` so `spring.config.import` properties defined in environment or system properties are ordered correctly. See gh-49324 Signed-off-by: qnnn <qiunan@cmbchina.com>
1 parent 7d21d72 commit 1ea07ee

4 files changed

Lines changed: 76 additions & 1 deletion

File tree

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.context.config;
1818

1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.Collection;
2122
import java.util.Collections;
2223
import java.util.LinkedHashSet;
@@ -57,6 +58,7 @@
5758
*
5859
* @author Phillip Webb
5960
* @author Madhura Bhave
61+
* @author Nan Chiu
6062
*/
6163
class ConfigDataEnvironment {
6264

@@ -197,7 +199,8 @@ ConfigDataEnvironmentContributors getContributors() {
197199

198200
private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
199201
List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
200-
addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
202+
addInitialImportPropertyContributors(initialContributors,
203+
bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
201204
addInitialImportContributors(initialContributors,
202205
bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
203206
addInitialImportContributors(initialContributors,
@@ -209,6 +212,15 @@ private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, C
209212
return binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other);
210213
}
211214

215+
private void addInitialImportPropertyContributors(List<ConfigDataEnvironmentContributor> initialContributors,
216+
ConfigDataLocation[] locations) {
217+
List<ConfigDataEnvironmentContributor> initialPropertiesContributors = new ArrayList<>();
218+
addInitialImportContributors(initialPropertiesContributors, locations);
219+
initialContributors.add(ConfigDataEnvironmentContributor.ofInitialImportProperty(
220+
initialPropertiesContributors, this.environment.getConversionService(), Arrays.asList(locations))
221+
);
222+
}
223+
212224
private void addInitialImportContributors(List<ConfigDataEnvironmentContributor> initialContributors,
213225
ConfigDataLocation[] locations) {
214226
for (int i = locations.length - 1; i >= 0; i--) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
*
5353
* @author Phillip Webb
5454
* @author Madhura Bhave
55+
* @author Nan Chiu
5556
*/
5657
class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> {
5758

@@ -404,6 +405,27 @@ static ConfigDataEnvironmentContributor ofInitialImport(ConfigDataLocation initi
404405
null, null, conversionService);
405406
}
406407

408+
/**
409+
* Factory method to create a {@link Kind#INITIAL_IMPORT_PROPERTY initial import property}
410+
* contributor. This contributor is a container that wraps initial imports from
411+
* {@code spring.config.import} property, allowing profile-specific imports to take
412+
* precedence over the imported locations.
413+
* @param contributors the contributors created from the import locations
414+
* @param conversionService the conversion service to use
415+
* @param locations the original import locations from the property
416+
* @return a new {@link ConfigDataEnvironmentContributor} instance
417+
*/
418+
static ConfigDataEnvironmentContributor ofInitialImportProperty(
419+
List<ConfigDataEnvironmentContributor> contributors,
420+
ConversionService conversionService,
421+
List<ConfigDataLocation> locations) {
422+
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children = new LinkedHashMap<>();
423+
ConfigDataProperties properties = new ConfigDataProperties(locations, null);
424+
children.put(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.unmodifiableList(contributors));
425+
return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT_PROPERTY, null, null, false, null, null, properties, null, children,
426+
conversionService);
427+
}
428+
407429
/**
408430
* Factory method to create a contributor that wraps an {@link Kind#EXISTING existing}
409431
* property source. The contributor provides access to existing properties, but
@@ -477,6 +499,11 @@ enum Kind {
477499
*/
478500
INITIAL_IMPORT,
479501

502+
/**
503+
* A container contributor that wraps initial imports from spring.config.import property.
504+
*/
505+
INITIAL_IMPORT_PROPERTY,
506+
480507
/**
481508
* An existing property source that contributes properties but no imports.
482509
*/

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
* @author Phillip Webb
4545
* @author Madhura Bhave
4646
* @author Scott Frederick
47+
* @author Nan Chiu
4748
*/
4849
class ConfigDataEnvironmentContributorTests {
4950

@@ -298,6 +299,21 @@ void ofInitialImportCreatedInitialImportContributor() {
298299
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
299300
}
300301

302+
@Test
303+
void ofInitialImportPropertyCreatedInitialImportPropertyContributor() {
304+
ConfigDataEnvironmentContributor innerContributor = ConfigDataEnvironmentContributor.ofInitialImport(TEST_LOCATION,
305+
this.conversionService);
306+
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImportProperty(
307+
Collections.singletonList(innerContributor), this.conversionService, Arrays.asList(TEST_LOCATION));
308+
assertThat(contributor.getKind()).isEqualTo(Kind.INITIAL_IMPORT_PROPERTY);
309+
assertThat(contributor.getResource()).isNull();
310+
assertThat(contributor.getImports()).containsExactly(TEST_LOCATION);
311+
assertThat(contributor.isActive(this.activationContext)).isTrue();
312+
assertThat(contributor.getPropertySource()).isNull();
313+
assertThat(contributor.getConfigurationPropertySource()).isNull();
314+
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).containsExactly(innerContributor);
315+
}
316+
301317
@Test
302318
void ofExistingCreatesExistingContributor() {
303319
MockPropertySource propertySource = new MockPropertySource();

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
*
7979
* @author Madhura Bhave
8080
* @author Phillip Webb
81+
* @author Nan Chiu
8182
*/
8283
class ConfigDataEnvironmentPostProcessorIntegrationTests {
8384

@@ -330,6 +331,25 @@ void runWhenHasActiveProfilesFromMultipleAdditionalLocationsWithOneSwitchedOffLo
330331
assertThat(property).isEqualTo("frommyprofilepropertiesfile");
331332
}
332333

334+
@Test
335+
@WithResource(name = "testproperties-1.properties", content = """
336+
my.property=fromtestproperties-1.properties
337+
""")
338+
@WithResource(name = "testproperties-1-myprofile.properties", content = """
339+
my.property=fromtestproperties-1-myprofile.properties
340+
""")
341+
@WithResource(name = "testproperties-2.properties", content = """
342+
my.property=fromtestproperties-2.properties
343+
""")
344+
void runWhenHasImportPropertyWithProfileSpecificFileTakesPrecedence() {
345+
ConfigurableApplicationContext context = this.application.run(
346+
"--spring.config.import=classpath:testproperties-1.properties,classpath:testproperties-2.properties",
347+
"--spring.profiles.active=myprofile");
348+
ConfigurableEnvironment environment = context.getEnvironment();
349+
String property = environment.getProperty("my.property");
350+
assertThat(property).isEqualTo("fromtestproperties-1-myprofile.properties");
351+
}
352+
333353
@Test
334354
void runWhenHasLocalFileLoadsWithLocalFileTakingPrecedenceOverClasspath() throws Exception {
335355
File localFile = new File(new File("."), "application.properties");

0 commit comments

Comments
 (0)