From 48636e3d6efe3295e3cedea8ea3c18481c1b70ec Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 21 Mar 2014 12:16:59 +0000 Subject: [PATCH] Add additional grouping of property sources by profile Before this change the PropertySources loaded from external config files were just added to the list for resolution in the order that they were loaded. That worked for simple cases, but when there are profiles active, and files themselves can activate profiles, it led to users not being able to change default settings easily (either on command line or in files, mostly in files). The solution proposed here is to group PropertySources by profile and resolve them in order of profile first, and then in order of the files being loaded. There are additional shenanigans because the order of the files being loaded also has to be carefully defined. The rule for users is that in a list of files to load (e.g. if set via spring.config.location), the last one wins (natural if you think of it as a merge of multiple maps). In addition, anything specified by a user takes precedence over the defaults (which was broken in some scenarios before). Additionally, fixes profile ordering in @ConfigurationProperties(path=...) Fixes gh-483 --- .../main/asciidoc/spring-boot-features.adoc | 22 ++++- .../config/ConfigFileApplicationListener.java | 89 ++++++++++------- ...urationPropertiesBindingPostProcessor.java | 8 +- .../EnumerableCompositePropertySource.java | 78 +++++++++++++++ .../boot/env/PropertySourcesLoader.java | 95 ++++++++++++++++--- .../ConfigFileApplicationListenerTests.java | 61 ++++++++---- .../EnableConfigurationPropertiesTests.java | 11 ++- 7 files changed, 293 insertions(+), 71 deletions(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/env/EnumerableCompositePropertySource.java diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 80ceffe7c07..6c8a9ae5a53 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -249,10 +249,10 @@ them using `SpringApplication.setAddCommandLineProperties(false)`. `SpringApplication` will load properties from `application.properties` files in the following locations and add them to the Spring `Environment`: -. The current directory . A `/config` subdir of the current directory. -. The classpath root +. The current directory . A classpath `/config` package +. The classpath root The list is ordered by precedence (locations higher in the list override lower items). @@ -261,13 +261,29 @@ an alternative to '.properties'. If you don't like `application.properties` as the configuration file name you can switch to another by specifying a `spring.config.name` environment property. You can also refer -to an explicit location using the `spring.config.location` environment property. +to an explicit location using the `spring.config.location` environment property (comma- +separated list of directory locations, or file paths). [indent=0] ---- $ java -jar myproject.jar --spring.config.name=myproject ---- +or + +[indent=0] +---- + $ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties +---- + +If `spring.config.location` contains directories (as opposed to files) +they should end in "/" (and will be appended with the names generated +from `spring.config.name` before being loaded). The default search +path `classpath:,classpath:/config,file:,file:config/` is always used, +irrespective of the value of `spring.config.location`. In that way you +can set up default values for your app in `application.properties` (or +whatever other basename you choose with `spring.config.name`) and +override it at runtime with a different file, keeping the defaults. [[boot-features-external-config-profile-specific-properties]] diff --git a/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java b/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java index 4f40702683c..96b39121b19 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java @@ -35,6 +35,7 @@ import org.springframework.boot.bind.PropertySourcesPropertyValues; import org.springframework.boot.bind.RelaxedDataBinder; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent; +import org.springframework.boot.env.EnumerableCompositePropertySource; import org.springframework.boot.env.PropertySourcesLoader; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; @@ -98,8 +99,8 @@ public class ConfigFileApplicationListener implements private static final String CONFIG_LOCATION_PROPERTY = "spring.config.location"; - private static final String DEFAULT_SEARCH_LOCATIONS = "file:./config/,file:./," - + "classpath:/config/,classpath:/"; + // Note the order is from least to most specific (last one wins) + private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"; private static final String DEFAULT_NAMES = "application"; @@ -197,8 +198,8 @@ public class ConfigFileApplicationListener implements * search location should be a directory path (ending in "/") and it will be prefixed * by the file names constructed from {@link #setSearchNames(String) search names} and * profiles (if any) plus file extensions supported by the properties loaders. - * Locations are considered in the order specified, with earlier items taking - * precedence. + * Locations are considered in the order specified, with later items taking precedence + * (like a map merge). */ public void setSearchLocations(String locations) { Assert.hasLength(locations, "Locations must not be empty"); @@ -288,15 +289,22 @@ public class ConfigFileApplicationListener implements } // The default profile for these purposes is represented as null. We add it - // last so that it is first out (active profiles will then override any - // settings in the defaults when the list is reversed later). + // last so that it is first out of the queue (active profiles will then + // override any settings in the defaults when the list is reversed later). this.profiles.add(null); while (!this.profiles.isEmpty()) { String profile = this.profiles.poll(); for (String location : getSearchLocations()) { - for (String name : getSearchNames()) { - load(location, name, profile); + if (!location.endsWith("/")) { + // location is a filename already, so don't search for more + // filenames + load(location, null, profile); + } + else { + for (String name : getSearchNames()) { + load(location, name, profile); + } } } } @@ -307,30 +315,39 @@ public class ConfigFileApplicationListener implements private void load(String location, String name, String profile) throws IOException { - // Try to load directly from the location - PropertySource locationPropertySource = load(location, profile); + String group = "profile=" + (profile == null ? "" : profile); - // If that fails, try a search - if (locationPropertySource == null) { + if (!StringUtils.hasText(name)) { + // Try to load directly from the location + loadIntoGroup(group, location, profile); + } + else { + // Search for a file with the given name for (String ext : this.propertiesLoader.getAllFileExtensions()) { if (profile != null) { // Try the profile specific file - load(location + name + "-" + profile + "." + ext, null); - load(location + name + "-" + profile + "." + ext, profile); + loadIntoGroup(group, location + name + "-" + profile + "." + ext, + null); + // Sometimes people put "spring.profiles: dev" in + // application-dev.yml (gh-340). Arguably we should try and error + // out on that, but we can be kind and load it anyway. + loadIntoGroup(group, location + name + "-" + profile + "." + ext, + profile); } - // Try the profile (if any) specific section of the normal file - load(location + name + "." + ext, profile); + // Also try the profile specific section (if any) of the normal file + loadIntoGroup(group, location + name + "." + ext, profile); } } } - private PropertySource load(String resourceLocation, String profile) - throws IOException { - Resource resource = this.resourceLoader.getResource(resourceLocation); + private PropertySource loadIntoGroup(String identifier, String location, + String profile) throws IOException { + Resource resource = this.resourceLoader.getResource(location); if (resource != null) { - String name = "applicationConfig: " + resource.getDescription(); + String name = "applicationConfig: [" + location + "]"; + String group = "applicationConfig: [" + identifier + "]"; PropertySource propertySource = this.propertiesLoader.load(resource, - name, profile); + group, name, profile); if (propertySource != null) { maybeActivateProfiles(propertySource .getProperty(ACTIVE_PROFILES_PROPERTY)); @@ -381,11 +398,9 @@ public class ConfigFileApplicationListener implements environment.setActiveProfiles(profiles.toArray(new String[profiles.size()])); } - public Set getSearchLocations() { + private Set getSearchLocations() { Set locations = new LinkedHashSet(); - locations.addAll(asResolvedSet( - ConfigFileApplicationListener.this.searchLocations, - DEFAULT_SEARCH_LOCATIONS)); + // User-configured settings take precedence, so we do them first if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { for (String path : asResolvedSet( this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) { @@ -398,10 +413,13 @@ public class ConfigFileApplicationListener implements locations.add(path); } } + locations.addAll(asResolvedSet( + ConfigFileApplicationListener.this.searchLocations, + DEFAULT_SEARCH_LOCATIONS)); return locations; } - public Set getSearchNames() { + private Set getSearchNames() { if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY), null); @@ -410,16 +428,10 @@ public class ConfigFileApplicationListener implements } private Set asResolvedSet(String value, String fallback) { - return asResolvedSet(value, fallback, true); - } - - private Set asResolvedSet(String value, String fallback, boolean reverse) { List list = Arrays.asList(StringUtils .commaDelimitedListToStringArray(value != null ? this.environment .resolvePlaceholders(value) : fallback)); - if (reverse) { - Collections.reverse(list); - } + Collections.reverse(list); return new LinkedHashSet(list); } @@ -428,7 +440,6 @@ public class ConfigFileApplicationListener implements for (PropertySource item : sources) { reorderedSources.add(item); } - Collections.reverse(reorderedSources); this.environment.getPropertySources().addLast( new ConfigurationPropertySources(reorderedSources)); } @@ -477,7 +488,15 @@ public class ConfigFileApplicationListener implements .remove(ConfigurationPropertySources.NAME); if (removed != null) { for (PropertySource propertySource : removed.sources) { - propertySources.addLast(propertySource); + if (propertySource instanceof EnumerableCompositePropertySource) { + EnumerableCompositePropertySource composite = (EnumerableCompositePropertySource) propertySource; + for (PropertySource nested : composite.getSource()) { + propertySources.addLast(nested); + } + } + else { + propertySources.addLast(propertySource); + } } } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java index a7d9f2ea35c..44432d7e28a 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.java @@ -338,10 +338,12 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc for (String location : locations) { Resource resource = this.resourceLoader.getResource(this.environment .resolvePlaceholders(location)); - for (String profile : this.environment.getActiveProfiles()) { - loader.load(resource, null, profile); + String[] profiles = this.environment.getActiveProfiles(); + for (int i = profiles.length; i-- > 0;) { + String profile = profiles[i]; + loader.load(resource, profile); } - loader.load(resource, null, null); + loader.load(resource); } return loader.getPropertySources(); } diff --git a/spring-boot/src/main/java/org/springframework/boot/env/EnumerableCompositePropertySource.java b/spring-boot/src/main/java/org/springframework/boot/env/EnumerableCompositePropertySource.java new file mode 100644 index 00000000000..9feaff94a6d --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/env/EnumerableCompositePropertySource.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012-2013 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 + * + * http://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.env; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; + +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.PropertySource; + +/** + * An mutable, enumerable, composite property source. New sources are added last (and + * hence resolved with lowest priority). + * + * @see PropertySource + * @see EnumerablePropertySource + * + * @author Dave Syer + */ +public class EnumerableCompositePropertySource extends + EnumerablePropertySource>> { + + private volatile String[] names; + + public EnumerableCompositePropertySource(String sourceName) { + super(sourceName, new LinkedHashSet>()); + } + + @Override + public Object getProperty(String name) { + for (PropertySource propertySource : getSource()) { + Object value = propertySource.getProperty(name); + if (value != null) { + return value; + } + } + return null; + } + + @Override + public String[] getPropertyNames() { + String[] result = this.names; + if (result == null) { + List names = new ArrayList(); + for (PropertySource source : new ArrayList>(getSource())) { + if (source instanceof EnumerablePropertySource) { + names.addAll(Arrays.asList(((EnumerablePropertySource) source) + .getPropertyNames())); + } + } + this.names = names.toArray(new String[0]); + result = this.names; + } + return result; + } + + public void add(PropertySource source) { + getSource().add(source); + this.names = null; + } +} diff --git a/spring-boot/src/main/java/org/springframework/boot/env/PropertySourcesLoader.java b/spring-boot/src/main/java/org/springframework/boot/env/PropertySourcesLoader.java index 25e9fd55e1b..4881ba0c581 100644 --- a/spring-boot/src/main/java/org/springframework/boot/env/PropertySourcesLoader.java +++ b/spring-boot/src/main/java/org/springframework/boot/env/PropertySourcesLoader.java @@ -64,6 +64,31 @@ public class PropertySourcesLoader { /** * Load the specified resource (if possible) and add it as the first source. * @param resource the source resource (may be {@code null}). + * @return the loaded property source or {@code null} + * @throws IOException + */ + public PropertySource load(Resource resource) throws IOException { + return this.load(resource, null); + } + + /** + * Load the profile-specific properties from the specified resource (if any) and add + * it as the first source. + * + * @param resource the source resource (may be {@code null}). + * @param profile a specific profile to load or {@code null} to load the default. + * @return the loaded property source or {@code null} + * @throws IOException + */ + public PropertySource load(Resource resource, String profile) throws IOException { + return this.load(resource, resource.getDescription(), profile); + } + + /** + * Load the profile-specific properties from the specified resource (if any), give the + * name provided and add it as the first source. + * + * @param resource the source resource (may be {@code null}). * @param name the root property name (may be {@code null}). * @param profile a specific profile to load or {@code null} to load the default. * @return the loaded property source or {@code null} @@ -71,13 +96,36 @@ public class PropertySourcesLoader { */ public PropertySource load(Resource resource, String name, String profile) throws IOException { + return this.load(resource, null, name, profile); + } + + /** + * Load the profile-specific properties from the specified resource (if any), give the + * name provided and add it to a group of property sources identified by the group + * name. Property sources are added to the end of a group, but new groups are added as + * the first in the chain being assembled. This means the normal sequence of calls is + * to first create the group for the default (null) profile, and then add specific + * groups afterwards (with the highest priority last). Property resolution from the + * resulting sources will consider all keys for a given group first and then move to + * the next group. + * + * @param resource the source resource (may be {@code null}). + * @param group an identifier for the group that this source belongs to + * @param name the root property name (may be {@code null}). + * @param profile a specific profile to load or {@code null} to load the default. + * @return the loaded property source or {@code null} + * @throws IOException + */ + public PropertySource load(Resource resource, String group, String name, + String profile) throws IOException { if (isFile(resource)) { - name = generatePropertySourceName(resource, name, profile); + String sourceName = generatePropertySourceName(name, profile); for (PropertySourceLoader loader : this.loaders) { if (canLoadFileExtension(loader, resource)) { - PropertySource source = loader.load(name, resource, profile); - addPropertySource(source); - return source; + PropertySource specific = loader.load(sourceName, resource, + profile); + addPropertySource(group, specific, profile); + return specific; } } } @@ -91,11 +139,7 @@ public class PropertySourcesLoader { .getFilename())); } - private String generatePropertySourceName(Resource resource, String name, - String profile) { - if (name == null) { - name = resource.getDescription(); - } + private String generatePropertySourceName(String name, String profile) { return (profile == null ? name : name + "#" + profile); } @@ -109,10 +153,37 @@ public class PropertySourcesLoader { return false; } - private void addPropertySource(PropertySource propertySource) { - if (propertySource != null) { - this.propertySources.addLast(propertySource); + private void addPropertySource(String basename, PropertySource source, + String profile) { + + if (source == null) { + return; } + + if (basename == null) { + this.propertySources.addLast(source); + return; + } + + EnumerableCompositePropertySource group = getGeneric(basename); + group.add(source); + if (this.propertySources.contains(group.getName())) { + this.propertySources.replace(group.getName(), group); + } + else { + this.propertySources.addFirst(group); + } + + } + + private EnumerableCompositePropertySource getGeneric(String name) { + PropertySource source = this.propertySources.get(name); + if (source instanceof EnumerableCompositePropertySource) { + return (EnumerableCompositePropertySource) source; + } + EnumerableCompositePropertySource composite = new EnumerableCompositePropertySource( + name); + return composite; } /** diff --git a/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java b/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java index 0e24edc573a..6f442bdd06e 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java @@ -36,6 +36,7 @@ import org.junit.rules.ExpectedException; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.config.ConfigFileApplicationListener.ConfigurationPropertySources; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.env.EnumerableCompositePropertySource; import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; @@ -91,8 +92,7 @@ public class ConfigFileApplicationListenerTests { @Test public void loadTwoPropertiesFile() throws Exception { EnvironmentTestUtils.addEnvironment(this.environment, "spring.config.location:" - + "classpath:testproperties.properties," - + "classpath:application.properties"); + + "classpath:application.properties,classpath:testproperties.properties"); this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("my.property"); assertThat(property, equalTo("frompropertiesfile")); @@ -101,8 +101,7 @@ public class ConfigFileApplicationListenerTests { @Test public void loadTwoPropertiesFilesWithProfiles() throws Exception { EnvironmentTestUtils.addEnvironment(this.environment, "spring.config.location:" - + "classpath:enableprofile.properties," - + "classpath:enableother.properties"); + + "classpath:enableprofile.properties,classpath:enableother.properties"); this.initializer.onApplicationEvent(this.event); assertEquals("other", StringUtils.arrayToCommaDelimitedString(this.environment .getActiveProfiles())); @@ -120,7 +119,25 @@ public class ConfigFileApplicationListenerTests { StringUtils.arrayToCommaDelimitedString(this.environment .getActiveProfiles())); String property = this.environment.getProperty("my.property"); - assertThat(property, equalTo("fromtwopropertiesfile")); + // The value from the second file wins (no profile specific configuration is + // actually loaded) + assertThat(property, equalTo("frompropertiesfile")); + } + + @Test + public void loadTwoPropertiesFilesWithProfilesAndSwitchOneOffFromSpecificLocation() + throws Exception { + EnvironmentTestUtils.addEnvironment(this.environment, + "spring.config.name:enabletwoprofiles", + "spring.config.location:classpath:enableprofile.properties"); + this.initializer.onApplicationEvent(this.event); + assertEquals("myprofile", + StringUtils.arrayToCommaDelimitedString(this.environment + .getActiveProfiles())); + String property = this.environment.getProperty("my.property"); + // The value from the second file wins (no profile specific configuration is + // actually loaded) + assertThat(property, equalTo("frompropertiesfile")); } @Test @@ -158,8 +175,8 @@ public class ConfigFileApplicationListenerTests { @Test public void loadTwoOfThreePropertiesFile() throws Exception { EnvironmentTestUtils.addEnvironment(this.environment, "spring.config.location:" - + "classpath:testproperties.properties," + "classpath:application.properties," + + "classpath:testproperties.properties," + "classpath:nonexistent.properties"); this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("my.property"); @@ -178,7 +195,8 @@ public class ConfigFileApplicationListenerTests { this.initializer.setSearchNames("moreproperties,testproperties"); this.initializer.onApplicationEvent(this.event); String property = this.environment.getProperty("my.property"); - assertThat(property, equalTo("frommorepropertiesfile")); + // The search order has highest precedence last (like merging a map) + assertThat(property, equalTo("frompropertiesfile")); } @Test @@ -273,6 +291,8 @@ public class ConfigFileApplicationListenerTests { public void yamlSetsProfiles() throws Exception { this.initializer.setSearchNames("testsetprofiles"); this.initializer.onApplicationEvent(this.event); + assertEquals("dev", StringUtils.arrayToCommaDelimitedString(this.environment + .getActiveProfiles())); String property = this.environment.getProperty("my.property"); assertThat(Arrays.asList(this.environment.getActiveProfiles()), contains("dev")); assertThat(property, equalTo("fromdevprofile")); @@ -283,13 +303,20 @@ public class ConfigFileApplicationListenerTests { assertEquals(2, sources.size()); List names = new ArrayList(); for (org.springframework.core.env.PropertySource source : sources) { - names.add(source.getName()); + if (source instanceof EnumerableCompositePropertySource) { + for (org.springframework.core.env.PropertySource nested : ((EnumerableCompositePropertySource) source) + .getSource()) { + names.add(nested.getName()); + } + } + else { + names.add(source.getName()); + } } assertThat( names, - contains( - "applicationConfig: class path resource [testsetprofiles.yml]#dev", - "applicationConfig: class path resource [testsetprofiles.yml]")); + contains("applicationConfig: [classpath:/testsetprofiles.yml]#dev", + "applicationConfig: [classpath:/testsetprofiles.yml]")); } @Test @@ -320,10 +347,10 @@ public class ConfigFileApplicationListenerTests { String property = this.environment.getProperty("my.property"); assertThat(property, equalTo("fromspecificlocation")); assertThat(this.environment, containsProperySource("applicationConfig: " - + "class path resource [specificlocation.properties]")); + + "[classpath:specificlocation.properties]")); // The default property source is still there assertThat(this.environment, containsProperySource("applicationConfig: " - + "class path resource [application.properties]")); + + "[classpath:/application.properties]")); assertThat(this.environment.getProperty("foo"), equalTo("bucket")); } @@ -333,8 +360,8 @@ public class ConfigFileApplicationListenerTests { EnvironmentTestUtils.addEnvironment(this.environment, "spring.config.location:" + location); this.initializer.onApplicationEvent(this.event); - assertThat(this.environment, containsProperySource("applicationConfig: " - + "URL [" + location + "]")); + assertThat(this.environment, containsProperySource("applicationConfig: [" + + location + "]")); } @Test @@ -343,8 +370,8 @@ public class ConfigFileApplicationListenerTests { EnvironmentTestUtils.addEnvironment(this.environment, "spring.config.location:" + location); this.initializer.onApplicationEvent(this.event); - assertThat(this.environment, containsProperySource("applicationConfig: " - + "URL [file:" + location + "]")); + assertThat(this.environment, containsProperySource("applicationConfig: [file:" + + location + "]")); } @Test diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesTests.java index f4ea7db23dc..37688c33a2b 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesTests.java @@ -277,6 +277,16 @@ public class EnableConfigurationPropertiesTests { assertEquals("bar", this.context.getBean(ResourceBindingProperties.class).name); } + @Test + public void testBindingDirectlyToFileWithTwoExplicitSpringProfiles() { + this.context.register(ResourceBindingProperties.class, TestConfiguration.class); + this.context.getEnvironment().setActiveProfiles("super", "other"); + this.context.refresh(); + assertEquals(1, + this.context.getBeanNamesForType(ResourceBindingProperties.class).length); + assertEquals("spam", this.context.getBean(ResourceBindingProperties.class).name); + } + @Test public void testBindingWithTwoBeans() { this.context.register(MoreConfiguration.class, TestConfiguration.class); @@ -357,7 +367,6 @@ public class EnableConfigurationPropertiesTests { assertEquals("value3", bean.mymap.get("key3")); // this should not fail!!! // mymap looks to contain - {key1=, key3=value3} - System.err.println(bean.mymap); assertEquals("value12", bean.mymap.get("key1.key2")); }