Add @PropertySources and ignoreResourceNotFound

Support repeatable @PropertySource annotations in Java 8 and add
@PropertySources container annotation for Java 6/7. Also add an
ignoreResourceNotFound attribute to @PropertySource allowing missing
property resources to be silently ignored.

This commit also introduces some generally useful methods to
AnnotationUtils for working with @Repeatable annotations.

Issue: SPR-8371
This commit is contained in:
Phillip Webb
2013-10-22 11:10:20 -07:00
parent 8917821e95
commit e95bd9e250
10 changed files with 364 additions and 52 deletions

View File

@@ -16,7 +16,9 @@
package org.springframework.context.annotation;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
@@ -32,6 +34,7 @@ import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;
/**
@@ -44,6 +47,7 @@ import org.springframework.util.ClassUtils;
* @author Mark Fisher
* @author Juergen Hoeller
* @author Chris Beams
* @author Phillip Webb
* @since 2.5
* @see ContextAnnotationAutowireCandidateResolver
* @see CommonAnnotationBeanPostProcessor
@@ -297,12 +301,40 @@ public class AnnotationConfigUtils {
return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
}
static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, Class<?> annoClass) {
return attributesFor(metadata, annoClass.getName());
static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, Class<?> annotationClass) {
return attributesFor(metadata, annotationClass.getName());
}
static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, String annoClassName) {
return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annoClassName, false));
static AnnotationAttributes attributesFor(AnnotatedTypeMetadata metadata, String annotationClassName) {
return AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(annotationClassName, false));
}
static Set<AnnotationAttributes> attributesForRepeatable(AnnotationMetadata metadata,
Class<?> containerClass, Class<?> annotationClass) {
return attributesForRepeatable(metadata, containerClass.getName(), annotationClass.getName());
}
@SuppressWarnings("unchecked")
static Set<AnnotationAttributes> attributesForRepeatable(AnnotationMetadata metadata,
String containerClassName, String annotationClassName) {
Set<AnnotationAttributes> result = new LinkedHashSet<AnnotationAttributes>();
addAttributesIfNotNull(result, metadata.getAnnotationAttributes(annotationClassName, false));
Map<String, Object> container = metadata.getAnnotationAttributes(containerClassName, false);
if (container != null && container.containsKey("value")) {
for (Map<String, Object> containedAttributes : (Map<String, Object>[]) container.get("value")) {
addAttributesIfNotNull(result, containedAttributes);
}
}
return Collections.unmodifiableSet(result);
}
private static void addAttributesIfNotNull(Set<AnnotationAttributes> result,
Map<String, Object> attributes) {
if (attributes != null) {
result.add(AnnotationAttributes.fromMap(attributes));
}
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.context.annotation;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -55,6 +56,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.core.type.AnnotationMetadata;
@@ -63,6 +65,8 @@ import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
@@ -112,7 +116,7 @@ class ConfigurationClassParser {
private final Map<String, ConfigurationClass> knownSuperclasses = new HashMap<String, ConfigurationClass>();
private final Stack<PropertySource<?>> propertySources = new Stack<PropertySource<?>>();
private final MultiValueMap<String, PropertySource<?>> propertySources = new LinkedMultiValueMap<String, PropertySource<?>>();
private final ImportStack importStack = new ImportStack();
@@ -218,9 +222,9 @@ class ConfigurationClassParser {
processMemberClasses(configClass, sourceClass);
// process any @PropertySource annotations
AnnotationAttributes propertySource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(),
org.springframework.context.annotation.PropertySource.class);
if (propertySource != null) {
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
processPropertySource(propertySource);
}
@@ -301,29 +305,29 @@ class ConfigurationClassParser {
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
String name = propertySource.getString("name");
String[] locations = propertySource.getStringArray("value");
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
int locationCount = locations.length;
if (locationCount == 0) {
throw new IllegalArgumentException("At least one @PropertySource(value) location is required");
}
for (int i = 0; i < locationCount; i++) {
locations[i] = this.environment.resolveRequiredPlaceholders(locations[i]);
}
ClassLoader classLoader = this.resourceLoader.getClassLoader();
if (!StringUtils.hasText(name)) {
for (String location : locations) {
this.propertySources.push(new ResourcePropertySource(location, classLoader));
}
}
else {
if (locationCount == 1) {
this.propertySources.push(new ResourcePropertySource(name, locations[0], classLoader));
}
else {
CompositePropertySource ps = new CompositePropertySource(name);
for (int i = locations.length - 1; i >= 0; i--) {
ps.addPropertySource(new ResourcePropertySource(locations[i], classLoader));
for (String location : locations) {
Resource resource = this.resourceLoader.getResource(
this.environment.resolveRequiredPlaceholders(location));
try {
if (!StringUtils.hasText(name) || this.propertySources.containsKey(name)) {
// We need to ensure unique names when the property source will
// ultimately end up in a composite
ResourcePropertySource ps = new ResourcePropertySource(resource);
this.propertySources.add((StringUtils.hasText(name) ? name : ps.getName()), ps);
}
else {
this.propertySources.add(name, new ResourcePropertySource(name, resource));
}
}
catch (FileNotFoundException ex) {
if (!ignoreResourceNotFound) {
throw ex;
}
this.propertySources.push(ps);
}
}
}
@@ -473,10 +477,27 @@ class ConfigurationClassParser {
return this.configurationClasses;
}
public Stack<PropertySource<?>> getPropertySources() {
return this.propertySources;
public List<PropertySource<?>> getPropertySources() {
List<PropertySource<?>> propertySources = new LinkedList<PropertySource<?>>();
for (Map.Entry<String, List<PropertySource<?>>> entry : this.propertySources.entrySet()) {
propertySources.add(0, collatePropertySources(entry.getKey(), entry.getValue()));
}
return propertySources;
}
private PropertySource<?> collatePropertySources(String name,
List<PropertySource<?>> propertySources) {
if (propertySources.size() == 1) {
return propertySources.get(0);
}
CompositePropertySource result = new CompositePropertySource(name);
for (int i = propertySources.size() - 1; i >= 0; i--) {
result.addPropertySource(propertySources.get(i));
}
return result;
}
ImportRegistry getImportRegistry() {
return this.importStack;
}

View File

@@ -21,13 +21,12 @@ import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanClassLoaderAware;
@@ -298,7 +297,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
parser.validate();
// Handle any @PropertySource annotations
Stack<PropertySource<?>> parsedPropertySources = parser.getPropertySources();
List<PropertySource<?>> parsedPropertySources = parser.getPropertySources();
if (!parsedPropertySources.isEmpty()) {
if (!(this.environment instanceof ConfigurableEnvironment)) {
logger.warn("Ignoring @PropertySource annotations. " +
@@ -306,8 +305,8 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
}
else {
MutablePropertySources envPropertySources = ((ConfigurableEnvironment)this.environment).getPropertySources();
while (!parsedPropertySources.isEmpty()) {
envPropertySources.addLast(parsedPropertySources.pop());
for (PropertySource<?> propertySource : parsedPropertySources) {
envPropertySources.addLast(propertySource);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2013 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.
@@ -18,6 +18,7 @@ package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@@ -132,7 +133,9 @@ import java.lang.annotation.Target;
* Javadoc for details.
*
* @author Chris Beams
* @author Phillip Webb
* @since 3.1
* @see PropertySources
* @see Configuration
* @see org.springframework.core.env.PropertySource
* @see org.springframework.core.env.ConfigurableEnvironment#getPropertySources()
@@ -141,6 +144,7 @@ import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
/**
@@ -166,4 +170,13 @@ public @interface PropertySource {
*/
String[] value();
/**
* Indicate if failure to find the a {@link #value() property resource} should be
* ignored.
* <p>{@code true} is appropriate if the properties file is completely optional.
* Default is {@code false}.
* @since 4.0
*/
boolean ignoreResourceNotFound() default false;
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2002-2013 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;
/**
* Container annotation that aggregates several {@link PropertySource} annotations.
*
* <p>Can be used natively, declaring several nested {@link PropertySource} annotations.
* Can also be used in conjunction with Java 8's support for repeatable annotations,
* where {@link PropertySource} can simply be declared several times on the same method,
* implicitly generating this container annotation.
*
* @author Phillip Webb
* @since 4.0
* @see PropertySource
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PropertySources {
PropertySource[] value();
}

View File

@@ -42,6 +42,7 @@ import java.lang.annotation.Target;
* @since 3.0
* @see EnableScheduling
* @see ScheduledAnnotationBeanPostProcessor
* @see Schedules
*/
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)

View File

@@ -117,17 +117,8 @@ public class ScheduledAnnotationBeanPostProcessor
ReflectionUtils.doWithMethods(targetClass, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Schedules schedules = AnnotationUtils.getAnnotation(method, Schedules.class);
if (schedules != null) {
for (Scheduled scheduled : schedules.value()) {
processScheduled(scheduled, method, bean);
}
}
else {
Scheduled scheduled = AnnotationUtils.getAnnotation(method, Scheduled.class);
if (scheduled != null) {
processScheduled(scheduled, method, bean);
}
for (Scheduled scheduled : AnnotationUtils.getRepeatableAnnotation(method, Schedules.class, Scheduled.class)) {
processScheduled(scheduled, method, bean);
}
}
});