Browse Source

Fix @PropertySource bug with multiple values

Prior to this commit, specifying a named @PropertySource with multiple
values would not work as expected. e.g.:

  @PropertySource(
      name = "ps",
      value = { "classpath:a.properties", "classpath:b.properties" })

In this scenario, the implementation would register a.properties with
the name "ps", and subsequently register b.properties with the name
"ps", overwriting the entry for a.properties.

To fix this behavior, a CompositePropertySource type has been introduced
which accepts a single name and a set of PropertySource objects to
iterate over. ConfigurationClassParser's @PropertySource parsing routine
has been updated to use this composite approach when necessary, i.e.
when both an explicit name and more than one location have been
specified.

Note that if no explicit name is specified, the generated property
source names are enough to distinguish the instances and avoid
overwriting each other; this is why the composite wrapper is not used
in these cases.

Issue: SPR-9127
pull/47/head
Chris Beams 14 years ago
parent
commit
41ade68b50
  1. 27
      org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
  2. 34
      org.springframework.context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java
  3. 3
      org.springframework.context/src/test/java/org/springframework/context/annotation/p1.properties
  4. 3
      org.springframework.context/src/test/java/org/springframework/context/annotation/p2.properties
  5. 65
      org.springframework.core/src/main/java/org/springframework/core/env/CompositePropertySource.java

27
org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java

@ -38,6 +38,7 @@ import org.springframework.beans.factory.support.BeanDefinitionReader; @@ -38,6 +38,7 @@ import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ResourceLoader;
@ -179,13 +180,27 @@ class ConfigurationClassParser { @@ -179,13 +180,27 @@ class ConfigurationClassParser {
if (propertySource != null) {
String name = propertySource.getString("name");
String[] locations = propertySource.getStringArray("value");
int nLocations = locations.length;
for (int i = 0; i < nLocations; i++) {
locations[0] = this.environment.resolveRequiredPlaceholders(locations[0]);
}
ClassLoader classLoader = this.resourceLoader.getClassLoader();
for (String location : locations) {
location = this.environment.resolveRequiredPlaceholders(location);
ResourcePropertySource ps = StringUtils.hasText(name) ?
new ResourcePropertySource(name, location, classLoader) :
new ResourcePropertySource(location, classLoader);
this.propertySources.push(ps);
if (!StringUtils.hasText(name)) {
for (String location : locations) {
this.propertySources.push(new ResourcePropertySource(location, classLoader));
}
}
else {
if (nLocations == 1) {
this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader));
}
else {
CompositePropertySource ps = new CompositePropertySource(name);
for (String location : locations) {
ps.addPropertySource(new ResourcePropertySource(location, classLoader));
}
this.propertySources.push(ps);
}
}
}

34
org.springframework.context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java

@ -118,6 +118,29 @@ public class PropertySourceAnnotationTests { @@ -118,6 +118,29 @@ public class PropertySourceAnnotationTests {
System.clearProperty("path.to.properties");
}
/**
* Corner bug reported in SPR-9127.
*/
@Test
public void withNameAndMultipleResourceLocations() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithNameAndMultipleResourceLocations.class);
ctx.refresh();
assertThat(ctx.getEnvironment().containsProperty("from.p1"), is(true));
assertThat(ctx.getEnvironment().containsProperty("from.p2"), is(true));
}
@Configuration
@PropertySource(
name = "psName",
value = {
"classpath:org/springframework/context/annotation/p1.properties",
"classpath:org/springframework/context/annotation/p2.properties"
})
static class ConfigWithNameAndMultipleResourceValues {
}
@Configuration
@PropertySource(value="classpath:${unresolvable}/p1.properties")
@ -178,4 +201,15 @@ public class PropertySourceAnnotationTests { @@ -178,4 +201,15 @@ public class PropertySourceAnnotationTests {
@PropertySource("classpath:org/springframework/context/annotation/p2.properties")
static class P2Config {
}
@Configuration
@PropertySource(
name = "psName",
value = {
"classpath:org/springframework/context/annotation/p1.properties",
"classpath:org/springframework/context/annotation/p2.properties"
})
static class ConfigWithNameAndMultipleResourceLocations {
}
}

3
org.springframework.context/src/test/java/org/springframework/context/annotation/p1.properties

@ -1 +1,2 @@ @@ -1 +1,2 @@
testbean.name=p1TestBean
testbean.name=p1TestBean
from.p1=p1Value

3
org.springframework.context/src/test/java/org/springframework/context/annotation/p2.properties

@ -1 +1,2 @@ @@ -1 +1,2 @@
testbean.name=p2TestBean
testbean.name=p2TestBean
from.p2=p2Value

65
org.springframework.core/src/main/java/org/springframework/core/env/CompositePropertySource.java vendored

@ -0,0 +1,65 @@ @@ -0,0 +1,65 @@
/*
* Copyright 2002-2012 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.core.env;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Composite {@link PropertySource} implementation that iterates over a set of
* {@link PropertySource} instances. Necessary in cases where multiple property sources
* share the same name, e.g. when multiple values are supplied to {@code @PropertySource}.
*
* @author Chris Beams
* @since 3.1.1
*/
public class CompositePropertySource extends PropertySource<Object> {
private Set<PropertySource<?>> propertySources = new LinkedHashSet<PropertySource<?>>();
/**
* Create a new {@code CompositePropertySource}.
*
* @param name the name of the property source
*/
public CompositePropertySource(String name) {
super(name);
}
@Override
public Object getProperty(String name) {
for (PropertySource<?> propertySource : this.propertySources) {
Object candidate = propertySource.getProperty(name);
if (candidate != null) {
return candidate;
}
}
return null;
}
public void addPropertySource(PropertySource<?> propertySource) {
this.propertySources.add(propertySource);
}
@Override
public String toString() {
return String.format("%s [name='%s', propertySources=%s]",
this.getClass().getSimpleName(), this.name, this.propertySources);
}
}
Loading…
Cancel
Save