From a3789120c9d5d77a7f3946c9d5d809195de6d243 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 22 Feb 2016 23:25:50 +0100 Subject: [PATCH] Support for @PropertySource annotations with custom implementation types Issue: SPR-8963 --- .../annotation/ConfigurationClassParser.java | 40 ++++++------ .../context/annotation/PropertySource.java | 24 +++++-- .../PropertySourceAnnotationTests.java | 63 ++++++++++++++++++- .../support/DefaultPropertySourceFactory.java | 39 ++++++++++++ .../io/support/PropertySourceFactory.java | 41 ++++++++++++ 5 files changed, 182 insertions(+), 25 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/core/io/support/DefaultPropertySourceFactory.java create mode 100644 spring-core/src/main/java/org/springframework/core/io/support/PropertySourceFactory.java diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 96f284d392..2e00f9a3e0 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -66,7 +66,9 @@ import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySource; import org.springframework.core.io.Resource; 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.PropertySourceFactory; import org.springframework.core.io.support.ResourcePropertySource; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.MethodMetadata; @@ -103,6 +105,8 @@ import org.springframework.util.StringUtils; */ class ConfigurationClassParser { + private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory(); + private static final Comparator DEFERRED_IMPORT_COMPARATOR = new Comparator() { @Override @@ -355,15 +359,26 @@ class ConfigurationClassParser { */ private void processPropertySource(AnnotationAttributes propertySource) throws IOException { String name = propertySource.getString("name"); + if (!StringUtils.hasLength(name)) { + name = null; + } String encoding = propertySource.getString("encoding"); + if (!StringUtils.hasLength(encoding)) { + encoding = null; + } String[] locations = propertySource.getStringArray("value"); - boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound"); Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required"); + boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound"); + + Class factoryClass = propertySource.getClass("factory"); + PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ? + DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiate(factoryClass)); + for (String location : locations) { try { String resolvedLocation = this.environment.resolveRequiredPlaceholders(location); Resource resource = this.resourceLoader.getResource(resolvedLocation); - addPropertySource(createPropertySource(name, encoding, resource)); + addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding))); } catch (IllegalArgumentException ex) { // from resolveRequiredPlaceholders @@ -380,34 +395,23 @@ class ConfigurationClassParser { } } - private ResourcePropertySource createPropertySource(String name, String encoding, Resource resource) throws IOException { - 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) { + private void addPropertySource(PropertySource propertySource) { String name = propertySource.getName(); MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources(); if (propertySources.contains(name) && this.propertySourceNames.contains(name)) { // We've already added a version, we need to extend it PropertySource existing = propertySources.get(name); + PropertySource newSource = (propertySource instanceof ResourcePropertySource ? + ((ResourcePropertySource) propertySource).withResourceName() : propertySource); if (existing instanceof CompositePropertySource) { - ((CompositePropertySource) existing).addFirstPropertySource(propertySource.withResourceName()); + ((CompositePropertySource) existing).addFirstPropertySource(newSource); } else { if (existing instanceof ResourcePropertySource) { existing = ((ResourcePropertySource) existing).withResourceName(); } CompositePropertySource composite = new CompositePropertySource(name); - composite.addPropertySource(propertySource.withResourceName()); + composite.addPropertySource(newSource); composite.addPropertySource(existing); propertySources.replace(name, composite); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java b/spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java index c060c4d1e8..4f9edf755b 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/PropertySource.java @@ -23,6 +23,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.core.io.support.PropertySourceFactory; + /** * Annotation providing a convenient and declarative mechanism for adding a * {@link org.springframework.core.env.PropertySource PropertySource} to Spring's @@ -133,6 +135,7 @@ import java.lang.annotation.Target; * javadocs for details. * * @author Chris Beams + * @author Juergen Hoeller * @author Phillip Webb * @since 3.1 * @see PropertySources @@ -155,12 +158,6 @@ public @interface PropertySource { */ 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. * For example, {@code "classpath:/com/myco/app.properties"} or @@ -184,4 +181,19 @@ public @interface PropertySource { */ 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. + *

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 factory() default PropertySourceFactory.class; + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java b/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java index 7150c4b0dc..6b347b0451 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/PropertySourceAnnotationTests.java @@ -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"); * you may not use this file except in compliance with the License. @@ -17,8 +17,12 @@ package org.springframework.context.annotation; 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.Iterator; +import java.util.Properties; import javax.inject.Inject; import org.junit.Rule; @@ -27,9 +31,13 @@ import org.junit.rules.ExpectedException; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.FactoryBean; +import org.springframework.core.annotation.AliasFor; import org.springframework.core.env.Environment; import org.springframework.core.env.MapPropertySource; 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 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 public void withUnresolvablePlaceholder() { 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("my" + name, props) { + @Override + public Object getProperty(String name) { + String value = props.getProperty(name); + return (value != null ? value.toUpperCase() : null); + } + }; + } + } + + @Configuration @PropertySource( name = "psName", diff --git a/spring-core/src/main/java/org/springframework/core/io/support/DefaultPropertySourceFactory.java b/spring-core/src/main/java/org/springframework/core/io/support/DefaultPropertySourceFactory.java new file mode 100644 index 0000000000..5ff4c9dfed --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/io/support/DefaultPropertySourceFactory.java @@ -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)); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceFactory.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceFactory.java new file mode 100644 index 0000000000..4ab2cfdf03 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertySourceFactory.java @@ -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; + +}