Skip to content

Commit ebed52c

Browse files
committed
Favor local, composed annotations in AnnotatedElementUtils
This commit updates the "get semantics" search algorithm used in `AnnotatedElementUtils` so that locally declared 'composed annotations' are favored over inherited annotations. Specifically, the internal `searchWithGetSemantics()` method now searches locally declared annotations before searching inherited annotations. All TODOs in `AnnotatedElementUtilsTests` have been completed, and all ignored tests have been reinstated. Issue: SPR-11598
1 parent 03ade48 commit ebed52c

File tree

3 files changed

+162
-123
lines changed

3 files changed

+162
-123
lines changed

spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java

Lines changed: 91 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
import java.lang.annotation.Annotation;
2020
import java.lang.reflect.AnnotatedElement;
2121
import java.lang.reflect.Method;
22+
import java.util.ArrayList;
23+
import java.util.Arrays;
2224
import java.util.HashSet;
2325
import java.util.LinkedHashSet;
26+
import java.util.List;
2427
import java.util.Map;
2528
import java.util.Set;
2629

@@ -71,11 +74,13 @@
7174
*
7275
* <h3>Support for {@code @Inherited}</h3>
7376
* <p>Methods following <em>get semantics</em> will honor the contract of
74-
* Java's {@link java.lang.annotation.Inherited @Inherited} annotation.
75-
* However, methods following <em>find semantics</em> will ignore the
76-
* presence of {@code @Inherited} since the <em>find</em> search algorithm
77-
* manually traverses type and method hierarchies and thereby implicitly
78-
* supports annotation inheritance without the need for {@code @Inherited}.
77+
* Java's {@link java.lang.annotation.Inherited @Inherited} annotation except
78+
* that locally declared annotations (including custom composed annotations)
79+
* will be favored over inherited annotations. In contrast, methods following
80+
* <em>find semantics</em> will completely ignore the presence of
81+
* {@code @Inherited} since the <em>find</em> search algorithm manually
82+
* traverses type and method hierarchies and thereby implicitly supports
83+
* annotation inheritance without the need for {@code @Inherited}.
7984
*
8085
* @author Phillip Webb
8186
* @author Juergen Hoeller
@@ -352,45 +357,8 @@ public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement ele
352357
*/
353358
public static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType,
354359
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
355-
return findAnnotationAttributes(element, annotationType, true, true, true, true, classValuesAsString,
356-
nestedAnnotationsAsMap);
357-
}
358-
359-
/**
360-
* Find the first annotation of the specified {@code annotationType} within
361-
* the annotation hierarchy <em>above</em> the supplied {@code element} and
362-
* merge that annotation's attributes with <em>matching</em> attributes from
363-
* annotations in lower levels of the annotation hierarchy.
364-
*
365-
* @param element the annotated element; never {@code null}
366-
* @param annotationType the fully qualified class name of the annotation
367-
* type to find; never {@code null} or empty
368-
* @param searchOnInterfaces whether to search on interfaces, if the
369-
* annotated element is a class
370-
* @param searchOnSuperclasses whether to search on superclasses, if
371-
* the annotated element is a class
372-
* @param searchOnMethodsInInterfaces whether to search on methods in
373-
* interfaces, if the annotated element is a method
374-
* @param searchOnMethodsInSuperclasses whether to search on methods
375-
* in superclasses, if the annotated element is a method
376-
* @param classValuesAsString whether to convert Class references into
377-
* Strings or to preserve them as Class references
378-
* @param nestedAnnotationsAsMap whether to convert nested Annotation
379-
* instances into {@code AnnotationAttributes} maps or to preserve them
380-
* as Annotation instances
381-
* @return the merged {@code AnnotationAttributes}, or {@code null} if
382-
* not found
383-
* @since 4.2
384-
* @see #searchWithFindSemantics
385-
* @see MergedAnnotationAttributesProcessor
386-
*/
387-
private static AnnotationAttributes findAnnotationAttributes(AnnotatedElement element, String annotationType,
388-
boolean searchOnInterfaces, boolean searchOnSuperclasses, boolean searchOnMethodsInInterfaces,
389-
boolean searchOnMethodsInSuperclasses, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
390-
391-
return searchWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses,
392-
searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, new MergedAnnotationAttributesProcessor(
393-
annotationType, classValuesAsString, nestedAnnotationsAsMap));
360+
return searchWithFindSemantics(element, annotationType, new MergedAnnotationAttributesProcessor(annotationType,
361+
classValuesAsString, nestedAnnotationsAsMap));
394362
}
395363

396364
/**
@@ -511,32 +479,28 @@ private static <T> T searchWithGetSemantics(AnnotatedElement element, String ann
511479

512480
if (visited.add(element)) {
513481
try {
514-
// Local annotations: declared OR inherited
515-
Annotation[] annotations = element.getAnnotations();
516482

517-
// Search in local annotations
518-
for (Annotation annotation : annotations) {
519-
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
520-
&& (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0)) {
521-
T result = processor.process(annotation, metaDepth);
522-
if (result != null) {
523-
return result;
524-
}
525-
}
483+
// Start searching within locally declared annotations
484+
List<Annotation> declaredAnnotations = Arrays.asList(element.getDeclaredAnnotations());
485+
T result = searchWithGetSemanticsInAnnotations(declaredAnnotations, annotationType, processor, visited,
486+
metaDepth);
487+
if (result != null) {
488+
return result;
526489
}
527490

528-
// Search in meta annotations on local annotations
529-
for (Annotation annotation : annotations) {
530-
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
531-
T result = searchWithGetSemantics(annotation.annotationType(), annotationType, processor,
532-
visited, metaDepth + 1);
533-
if (result != null) {
534-
processor.postProcess(annotation, result);
535-
return result;
536-
}
491+
List<Annotation> inheritedAnnotations = new ArrayList<Annotation>();
492+
for (Annotation annotation : element.getAnnotations()) {
493+
if (!declaredAnnotations.contains(annotation)) {
494+
inheritedAnnotations.add(annotation);
537495
}
538496
}
539497

498+
// Continue searching within inherited annotations
499+
result = searchWithGetSemanticsInAnnotations(inheritedAnnotations, annotationType, processor, visited,
500+
metaDepth);
501+
if (result != null) {
502+
return result;
503+
}
540504
}
541505
catch (Exception ex) {
542506
AnnotationUtils.logIntrospectionFailure(element, ex);
@@ -545,6 +509,69 @@ private static <T> T searchWithGetSemantics(AnnotatedElement element, String ann
545509
return null;
546510
}
547511

512+
/**
513+
* This method is invoked by
514+
* {@link #searchWithGetSemantics(AnnotatedElement, String, Processor, Set, int)}
515+
* to perform the actual search within the supplied list of annotations.
516+
* <p>This method should be invoked first with locally declared annotations
517+
* and then subsequently with inherited annotations, thereby allowing
518+
* local annotations to take precedence over inherited annotations.
519+
*
520+
* <p>The {@code metaDepth} parameter is explained in the
521+
* {@link Processor#process process()} method of the {@link Processor}
522+
* API.
523+
*
524+
* @param annotations the annotations to search in; never {@code null}
525+
* @param annotationType the fully qualified class name of the annotation
526+
* type to find; never {@code null} or empty
527+
* @param processor the processor to delegate to
528+
* @param visited the set of annotated elements that have already been visited
529+
* @param metaDepth the meta-depth of the annotation
530+
* @return the result of the processor, potentially {@code null}
531+
*/
532+
private static <T> T searchWithGetSemanticsInAnnotations(List<Annotation> annotations, String annotationType,
533+
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
534+
535+
// Search in annotations
536+
for (Annotation annotation : annotations) {
537+
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
538+
&& (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0)) {
539+
T result = processor.process(annotation, metaDepth);
540+
if (result != null) {
541+
return result;
542+
}
543+
}
544+
}
545+
546+
// Recursively search in meta-annotations
547+
for (Annotation annotation : annotations) {
548+
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
549+
T result = searchWithGetSemantics(annotation.annotationType(), annotationType, processor, visited,
550+
metaDepth + 1);
551+
if (result != null) {
552+
processor.postProcess(annotation, result);
553+
return result;
554+
}
555+
}
556+
}
557+
558+
return null;
559+
}
560+
561+
/**
562+
* Search for annotations of the specified {@code annotationType} on
563+
* the specified {@code element}, following <em>find semantics</em>.
564+
*
565+
* @param element the annotated element; never {@code null}
566+
* @param annotationType the fully qualified class name of the annotation
567+
* type to find; never {@code null} or empty
568+
* @param processor the processor to delegate to
569+
* @return the result of the processor, potentially {@code null}
570+
*/
571+
private static <T> T searchWithFindSemantics(AnnotatedElement element, String annotationType, Processor<T> processor) {
572+
return searchWithFindSemantics(element, annotationType, true, true, true, true, processor);
573+
}
574+
548575
/**
549576
* Search for annotations of the specified {@code annotationType} on
550577
* the specified {@code element}, following <em>find semantics</em>.

0 commit comments

Comments
 (0)