Support for @PropertySource annotations with custom implementation types

Issue: SPR-8963
This commit is contained in:
Juergen Hoeller 2016-02-22 23:25:50 +01:00
parent 8ff9e818a5
commit a3789120c9
5 changed files with 182 additions and 25 deletions

View File

@ -66,7 +66,9 @@ import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource; import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.core.io.support.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;
@ -103,6 +105,8 @@ import org.springframework.util.StringUtils;
*/ */
class ConfigurationClassParser { class ConfigurationClassParser {
private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory();
private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR = private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
new Comparator<ConfigurationClassParser.DeferredImportSelectorHolder>() { new Comparator<ConfigurationClassParser.DeferredImportSelectorHolder>() {
@Override @Override
@ -355,15 +359,26 @@ class ConfigurationClassParser {
*/ */
private void processPropertySource(AnnotationAttributes propertySource) throws IOException { private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name"); String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
String encoding = propertySource.getString("encoding"); String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
String[] locations = propertySource.getStringArray("value"); String[] locations = propertySource.getStringArray("value");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required"); Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiate(factoryClass));
for (String location : locations) { for (String location : locations) {
try { try {
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location); String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
Resource resource = this.resourceLoader.getResource(resolvedLocation); Resource resource = this.resourceLoader.getResource(resolvedLocation);
addPropertySource(createPropertySource(name, encoding, resource)); addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
} }
catch (IllegalArgumentException ex) { catch (IllegalArgumentException ex) {
// from resolveRequiredPlaceholders // from resolveRequiredPlaceholders
@ -380,34 +395,23 @@ class ConfigurationClassParser {
} }
} }
private ResourcePropertySource createPropertySource(String name, String encoding, Resource resource) throws IOException { private void addPropertySource(PropertySource<?> propertySource) {
if (StringUtils.hasText(name)) {
return (StringUtils.hasText(encoding) ?
new ResourcePropertySource(name, new EncodedResource(resource, encoding)) :
new ResourcePropertySource(name, resource));
}
else {
return (StringUtils.hasText(encoding) ?
new ResourcePropertySource(new EncodedResource(resource, encoding)) :
new ResourcePropertySource(resource));
}
}
private void addPropertySource(ResourcePropertySource propertySource) {
String name = propertySource.getName(); String name = propertySource.getName();
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources(); MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
if (propertySources.contains(name) && this.propertySourceNames.contains(name)) { if (propertySources.contains(name) && this.propertySourceNames.contains(name)) {
// We've already added a version, we need to extend it // We've already added a version, we need to extend it
PropertySource<?> existing = propertySources.get(name); PropertySource<?> existing = propertySources.get(name);
PropertySource<?> newSource = (propertySource instanceof ResourcePropertySource ?
((ResourcePropertySource) propertySource).withResourceName() : propertySource);
if (existing instanceof CompositePropertySource) { if (existing instanceof CompositePropertySource) {
((CompositePropertySource) existing).addFirstPropertySource(propertySource.withResourceName()); ((CompositePropertySource) existing).addFirstPropertySource(newSource);
} }
else { else {
if (existing instanceof ResourcePropertySource) { if (existing instanceof ResourcePropertySource) {
existing = ((ResourcePropertySource) existing).withResourceName(); existing = ((ResourcePropertySource) existing).withResourceName();
} }
CompositePropertySource composite = new CompositePropertySource(name); CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(propertySource.withResourceName()); composite.addPropertySource(newSource);
composite.addPropertySource(existing); composite.addPropertySource(existing);
propertySources.replace(name, composite); propertySources.replace(name, composite);
} }

View File

@ -23,6 +23,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import org.springframework.core.io.support.PropertySourceFactory;
/** /**
* Annotation providing a convenient and declarative mechanism for adding a * 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.PropertySource PropertySource} to Spring's
@ -133,6 +135,7 @@ import java.lang.annotation.Target;
* javadocs for details. * javadocs for details.
* *
* @author Chris Beams * @author Chris Beams
* @author Juergen Hoeller
* @author Phillip Webb * @author Phillip Webb
* @since 3.1 * @since 3.1
* @see PropertySources * @see PropertySources
@ -155,12 +158,6 @@ public @interface PropertySource {
*/ */
String name() default ""; String name() default "";
/**
* A specific character encoding for the given resources, e.g. "UTF-8".
* @since 4.3
*/
String encoding() default "";
/** /**
* Indicate the resource location(s) of the properties file to be loaded. * Indicate the resource location(s) of the properties file to be loaded.
* For example, {@code "classpath:/com/myco/app.properties"} or * For example, {@code "classpath:/com/myco/app.properties"} or
@ -184,4 +181,19 @@ public @interface PropertySource {
*/ */
boolean ignoreResourceNotFound() default false; boolean ignoreResourceNotFound() default false;
/**
* A specific character encoding for the given resources, e.g. "UTF-8".
* @since 4.3
*/
String encoding() default "";
/**
* Specify a custom {@link PropertySourceFactory}, if any.
* <p>By default, a default factory for standard resource files will be used.
* @since 4.3
* @see org.springframework.core.io.support.DefaultPropertySourceFactory
* @see org.springframework.core.io.support.ResourcePropertySource
*/
Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,8 +17,12 @@
package org.springframework.context.annotation; package org.springframework.context.annotation;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.Properties;
import javax.inject.Inject; import javax.inject.Inject;
import org.junit.Rule; import org.junit.Rule;
@ -27,9 +31,13 @@ import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.core.io.support.PropertySourceFactory;
import org.springframework.tests.sample.beans.TestBean; import org.springframework.tests.sample.beans.TestBean;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
@ -111,6 +119,22 @@ public class PropertySourceAnnotationTests {
} }
} }
@Test
public void withCustomFactory() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithImplicitName.class, WithCustomFactory.class);
ctx.refresh();
assertThat(ctx.getBean(TestBean.class).getName(), equalTo("P2TESTBEAN"));
}
@Test
public void withCustomFactoryAsMeta() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigWithImplicitName.class, WithCustomFactoryAsMeta.class);
ctx.refresh();
assertThat(ctx.getBean(TestBean.class).getName(), equalTo("P2TESTBEAN"));
}
@Test @Test
public void withUnresolvablePlaceholder() { public void withUnresolvablePlaceholder() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
@ -354,6 +378,43 @@ public class PropertySourceAnnotationTests {
} }
@Configuration
@PropertySource(value = "classpath:org/springframework/context/annotation/p2.properties", factory = MyCustomFactory.class)
static class WithCustomFactory {
}
@Configuration
@MyPropertySource(value = "classpath:org/springframework/context/annotation/p2.properties")
static class WithCustomFactoryAsMeta {
}
@Retention(RetentionPolicy.RUNTIME)
@PropertySource(value = {}, factory = MyCustomFactory.class)
public @interface MyPropertySource {
@AliasFor(annotation = PropertySource.class)
String value();
}
public static class MyCustomFactory implements PropertySourceFactory {
@Override
public org.springframework.core.env.PropertySource createPropertySource(String name, EncodedResource resource) throws IOException {
Properties props = PropertiesLoaderUtils.loadProperties(resource);
return new org.springframework.core.env.PropertySource<Properties>("my" + name, props) {
@Override
public Object getProperty(String name) {
String value = props.getProperty(name);
return (value != null ? value.toUpperCase() : null);
}
};
}
}
@Configuration @Configuration
@PropertySource( @PropertySource(
name = "psName", name = "psName",

View File

@ -0,0 +1,39 @@
/*
* Copyright 2002-2016 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.io.support;
import java.io.IOException;
import org.springframework.core.env.PropertySource;
/**
* The default implementation for {@link PropertySourceFactory},
* wrapping every resource in a {@link ResourcePropertySource}.
*
* @author Juergen Hoeller
* @since 4.3
* @see PropertySourceFactory
* @see ResourcePropertySource
*/
public class DefaultPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2002-2016 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.io.support;
import java.io.IOException;
import org.springframework.core.env.PropertySource;
/**
* Strategy interface for creating resource-based {@link PropertySource} wrappers.
*
* @author Juergen Hoeller
* @since 4.3
* @see DefaultPropertySourceFactory
*/
public interface PropertySourceFactory {
/**
* Create a {@link PropertySource} that wraps the given resource.
* @param name the name of the property source
* @param resource the resource (potentially encoded) to wrap
* @return the new {@link PropertySource} (never {@code null})
* @throws IOException if resource resolution failed
*/
PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException;
}