diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 9366840804..9eef44d046 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -555,17 +555,27 @@ before being loaded, including profile-specific file names). Files specified in and are overridden by any profile-specific properties. Config locations are searched in reverse order. By default, the configured locations are -`classpath:/,classpath:/config/,file:./,file:./config/`. The resulting search order is: +`classpath:/,classpath:/config/,file:./,file:./config/`. The resulting search order is the +following: . `file:./config/` . `file:./` . `classpath:/config/` . `classpath:/` -When custom config locations are configured, they are used in addition to the default -locations. Custom locations are searched before the default locations. For example, if -custom locations of `classpath:/custom-config/,file:./custom-config/` are configured, the -search order becomes: +When custom config locations are configured using `spring.config.location` they replace +the default locations. For example, if `spring.config.location` is configured with the +value `classpath:/custom-config/,file:./custom-config/`, the search order becomes the +following: + +. `file:./custom-config/` +. `classpath:custom-config/` + +Alternatively, when custom config locations are configured using +`spring.config.addition-location`, they are used in addition to the default locations. +Additional locations are search before the default locations. For example, if +additional locations of `classpath:/custom-config/,file:./custom-config/` are configured, +the search order becomes the following: . `file:./custom-config/` . `classpath:custom-config/` diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java index 0b1e839859..a3fbcc59be 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java @@ -121,6 +121,11 @@ public class ConfigFileApplicationListener */ public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location"; + /** + * The "config additional location" property name. + */ + public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location"; + /** * The default order for the processor. */ @@ -563,11 +568,22 @@ public class ConfigFileApplicationListener } private Set getSearchLocations() { - Set locations = new LinkedHashSet<>(); - // User-configured settings take precedence, so we do them first if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { + return getSearchLocations(CONFIG_LOCATION_PROPERTY); + } + Set locations = getSearchLocations( + CONFIG_ADDITIONAL_LOCATION_PROPERTY); + locations.addAll( + asResolvedSet(ConfigFileApplicationListener.this.searchLocations, + DEFAULT_SEARCH_LOCATIONS)); + return locations; + } + + private Set getSearchLocations(String propertyName) { + Set locations = new LinkedHashSet<>(); + if (this.environment.containsProperty(propertyName)) { for (String path : asResolvedSet( - this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) { + this.environment.getProperty(propertyName), null)) { if (!path.contains("$")) { path = StringUtils.cleanPath(path); if (!ResourceUtils.isUrl(path)) { @@ -577,9 +593,6 @@ public class ConfigFileApplicationListener locations.add(path); } } - locations.addAll( - asResolvedSet(ConfigFileApplicationListener.this.searchLocations, - DEFAULT_SEARCH_LOCATIONS)); return locations; } diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 04dabaef52..72661f5926 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -132,6 +132,12 @@ "description": "Skip search of BeanInfo classes.", "defaultValue": true }, + { + "name": "spring.config.additional-location", + "type": "java.lang.String", + "sourceType": "org.springframework.boot.context.config.ConfigFileApplicationListener", + "description": "Config file locations used in addition to the defaults." + }, { "name": "spring.config.name", "type": "java.lang.String", @@ -143,7 +149,7 @@ "name": "spring.config.location", "type": "java.lang.String", "sourceType": "org.springframework.boot.context.config.ConfigFileApplicationListener", - "description": "Config file locations." + "description": "Config file locations that replace the defaults." }, { "name": "spring.jta.atomikos.properties.console-log-level", diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java index 23786e5741..75dc8aca81 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java @@ -154,8 +154,20 @@ public class ConfigFileApplicationListenerTests { @Test public void loadTwoPropertiesFilesWithProfiles() throws Exception { TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, - "spring.config.location=" - + "classpath:enableprofile.properties,classpath:enableother.properties"); + "spring.config.location=classpath:enableprofile.properties," + + "classpath:enableother.properties"); + this.initializer.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getActiveProfiles()).containsExactly("other"); + String property = this.environment.getProperty("my.property"); + assertThat(property).isEqualTo("fromenableotherpropertiesfile"); + } + + @Test + public void loadTwoPropertiesFilesWithProfilesUsingAdditionalLocation() + throws Exception { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "spring.config.additional-location=classpath:enableprofile.properties," + + "classpath:enableother.properties"); this.initializer.postProcessEnvironment(this.environment, this.application); assertThat(this.environment.getActiveProfiles()).containsExactly("other"); String property = this.environment.getProperty("my.property"); @@ -564,6 +576,22 @@ public class ConfigFileApplicationListenerTests { this.initializer.postProcessEnvironment(this.environment, this.application); String property = this.environment.getProperty("the.property"); assertThat(property).isEqualTo("fromspecificlocation"); + assertThat(this.environment).has(matchingPropertySource( + "applicationConfig: " + "[classpath:specificlocation.properties]")); + // The default property source is not there + assertThat(this.environment).doesNotHave(matchingPropertySource( + "applicationConfig: " + "[classpath:/application.properties]")); + assertThat(this.environment.getProperty("foo")).isNull(); + } + + @Test + public void specificResourceFromAdditionalLocation() throws Exception { + String additionalLocation = "classpath:specificlocation.properties"; + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "spring.config.additional-location=" + additionalLocation); + this.initializer.postProcessEnvironment(this.environment, this.application); + String property = this.environment.getProperty("the.property"); + assertThat(property).isEqualTo("fromspecificlocation"); assertThat(this.environment).has(matchingPropertySource( "applicationConfig: " + "[classpath:specificlocation.properties]")); // The default property source is still there @@ -816,6 +844,34 @@ public class ConfigFileApplicationListenerTests { .containsExactly("testPropertySource"); } + @Test + public void additionalLocationTakesPrecedenceOverDefaultLocation() throws Exception { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "spring.config.additional-location=classpath:override.properties"); + this.initializer.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty("foo")).isEqualTo("bar"); + assertThat(this.environment.getProperty("value")).isEqualTo("1234"); + } + + @Test + public void lastAdditionalLocationWins() throws Exception { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "spring.config.additional-location=classpath:override.properties," + + "classpath:some.properties"); + this.initializer.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty("foo")).isEqualTo("spam"); + assertThat(this.environment.getProperty("value")).isEqualTo("1234"); + } + + @Test + public void locationReplaceDefaultLocation() throws Exception { + TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, + "spring.config.location=classpath:override.properties"); + this.initializer.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty("foo")).isEqualTo("bar"); + assertThat(this.environment.getProperty("value")).isNull(); + } + private Condition matchingPropertySource( final String sourceName) { return new Condition(