Browse Source
Update the `ConfigData` import support to allow individual property sources to be imported with a higher precedence than profile specific imports. Prior to this commit, imported sources would always have a higher precedence than the file that imported them, but a lower precedence than any profile-specific variant of the same file. For example, given an `application.properties` that imports `myconfig`, the contributor tree would be as follows: ROOT +- `application.properties` | +- myconfig +- `application-<profile>.properties` The precedence would be: 1) `application-<profile>.properties` 2) myconfig 3) `application.properties` This works well for most situations, but can be confusing if import is for a profile-specific property source. For example: ROOT +- `application.properties` | +- myconfig | +- myconfig-<profile> +- `application-<profile>.properties` Results in the order precedence of: 1) `application-<profile>.properties` 2) myconfig-<profile> 3) myconfig 4) `application.properties` This means that whilst `myconfig` overrides `application.properties`, `myconfig-profile` does not override `application-<profile>.properties`. For this specific situation, the preferable order would be: 1) myconfig-<profile> 2) `application-<profile>.properties` 3) myconfig 4) `application.properties` To support this alternative ordering a new `PROFILE_SPECIFIC` config data option has been added. Additionally, options may now be specified on a per-source basis by using the `PropertySourceOptions` interface. Fixes gh-25766pull/26435/head
11 changed files with 523 additions and 47 deletions
@ -0,0 +1,132 @@
@@ -0,0 +1,132 @@
|
||||
/* |
||||
* 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. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.boot.context.config; |
||||
|
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.util.ArrayList; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.io.TempDir; |
||||
|
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.WebApplicationType; |
||||
import org.springframework.boot.context.config.ConfigData.Option; |
||||
import org.springframework.boot.context.config.ConfigData.Options; |
||||
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessorIntegrationTests.Config; |
||||
import org.springframework.context.ConfigurableApplicationContext; |
||||
import org.springframework.core.env.MapPropertySource; |
||||
import org.springframework.core.env.PropertySource; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Integration tests for {@link ConfigDataEnvironmentPostProcessor} config data imports |
||||
* that are combined with profile-specific files. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
class ConfigDataEnvironmentPostProcessorImportCombinedWithProfileSpecificIntegrationTests { |
||||
|
||||
private SpringApplication application; |
||||
|
||||
@TempDir |
||||
public File temp; |
||||
|
||||
@BeforeEach |
||||
void setup() { |
||||
this.application = new SpringApplication(Config.class); |
||||
this.application.setWebApplicationType(WebApplicationType.NONE); |
||||
} |
||||
|
||||
@Test |
||||
void testWithoutProfile() { |
||||
ConfigurableApplicationContext context = this.application |
||||
.run("--spring.config.name=configimportwithprofilespecific"); |
||||
String value = context.getEnvironment().getProperty("prop"); |
||||
assertThat(value).isEqualTo("fromicwps1"); |
||||
} |
||||
|
||||
@Test |
||||
void testWithProfile() { |
||||
ConfigurableApplicationContext context = this.application |
||||
.run("--spring.config.name=configimportwithprofilespecific", "--spring.profiles.active=prod"); |
||||
String value = context.getEnvironment().getProperty("prop"); |
||||
assertThat(value).isEqualTo("fromicwps2"); |
||||
} |
||||
|
||||
static class LocationResolver implements ConfigDataLocationResolver<Resource> { |
||||
|
||||
@Override |
||||
public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { |
||||
return location.hasPrefix("icwps:"); |
||||
} |
||||
|
||||
@Override |
||||
public List<Resource> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) |
||||
throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException { |
||||
return Collections.emptyList(); |
||||
} |
||||
|
||||
@Override |
||||
public List<Resource> resolveProfileSpecific(ConfigDataLocationResolverContext context, |
||||
ConfigDataLocation location, Profiles profiles) throws ConfigDataLocationNotFoundException { |
||||
return Collections.singletonList(new Resource(profiles)); |
||||
} |
||||
|
||||
} |
||||
|
||||
static class Loader implements ConfigDataLoader<Resource> { |
||||
|
||||
@Override |
||||
public ConfigData load(ConfigDataLoaderContext context, Resource resource) |
||||
throws IOException, ConfigDataResourceNotFoundException { |
||||
List<PropertySource<?>> propertySources = new ArrayList<>(); |
||||
Map<PropertySource<?>, Options> propertySourceOptions = new HashMap<>(); |
||||
propertySources.add(new MapPropertySource("icwps1", Collections.singletonMap("prop", "fromicwps1"))); |
||||
if (resource.profiles.isAccepted("prod")) { |
||||
MapPropertySource profileSpecificPropertySource = new MapPropertySource("icwps2", |
||||
Collections.singletonMap("prop", "fromicwps2")); |
||||
propertySources.add(profileSpecificPropertySource); |
||||
propertySourceOptions.put(profileSpecificPropertySource, Options.of(Option.PROFILE_SPECIFIC)); |
||||
} |
||||
return new ConfigData(propertySources, propertySourceOptions::get); |
||||
} |
||||
|
||||
} |
||||
|
||||
private static class Resource extends ConfigDataResource { |
||||
|
||||
private final Profiles profiles; |
||||
|
||||
Resource(Profiles profiles) { |
||||
this.profiles = profiles; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "icwps:"; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue