Skip to content

Commit

Permalink
Merge branch '2.4.x'
Browse files Browse the repository at this point in the history
Closes gh-26782
  • Loading branch information
philwebb committed Jun 7, 2021
2 parents e45b1b6 + 7396e1e commit abd9267
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 29 deletions.
Expand Up @@ -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"]
----
Expand All @@ -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:
Expand All @@ -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/`

Expand Down Expand Up @@ -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.

Expand Down
Expand Up @@ -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]);
}

Expand Down
Expand Up @@ -97,6 +97,32 @@ public Origin getOrigin() {
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) {
Expand Down
Expand Up @@ -116,7 +116,16 @@ public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDat
@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,
Expand All @@ -139,15 +148,17 @@ public List<StandardConfigDataResource> resolveProfileSpecific(ConfigDataLocatio
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;
}
Expand Down
Expand Up @@ -772,6 +772,21 @@ void runWhenHasProfileSpecificImportWithCustomImportDoesNotResolveProfileSpecifi
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) {

Expand Down
@@ -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.
Expand Down Expand Up @@ -134,4 +134,36 @@ void ofReturnsLocation() {
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

0 comments on commit abd9267

Please sign in to comment.