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

@@ -19,12 +19,18 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* General utility methods for working with annotations, handling bridge methods (which the compiler
@@ -43,6 +49,7 @@ import org.springframework.util.Assert;
* @author Sam Brannen
* @author Mark Fisher
* @author Chris Beams
* @author Phillip Webb
* @since 2.0
* @see java.lang.reflect.Method#getAnnotations()
* @see java.lang.reflect.Method#getAnnotation(Class)
@@ -117,6 +124,45 @@ public abstract class AnnotationUtils {
return getAnnotation((AnnotatedElement) resolvedMethod, annotationType);
}
/**
* Get the possibly repeating {@link Annotation}s of {@code annotationType} from the
* supplied {@link Method}. Deals with both a single direct annotation and repeating
* annotations nested within a containing annotation.
* <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
* @param method the method to look for annotations on
* @param containerAnnotationType the class of the container that holds the annotations
* @param annotationType the annotation class to look for
* @return the annotations found
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
* @since 4.0
*/
public static <A extends Annotation> Set<A> getRepeatableAnnotation(Method method,
Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
return getRepeatableAnnotation((AnnotatedElement) resolvedMethod,
containerAnnotationType, annotationType);
}
/**
* Get the possibly repeating {@link Annotation}s of {@code annotationType} from the
* supplied {@link AnnotatedElement}. Deals with both a single direct annotation and
* repeating annotations nested within a containing annotation.
* <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
* @param annotatedElement the element to look for annotations on
* @param containerAnnotationType the class of the container that holds the annotations
* @param annotationType the annotation class to look for
* @return the annotations found
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method)
* @since 4.0
*/
public static <A extends Annotation> Set<A> getRepeatableAnnotation(AnnotatedElement annotatedElement,
Class<? extends Annotation> containerAnnotationType, Class<A> annotationType) {
if (annotatedElement.getAnnotations().length == 0) {
return Collections.emptySet();
}
return new AnnotationCollector<A>(containerAnnotationType, annotationType).getResult(annotatedElement);
}
/**
* Get a single {@link Annotation} of {@code annotationType} from the supplied {@link Method},
* traversing its super methods if no annotation can be found on the given method itself.
@@ -521,4 +567,59 @@ public abstract class AnnotationUtils {
}
}
private static class AnnotationCollector<A extends Annotation> {
private final Class<? extends Annotation> containerAnnotationType;
private final Class<A> annotationType;
private final Set<AnnotatedElement> visited = new HashSet<AnnotatedElement>();
private final Set<A> result = new LinkedHashSet<A>();
public AnnotationCollector(Class<? extends Annotation> containerAnnotationType,
Class<A> annotationType) {
this.containerAnnotationType = containerAnnotationType;
this.annotationType = annotationType;
}
public Set<A> getResult(AnnotatedElement element) {
process(element);
return Collections.unmodifiableSet(this.result);
}
@SuppressWarnings("unchecked")
private void process(AnnotatedElement annotatedElement) {
if (this.visited.add(annotatedElement)) {
for (Annotation annotation : annotatedElement.getAnnotations()) {
if (ObjectUtils.nullSafeEquals(this.annotationType, annotation.annotationType())) {
this.result.add((A) annotation);
}
else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, annotation.annotationType())) {
result.addAll(Arrays.asList(getValue(annotation)));
}
else {
process(annotation.annotationType());
}
}
}
}
@SuppressWarnings("unchecked")
private A[] getValue(Annotation annotation) {
try {
Method method = annotation.annotationType().getDeclaredMethod("value");
return (A[]) method.invoke(annotation);
}
catch (Exception ex) {
throw new IllegalStateException("Unable to read value from repeating annotation container "
+ this.containerAnnotationType.getName(), ex);
}
}
}
}

View File

@@ -18,17 +18,21 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.core.annotation.AnnotationUtils.*;
@@ -37,6 +41,7 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
* @author Juergen Hoeller
* @author Sam Brannen
* @author Chris Beams
* @author Phillip Webb
*/
public class AnnotationUtilsTests {
@@ -268,6 +273,19 @@ public class AnnotationUtilsTests {
assertNotNull(order);
}
@Test
public void testGetRepeatableFromMethod() throws Exception {
Method method = InterfaceWithRepeated.class.getMethod("foo");
Set<MyRepeatable> annotions = AnnotationUtils.getRepeatableAnnotation(method,
MyRepeatableContainer.class, MyRepeatable.class);
Set<String> values = new HashSet<String>();
for (MyRepeatable myRepeatable : annotions) {
values.add(myRepeatable.value());
}
assertThat(values, equalTo((Set<String>) new HashSet<String>(
Arrays.asList("a", "b", "c", "meta"))));
}
@Component(value = "meta1")
@Retention(RetentionPolicy.RUNTIME)
@@ -428,10 +446,46 @@ public class AnnotationUtilsTests {
}
}
public static interface InterfaceWithRepeated {
@MyRepeatable("a")
@MyRepeatableContainer({ @MyRepeatable("b"), @MyRepeatable("c") })
@MyRepeatableMeta
void foo();
}
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Transactional {
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface MyRepeatableContainer {
MyRepeatable[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Repeatable(MyRepeatableContainer.class)
@interface MyRepeatable {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@MyRepeatable("meta")
@interface MyRepeatableMeta {
}