From 1ea07eeb29e670274a3dd046a885eb8411afbe1e Mon Sep 17 00:00:00 2001 From: qnnn Date: Wed, 25 Feb 2026 20:57:47 +0800 Subject: [PATCH 1/2] 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 --- .../context/config/ConfigDataEnvironment.java | 14 +++++++++- .../ConfigDataEnvironmentContributor.java | 27 +++++++++++++++++++ ...ConfigDataEnvironmentContributorTests.java | 16 +++++++++++ ...ironmentPostProcessorIntegrationTests.java | 20 ++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java index 77abf8d2d9d..c7aeae88d42 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java @@ -17,6 +17,7 @@ package org.springframework.boot.context.config; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; @@ -57,6 +58,7 @@ import org.springframework.util.StringUtils; * * @author Phillip Webb * @author Madhura Bhave + * @author Nan Chiu */ class ConfigDataEnvironment { @@ -197,7 +199,8 @@ class ConfigDataEnvironment { private List getInitialImportContributors(Binder binder) { List initialContributors = new ArrayList<>(); - addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS)); + addInitialImportPropertyContributors(initialContributors, + bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS)); addInitialImportContributors(initialContributors, bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS)); addInitialImportContributors(initialContributors, @@ -209,6 +212,15 @@ class ConfigDataEnvironment { return binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other); } + private void addInitialImportPropertyContributors(List initialContributors, + ConfigDataLocation[] locations) { + List initialPropertiesContributors = new ArrayList<>(); + addInitialImportContributors(initialPropertiesContributors, locations); + initialContributors.add(ConfigDataEnvironmentContributor.ofInitialImportProperty( + initialPropertiesContributors, this.environment.getConversionService(), Arrays.asList(locations)) + ); + } + private void addInitialImportContributors(List initialContributors, ConfigDataLocation[] locations) { for (int i = locations.length - 1; i >= 0; i--) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java index 3c85a5b9ed9..0e1af5e8ef7 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java @@ -52,6 +52,7 @@ import org.springframework.util.CollectionUtils; * * @author Phillip Webb * @author Madhura Bhave + * @author Nan Chiu */ class ConfigDataEnvironmentContributor implements Iterable { @@ -404,6 +405,27 @@ class ConfigDataEnvironmentContributor implements Iterable contributors, + ConversionService conversionService, + List locations) { + Map> children = new LinkedHashMap<>(); + ConfigDataProperties properties = new ConfigDataProperties(locations, null); + children.put(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.unmodifiableList(contributors)); + return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT_PROPERTY, null, null, false, null, null, properties, null, children, + conversionService); + } + /** * Factory method to create a contributor that wraps an {@link Kind#EXISTING existing} * property source. The contributor provides access to existing properties, but @@ -477,6 +499,11 @@ class ConfigDataEnvironmentContributor implements Iterable Date: Fri, 6 Mar 2026 08:57:36 -0500 Subject: [PATCH 2/2] Polish 'Fix inconsistent ordering of config imports' See gh-49324 --- .../context/config/ConfigDataEnvironment.java | 26 +++++---------- .../ConfigDataEnvironmentContributor.java | 33 ++----------------- ...ConfigDataEnvironmentContributorTests.java | 27 ++++----------- ...onfigDataEnvironmentContributorsTests.java | 28 ++++++++-------- .../config/ConfigDataEnvironmentTests.java | 2 +- 5 files changed, 34 insertions(+), 82 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java index c7aeae88d42..47d400302b1 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java @@ -17,7 +17,6 @@ package org.springframework.boot.context.config; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; @@ -199,8 +198,7 @@ class ConfigDataEnvironment { private List getInitialImportContributors(Binder binder) { List initialContributors = new ArrayList<>(); - addInitialImportPropertyContributors(initialContributors, - bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS)); + addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS)); addInitialImportContributors(initialContributors, bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS)); addInitialImportContributors(initialContributors, @@ -212,27 +210,21 @@ class ConfigDataEnvironment { return binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other); } - private void addInitialImportPropertyContributors(List initialContributors, + private void addInitialImportContributors(List initialContributors, ConfigDataLocation[] locations) { - List initialPropertiesContributors = new ArrayList<>(); - addInitialImportContributors(initialPropertiesContributors, locations); - initialContributors.add(ConfigDataEnvironmentContributor.ofInitialImportProperty( - initialPropertiesContributors, this.environment.getConversionService(), Arrays.asList(locations)) - ); + addInitialImportContributors(initialContributors, List.of(locations)); } private void addInitialImportContributors(List initialContributors, - ConfigDataLocation[] locations) { - for (int i = locations.length - 1; i >= 0; i--) { - initialContributors.add(createInitialImportContributor(locations[i])); + List locations) { + if (!locations.isEmpty()) { + this.logger.trace(LogMessage.format("Adding initial config data import from locations %s", locations)); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImports(locations, + this.environment.getConversionService()); + initialContributors.add(contributor); } } - private ConfigDataEnvironmentContributor createInitialImportContributor(ConfigDataLocation location) { - this.logger.trace(LogMessage.format("Adding initial config data import from location '%s'", location)); - return ConfigDataEnvironmentContributor.ofInitialImport(location, this.environment.getConversionService()); - } - /** * Process all contributions and apply any newly imported property sources to the * {@link Environment}. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java index 0e1af5e8ef7..1b83d161069 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java @@ -393,39 +393,17 @@ class ConfigDataEnvironmentContributor implements Iterable initialImports, ConversionService conversionService) { - List imports = Collections.singletonList(initialImport); - ConfigDataProperties properties = new ConfigDataProperties(imports, null); + ConfigDataProperties properties = new ConfigDataProperties(initialImports, null); return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, false, null, null, properties, null, null, conversionService); } - /** - * Factory method to create a {@link Kind#INITIAL_IMPORT_PROPERTY initial import property} - * contributor. This contributor is a container that wraps initial imports from - * {@code spring.config.import} property, allowing profile-specific imports to take - * precedence over the imported locations. - * @param contributors the contributors created from the import locations - * @param conversionService the conversion service to use - * @param locations the original import locations from the property - * @return a new {@link ConfigDataEnvironmentContributor} instance - */ - static ConfigDataEnvironmentContributor ofInitialImportProperty( - List contributors, - ConversionService conversionService, - List locations) { - Map> children = new LinkedHashMap<>(); - ConfigDataProperties properties = new ConfigDataProperties(locations, null); - children.put(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.unmodifiableList(contributors)); - return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT_PROPERTY, null, null, false, null, null, properties, null, children, - conversionService); - } - /** * Factory method to create a contributor that wraps an {@link Kind#EXISTING existing} * property source. The contributor provides access to existing properties, but @@ -499,11 +477,6 @@ class ConfigDataEnvironmentContributor implements Iterable TEST_LOCATIONS = List.of(TEST_LOCATION); + private final ConfigDataActivationContext activationContext = new ConfigDataActivationContext( CloudPlatform.KUBERNETES, null); @@ -57,14 +59,14 @@ class ConfigDataEnvironmentContributorTests { @Test void getKindReturnsKind() { - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(TEST_LOCATION, + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImports(TEST_LOCATIONS, this.conversionService); assertThat(contributor.getKind()).isEqualTo(Kind.INITIAL_IMPORT); } @Test void isActiveWhenPropertiesIsNullReturnsTrue() { - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(TEST_LOCATION, + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImports(TEST_LOCATIONS, this.conversionService); assertThat(contributor.isActive(null)).isTrue(); } @@ -287,33 +289,18 @@ class ConfigDataEnvironmentContributorTests { } @Test - void ofInitialImportCreatedInitialImportContributor() { - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(TEST_LOCATION, + void ofInitialImportsCreatedInitialImportContributor() { + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImports(TEST_LOCATIONS, this.conversionService); assertThat(contributor.getKind()).isEqualTo(Kind.INITIAL_IMPORT); assertThat(contributor.getResource()).isNull(); - assertThat(contributor.getImports()).containsExactly(TEST_LOCATION); + assertThat(contributor.getImports()).isEqualTo(TEST_LOCATIONS); assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.getPropertySource()).isNull(); assertThat(contributor.getConfigurationPropertySource()).isNull(); assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty(); } - @Test - void ofInitialImportPropertyCreatedInitialImportPropertyContributor() { - ConfigDataEnvironmentContributor innerContributor = ConfigDataEnvironmentContributor.ofInitialImport(TEST_LOCATION, - this.conversionService); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImportProperty( - Collections.singletonList(innerContributor), this.conversionService, Arrays.asList(TEST_LOCATION)); - assertThat(contributor.getKind()).isEqualTo(Kind.INITIAL_IMPORT_PROPERTY); - assertThat(contributor.getResource()).isNull(); - assertThat(contributor.getImports()).containsExactly(TEST_LOCATION); - assertThat(contributor.isActive(this.activationContext)).isTrue(); - assertThat(contributor.getPropertySource()).isNull(); - assertThat(contributor.getConfigurationPropertySource()).isNull(); - assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).containsExactly(innerContributor); - } - @Test void ofExistingCreatesExistingContributor() { MockPropertySource propertySource = new MockPropertySource(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java index 3dd1855a969..dd4db331b55 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java @@ -91,8 +91,8 @@ class ConfigDataEnvironmentContributorsTests { @Test void createCreatesWithInitialContributors() { - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1, - this.conversionService); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor + .ofInitialImports(List.of(LOCATION_1), this.conversionService); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, List.of(contributor), this.conversionService, ConfigDataEnvironmentUpdateListener.NONE); @@ -123,8 +123,8 @@ class ConfigDataEnvironmentContributorsTests { new ConfigData(List.of(propertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) .willReturn(imported); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1, - this.conversionService); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor + .ofInitialImports(List.of(LOCATION_1), this.conversionService); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, List.of(contributor), this.conversionService, ConfigDataEnvironmentUpdateListener.NONE); @@ -155,8 +155,8 @@ class ConfigDataEnvironmentContributorsTests { new ConfigData(List.of(secondPropertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations))) .willReturn(secondImported); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1, - this.conversionService); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor + .ofInitialImports(List.of(LOCATION_1), this.conversionService); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, List.of(contributor), this.conversionService, ConfigDataEnvironmentUpdateListener.NONE); @@ -184,8 +184,8 @@ class ConfigDataEnvironmentContributorsTests { new ConfigData(List.of(propertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) .willReturn(imported); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1, - this.conversionService); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor + .ofInitialImports(List.of(LOCATION_1), this.conversionService); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, Arrays.asList(existingContributor, contributor), this.conversionService, ConfigDataEnvironmentUpdateListener.NONE); @@ -215,8 +215,8 @@ class ConfigDataEnvironmentContributorsTests { new ConfigData(List.of(secondPropertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations))) .willReturn(secondImported); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1, - this.conversionService); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor + .ofInitialImports(List.of(LOCATION_1), this.conversionService); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, List.of(contributor), this.conversionService, ConfigDataEnvironmentUpdateListener.NONE); @@ -243,8 +243,8 @@ class ConfigDataEnvironmentContributorsTests { new ConfigData(List.of(propertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) .willReturn(imported); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1, - this.conversionService); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor + .ofInitialImports(List.of(LOCATION_1), this.conversionService); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, Arrays.asList(existingContributor, contributor), this.conversionService, ConfigDataEnvironmentUpdateListener.NONE); @@ -269,8 +269,8 @@ class ConfigDataEnvironmentContributorsTests { new ConfigData(List.of(propertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) .willReturn(imported); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1, - this.conversionService); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor + .ofInitialImports(List.of(LOCATION_1), this.conversionService); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, Arrays.asList(existingContributor, contributor), this.conversionService, ConfigDataEnvironmentUpdateListener.NONE); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java index 0e23beec4a7..bb0b3501f68 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java @@ -144,7 +144,7 @@ class ConfigDataEnvironmentTests { .map(ConfigDataEnvironmentContributor::getImports) .map(Object::toString) .toArray(); - assertThat(imports).containsExactly("[i2]", "[i1]", "[a2]", "[a1]", "[l2]", "[l1]"); + assertThat(imports).containsExactly("[i1, i2]", "[a1, a2]", "[l1, l2]"); } @Test