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:
parent
8917821e95
commit
e95bd9e250
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,29 +16,35 @@
|
|||
|
||||
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.io.FileNotFoundException;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests the processing of @PropertySource annotations on @Configuration classes.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Phillip Webb
|
||||
* @since 3.1
|
||||
*/
|
||||
public class PropertySourceAnnotationTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
|
||||
@Test
|
||||
public void withExplicitName() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
|
|
@ -149,6 +155,29 @@ public class PropertySourceAnnotationTests {
|
|||
ctx.refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withPropertySources() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithPropertySources.class);
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p1"), is(true));
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p2"), is(true));
|
||||
// p2 should 'win' as it was registered last
|
||||
assertThat(ctx.getEnvironment().getProperty("testbean.name"), equalTo("p2TestBean"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withMissingPropertySource() {
|
||||
thrown.expect(BeanDefinitionStoreException.class);
|
||||
thrown.expectCause(isA(FileNotFoundException.class));
|
||||
new AnnotationConfigApplicationContext(ConfigWithMissingPropertySource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withIgnoredPropertySource() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigWithIgnoredPropertySource.class);
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p1"), is(true));
|
||||
assertThat(ctx.getEnvironment().containsProperty("from.p2"), is(true));
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySource(value="classpath:${unresolvable}/p1.properties")
|
||||
|
|
@ -231,6 +260,33 @@ public class PropertySourceAnnotationTests {
|
|||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySources({
|
||||
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/p1.properties"),
|
||||
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/p2.properties")
|
||||
})
|
||||
static class ConfigWithPropertySources {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@PropertySources({
|
||||
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/p1.properties"),
|
||||
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/missing.properties"),
|
||||
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/p2.properties")
|
||||
})
|
||||
static class ConfigWithMissingPropertySource {
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@PropertySources({
|
||||
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/p1.properties"),
|
||||
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/missing.properties", ignoreResourceNotFound=true),
|
||||
@PropertySource(name = "psName", value="classpath:org/springframework/context/annotation/p2.properties")
|
||||
})
|
||||
static class ConfigWithIgnoredPropertySource {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@PropertySource(value = {})
|
||||
static class ConfigWithEmptyResourceLocations {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue