Introduce @PropertySource
Allows a convenient mechanism for contributing a PropertySource to the enclosing Spring Environment. See @PropertySource Javadoc for complete details and PropertySourceAnnotationTests for examples. Issue: SPR-8314
This commit is contained in:
parent
314a054a9b
commit
c8bc54e0cc
|
|
@ -27,6 +27,8 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||||
import org.springframework.beans.factory.parsing.Location;
|
import org.springframework.beans.factory.parsing.Location;
|
||||||
|
|
@ -34,8 +36,12 @@ import org.springframework.beans.factory.parsing.Problem;
|
||||||
import org.springframework.beans.factory.parsing.ProblemReporter;
|
import org.springframework.beans.factory.parsing.ProblemReporter;
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.env.MutablePropertySources;
|
||||||
|
import org.springframework.core.env.PropertySource;
|
||||||
import org.springframework.core.io.ResourceLoader;
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.core.io.ResourcePropertySource;
|
||||||
import org.springframework.core.type.AnnotationMetadata;
|
import org.springframework.core.type.AnnotationMetadata;
|
||||||
import org.springframework.core.type.MethodMetadata;
|
import org.springframework.core.type.MethodMetadata;
|
||||||
import org.springframework.core.type.StandardAnnotationMetadata;
|
import org.springframework.core.type.StandardAnnotationMetadata;
|
||||||
|
|
@ -64,6 +70,8 @@ import org.springframework.util.StringUtils;
|
||||||
*/
|
*/
|
||||||
class ConfigurationClassParser {
|
class ConfigurationClassParser {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory.getLog(ConfigurationClassParser.class);
|
||||||
|
|
||||||
private final MetadataReaderFactory metadataReaderFactory;
|
private final MetadataReaderFactory metadataReaderFactory;
|
||||||
|
|
||||||
private final ProblemReporter problemReporter;
|
private final ProblemReporter problemReporter;
|
||||||
|
|
@ -73,6 +81,9 @@ class ConfigurationClassParser {
|
||||||
private final Set<ConfigurationClass> configurationClasses =
|
private final Set<ConfigurationClass> configurationClasses =
|
||||||
new LinkedHashSet<ConfigurationClass>();
|
new LinkedHashSet<ConfigurationClass>();
|
||||||
|
|
||||||
|
private final Stack<PropertySource<?>> propertySources =
|
||||||
|
new Stack<PropertySource<?>>();
|
||||||
|
|
||||||
private final Environment environment;
|
private final Environment environment;
|
||||||
|
|
||||||
private final ResourceLoader resourceLoader;
|
private final ResourceLoader resourceLoader;
|
||||||
|
|
@ -152,6 +163,18 @@ class ConfigurationClassParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void doProcessConfigurationClass(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException {
|
protected void doProcessConfigurationClass(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException {
|
||||||
|
Map<String, Object> propertySourceAttributes =
|
||||||
|
metadata.getAnnotationAttributes(org.springframework.context.annotation.PropertySource.class.getName());
|
||||||
|
if (propertySourceAttributes != null) {
|
||||||
|
String name = (String) propertySourceAttributes.get("name");
|
||||||
|
String location = (String) propertySourceAttributes.get("value");
|
||||||
|
ClassLoader classLoader = this.resourceLoader.getClassLoader();
|
||||||
|
ResourcePropertySource ps = StringUtils.hasText(name) ?
|
||||||
|
new ResourcePropertySource(name, location, classLoader) :
|
||||||
|
new ResourcePropertySource(location, classLoader);
|
||||||
|
this.propertySources.push(ps);
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Object> componentScanAttributes = metadata.getAnnotationAttributes(ComponentScan.class.getName());
|
Map<String, Object> componentScanAttributes = metadata.getAnnotationAttributes(ComponentScan.class.getName());
|
||||||
if (componentScanAttributes != null) {
|
if (componentScanAttributes != null) {
|
||||||
// the config class is annotated with @ComponentScan -> perform the scan immediately
|
// the config class is annotated with @ComponentScan -> perform the scan immediately
|
||||||
|
|
@ -236,6 +259,10 @@ class ConfigurationClassParser {
|
||||||
return this.configurationClasses;
|
return this.configurationClasses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Stack<PropertySource<?>> getPropertySources() {
|
||||||
|
return this.propertySources;
|
||||||
|
}
|
||||||
|
|
||||||
public ImportRegistry getImportRegistry() {
|
public ImportRegistry getImportRegistry() {
|
||||||
return this.importStack;
|
return this.importStack;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
@ -49,7 +50,10 @@ import org.springframework.context.ResourceLoaderAware;
|
||||||
import org.springframework.context.annotation.ConfigurationClassParser.ImportRegistry;
|
import org.springframework.context.annotation.ConfigurationClassParser.ImportRegistry;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.PriorityOrdered;
|
import org.springframework.core.PriorityOrdered;
|
||||||
|
import org.springframework.core.env.ConfigurableEnvironment;
|
||||||
import org.springframework.core.env.Environment;
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.env.MutablePropertySources;
|
||||||
|
import org.springframework.core.env.PropertySource;
|
||||||
import org.springframework.core.io.DefaultResourceLoader;
|
import org.springframework.core.io.DefaultResourceLoader;
|
||||||
import org.springframework.core.io.ResourceLoader;
|
import org.springframework.core.io.ResourceLoader;
|
||||||
import org.springframework.core.type.AnnotationMetadata;
|
import org.springframework.core.type.AnnotationMetadata;
|
||||||
|
|
@ -242,6 +246,19 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||||
}
|
}
|
||||||
parser.validate();
|
parser.validate();
|
||||||
|
|
||||||
|
// Handle any @PropertySource annotations
|
||||||
|
if (!(this.environment instanceof ConfigurableEnvironment)) {
|
||||||
|
logger.warn("Ignoring @PropertySource annotations. " +
|
||||||
|
"Reason: Environment must implement ConfigurableEnvironment");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
MutablePropertySources envPropertySources = ((ConfigurableEnvironment)this.environment).getPropertySources();
|
||||||
|
Stack<PropertySource<?>> parsedPropertySources = parser.getPropertySources();
|
||||||
|
while (!parsedPropertySources.isEmpty()) {
|
||||||
|
envPropertySources.addLast(parsedPropertySources.pop());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Read the model and create bean definitions based on its content
|
// Read the model and create bean definitions based on its content
|
||||||
reader.loadBeanDefinitions(parser.getConfigurationClasses());
|
reader.loadBeanDefinitions(parser.getConfigurationClasses());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2011 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.context.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotation providing a convenient and declarative mechanism for adding a
|
||||||
|
* {@link org.springframework.core.env.PropertySource PropertySource} to Spring's
|
||||||
|
* {@link org.springframework.core.env.Environment Environment}. To be used in
|
||||||
|
* conjunction with @{@link Configuration} classes.
|
||||||
|
*
|
||||||
|
* <h3>Example usage</h3>
|
||||||
|
* <p>Given a file {@code app.properties} containing the key/value pair
|
||||||
|
* {@code testbean.name=myTestBean}, the following {@code @Configuration} class
|
||||||
|
* uses {@code @PropertySource} to contribute {@code app.properties} to the
|
||||||
|
* {@code Environment}'s set of {@code PropertySources}.
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* @Configuration
|
||||||
|
* @PropertySource("classpath:/com/myco/app.properties")
|
||||||
|
* public class AppConfig {
|
||||||
|
* @Autowired
|
||||||
|
* Environment env;
|
||||||
|
*
|
||||||
|
* @Bean
|
||||||
|
* public TestBean testBean() {
|
||||||
|
* TestBean testBean = new TestBean();
|
||||||
|
* testBean.setName(env.getProperty("testbean.name"));
|
||||||
|
* return testBean;
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* Notice that the {@code Environment} object is @{@link Autowired} into the
|
||||||
|
* configuration class and then used when populating the {@code TestBean}
|
||||||
|
* object. Given the configuration above, a call to {@code testBean.getName()} will
|
||||||
|
* return "myTestBean".
|
||||||
|
*
|
||||||
|
* <h3>A note on property overriding with @PropertySource</h3>
|
||||||
|
* In cases where a given property key exists in more than one {@code .properties}
|
||||||
|
* file, the last {@code @PropertySource} annotation processed will 'win' and override.
|
||||||
|
*
|
||||||
|
* For example, given two properties files {@code a.properties} and
|
||||||
|
* {@code b.properties}, consider the following two configuration classes
|
||||||
|
* that reference them with {@code @PropertySource} annotations:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* @Configuration
|
||||||
|
* @PropertySource("classpath:/com/myco/a.properties")
|
||||||
|
* public class ConfigA { }
|
||||||
|
*
|
||||||
|
* @Configuration
|
||||||
|
* @PropertySource("classpath:/com/myco/b.properties")
|
||||||
|
* public class ConfigB { }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* The override ordering depends on the order in which these classes are registered
|
||||||
|
* with the application context.
|
||||||
|
* <pre>
|
||||||
|
* AnnotationConfigApplicationContext ctx =
|
||||||
|
* new AnnotationConfigApplicationContext();
|
||||||
|
* ctx.register(ConfigA.class);
|
||||||
|
* ctx.register(ConfigB.class);
|
||||||
|
* ctx.refresh();
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* In the scenario above, the properties in {@code b.properties} will override any
|
||||||
|
* duplicates that exist in {@code a.properties}, because {@code ConfigB} was registered
|
||||||
|
* last.
|
||||||
|
*
|
||||||
|
* <p>In certain situations, it may not be possible or practical to tightly control
|
||||||
|
* property source ordering when using {@code @ProperySource} annotations. For example,
|
||||||
|
* if the {@code @Configuration} classes above were registered via component-scanning,
|
||||||
|
* the ordering is difficult to predict. In such cases - and if overriding is important -
|
||||||
|
* it is recommended that the user fall back to using the programmatic PropertySource API.
|
||||||
|
* See {@link org.springframework.core.env.ConfigurableEnvironment ConfigurableEnvironment} and
|
||||||
|
* {@link org.springframework.core.env.MutablePropertySources MutablePropertySources} Javadoc
|
||||||
|
* for details.
|
||||||
|
|
||||||
|
*
|
||||||
|
* @author Chris Beams
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface PropertySource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate the name of this PropertySource. If omitted, a name
|
||||||
|
* will be generated based on the description of the underlying
|
||||||
|
* resource.
|
||||||
|
* @see org.springframework.core.env.PropertySource#getName()
|
||||||
|
* @see org.springframework.core.io.Resource#getDescription()
|
||||||
|
*/
|
||||||
|
String name() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate the resource location of the properties file to be loaded.
|
||||||
|
* For example, {@code "classpath:/com/myco/app.properties"} or
|
||||||
|
* {@code "file:/path/to/file"}. Note that resource location wildcards
|
||||||
|
* are not permitted, and that a location must evaluate to exactly one
|
||||||
|
* {@code .properties} resource.
|
||||||
|
*/
|
||||||
|
String value();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2011 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.context.annotation;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.core.env.MutablePropertySources;
|
||||||
|
|
||||||
|
import test.beans.TestBean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the processing of @PropertySource annotations on @Configuration classes.
|
||||||
|
*
|
||||||
|
* @author Chris Beams
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
public class PropertySourceAnnotationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withExplicitName() {
|
||||||
|
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||||
|
ctx.register(ConfigWithExplicitName.class);
|
||||||
|
ctx.refresh();
|
||||||
|
assertTrue("property source p1 was not added",
|
||||||
|
ctx.getEnvironment().getPropertySources().contains("p1"));
|
||||||
|
assertThat(ctx.getBean(TestBean.class).getName(), equalTo("p1TestBean"));
|
||||||
|
|
||||||
|
// assert that the property source was added last to the set of sources
|
||||||
|
String name;
|
||||||
|
MutablePropertySources sources = ctx.getEnvironment().getPropertySources();
|
||||||
|
Iterator<org.springframework.core.env.PropertySource<?>> iterator = sources.iterator();
|
||||||
|
do {
|
||||||
|
name = iterator.next().getName();
|
||||||
|
}
|
||||||
|
while(iterator.hasNext());
|
||||||
|
|
||||||
|
assertThat(name, is("p1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withImplicitName() {
|
||||||
|
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||||
|
ctx.register(ConfigWithImplicitName.class);
|
||||||
|
ctx.refresh();
|
||||||
|
assertTrue("property source p1 was not added",
|
||||||
|
ctx.getEnvironment().getPropertySources().contains("class path resource [org/springframework/context/annotation/p1.properties]"));
|
||||||
|
assertThat(ctx.getBean(TestBean.class).getName(), equalTo("p1TestBean"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the LIFO behavior of @PropertySource annotaitons.
|
||||||
|
* The last one registered should 'win'.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void orderingIsLifo() {
|
||||||
|
{
|
||||||
|
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||||
|
ctx.register(ConfigWithImplicitName.class, P2Config.class);
|
||||||
|
ctx.refresh();
|
||||||
|
// p2 should 'win' as it was registered last
|
||||||
|
assertThat(ctx.getBean(TestBean.class).getName(), equalTo("p2TestBean"));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||||
|
ctx.register(P2Config.class, ConfigWithImplicitName.class);
|
||||||
|
ctx.refresh();
|
||||||
|
// p1 should 'win' as it was registered last
|
||||||
|
assertThat(ctx.getBean(TestBean.class).getName(), equalTo("p1TestBean"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@PropertySource(name="p1", value="classpath:org/springframework/context/annotation/p1.properties")
|
||||||
|
static class ConfigWithExplicitName {
|
||||||
|
@Inject Environment env;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestBean testBean() {
|
||||||
|
return new TestBean(env.getProperty("testbean.name"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@PropertySource("classpath:org/springframework/context/annotation/p1.properties")
|
||||||
|
static class ConfigWithImplicitName {
|
||||||
|
@Inject Environment env;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestBean testBean() {
|
||||||
|
return new TestBean(env.getProperty("testbean.name"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@PropertySource("classpath:org/springframework/context/annotation/p2.properties")
|
||||||
|
static class P2Config {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
testbean.name=p1TestBean
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
testbean.name=p2TestBean
|
||||||
Loading…
Reference in New Issue