From 17e256f16b3a1ed53e4609954bae545e7e840879 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 24 Mar 2026 13:18:44 -0700 Subject: [PATCH] Restore correct grouping of initial ConfigDataContributors Update `ConfigDataEnvironment` to restore previous property override behavior by correctly grouping the initial `ConfigDataContributor` instances. This regression was cause by the polish commit 646db448 which attempted to simplify the fix for gh-49324 by including all initial contributors in a single contributor node. This change inadvertently changed the iteration order for the `spring.config.location` and `spring.config.additional-location` properties which still need to be expanded and added in reverse order. Fixes gh-49724 --- .../context/config/ConfigDataEnvironment.java | 26 ++++++++++--------- .../config/ConfigDataEnvironmentTests.java | 18 ++++++++++++- 2 files changed, 31 insertions(+), 13 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 47d400302b1..4c96ac0bc6f 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 @@ -198,21 +198,23 @@ class ConfigDataEnvironment { private List getInitialImportContributors(Binder binder) { List initialContributors = new ArrayList<>(); - addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS)); - addInitialImportContributors(initialContributors, - bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS)); - addInitialImportContributors(initialContributors, - bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS)); + addInitialImportContributors(initialContributors, binder, IMPORT_PROPERTY, EMPTY_LOCATIONS, false); + addInitialImportContributors(initialContributors, binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS, true); + addInitialImportContributors(initialContributors, binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS, true); return initialContributors; } - private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, ConfigDataLocation[] other) { - return binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other); - } - - private void addInitialImportContributors(List initialContributors, - ConfigDataLocation[] locations) { - addInitialImportContributors(initialContributors, List.of(locations)); + private void addInitialImportContributors(List initialContributors, Binder binder, + String propertyName, ConfigDataLocation[] defaultValue, boolean registerIndividually) { + ConfigDataLocation[] locations = binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(defaultValue); + if (registerIndividually) { + for (int i = locations.length - 1; i >= 0; i--) { + addInitialImportContributors(initialContributors, List.of(locations[i])); + } + } + else { + addInitialImportContributors(initialContributors, List.of(locations)); + } } private void addInitialImportContributors(List initialContributors, 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 bb0b3501f68..2c4a194dfea 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 @@ -18,6 +18,8 @@ package org.springframework.boot.context.config; import java.io.IOException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; @@ -29,6 +31,7 @@ import java.util.function.Supplier; import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -144,7 +147,7 @@ class ConfigDataEnvironmentTests { .map(ConfigDataEnvironmentContributor::getImports) .map(Object::toString) .toArray(); - assertThat(imports).containsExactly("[i1, i2]", "[a1, a2]", "[l1, l2]"); + assertThat(imports).containsExactly("[i1, i2]", "[a2]", "[a1]", "[l2]", "[l1]"); } @Test @@ -377,6 +380,19 @@ class ConfigDataEnvironmentTests { .containsOnly(SeparateClassLoaderConfigDataLoader.class); } + @Test // gh-49724 + @WithResource(name = "application-local.properties", content = "test.property=classpath-local") + void processAndApplyWhenExternalFileConfigOverridesProfileSpecificClasspathConfig(@TempDir Path tempDir) + throws IOException { + Files.writeString(tempDir.resolve("application.properties"), "test.property=file-default\n"); + this.environment.setProperty("spring.config.location", + "optional:classpath:/,optional:file:" + tempDir.toAbsolutePath() + "/"); + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, + this.environment, this.resourceLoader, List.of("local"), null); + configDataEnvironment.processAndApply(); + assertThat(this.environment.getProperty("test.property")).isEqualTo("file-default"); + } + private String getConfigLocation(TestInfo info) { return "optional:classpath:" + info.getTestClass().get().getName().replace('.', '/') + "-" + info.getTestMethod().get().getName() + ".properties";