diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java index 68ff61f1d1c4..422df84edd6f 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java +++ b/core/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; @@ -59,6 +60,7 @@ * * @author Phillip Webb * @author Madhura Bhave + * @author Nan Chiu */ class ConfigDataEnvironment { @@ -199,7 +201,8 @@ ConfigDataEnvironmentContributors getContributors() { 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, @@ -211,6 +214,15 @@ private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, C 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/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java index c4cb3160a625..0f9207aa94d4 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java @@ -56,6 +56,7 @@ * * @author Phillip Webb * @author Madhura Bhave + * @author Nan Chiu */ class ConfigDataEnvironmentContributor implements Iterable { @@ -413,6 +414,27 @@ static ConfigDataEnvironmentContributor ofInitialImport(ConfigDataLocation initi 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 @@ -488,6 +510,11 @@ enum Kind { */ INITIAL_IMPORT, + /** + * A container contributor that wraps initial imports from spring.config.import property. + */ + INITIAL_IMPORT_PROPERTY, + /** * An existing property source that contributes properties but no imports. */ diff --git a/core/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java b/core/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java index eaa2a601ce71..9bbaef82ac46 100644 --- a/core/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java +++ b/core/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java @@ -47,6 +47,7 @@ * @author Phillip Webb * @author Madhura Bhave * @author Scott Frederick + * @author Nan Chiu */ class ConfigDataEnvironmentContributorTests { @@ -303,6 +304,21 @@ void ofInitialImportCreatedInitialImportContributor() { 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/core/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java b/core/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java index ed7460ceff37..1b7d1f30a00d 100644 --- a/core/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java +++ b/core/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java @@ -79,6 +79,7 @@ * * @author Madhura Bhave * @author Phillip Webb + * @author Nan Chiu */ class ConfigDataEnvironmentPostProcessorIntegrationTests { @@ -332,6 +333,25 @@ void runWhenHasActiveProfilesFromMultipleAdditionalLocationsWithOneSwitchedOffLo assertThat(property).isEqualTo("frommyprofilepropertiesfile"); } + @Test + @WithResource(name = "testproperties-1.properties", content = """ + my.property=fromtestproperties-1.properties + """) + @WithResource(name = "testproperties-1-myprofile.properties", content = """ + my.property=fromtestproperties-1-myprofile.properties + """) + @WithResource(name = "testproperties-2.properties", content = """ + my.property=fromtestproperties-2.properties + """) + void runWhenHasImportPropertyWithProfileSpecificFileTakesPrecedence() { + ConfigurableApplicationContext context = this.application.run( + "--spring.config.import=classpath:testproperties-1.properties,classpath:testproperties-2.properties", + "--spring.profiles.active=myprofile"); + ConfigurableEnvironment environment = context.getEnvironment(); + String property = environment.getProperty("my.property"); + assertThat(property).isEqualTo("fromtestproperties-1-myprofile.properties"); + } + @Test void runWhenHasLocalFileLoadsWithLocalFileTakingPrecedenceOverClasspath() throws Exception { File localFile = new File(new File("."), "application.properties");