Merge branch '2.4.x'

Closes gh-26782
pull/26813/head
Phillip Webb 3 years ago
commit abd926788f

@ -103,25 +103,29 @@ This means that the JSON cannot override properties from lower order property so
=== External Application Properties [[features.external-config.files]]
Spring Boot will automatically find and load `application.properties` and `application.yaml` files from the following locations when your application starts:
. The classpath root
. The classpath `/config` package
. The current directory
. The `/config` subdirectory in the current directory
. Immediate child directories of the `/config` subdirectory
. From the classpath
.. The classpath root
.. The classpath `/config` package
. From the current directory
.. The current directory
.. The `/config` subdirectory in the current directory
.. Immediate child directories of the `/config` subdirectory
The list is ordered by precedence (with values from lower items overriding earlier ones).
Documents from the loaded files are added as `PropertySources` to the Spring `Environment`.
If you do not like `application` as the configuration file name, you can switch to another file name by specifying a configprop:spring.config.name[] environment property.
You can also refer to an explicit location by using the `spring.config.location` environment property (which is a comma-separated list of directory locations or file paths).
The following example shows how to specify a different file name:
For example, to look for `myproject.properties` and `myproject.yaml` files you can run your application as follows:
[source,shell,indent=0,subs="verbatim"]
----
$ java -jar myproject.jar --spring.config.name=myproject
----
The following example shows how to specify two locations:
You can also refer to an explicit location by using the configprop:spring.config.location[] environment property.
This properties accepts a comma-separated list of one or more locations to check.
The following example shows how to specify two distinct files:
[source,shell,indent=0,subs="verbatim"]
----
@ -135,14 +139,19 @@ TIP: Use the prefix `optional:` if the <<features#features.external-config.files
WARNING: `spring.config.name`, `spring.config.location`, and `spring.config.additional-location` are used very early to determine which files have to be loaded.
They must be defined as an environment property (typically an OS environment variable, a system property, or a command-line argument).
If `spring.config.location` contains directories (as opposed to files), they must end in `/` or the system-dependent `File.separator`.
If `spring.config.location` contains directories (as opposed to files), they should end in `/`.
At runtime they will be appended with the names generated from `spring.config.name` before being loaded.
If `spring.config.location` contains files, they are used as-is.
Files specified in `spring.config.location` are used as-is.
Whether specified directly or contained in a directory, file references must include a file extension in their name.
Typical extensions that are supported out-of-the-box are `.properties`, `.yaml`, and `.yml`.
In most situations, each configprop:spring.config.location[] item you add will reference a single file or directory.
Locations are processed in the order that they are defined and later ones can override the values of earlier ones.
When multiple locations are specified, the later ones can override the values of earlier ones.
[[features.external-config.files.location-groups]]
If you have a complex location setup, and you use profile-specific configuration files, you may need to provide further hints so that Spring Boot knows how they should be grouped.
A location group is a collection of locations that are all considered at the same level.
For example, you might want to group all classpath locations, then all external locations.
Items within a location group should be separated with `;`.
See the example in the "`<<features.external-config.files.profile-specific>>`" section for more details.
Locations configured by using `spring.config.location` replace the default locations.
For example, if `spring.config.location` is configured with the value `optional:classpath:/custom-config/,optional:file:./custom-config/`, the complete set of locations considered is:
@ -154,11 +163,8 @@ If you prefer to add additional locations, rather than replacing them, you can u
Properties loaded from additional locations can override those in the default locations.
For example, if `spring.config.additional-location` is configured with the value `optional:classpath:/custom-config/,optional:file:./custom-config/`, the complete set of locations considered is:
. `optional:classpath:/`
. `optional:classpath:/config/`
. `optional:file:./`
. `optional:file:./config/`
. `optional:file:./config/*/`
. `optional:classpath:/;optional:classpath:/config/`
. `optional:file:./;optional:file:./config/;optional:file:./config/*/`
. `optional:classpath:custom-config/`
. `optional:file:./custom-config/`
@ -219,6 +225,35 @@ Profile-specific properties are loaded from the same locations as standard `appl
If several profiles are specified, a last-wins strategy applies.
For example, if profiles `prod,live` are specified by the configprop:spring.profiles.active[] property, values in `application-prod.properties` can be overridden by those in `application-live.properties`.
[NOTE]
====
The last-wins strategy applies at the <<features.external-config.files.location-groups,location group>> level.
A configprop:spring.config.location[] of `classpath:/cfg/,classpath:/ext/` will not have the same override rules as `classpath:/cfg/;classpath:/ext/`.
For example, continuing our `prod,live` example above, we might have the following files:
----
/cfg
application-live.properties
/ext
application-live.properties
application-prod.properties
----
When we have a configprop:spring.config.location[] of `classpath:/cfg/,classpath:/ext/` we process all `/cfg` files before all `/ext` files:
. `/cfg/application-live.properties`
. `/ext/application-prod.properties`
. `/ext/application-live.properties`
When we have `classpath:/cfg/;classpath:/ext/` instead (with a `;` delimiter) we process `/cfg` and `/ext` at the same level:
. `/ext/application-prod.properties`
. `/cfg/application-live.properties`
. `/ext/application-live.properties`
====
The `Environment` has a set of default profiles (by default, `[default]`) that are used if no active profiles are set.
In other words, if no profiles are explicitly activated, then properties from `application-default` are considered.

@ -87,11 +87,8 @@ class ConfigDataEnvironment {
static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
static {
List<ConfigDataLocation> locations = new ArrayList<>();
locations.add(ConfigDataLocation.of("optional:classpath:/"));
locations.add(ConfigDataLocation.of("optional:classpath:/config/"));
locations.add(ConfigDataLocation.of("optional:file:./"));
locations.add(ConfigDataLocation.of("optional:file:./config/"));
locations.add(ConfigDataLocation.of("optional:file:./config/*/"));
locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
}

@ -97,6 +97,32 @@ public final class ConfigDataLocation implements OriginProvider {
return this.origin;
}
/**
* Return an array of {@link ConfigDataLocation} elements built by splitting this
* {@link ConfigDataLocation} around a delimiter of {@code ";"}.
* @return the split locations
* @since 2.4.7
*/
public ConfigDataLocation[] split() {
return split(";");
}
/**
* Return an array of {@link ConfigDataLocation} elements built by splitting this
* {@link ConfigDataLocation} around the specified delimiter.
* @param delimiter the delimiter to split on
* @return the split locations
* @since 2.4.7
*/
public ConfigDataLocation[] split(String delimiter) {
String[] values = StringUtils.delimitedListToStringArray(toString(), delimiter);
ConfigDataLocation[] result = new ConfigDataLocation[values.length];
for (int i = 0; i < values.length; i++) {
result[i] = of(values[i]).withOrigin(getOrigin());
}
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {

@ -116,7 +116,16 @@ public class StandardConfigDataLocationResolver
@Override
public List<StandardConfigDataResource> resolve(ConfigDataLocationResolverContext context,
ConfigDataLocation location) throws ConfigDataNotFoundException {
return resolve(getReferences(context, location));
return resolve(getReferences(context, location.split()));
}
private Set<StandardConfigDataReference> getReferences(ConfigDataLocationResolverContext context,
ConfigDataLocation[] configDataLocations) {
Set<StandardConfigDataReference> references = new LinkedHashSet<>();
for (ConfigDataLocation configDataLocation : configDataLocations) {
references.addAll(getReferences(context, configDataLocation));
}
return references;
}
private Set<StandardConfigDataReference> getReferences(ConfigDataLocationResolverContext context,
@ -139,15 +148,17 @@ public class StandardConfigDataLocationResolver
if (context.getParent() != null) {
return null;
}
return resolve(getProfileSpecificReferences(context, location, profiles));
return resolve(getProfileSpecificReferences(context, location.split(), profiles));
}
private Set<StandardConfigDataReference> getProfileSpecificReferences(ConfigDataLocationResolverContext context,
ConfigDataLocation configDataLocation, Profiles profiles) {
ConfigDataLocation[] configDataLocations, Profiles profiles) {
Set<StandardConfigDataReference> references = new LinkedHashSet<>();
String resourceLocation = getResourceLocation(context, configDataLocation);
for (String profile : profiles) {
references.addAll(getReferences(configDataLocation, resourceLocation, profile));
for (ConfigDataLocation configDataLocation : configDataLocations) {
String resourceLocation = getResourceLocation(context, configDataLocation);
references.addAll(getReferences(configDataLocation, resourceLocation, profile));
}
}
return references;
}

@ -772,6 +772,21 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
assertThat(environment.containsProperty("test:boot:ps")).isFalse();
}
@Test // gh-26593
void runWhenHasFilesInRootAndConfigWithProfiles() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.name=file-in-root-and-config-with-profile", "--spring.profiles.active=p1,p2");
ConfigurableEnvironment environment = context.getEnvironment();
assertThat(environment.containsProperty("file-in-root-and-config-with-profile")).isTrue();
assertThat(environment.containsProperty("file-in-root-and-config-with-profile-p1")).isTrue();
assertThat(environment.containsProperty("file-in-root-and-config-with-profile-p2")).isTrue();
assertThat(environment.containsProperty("config-file-in-root-and-config-with-profile")).isTrue();
assertThat(environment.containsProperty("config-file-in-root-and-config-with-profile-p1")).isTrue();
assertThat(environment.containsProperty("config-file-in-root-and-config-with-profile-p2")).isTrue();
assertThat(environment.getProperty("v1")).isEqualTo("config-file-in-root-and-config-with-profile-p2");
assertThat(environment.getProperty("v2")).isEqualTo("file-in-root-and-config-with-profile-p2");
}
private Condition<ConfigurableEnvironment> matchingPropertySource(final String sourceName) {
return new Condition<ConfigurableEnvironment>("environment containing property source " + sourceName) {

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -134,4 +134,36 @@ class ConfigDataLocationTests {
assertThat(ConfigDataLocation.of("test")).hasToString("test");
}
@Test
void splitWhenNoSemiColonReturnsSingleElement() {
ConfigDataLocation location = ConfigDataLocation.of("test");
ConfigDataLocation[] split = location.split();
assertThat(split).containsExactly(ConfigDataLocation.of("test"));
}
@Test
void splitWhenSemiColonReturnsElements() {
ConfigDataLocation location = ConfigDataLocation.of("one;two;three");
ConfigDataLocation[] split = location.split();
assertThat(split).containsExactly(ConfigDataLocation.of("one"), ConfigDataLocation.of("two"),
ConfigDataLocation.of("three"));
}
@Test
void splitOnCharReturnsElements() {
ConfigDataLocation location = ConfigDataLocation.of("one::two::three");
ConfigDataLocation[] split = location.split("::");
assertThat(split).containsExactly(ConfigDataLocation.of("one"), ConfigDataLocation.of("two"),
ConfigDataLocation.of("three"));
}
@Test
void splitWhenHasOriginReturnsElementsWithOriginSet() {
Origin origin = mock(Origin.class);
ConfigDataLocation location = ConfigDataLocation.of("a;b").withOrigin(origin);
ConfigDataLocation[] split = location.split();
assertThat(split[0].getOrigin()).isEqualTo(origin);
assertThat(split[1].getOrigin()).isEqualTo(origin);
}
}

@ -0,0 +1,3 @@
config-file-in-root-and-config-with-profile-p1=true
v1=config-file-in-root-and-config-with-profile-p1
#v2 intentionally missing

@ -0,0 +1,3 @@
config-file-in-root-and-config-with-profile-p2=true
v1=config-file-in-root-and-config-with-profile-p2
#v2 intentionally missing

@ -0,0 +1,3 @@
config-file-in-root-and-config-with-profile=true
v1=config-file-in-root-and-config-with-profile
v2=config-file-in-root-and-config-with-profile

@ -0,0 +1,3 @@
file-in-root-and-config-with-profile-p1=true
v1=file-in-root-and-config-with-profile-p1
v2=file-in-root-and-config-with-profile-p1

@ -0,0 +1,3 @@
file-in-root-and-config-with-profile-p2=true
v1=file-in-root-and-config-with-profile-p2
v2=file-in-root-and-config-with-profile-p2

@ -0,0 +1,3 @@
file-in-root-and-config-with-profile=true
v1=file-in-root-and-config-with-profile
v2=file-in-root-and-config-with-profile
Loading…
Cancel
Save