Skip to content

Commit 41ade68

Browse files
committed
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
1 parent ff862ad commit 41ade68

File tree

5 files changed

+124
-8
lines changed

5 files changed

+124
-8
lines changed

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3939
import org.springframework.beans.factory.support.BeanNameGenerator;
4040
import org.springframework.core.annotation.AnnotationAttributes;
41+
import org.springframework.core.env.CompositePropertySource;
4142
import org.springframework.core.env.Environment;
4243
import org.springframework.core.env.PropertySource;
4344
import org.springframework.core.io.ResourceLoader;
@@ -179,13 +180,27 @@ protected AnnotationMetadata doProcessConfigurationClass(
179180
if (propertySource != null) {
180181
String name = propertySource.getString("name");
181182
String[] locations = propertySource.getStringArray("value");
183+
int nLocations = locations.length;
184+
for (int i = 0; i < nLocations; i++) {
185+
locations[0] = this.environment.resolveRequiredPlaceholders(locations[0]);
186+
}
182187
ClassLoader classLoader = this.resourceLoader.getClassLoader();
183-
for (String location : locations) {
184-
location = this.environment.resolveRequiredPlaceholders(location);
185-
ResourcePropertySource ps = StringUtils.hasText(name) ?
186-
new ResourcePropertySource(name, location, classLoader) :
187-
new ResourcePropertySource(location, classLoader);
188-
this.propertySources.push(ps);
188+
if (!StringUtils.hasText(name)) {
189+
for (String location : locations) {
190+
this.propertySources.push(new ResourcePropertySource(location, classLoader));
191+
}
192+
}
193+
else {
194+
if (nLocations == 1) {
195+
this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader));
196+
}
197+
else {
198+
CompositePropertySource ps = new CompositePropertySource(name);
199+
for (String location : locations) {
200+
ps.addPropertySource(new ResourcePropertySource(location, classLoader));
201+
}
202+
this.propertySources.push(ps);
203+
}
189204
}
190205
}
191206

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,29 @@ public void withResolvablePlaceholder() {
118118
System.clearProperty("path.to.properties");
119119
}
120120

121+
/**
122+
* Corner bug reported in SPR-9127.
123+
*/
124+
@Test
125+
public void withNameAndMultipleResourceLocations() {
126+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
127+
ctx.register(ConfigWithNameAndMultipleResourceLocations.class);
128+
ctx.refresh();
129+
assertThat(ctx.getEnvironment().containsProperty("from.p1"), is(true));
130+
assertThat(ctx.getEnvironment().containsProperty("from.p2"), is(true));
131+
}
132+
133+
134+
@Configuration
135+
@PropertySource(
136+
name = "psName",
137+
value = {
138+
"classpath:org/springframework/context/annotation/p1.properties",
139+
"classpath:org/springframework/context/annotation/p2.properties"
140+
})
141+
static class ConfigWithNameAndMultipleResourceValues {
142+
}
143+
121144

122145
@Configuration
123146
@PropertySource(value="classpath:${unresolvable}/p1.properties")
@@ -178,4 +201,15 @@ public TestBean testBean() {
178201
@PropertySource("classpath:org/springframework/context/annotation/p2.properties")
179202
static class P2Config {
180203
}
204+
205+
206+
@Configuration
207+
@PropertySource(
208+
name = "psName",
209+
value = {
210+
"classpath:org/springframework/context/annotation/p1.properties",
211+
"classpath:org/springframework/context/annotation/p2.properties"
212+
})
213+
static class ConfigWithNameAndMultipleResourceLocations {
214+
}
181215
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
testbean.name=p1TestBean
1+
testbean.name=p1TestBean
2+
from.p1=p1Value
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
testbean.name=p2TestBean
1+
testbean.name=p2TestBean
2+
from.p2=p2Value
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.env;
18+
19+
import java.util.LinkedHashSet;
20+
import java.util.Set;
21+
22+
/**
23+
* Composite {@link PropertySource} implementation that iterates over a set of
24+
* {@link PropertySource} instances. Necessary in cases where multiple property sources
25+
* share the same name, e.g. when multiple values are supplied to {@code @PropertySource}.
26+
*
27+
* @author Chris Beams
28+
* @since 3.1.1
29+
*/
30+
public class CompositePropertySource extends PropertySource<Object> {
31+
32+
private Set<PropertySource<?>> propertySources = new LinkedHashSet<PropertySource<?>>();
33+
34+
35+
/**
36+
* Create a new {@code CompositePropertySource}.
37+
*
38+
* @param name the name of the property source
39+
*/
40+
public CompositePropertySource(String name) {
41+
super(name);
42+
}
43+
44+
45+
@Override
46+
public Object getProperty(String name) {
47+
for (PropertySource<?> propertySource : this.propertySources) {
48+
Object candidate = propertySource.getProperty(name);
49+
if (candidate != null) {
50+
return candidate;
51+
}
52+
}
53+
return null;
54+
}
55+
56+
public void addPropertySource(PropertySource<?> propertySource) {
57+
this.propertySources.add(propertySource);
58+
}
59+
60+
@Override
61+
public String toString() {
62+
return String.format("%s [name='%s', propertySources=%s]",
63+
this.getClass().getSimpleName(), this.name, this.propertySources);
64+
}
65+
}

0 commit comments

Comments
 (0)