Skip to content

Commit a378912

Browse files
committed
Support for @propertysource annotations with custom implementation types
Issue: SPR-8963
1 parent 8ff9e81 commit a378912

File tree

5 files changed

+182
-25
lines changed

5 files changed

+182
-25
lines changed

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

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@
6666
import org.springframework.core.env.PropertySource;
6767
import org.springframework.core.io.Resource;
6868
import org.springframework.core.io.ResourceLoader;
69+
import org.springframework.core.io.support.DefaultPropertySourceFactory;
6970
import org.springframework.core.io.support.EncodedResource;
71+
import org.springframework.core.io.support.PropertySourceFactory;
7072
import org.springframework.core.io.support.ResourcePropertySource;
7173
import org.springframework.core.type.AnnotationMetadata;
7274
import org.springframework.core.type.MethodMetadata;
@@ -103,6 +105,8 @@
103105
*/
104106
class ConfigurationClassParser {
105107

108+
private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory();
109+
106110
private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
107111
new Comparator<ConfigurationClassParser.DeferredImportSelectorHolder>() {
108112
@Override
@@ -355,15 +359,26 @@ private void processMemberClasses(ConfigurationClass configClass, SourceClass so
355359
*/
356360
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
357361
String name = propertySource.getString("name");
362+
if (!StringUtils.hasLength(name)) {
363+
name = null;
364+
}
358365
String encoding = propertySource.getString("encoding");
366+
if (!StringUtils.hasLength(encoding)) {
367+
encoding = null;
368+
}
359369
String[] locations = propertySource.getStringArray("value");
360-
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
361370
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
371+
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
372+
373+
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
374+
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
375+
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiate(factoryClass));
376+
362377
for (String location : locations) {
363378
try {
364379
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
365380
Resource resource = this.resourceLoader.getResource(resolvedLocation);
366-
addPropertySource(createPropertySource(name, encoding, resource));
381+
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
367382
}
368383
catch (IllegalArgumentException ex) {
369384
// from resolveRequiredPlaceholders
@@ -380,34 +395,23 @@ private void processPropertySource(AnnotationAttributes propertySource) throws I
380395
}
381396
}
382397

383-
private ResourcePropertySource createPropertySource(String name, String encoding, Resource resource) throws IOException {
384-
if (StringUtils.hasText(name)) {
385-
return (StringUtils.hasText(encoding) ?
386-
new ResourcePropertySource(name, new EncodedResource(resource, encoding)) :
387-
new ResourcePropertySource(name, resource));
388-
}
389-
else {
390-
return (StringUtils.hasText(encoding) ?
391-
new ResourcePropertySource(new EncodedResource(resource, encoding)) :
392-
new ResourcePropertySource(resource));
393-
}
394-
}
395-
396-
private void addPropertySource(ResourcePropertySource propertySource) {
398+
private void addPropertySource(PropertySource<?> propertySource) {
397399
String name = propertySource.getName();
398400
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
399401
if (propertySources.contains(name) && this.propertySourceNames.contains(name)) {
400402
// We've already added a version, we need to extend it
401403
PropertySource<?> existing = propertySources.get(name);
404+
PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
405+
((ResourcePropertySource) propertySource).withResourceName() : propertySource);
402406
if (existing instanceof CompositePropertySource) {
403-
((CompositePropertySource) existing).addFirstPropertySource(propertySource.withResourceName());
407+
((CompositePropertySource) existing).addFirstPropertySource(newSource);
404408
}
405409
else {
406410
if (existing instanceof ResourcePropertySource) {
407411
existing = ((ResourcePropertySource) existing).withResourceName();
408412
}
409413
CompositePropertySource composite = new CompositePropertySource(name);
410-
composite.addPropertySource(propertySource.withResourceName());
414+
composite.addPropertySource(newSource);
411415
composite.addPropertySource(existing);
412416
propertySources.replace(name, composite);
413417
}

spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.lang.annotation.RetentionPolicy;
2424
import java.lang.annotation.Target;
2525

26+
import org.springframework.core.io.support.PropertySourceFactory;
27+
2628
/**
2729
* Annotation providing a convenient and declarative mechanism for adding a
2830
* {@link org.springframework.core.env.PropertySource PropertySource} to Spring's
@@ -133,6 +135,7 @@
133135
* javadocs for details.
134136
*
135137
* @author Chris Beams
138+
* @author Juergen Hoeller
136139
* @author Phillip Webb
137140
* @since 3.1
138141
* @see PropertySources
@@ -155,12 +158,6 @@
155158
*/
156159
String name() default "";
157160

158-
/**
159-
* A specific character encoding for the given resources, e.g. "UTF-8".
160-
* @since 4.3
161-
*/
162-
String encoding() default "";
163-
164161
/**
165162
* Indicate the resource location(s) of the properties file to be loaded.
166163
* For example, {@code "classpath:/com/myco/app.properties"} or
@@ -184,4 +181,19 @@
184181
*/
185182
boolean ignoreResourceNotFound() default false;
186183

184+
/**
185+
* A specific character encoding for the given resources, e.g. "UTF-8".
186+
* @since 4.3
187+
*/
188+
String encoding() default "";
189+
190+
/**
191+
* Specify a custom {@link PropertySourceFactory}, if any.
192+
* <p>By default, a default factory for standard resource files will be used.
193+
* @since 4.3
194+
* @see org.springframework.core.io.support.DefaultPropertySourceFactory
195+
* @see org.springframework.core.io.support.ResourcePropertySource
196+
*/
197+
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
198+
187199
}

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

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,8 +17,12 @@
1717
package org.springframework.context.annotation;
1818

1919
import java.io.FileNotFoundException;
20+
import java.io.IOException;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
2023
import java.util.Collections;
2124
import java.util.Iterator;
25+
import java.util.Properties;
2226
import javax.inject.Inject;
2327

2428
import org.junit.Rule;
@@ -27,9 +31,13 @@
2731

2832
import org.springframework.beans.factory.BeanDefinitionStoreException;
2933
import org.springframework.beans.factory.FactoryBean;
34+
import org.springframework.core.annotation.AliasFor;
3035
import org.springframework.core.env.Environment;
3136
import org.springframework.core.env.MapPropertySource;
3237
import org.springframework.core.env.MutablePropertySources;
38+
import org.springframework.core.io.support.EncodedResource;
39+
import org.springframework.core.io.support.PropertiesLoaderUtils;
40+
import org.springframework.core.io.support.PropertySourceFactory;
3341
import org.springframework.tests.sample.beans.TestBean;
3442

3543
import static org.hamcrest.CoreMatchers.*;
@@ -111,6 +119,22 @@ public void orderingIsLifo() {
111119
}
112120
}
113121

122+
@Test
123+
public void withCustomFactory() {
124+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
125+
ctx.register(ConfigWithImplicitName.class, WithCustomFactory.class);
126+
ctx.refresh();
127+
assertThat(ctx.getBean(TestBean.class).getName(), equalTo("P2TESTBEAN"));
128+
}
129+
130+
@Test
131+
public void withCustomFactoryAsMeta() {
132+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
133+
ctx.register(ConfigWithImplicitName.class, WithCustomFactoryAsMeta.class);
134+
ctx.refresh();
135+
assertThat(ctx.getBean(TestBean.class).getName(), equalTo("P2TESTBEAN"));
136+
}
137+
114138
@Test
115139
public void withUnresolvablePlaceholder() {
116140
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
@@ -354,6 +378,43 @@ static class P2Config {
354378
}
355379

356380

381+
@Configuration
382+
@PropertySource(value = "classpath:org/springframework/context/annotation/p2.properties", factory = MyCustomFactory.class)
383+
static class WithCustomFactory {
384+
}
385+
386+
387+
@Configuration
388+
@MyPropertySource(value = "classpath:org/springframework/context/annotation/p2.properties")
389+
static class WithCustomFactoryAsMeta {
390+
}
391+
392+
393+
@Retention(RetentionPolicy.RUNTIME)
394+
@PropertySource(value = {}, factory = MyCustomFactory.class)
395+
public @interface MyPropertySource {
396+
397+
@AliasFor(annotation = PropertySource.class)
398+
String value();
399+
}
400+
401+
402+
public static class MyCustomFactory implements PropertySourceFactory {
403+
404+
@Override
405+
public org.springframework.core.env.PropertySource createPropertySource(String name, EncodedResource resource) throws IOException {
406+
Properties props = PropertiesLoaderUtils.loadProperties(resource);
407+
return new org.springframework.core.env.PropertySource<Properties>("my" + name, props) {
408+
@Override
409+
public Object getProperty(String name) {
410+
String value = props.getProperty(name);
411+
return (value != null ? value.toUpperCase() : null);
412+
}
413+
};
414+
}
415+
}
416+
417+
357418
@Configuration
358419
@PropertySource(
359420
name = "psName",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2002-2016 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.io.support;
18+
19+
import java.io.IOException;
20+
21+
import org.springframework.core.env.PropertySource;
22+
23+
/**
24+
* The default implementation for {@link PropertySourceFactory},
25+
* wrapping every resource in a {@link ResourcePropertySource}.
26+
*
27+
* @author Juergen Hoeller
28+
* @since 4.3
29+
* @see PropertySourceFactory
30+
* @see ResourcePropertySource
31+
*/
32+
public class DefaultPropertySourceFactory implements PropertySourceFactory {
33+
34+
@Override
35+
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
36+
return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
37+
}
38+
39+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2002-2016 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.io.support;
18+
19+
import java.io.IOException;
20+
21+
import org.springframework.core.env.PropertySource;
22+
23+
/**
24+
* Strategy interface for creating resource-based {@link PropertySource} wrappers.
25+
*
26+
* @author Juergen Hoeller
27+
* @since 4.3
28+
* @see DefaultPropertySourceFactory
29+
*/
30+
public interface PropertySourceFactory {
31+
32+
/**
33+
* Create a {@link PropertySource} that wraps the given resource.
34+
* @param name the name of the property source
35+
* @param resource the resource (potentially encoded) to wrap
36+
* @return the new {@link PropertySource} (never {@code null})
37+
* @throws IOException if resource resolution failed
38+
*/
39+
PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException;
40+
41+
}

0 commit comments

Comments
 (0)