Support annotation attribute aliases and overrides via @AliasFor
This commit introduces first-class support for aliases for annotation
attributes. Specifically, this commit introduces a new @AliasFor
annotation that can be used to declare a pair of aliased attributes
within a single annotation or an alias from an attribute in a custom
composed annotation to an attribute in a meta-annotation.
To support @AliasFor within annotation instances, AnnotationUtils has
been overhauled to "synthesize" any annotations returned by "get" and
"find" searches. A SynthesizedAnnotation is an annotation that is
wrapped in a JDK dynamic proxy which provides run-time support for
@AliasFor semantics. SynthesizedAnnotationInvocationHandler is the
actual handler behind the proxy.
In addition, the contract for @AliasFor is fully validated, and an
AnnotationConfigurationException is thrown in case invalid
configuration is detected.
For example, @ContextConfiguration from the spring-test module is now
declared as follows:
public @interface ContextConfiguration {
@AliasFor(attribute = "locations")
String[] value() default {};
@AliasFor(attribute = "value")
String[] locations() default {};
// ...
}
The following annotations and their related support classes have been
modified to use @AliasFor.
- @ManagedResource
- @ContextConfiguration
- @ActiveProfiles
- @TestExecutionListeners
- @TestPropertySource
- @Sql
- @ControllerAdvice
- @RequestMapping
Similarly, support for AnnotationAttributes has been reworked to
support @AliasFor as well. This allows for fine-grained control over
exactly which attributes are overridden within an annotation hierarchy.
In fact, it is now possible to declare an alias for the 'value'
attribute of a meta-annotation.
For example, given the revised declaration of @ContextConfiguration
above, one can now develop a composed annotation with a custom
attribute override as follows.
@ContextConfiguration
public @interface MyTestConfig {
@AliasFor(
annotation = ContextConfiguration.class,
attribute = "locations"
)
String[] xmlFiles();
// ...
}
Consequently, the following are functionally equivalent.
- @MyTestConfig(xmlFiles = "test.xml")
- @ContextConfiguration("test.xml")
- @ContextConfiguration(locations = "test.xml").
Issue: SPR-11512, SPR-11513
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.annotation;
|
||||
|
||||
import java.lang.annotation.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;
|
||||
|
||||
/**
|
||||
* TODO Document @AliasFor.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.2
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@Documented
|
||||
public @interface AliasFor {
|
||||
|
||||
String attribute();
|
||||
|
||||
Class<? extends Annotation> annotation() default Annotation.class;
|
||||
|
||||
}
|
||||
@@ -31,6 +31,8 @@ import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* General utility methods for finding annotations and meta-annotations on
|
||||
@@ -145,7 +147,7 @@ public class AnnotatedElementUtils {
|
||||
searchWithGetSemantics(annotation.annotationType(), annotationType, new SimpleAnnotationProcessor<Object>() {
|
||||
|
||||
@Override
|
||||
public Object process(Annotation annotation, int metaDepth) {
|
||||
public Object process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
|
||||
types.add(annotation.annotationType().getName());
|
||||
return CONTINUE;
|
||||
}
|
||||
@@ -153,6 +155,7 @@ public class AnnotatedElementUtils {
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
|
||||
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
|
||||
}
|
||||
|
||||
@@ -179,7 +182,7 @@ public class AnnotatedElementUtils {
|
||||
|
||||
return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Boolean>() {
|
||||
@Override
|
||||
public Boolean process(Annotation annotation, int metaDepth) {
|
||||
public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
|
||||
boolean found = annotation.annotationType().getName().equals(annotationType);
|
||||
return ((found && (metaDepth > 0)) ? Boolean.TRUE : CONTINUE);
|
||||
}
|
||||
@@ -208,7 +211,7 @@ public class AnnotatedElementUtils {
|
||||
|
||||
return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Boolean>() {
|
||||
@Override
|
||||
public Boolean process(Annotation annotation, int metaDepth) {
|
||||
public Boolean process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
|
||||
boolean found = annotation.annotationType().getName().equals(annotationType);
|
||||
return (found ? Boolean.TRUE : CONTINUE);
|
||||
}
|
||||
@@ -273,8 +276,12 @@ public class AnnotatedElementUtils {
|
||||
*/
|
||||
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
|
||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
return searchWithGetSemantics(element, annotationType, new MergedAnnotationAttributesProcessor(annotationType,
|
||||
classValuesAsString, nestedAnnotationsAsMap));
|
||||
|
||||
AnnotationAttributes attributes = searchWithGetSemantics(element, annotationType,
|
||||
new MergedAnnotationAttributesProcessor(annotationType, classValuesAsString, nestedAnnotationsAsMap));
|
||||
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString,
|
||||
nestedAnnotationsAsMap);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -297,7 +304,7 @@ public class AnnotatedElementUtils {
|
||||
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element,
|
||||
Class<? extends Annotation> annotationType) {
|
||||
Assert.notNull(annotationType, "annotationType must not be null");
|
||||
return findAnnotationAttributes(element, annotationType.getName(), false, false);
|
||||
return findAnnotationAttributes(element, annotationType.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -357,8 +364,12 @@ public class AnnotatedElementUtils {
|
||||
*/
|
||||
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType,
|
||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
return searchWithFindSemantics(element, annotationType, new MergedAnnotationAttributesProcessor(annotationType,
|
||||
classValuesAsString, nestedAnnotationsAsMap));
|
||||
|
||||
AnnotationAttributes attributes = searchWithFindSemantics(element, annotationType,
|
||||
new MergedAnnotationAttributesProcessor(annotationType, classValuesAsString, nestedAnnotationsAsMap));
|
||||
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString,
|
||||
nestedAnnotationsAsMap);
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -417,7 +428,7 @@ public class AnnotatedElementUtils {
|
||||
searchWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Void>() {
|
||||
|
||||
@Override
|
||||
public Void process(Annotation annotation, int metaDepth) {
|
||||
public Void process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
|
||||
boolean found = annotation.annotationType().getName().equals(annotationType);
|
||||
if (found) {
|
||||
AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes(annotation,
|
||||
@@ -450,6 +461,7 @@ public class AnnotatedElementUtils {
|
||||
return searchWithGetSemantics(element, annotationType, processor, new HashSet<AnnotatedElement>(), 0);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
|
||||
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
|
||||
}
|
||||
}
|
||||
@@ -482,8 +494,8 @@ public class AnnotatedElementUtils {
|
||||
|
||||
// Start searching within locally declared annotations
|
||||
List<Annotation> declaredAnnotations = Arrays.asList(element.getDeclaredAnnotations());
|
||||
T result = searchWithGetSemanticsInAnnotations(declaredAnnotations, annotationType, processor, visited,
|
||||
metaDepth);
|
||||
T result = searchWithGetSemanticsInAnnotations(element, declaredAnnotations, annotationType, processor,
|
||||
visited, metaDepth);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
@@ -496,16 +508,17 @@ public class AnnotatedElementUtils {
|
||||
}
|
||||
|
||||
// Continue searching within inherited annotations
|
||||
result = searchWithGetSemanticsInAnnotations(inheritedAnnotations, annotationType, processor, visited,
|
||||
metaDepth);
|
||||
result = searchWithGetSemanticsInAnnotations(element, inheritedAnnotations, annotationType, processor,
|
||||
visited, metaDepth);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
AnnotationUtils.logIntrospectionFailure(element, ex);
|
||||
AnnotationUtils.handleIntrospectionFailure(element, ex);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -521,6 +534,8 @@ public class AnnotatedElementUtils {
|
||||
* {@link Processor#process process()} method of the {@link Processor}
|
||||
* API.
|
||||
*
|
||||
* @param annotatedElement the element that is annotated with the supplied
|
||||
* annotations, used for contextual logging; may be {@code null} if unknown
|
||||
* @param annotations the annotations to search in; never {@code null}
|
||||
* @param annotationType the fully qualified class name of the annotation
|
||||
* type to find; never {@code null} or empty
|
||||
@@ -529,14 +544,15 @@ public class AnnotatedElementUtils {
|
||||
* @param metaDepth the meta-depth of the annotation
|
||||
* @return the result of the processor, potentially {@code null}
|
||||
*/
|
||||
private static <T> T searchWithGetSemanticsInAnnotations(List<Annotation> annotations, String annotationType,
|
||||
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
|
||||
private static <T> T searchWithGetSemanticsInAnnotations(AnnotatedElement annotatedElement,
|
||||
List<Annotation> annotations, String annotationType, Processor<T> processor, Set<AnnotatedElement> visited,
|
||||
int metaDepth) {
|
||||
|
||||
// Search in annotations
|
||||
for (Annotation annotation : annotations) {
|
||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
|
||||
&& (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0)) {
|
||||
T result = processor.process(annotation, metaDepth);
|
||||
T result = processor.process(annotatedElement, annotation, metaDepth);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
@@ -549,7 +565,7 @@ public class AnnotatedElementUtils {
|
||||
T result = searchWithGetSemantics(annotation.annotationType(), annotationType, processor, visited,
|
||||
metaDepth + 1);
|
||||
if (result != null) {
|
||||
processor.postProcess(annotation, result);
|
||||
processor.postProcess(annotatedElement, annotation, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -599,6 +615,7 @@ public class AnnotatedElementUtils {
|
||||
searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, processor, new HashSet<AnnotatedElement>(), 0);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
|
||||
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
|
||||
}
|
||||
}
|
||||
@@ -646,7 +663,7 @@ public class AnnotatedElementUtils {
|
||||
for (Annotation annotation : annotations) {
|
||||
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
|
||||
&& (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0)) {
|
||||
T result = processor.process(annotation, metaDepth);
|
||||
T result = processor.process(element, annotation, metaDepth);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
@@ -660,7 +677,7 @@ public class AnnotatedElementUtils {
|
||||
searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces,
|
||||
searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1);
|
||||
if (result != null) {
|
||||
processor.postProcess(annotation, result);
|
||||
processor.postProcess(annotation.annotationType(), annotation, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -756,7 +773,7 @@ public class AnnotatedElementUtils {
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
AnnotationUtils.logIntrospectionFailure(element, ex);
|
||||
AnnotationUtils.handleIntrospectionFailure(element, ex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -840,12 +857,15 @@ public class AnnotatedElementUtils {
|
||||
* of 0; a meta-annotation will have a depth of 1; and a
|
||||
* meta-meta-annotation will have a depth of 2; etc.
|
||||
*
|
||||
* @param annotatedElement the element that is annotated with the
|
||||
* supplied annotation, used for contextual logging; may be
|
||||
* {@code null} if unknown
|
||||
* @param annotation the annotation to process
|
||||
* @param metaDepth the meta-depth of the annotation
|
||||
* @return the result of the processing, or {@code null} to continue
|
||||
* searching for additional annotations
|
||||
*/
|
||||
T process(Annotation annotation, int metaDepth);
|
||||
T process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth);
|
||||
|
||||
/**
|
||||
* Post-process the result returned by the {@link #process} method.
|
||||
@@ -855,10 +875,13 @@ public class AnnotatedElementUtils {
|
||||
* {@link AnnotatedElement} and an invocation of {@link #process}
|
||||
* that returned a non-null value.
|
||||
*
|
||||
* @param annotatedElement the element that is annotated with the
|
||||
* supplied annotation, used for contextual logging; may be
|
||||
* {@code null} if unknown
|
||||
* @param annotation the annotation to post-process
|
||||
* @param result the result to post-process
|
||||
*/
|
||||
void postProcess(Annotation annotation, T result);
|
||||
void postProcess(AnnotatedElement annotatedElement, Annotation annotation, T result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -872,7 +895,7 @@ public class AnnotatedElementUtils {
|
||||
* <em>No-op</em>.
|
||||
*/
|
||||
@Override
|
||||
public final void postProcess(Annotation annotation, T result) {
|
||||
public final void postProcess(AnnotatedElement annotatedElement, Annotation annotation, T result) {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
@@ -887,30 +910,64 @@ public class AnnotatedElementUtils {
|
||||
*/
|
||||
private static class MergedAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {
|
||||
|
||||
private final String annotationType;
|
||||
private final String annotationTypeName;
|
||||
private final boolean classValuesAsString;
|
||||
private final boolean nestedAnnotationsAsMap;
|
||||
|
||||
|
||||
MergedAnnotationAttributesProcessor(String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
this.annotationType = annotationType;
|
||||
MergedAnnotationAttributesProcessor(String annotationType, boolean classValuesAsString,
|
||||
boolean nestedAnnotationsAsMap) {
|
||||
this.annotationTypeName = annotationType;
|
||||
this.classValuesAsString = classValuesAsString;
|
||||
this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationAttributes process(Annotation annotation, int metaDepth) {
|
||||
boolean found = annotation.annotationType().getName().equals(annotationType);
|
||||
return (found ? AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap) : null);
|
||||
public AnnotationAttributes process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
|
||||
boolean found = annotation.annotationType().getName().equals(this.annotationTypeName);
|
||||
return (found ? AnnotationUtils.getAnnotationAttributes(annotatedElement, annotation,
|
||||
this.classValuesAsString, this.nestedAnnotationsAsMap, true, false) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcess(Annotation annotation, AnnotationAttributes attributes) {
|
||||
for (String key : attributes.keySet()) {
|
||||
if (!AnnotationUtils.VALUE.equals(key)) {
|
||||
Object value = AnnotationUtils.getValue(annotation, key);
|
||||
if (value != null) {
|
||||
attributes.put(key, AnnotationUtils.adaptValue(value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
public void postProcess(AnnotatedElement element, Annotation annotation, AnnotationAttributes attributes) {
|
||||
annotation = AnnotationUtils.synthesizeAnnotation(annotation);
|
||||
Class<? extends Annotation> targetAnnotationType = attributes.annotationType();
|
||||
|
||||
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) {
|
||||
String attributeName = attributeMethod.getName();
|
||||
String aliasedAttributeName = AnnotationUtils.getAliasedAttributeName(attributeMethod,
|
||||
targetAnnotationType);
|
||||
|
||||
// Explicit annotation attribute override declared via @AliasFor
|
||||
if (StringUtils.hasText(aliasedAttributeName)) {
|
||||
if (attributes.containsKey(aliasedAttributeName)) {
|
||||
Object value = AnnotationUtils.getValue(annotation, attributeName);
|
||||
attributes.put(aliasedAttributeName, AnnotationUtils.adaptValue(element, value,
|
||||
this.classValuesAsString, this.nestedAnnotationsAsMap));
|
||||
}
|
||||
}
|
||||
// Implicit annotation attribute override based on convention
|
||||
else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) {
|
||||
Object value = AnnotationUtils.getValue(annotation, attributeName);
|
||||
Object adaptedValue = AnnotationUtils.adaptValue(element, value, this.classValuesAsString,
|
||||
this.nestedAnnotationsAsMap);
|
||||
attributes.put(attributeName, adaptedValue);
|
||||
|
||||
// If an aliased attribute defined by @AliasFor semantics does not
|
||||
// already have an explicit value, ensure that the aliased attribute
|
||||
// is also present in the map with a value identical to its mirror
|
||||
// alias.
|
||||
Method attributeMethodInTarget = ReflectionUtils.findMethod(targetAnnotationType, attributeName);
|
||||
if (attributeMethodInTarget != null) {
|
||||
String aliasedAttributeNameInTarget = AnnotationUtils.getAliasedAttributeName(
|
||||
attributeMethodInTarget, null);
|
||||
if (aliasedAttributeNameInTarget != null) {
|
||||
Object aliasedValueInTarget = attributes.get(aliasedAttributeNameInTarget);
|
||||
if (aliasedValueInTarget == null) {
|
||||
attributes.put(aliasedAttributeNameInTarget, adaptedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.core.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -25,21 +26,41 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link LinkedHashMap} subclass representing annotation attribute key/value pairs
|
||||
* as read by Spring's reflection- or ASM-based {@link org.springframework.core.type.AnnotationMetadata}
|
||||
* implementations. Provides 'pseudo-reification' to avoid noisy Map generics in the calling code
|
||||
* as well as convenience methods for looking up annotation attributes in a type-safe fashion.
|
||||
* {@link LinkedHashMap} subclass representing annotation attribute key/value
|
||||
* pairs as read by Spring's reflection- or ASM-based
|
||||
* {@link org.springframework.core.type.AnnotationMetadata} implementations,
|
||||
* {@link AnnotationUtils}, and {@link AnnotatedElementUtils}.
|
||||
*
|
||||
* <p>Provides 'pseudo-reification' to avoid noisy Map generics in the calling
|
||||
* code as well as convenience methods for looking up annotation attributes
|
||||
* in a type-safe fashion.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Sam Brannen
|
||||
* @since 3.1.1
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||
|
||||
private final Class<? extends Annotation> annotationType;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new, empty {@link AnnotationAttributes} instance.
|
||||
*/
|
||||
public AnnotationAttributes() {
|
||||
this.annotationType = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new, empty {@link AnnotationAttributes} instance for the
|
||||
* specified {@code annotationType}.
|
||||
* @param annotationType the type of annotation represented by this
|
||||
* {@code AnnotationAttributes} instance
|
||||
* @since 4.2
|
||||
*/
|
||||
public AnnotationAttributes(Class<? extends Annotation> annotationType) {
|
||||
this.annotationType = annotationType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,6 +70,7 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||
*/
|
||||
public AnnotationAttributes(int initialCapacity) {
|
||||
super(initialCapacity);
|
||||
this.annotationType = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,8 +81,18 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||
*/
|
||||
public AnnotationAttributes(Map<String, Object> map) {
|
||||
super(map);
|
||||
this.annotationType = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of annotation represented by this {@code AnnotationAttributes}
|
||||
* instance.
|
||||
* @return the annotation type, or {@code null} if unknown
|
||||
* @since 4.2
|
||||
*/
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return this.annotationType;
|
||||
}
|
||||
|
||||
public String getString(String attributeName) {
|
||||
return doGet(attributeName, String.class);
|
||||
@@ -106,7 +138,9 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||
Assert.hasText(attributeName, "attributeName must not be null or empty");
|
||||
Object value = get(attributeName);
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException(String.format("Attribute '%s' not found", attributeName));
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Attribute '%s' not found in attributes for annotation [%s]",
|
||||
attributeName, (annotationType() != null ? annotationType.getName() : "unknown")));
|
||||
}
|
||||
if (!expectedType.isInstance(value)) {
|
||||
if (expectedType.isArray() && expectedType.getComponentType().isInstance(value)) {
|
||||
@@ -115,9 +149,10 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||
value = arrayValue;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Attribute '%s' is of type [%s], but [%s] was expected.",
|
||||
attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName()));
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Attribute '%s' is of type [%s], but [%s] was expected in attributes for annotation [%s]",
|
||||
attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName(),
|
||||
(annotationType() != null ? annotationType.getName() : "unknown")));
|
||||
}
|
||||
}
|
||||
return (T) value;
|
||||
@@ -150,10 +185,11 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||
|
||||
|
||||
/**
|
||||
* Return an {@link AnnotationAttributes} instance based on the given map; if the map
|
||||
* is already an {@code AnnotationAttributes} instance, it is casted and returned
|
||||
* immediately without creating any new instance; otherwise create a new instance by
|
||||
* wrapping the map with the {@link #AnnotationAttributes(Map)} constructor.
|
||||
* Return an {@link AnnotationAttributes} instance based on the given map.
|
||||
* <p>If the map is already an {@code AnnotationAttributes} instance, it
|
||||
* will be cast and returned immediately without creating a new instance.
|
||||
* Otherwise a new instance will be created by passing the supplied map
|
||||
* to the {@link #AnnotationAttributes(Map)} constructor.
|
||||
* @param map original source of annotation attribute key/value pairs
|
||||
*/
|
||||
public static AnnotationAttributes fromMap(Map<String, Object> map) {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.annotation;
|
||||
|
||||
/**
|
||||
* Thrown by {@link AnnotationUtils} if an annotation is improperly configured.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.2
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class AnnotationConfigurationException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* Construct a new {@code AnnotationConfigurationException} with the
|
||||
* supplied message.
|
||||
* @param message the detail message
|
||||
*/
|
||||
public AnnotationConfigurationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@code AnnotationConfigurationException} with the
|
||||
* supplied message and cause.
|
||||
* @param message the detail message
|
||||
* @param cause the root cause
|
||||
*/
|
||||
public AnnotationConfigurationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,9 +18,13 @@ package org.springframework.core.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -29,9 +33,9 @@ import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -88,9 +92,17 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public abstract class AnnotationUtils {
|
||||
|
||||
/** The attribute name for annotations with a single element */
|
||||
/**
|
||||
* The attribute name for annotations with a single element.
|
||||
*/
|
||||
public static final String VALUE = "value";
|
||||
|
||||
/**
|
||||
* A object that can be stored in {@link AnnotationAttributes} as a
|
||||
* placeholder for an attribute's declared default value.
|
||||
*/
|
||||
public static final Object DEFAULT_VALUE_PLACEHOLDER = "<SPRING DEFAULT VALUE PLACEHOLDER>";
|
||||
|
||||
|
||||
private static final Map<AnnotationCacheKey, Annotation> findAnnotationCache =
|
||||
new ConcurrentReferenceHashMap<AnnotationCacheKey, Annotation>(256);
|
||||
@@ -116,14 +128,15 @@ public abstract class AnnotationUtils {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <A extends Annotation> A getAnnotation(Annotation ann, Class<A> annotationType) {
|
||||
if (annotationType.isInstance(ann)) {
|
||||
return (A) ann;
|
||||
return synthesizeAnnotation((A) ann);
|
||||
}
|
||||
Class<? extends Annotation> annotatedElement = ann.annotationType();
|
||||
try {
|
||||
return ann.annotationType().getAnnotation(annotationType);
|
||||
return synthesizeAnnotation(annotatedElement, annotatedElement.getAnnotation(annotationType));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Assuming nested Class values not resolvable within annotation attributes...
|
||||
logIntrospectionFailure(ann.annotationType(), ex);
|
||||
handleIntrospectionFailure(annotatedElement, ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -151,11 +164,11 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
return ann;
|
||||
return synthesizeAnnotation(annotatedElement, ann);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Assuming nested Class values not resolvable within annotation attributes...
|
||||
logIntrospectionFailure(annotatedElement, ex);
|
||||
handleIntrospectionFailure(annotatedElement, ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -195,7 +208,7 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Assuming nested Class values not resolvable within annotation attributes...
|
||||
logIntrospectionFailure(annotatedElement, ex);
|
||||
handleIntrospectionFailure(annotatedElement, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -218,7 +231,7 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Assuming nested Class values not resolvable within annotation attributes...
|
||||
logIntrospectionFailure(method, ex);
|
||||
handleIntrospectionFailure(method, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -274,7 +287,7 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Assuming nested Class values not resolvable within annotation attributes...
|
||||
logIntrospectionFailure(annotatedElement, ex);
|
||||
handleIntrospectionFailure(annotatedElement, ex);
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
@@ -298,7 +311,8 @@ public abstract class AnnotationUtils {
|
||||
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
|
||||
// Do NOT store result in the findAnnotationCache since doing so could break
|
||||
// findAnnotation(Class, Class) and findAnnotation(Method, Class).
|
||||
return findAnnotation(annotatedElement, annotationType, new HashSet<Annotation>());
|
||||
return synthesizeAnnotation(annotatedElement,
|
||||
findAnnotation(annotatedElement, annotationType, new HashSet<Annotation>()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -332,7 +346,7 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Assuming nested Class values not resolvable within annotation attributes...
|
||||
logIntrospectionFailure(annotatedElement, ex);
|
||||
handleIntrospectionFailure(annotatedElement, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -389,7 +403,7 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return synthesizeAnnotation(method, result);
|
||||
}
|
||||
|
||||
private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) {
|
||||
@@ -426,7 +440,7 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Assuming nested Class values not resolvable within annotation attributes...
|
||||
logIntrospectionFailure(ifcMethod, ex);
|
||||
handleIntrospectionFailure(ifcMethod, ex);
|
||||
}
|
||||
}
|
||||
annotatedInterfaceCache.put(iface, found);
|
||||
@@ -465,7 +479,7 @@ public abstract class AnnotationUtils {
|
||||
findAnnotationCache.put(cacheKey, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return synthesizeAnnotation(clazz, result);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -499,7 +513,7 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Assuming nested Class values not resolvable within annotation attributes...
|
||||
logIntrospectionFailure(clazz, ex);
|
||||
handleIntrospectionFailure(clazz, ex);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -617,7 +631,7 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Assuming nested Class values not resolvable within annotation attributes...
|
||||
logIntrospectionFailure(clazz, ex);
|
||||
handleIntrospectionFailure(clazz, ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -682,9 +696,10 @@ public abstract class AnnotationUtils {
|
||||
* @return the Map of annotation attributes, with attribute names as keys and
|
||||
* corresponding attribute values as values; never {@code null}
|
||||
* @see #getAnnotationAttributes(Annotation, boolean, boolean)
|
||||
* @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
|
||||
*/
|
||||
public static Map<String, Object> getAnnotationAttributes(Annotation annotation) {
|
||||
return getAnnotationAttributes(annotation, false, false);
|
||||
return getAnnotationAttributes(null, annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -706,8 +721,7 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given annotation's attributes as an {@link AnnotationAttributes}
|
||||
* map structure.
|
||||
* Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map.
|
||||
* <p>This method provides fully recursive annotation reading capabilities on par with
|
||||
* the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}.
|
||||
* @param annotation the annotation to retrieve the attributes for
|
||||
@@ -724,19 +738,106 @@ public abstract class AnnotationUtils {
|
||||
*/
|
||||
public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, boolean classValuesAsString,
|
||||
boolean nestedAnnotationsAsMap) {
|
||||
return getAnnotationAttributes(null, annotation, classValuesAsString, nestedAnnotationsAsMap);
|
||||
}
|
||||
|
||||
AnnotationAttributes attrs = new AnnotationAttributes();
|
||||
Method[] methods = annotation.annotationType().getDeclaredMethods();
|
||||
for (Method method : methods) {
|
||||
if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) {
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
Object value = method.invoke(annotation);
|
||||
attrs.put(method.getName(), adaptValue(value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
/**
|
||||
* Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map.
|
||||
* <p>Equivalent to calling {@link #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)}
|
||||
* with the {@code classValuesAsString} and {@code nestedAnnotationsAsMap} parameters
|
||||
* set to {@code false}.
|
||||
* @param annotation the annotation to retrieve the attributes for
|
||||
* @return the Map of annotation attributes, with attribute names as keys and
|
||||
* corresponding attribute values as values; never {@code null}
|
||||
* @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
|
||||
* @since 4.2
|
||||
*/
|
||||
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation) {
|
||||
return getAnnotationAttributes(annotatedElement, annotation, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map.
|
||||
* <p>This method provides fully recursive annotation reading capabilities on par with
|
||||
* the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}.
|
||||
* @param annotatedElement the element that is annotated with the supplied annotation,
|
||||
* used for contextual logging; may be {@code null} if unknown
|
||||
* @param annotation the annotation to retrieve the attributes for
|
||||
* @param classValuesAsString whether to convert Class references into Strings (for
|
||||
* compatibility with {@link org.springframework.core.type.AnnotationMetadata})
|
||||
* or to preserve them as Class references
|
||||
* @param nestedAnnotationsAsMap whether to convert nested Annotation instances into
|
||||
* {@link AnnotationAttributes} maps (for compatibility with
|
||||
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
|
||||
* Annotation instances
|
||||
* @param defaultValuesAsPlaceholder whether to replace default values with
|
||||
* {@link #DEFAULT_VALUE_PLACEHOLDER} or leave them as is
|
||||
* @return the annotation attributes (a specialized Map) with attribute names as keys
|
||||
* and corresponding attribute values as values; never {@code null}
|
||||
* @since 4.2
|
||||
*/
|
||||
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
|
||||
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
|
||||
return getAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap,
|
||||
false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map.
|
||||
*
|
||||
* <p>This method provides fully recursive annotation reading capabilities on par with
|
||||
* the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}.
|
||||
*
|
||||
* @param annotatedElement the element that is annotated with the supplied annotation,
|
||||
* used for contextual logging; may be {@code null} if unknown
|
||||
* @param annotation the annotation to retrieve the attributes for
|
||||
* @param classValuesAsString whether to convert Class references into Strings (for
|
||||
* compatibility with {@link org.springframework.core.type.AnnotationMetadata})
|
||||
* or to preserve them as Class references
|
||||
* @param nestedAnnotationsAsMap whether to convert nested Annotation instances into
|
||||
* {@link AnnotationAttributes} maps (for compatibility with
|
||||
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
|
||||
* Annotation instances
|
||||
* @param defaultValuesAsPlaceholder whether to replace default values with
|
||||
* {@link #DEFAULT_VALUE_PLACEHOLDER} or leave them as is
|
||||
* @param synthesizeAnnotation whether or not the annotation should be
|
||||
* {@linkplain #synthesizeAnnotation synthesized} before processing
|
||||
* @return the annotation attributes (a specialized Map) with attribute names as keys
|
||||
* and corresponding attribute values as values; never {@code null}
|
||||
* @since 4.2
|
||||
*/
|
||||
static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
|
||||
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap,
|
||||
boolean defaultValuesAsPlaceholder, boolean synthesizeAnnotation) {
|
||||
|
||||
if (synthesizeAnnotation) {
|
||||
annotation = synthesizeAnnotation(annotatedElement, annotation);
|
||||
}
|
||||
|
||||
Class<? extends Annotation> annotationType = annotation.annotationType();
|
||||
AnnotationAttributes attrs = new AnnotationAttributes(annotationType);
|
||||
for (Method method : getAttributeMethods(annotationType)) {
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
Object value = method.invoke(annotation);
|
||||
|
||||
Object defaultValue = method.getDefaultValue();
|
||||
if (defaultValuesAsPlaceholder && (defaultValue != null)) {
|
||||
if (ObjectUtils.nullSafeEquals(value, defaultValue)) {
|
||||
value = DEFAULT_VALUE_PLACEHOLDER;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Could not obtain annotation attribute values", ex);
|
||||
|
||||
attrs.put(method.getName(),
|
||||
adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
if (ex instanceof InvocationTargetException) {
|
||||
Throwable targetException = ((InvocationTargetException) ex).getTargetException();
|
||||
rethrowAnnotationConfigurationException(targetException);
|
||||
}
|
||||
throw new IllegalStateException("Could not obtain annotation attribute value for " + method, ex);
|
||||
}
|
||||
}
|
||||
return attrs;
|
||||
@@ -744,6 +845,10 @@ public abstract class AnnotationUtils {
|
||||
|
||||
/**
|
||||
* Adapt the given value according to the given class and nested annotation settings.
|
||||
* <p>Nested annotations will be
|
||||
* {@linkplain #synthesizeAnnotation(AnnotatedElement, Annotation) synthesized}.
|
||||
* @param annotatedElement the element that is annotated, used for contextual
|
||||
* logging; may be {@code null} if unknown
|
||||
* @param value the annotation attribute value
|
||||
* @param classValuesAsString whether to turn Class references into Strings (for
|
||||
* compatibility with {@link org.springframework.core.type.AnnotationMetadata})
|
||||
@@ -754,34 +859,57 @@ public abstract class AnnotationUtils {
|
||||
* Annotation instances
|
||||
* @return the adapted value, or the original value if no adaptation is needed
|
||||
*/
|
||||
static Object adaptValue(Object value, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
static Object adaptValue(AnnotatedElement annotatedElement, Object value, boolean classValuesAsString,
|
||||
boolean nestedAnnotationsAsMap) {
|
||||
|
||||
if (classValuesAsString) {
|
||||
if (value instanceof Class) {
|
||||
value = ((Class<?>) value).getName();
|
||||
return ((Class<?>) value).getName();
|
||||
}
|
||||
else if (value instanceof Class[]) {
|
||||
Class<?>[] clazzArray = (Class<?>[]) value;
|
||||
String[] newValue = new String[clazzArray.length];
|
||||
String[] classNames = new String[clazzArray.length];
|
||||
for (int i = 0; i < clazzArray.length; i++) {
|
||||
newValue[i] = clazzArray[i].getName();
|
||||
classNames[i] = clazzArray[i].getName();
|
||||
}
|
||||
value = newValue;
|
||||
return classNames;
|
||||
}
|
||||
}
|
||||
if (nestedAnnotationsAsMap && value instanceof Annotation) {
|
||||
return getAnnotationAttributes((Annotation) value, classValuesAsString, true);
|
||||
}
|
||||
else if (nestedAnnotationsAsMap && value instanceof Annotation[]) {
|
||||
Annotation[] realAnnotations = (Annotation[]) value;
|
||||
AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
|
||||
for (int i = 0; i < realAnnotations.length; i++) {
|
||||
mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], classValuesAsString, true);
|
||||
|
||||
if (value instanceof Annotation) {
|
||||
Annotation annotation = (Annotation) value;
|
||||
|
||||
if (nestedAnnotationsAsMap) {
|
||||
return getAnnotationAttributes(annotatedElement, annotation, classValuesAsString,
|
||||
nestedAnnotationsAsMap);
|
||||
}
|
||||
else {
|
||||
return synthesizeAnnotation(annotatedElement, annotation);
|
||||
}
|
||||
return mappedAnnotations;
|
||||
}
|
||||
else {
|
||||
return value;
|
||||
|
||||
if (value instanceof Annotation[]) {
|
||||
Annotation[] annotations = (Annotation[]) value;
|
||||
|
||||
if (nestedAnnotationsAsMap) {
|
||||
AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[annotations.length];
|
||||
for (int i = 0; i < annotations.length; i++) {
|
||||
mappedAnnotations[i] = getAnnotationAttributes(annotatedElement, annotations[i],
|
||||
classValuesAsString, nestedAnnotationsAsMap);
|
||||
}
|
||||
return mappedAnnotations;
|
||||
}
|
||||
else {
|
||||
Annotation[] synthesizedAnnotations = new Annotation[annotations.length];
|
||||
for (int i = 0; i < annotations.length; i++) {
|
||||
synthesizedAnnotations[i] = synthesizeAnnotation(annotatedElement, annotations[i]);
|
||||
}
|
||||
return synthesizedAnnotations;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -803,7 +931,7 @@ public abstract class AnnotationUtils {
|
||||
* @see #getValue(Annotation)
|
||||
*/
|
||||
public static Object getValue(Annotation annotation, String attributeName) {
|
||||
if (annotation == null || !StringUtils.hasLength(attributeName)) {
|
||||
if (annotation == null || !StringUtils.hasText(attributeName)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
@@ -861,7 +989,7 @@ public abstract class AnnotationUtils {
|
||||
* @see #getDefaultValue(Annotation, String)
|
||||
*/
|
||||
public static Object getDefaultValue(Class<? extends Annotation> annotationType, String attributeName) {
|
||||
if (annotationType == null || !StringUtils.hasLength(attributeName)) {
|
||||
if (annotationType == null || !StringUtils.hasText(attributeName)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
@@ -872,14 +1000,343 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO Document synthesizeAnnotation().
|
||||
*
|
||||
* @param annotation the annotation to synthesize
|
||||
* @since 4.2
|
||||
* @see #synthesizeAnnotation(AnnotatedElement, Annotation)
|
||||
*/
|
||||
public static <A extends Annotation> A synthesizeAnnotation(A annotation) {
|
||||
return synthesizeAnnotation(null, annotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an introspection failure (in particular {@code TypeNotPresentExceptions}) -
|
||||
* before moving on, pretending there were no annotations on this specific element.
|
||||
* TODO Document synthesizeAnnotation().
|
||||
*
|
||||
* @param annotatedElement the element that is annotated with the supplied
|
||||
* annotation, used for contextual logging; may be {@code null} if unknown
|
||||
* @param annotation the annotation to synthesize
|
||||
* @since 4.2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <A extends Annotation> A synthesizeAnnotation(AnnotatedElement annotatedElement, A annotation) {
|
||||
if (annotation == null) {
|
||||
return null;
|
||||
}
|
||||
if (annotation instanceof SynthesizedAnnotation) {
|
||||
return annotation;
|
||||
}
|
||||
|
||||
Class<? extends Annotation> annotationType = annotation.annotationType();
|
||||
|
||||
// No need to synthesize?
|
||||
if (!isSynthesizable(annotationType)) {
|
||||
return annotation;
|
||||
}
|
||||
|
||||
InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(annotatedElement, annotation, getAliasMap(annotationType));
|
||||
A synthesizedAnnotation = (A) Proxy.newProxyInstance(ClassUtils.getDefaultClassLoader(), new Class<?>[] {
|
||||
(Class<A>) annotationType, SynthesizedAnnotation.class }, handler);
|
||||
|
||||
return synthesizedAnnotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO Document getAliasMap().
|
||||
* @since 4.2
|
||||
*/
|
||||
private static Map<String, String> getAliasMap(Class<? extends Annotation> annotationType) {
|
||||
if (annotationType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
for (Method attribute : getAttributeMethods(annotationType)) {
|
||||
String attributeName = attribute.getName();
|
||||
String aliasedAttributeName = getAliasedAttributeName(attribute);
|
||||
if (aliasedAttributeName != null) {
|
||||
map.put(attributeName, aliasedAttributeName);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO Document isSynthesizable().
|
||||
* @since 4.2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static boolean isSynthesizable(Class<? extends Annotation> annotationType) {
|
||||
|
||||
for (Method attribute : getAttributeMethods(annotationType)) {
|
||||
if (getAliasedAttributeName(attribute) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Class<?> returnType = attribute.getReturnType();
|
||||
|
||||
if (Annotation[].class.isAssignableFrom(returnType)) {
|
||||
Class<? extends Annotation> nestedAnnotationType = (Class<? extends Annotation>) returnType.getComponentType();
|
||||
if (isSynthesizable(nestedAnnotationType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (Annotation.class.isAssignableFrom(returnType)) {
|
||||
Class<? extends Annotation> nestedAnnotationType = (Class<? extends Annotation>) returnType;
|
||||
if (isSynthesizable(nestedAnnotationType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the aliased attribute configured via
|
||||
* {@link AliasFor @AliasFor} on the supplied annotation {@code attribute}.
|
||||
*
|
||||
* <p>This method does not resolve aliases in other annotations. In
|
||||
* other words, if {@code @AliasFor} is present on the supplied
|
||||
* {@code attribute} but {@linkplain AliasFor#annotation references an
|
||||
* annotation} other than {@link Annotation}, this method will return
|
||||
* {@code null} immediately.
|
||||
*
|
||||
* @param attribute the attribute to find an alias for
|
||||
* @return the name of the aliased attribute, or {@code null} if not found
|
||||
* @throws IllegalArgumentException if the supplied attribute method is
|
||||
* not from an annotation, or if the supplied target type is {@link Annotation}
|
||||
* @throws AnnotationConfigurationException if invalid configuration of
|
||||
* {@code @AliasFor} is detected
|
||||
* @see #getAliasedAttributeName(Method, Class)
|
||||
* @since 4.2
|
||||
*/
|
||||
static String getAliasedAttributeName(Method attribute) {
|
||||
return getAliasedAttributeName(attribute, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the aliased attribute configured via
|
||||
* {@link AliasFor @AliasFor} on the supplied annotation {@code attribute}.
|
||||
*
|
||||
* @param attribute the attribute to find an alias for
|
||||
* @param targetAnnotationType the type of annotation in which the
|
||||
* aliased attribute is allowed to be declared; {@code null} implies
|
||||
* <em>within the same annotation</em>
|
||||
* @return the name of the aliased attribute, or {@code null} if not found
|
||||
* @throws IllegalArgumentException if the supplied attribute method is
|
||||
* not from an annotation, or if the supplied target type is {@link Annotation}
|
||||
* @throws AnnotationConfigurationException if invalid configuration of
|
||||
* {@code @AliasFor} is detected
|
||||
* @since 4.2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static String getAliasedAttributeName(Method attribute, Class<? extends Annotation> targetAnnotationType) {
|
||||
Class<?> declaringClass = attribute.getDeclaringClass();
|
||||
Assert.isTrue(declaringClass.isAnnotation(), "attribute method must be from an annotation");
|
||||
Assert.isTrue(!Annotation.class.equals(targetAnnotationType),
|
||||
"targetAnnotationType must not be java.lang.annotation.Annotation");
|
||||
|
||||
AliasFor aliasFor = attribute.getAnnotation(AliasFor.class);
|
||||
|
||||
// Nothing to check
|
||||
if (aliasFor == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<? extends Annotation> sourceAnnotationType = (Class<? extends Annotation>) declaringClass;
|
||||
Class<? extends Annotation> aliasedAnnotationType = aliasFor.annotation();
|
||||
|
||||
boolean searchWithinSameAnnotation = (targetAnnotationType == null);
|
||||
boolean sameTargetDeclared = (sourceAnnotationType.equals(aliasedAnnotationType) || Annotation.class.equals(aliasedAnnotationType));
|
||||
|
||||
// Wrong search scope?
|
||||
if (searchWithinSameAnnotation && !sameTargetDeclared) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String attributeName = attribute.getName();
|
||||
String aliasedAttributeName = aliasFor.attribute();
|
||||
|
||||
if (!StringUtils.hasText(aliasedAttributeName)) {
|
||||
String msg = String.format(
|
||||
"@AliasFor declaration on attribute [%s] in annotation [%s] is missing required 'attribute' value.",
|
||||
attributeName, sourceAnnotationType.getName());
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
if (sameTargetDeclared) {
|
||||
aliasedAnnotationType = sourceAnnotationType;
|
||||
}
|
||||
|
||||
Method aliasedAttribute = null;
|
||||
try {
|
||||
aliasedAttribute = aliasedAnnotationType.getDeclaredMethod(aliasedAttributeName);
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
String msg = String.format(
|
||||
"Attribute [%s] in annotation [%s] is declared as an @AliasFor nonexistent attribute [%s] in annotation [%s].",
|
||||
attributeName, sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
|
||||
throw new AnnotationConfigurationException(msg, e);
|
||||
}
|
||||
|
||||
if (sameTargetDeclared) {
|
||||
AliasFor mirrorAliasFor = aliasedAttribute.getAnnotation(AliasFor.class);
|
||||
if (mirrorAliasFor == null) {
|
||||
String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s].",
|
||||
aliasedAttributeName, sourceAnnotationType.getName(), attributeName);
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
String mirrorAliasedAttributeName = mirrorAliasFor.attribute();
|
||||
if (!attributeName.equals(mirrorAliasedAttributeName)) {
|
||||
String msg = String.format(
|
||||
"Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
|
||||
aliasedAttributeName, sourceAnnotationType.getName(), attributeName, mirrorAliasedAttributeName);
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> returnType = attribute.getReturnType();
|
||||
Class<?> aliasedReturnType = aliasedAttribute.getReturnType();
|
||||
if (!returnType.equals(aliasedReturnType)) {
|
||||
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] "
|
||||
+ "and attribute [%s] in annotation [%s] must declare the same return type.", attributeName,
|
||||
sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
if (sameTargetDeclared) {
|
||||
Object defaultValue = attribute.getDefaultValue();
|
||||
Object aliasedDefaultValue = aliasedAttribute.getDefaultValue();
|
||||
|
||||
if ((defaultValue == null) || (aliasedDefaultValue == null)) {
|
||||
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] "
|
||||
+ "and attribute [%s] in annotation [%s] must declare default values.", attributeName,
|
||||
sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(defaultValue, aliasedDefaultValue)) {
|
||||
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] "
|
||||
+ "and attribute [%s] in annotation [%s] must declare the same default value.", attributeName,
|
||||
sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return aliasedAttributeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO Document getAttributeMethods().
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
static List<Method> getAttributeMethods(Class<? extends Annotation> annotationType) {
|
||||
List<Method> methods = new ArrayList<Method>();
|
||||
for (Method method : annotationType.getDeclaredMethods()) {
|
||||
if ((method.getParameterTypes().length == 0) && (method.getReturnType() != void.class)) {
|
||||
methods.add(method);
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO Document postProcessAnnotationAttributes().
|
||||
*
|
||||
* @param annotatedElement the element that is annotated with the supplied
|
||||
* annotation, used for contextual logging; may be {@code null} if unknown
|
||||
* @param attributes the annotation attributes to validate
|
||||
* @since 4.2
|
||||
*/
|
||||
static void postProcessAnnotationAttributes(AnnotatedElement element, AnnotationAttributes attributes,
|
||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
|
||||
// Abort?
|
||||
if (attributes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Class<? extends Annotation> annotationType = attributes.annotationType();
|
||||
Map<String, String> aliasMap = getAliasMap(annotationType);
|
||||
|
||||
// Validate @AliasFor configuration
|
||||
if (aliasMap != null) {
|
||||
Set<String> validated = new HashSet<String>();
|
||||
|
||||
for (String attributeName : aliasMap.keySet()) {
|
||||
String aliasedAttributeName = aliasMap.get(attributeName);
|
||||
|
||||
if (validated.add(attributeName) && validated.add(aliasedAttributeName)) {
|
||||
Object value = attributes.get(attributeName);
|
||||
Object aliasedValue = attributes.get(aliasedAttributeName);
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && !DEFAULT_VALUE_PLACEHOLDER.equals(value)
|
||||
&& !DEFAULT_VALUE_PLACEHOLDER.equals(aliasedValue)) {
|
||||
String elementAsString = (element == null ? "unknown element" : element.toString());
|
||||
String msg = String.format(
|
||||
"In AnnotationAttributes for annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are "
|
||||
+ "declared with values of [%s] and [%s], but only one declaration is permitted.",
|
||||
annotationType.getName(), elementAsString, attributeName, aliasedAttributeName,
|
||||
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
// Replace default values with aliased values...
|
||||
if (DEFAULT_VALUE_PLACEHOLDER.equals(value)) {
|
||||
attributes.put(attributeName,
|
||||
adaptValue(element, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
if (DEFAULT_VALUE_PLACEHOLDER.equals(aliasedValue)) {
|
||||
attributes.put(aliasedAttributeName,
|
||||
adaptValue(element, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String attributeName : attributes.keySet()) {
|
||||
Object value = attributes.get(attributeName);
|
||||
if (DEFAULT_VALUE_PLACEHOLDER.equals(value)) {
|
||||
attributes.put(attributeName,
|
||||
adaptValue(element, getDefaultValue(annotationType, attributeName), classValuesAsString,
|
||||
nestedAnnotationsAsMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>If the supplied throwable is an {@link AnnotationConfigurationException},
|
||||
* it will be cast to an {@code AnnotationConfigurationException} and thrown,
|
||||
* allowing it to propagate to the caller.
|
||||
* <p>Otherwise, this method does nothing.
|
||||
* @since 4.2
|
||||
*/
|
||||
static void rethrowAnnotationConfigurationException(Throwable t) {
|
||||
if (t instanceof AnnotationConfigurationException) {
|
||||
throw (AnnotationConfigurationException) t;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the supplied annotation introspection exception.
|
||||
* <p>If the supplied exception is an {@link AnnotationConfigurationException},
|
||||
* it will simply be thrown, allowing it to propagate to the caller, and
|
||||
* nothing will be logged.
|
||||
* <p>Otherwise, this method logs an introspection failure (in particular
|
||||
* {@code TypeNotPresentExceptions}) — before moving on, pretending
|
||||
* there were no annotations on this specific element.
|
||||
* @param element the element that we tried to introspect annotations on
|
||||
* @param ex the exception that we encountered
|
||||
* @see #rethrowAnnotationConfigurationException
|
||||
*/
|
||||
static void logIntrospectionFailure(AnnotatedElement element, Exception ex) {
|
||||
static void handleIntrospectionFailure(AnnotatedElement element, Exception ex) {
|
||||
|
||||
rethrowAnnotationConfigurationException(ex);
|
||||
|
||||
Log loggerToUse = logger;
|
||||
if (loggerToUse == null) {
|
||||
loggerToUse = LogFactory.getLog(AnnotationUtils.class);
|
||||
@@ -896,7 +1353,6 @@ public abstract class AnnotationUtils {
|
||||
if (loggerToUse.isInfoEnabled()) {
|
||||
logger.info("Failed to introspect annotations on [" + element + "]: " + ex);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -962,10 +1418,10 @@ public abstract class AnnotationUtils {
|
||||
for (Annotation ann : element.getAnnotations()) {
|
||||
Class<? extends Annotation> currentAnnotationType = ann.annotationType();
|
||||
if (ObjectUtils.nullSafeEquals(this.annotationType, currentAnnotationType)) {
|
||||
this.result.add((A) ann);
|
||||
this.result.add(synthesizeAnnotation(element, (A) ann));
|
||||
}
|
||||
else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, currentAnnotationType)) {
|
||||
this.result.addAll(getValue(ann));
|
||||
this.result.addAll(getValue(element, ann));
|
||||
}
|
||||
else if (!isInJavaLangAnnotationPackage(ann)) {
|
||||
process(currentAnnotationType);
|
||||
@@ -973,17 +1429,23 @@ public abstract class AnnotationUtils {
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logIntrospectionFailure(element, ex);
|
||||
handleIntrospectionFailure(element, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<A> getValue(Annotation annotation) {
|
||||
private List<A> getValue(AnnotatedElement element, Annotation annotation) {
|
||||
try {
|
||||
Method method = annotation.annotationType().getDeclaredMethod("value");
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
return Arrays.asList((A[]) method.invoke(annotation));
|
||||
A[] annotations = (A[]) method.invoke(annotation);
|
||||
|
||||
List<A> synthesizedAnnotations = new ArrayList<A>();
|
||||
for (A anno : annotations) {
|
||||
synthesizedAnnotations.add(synthesizeAnnotation(element, anno));
|
||||
}
|
||||
return synthesizedAnnotations;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Unable to read value from repeating annotation container -> ignore it.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.annotation;
|
||||
|
||||
/**
|
||||
* Marker interface implemented by synthesized annotation proxies.
|
||||
*
|
||||
* <p>Used to detect whether an annotation has already been synthesized.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.2
|
||||
*/
|
||||
interface SynthesizedAnnotation {
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* TODO Document SynthesizedAnnotationInvocationHandler.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.2
|
||||
*/
|
||||
class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final AnnotatedElement annotatedElement;
|
||||
|
||||
private final Annotation annotation;
|
||||
|
||||
private final Map<String, String> aliasPairs;
|
||||
|
||||
|
||||
public SynthesizedAnnotationInvocationHandler(Annotation annotation, Map<String, String> aliasPairs) {
|
||||
this(null, annotation, aliasPairs);
|
||||
}
|
||||
|
||||
public SynthesizedAnnotationInvocationHandler(AnnotatedElement annotatedElement, Annotation annotation,
|
||||
Map<String, String> aliasPairs) {
|
||||
this.annotatedElement = annotatedElement;
|
||||
this.annotation = annotation;
|
||||
this.aliasPairs = aliasPairs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
String attributeName = method.getName();
|
||||
Class<?> returnType = method.getReturnType();
|
||||
boolean nestedAnnotation = (Annotation[].class.isAssignableFrom(returnType) || Annotation.class.isAssignableFrom(returnType));
|
||||
String aliasedAttributeName = aliasPairs.get(attributeName);
|
||||
boolean aliasPresent = aliasedAttributeName != null;
|
||||
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
Object value = ReflectionUtils.invokeMethod(method, this.annotation, args);
|
||||
|
||||
// Nothing special to do?
|
||||
if (!aliasPresent && !nestedAnnotation) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (aliasPresent) {
|
||||
Method aliasedMethod = null;
|
||||
try {
|
||||
aliasedMethod = annotation.annotationType().getDeclaredMethod(aliasedAttributeName);
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
String msg = String.format("In annotation [%s], attribute [%s] is declared as an @AliasFor [%s], "
|
||||
+ "but attribute [%s] does not exist.", annotation.annotationType().getName(), attributeName,
|
||||
aliasedAttributeName, aliasedAttributeName);
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
ReflectionUtils.makeAccessible(aliasedMethod);
|
||||
Object aliasedValue = ReflectionUtils.invokeMethod(aliasedMethod, this.annotation, args);
|
||||
Object defaultValue = AnnotationUtils.getDefaultValue(annotation, attributeName);
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && !ObjectUtils.nullSafeEquals(value, defaultValue)
|
||||
&& !ObjectUtils.nullSafeEquals(aliasedValue, defaultValue)) {
|
||||
String elementAsString = (annotatedElement == null ? "unknown element" : annotatedElement.toString());
|
||||
String msg = String.format(
|
||||
"In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are "
|
||||
+ "declared with values of [%s] and [%s], but only one declaration is permitted.",
|
||||
annotation.annotationType().getName(), elementAsString, attributeName, aliasedAttributeName,
|
||||
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
// If the user didn't declare the annotation with an explicit value, return
|
||||
// the value of the alias.
|
||||
if (ObjectUtils.nullSafeEquals(value, defaultValue)) {
|
||||
value = aliasedValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Synthesize nested annotations before returning them.
|
||||
if (value instanceof Annotation) {
|
||||
value = AnnotationUtils.synthesizeAnnotation(annotatedElement, (Annotation) value);
|
||||
}
|
||||
else if (value instanceof Annotation[]) {
|
||||
Annotation[] annotations = (Annotation[]) value;
|
||||
for (int i = 0; i < annotations.length; i++) {
|
||||
annotations[i] = AnnotationUtils.synthesizeAnnotation(annotatedElement, annotations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
/**
|
||||
* Core support package for Java 5 annotations.
|
||||
* Core support package for annotations, meta-annotations, and composed
|
||||
* annotations with attribute overrides.
|
||||
*/
|
||||
package org.springframework.core.annotation;
|
||||
|
||||
Reference in New Issue
Block a user