Skip to content

Commit ba84458

Browse files
committed
Overhaul AnnotatedElementUtils
- Methods which search for a specific annotation now properly ensure that the sought annotation was actually found. - Both the "get" and the "find" search algorithms no longer needlessly traverse meta-annotation hierarchies twice. - Both the "get" and the "find" search algorithms now properly increment the metaDepth when recursively searching within the meta-annotation hierarchy. - Redesigned getMetaAnnotationTypes() so that it doesn't needlessly search irrelevant annotations. - Documented and tested hasMetaAnnotationTypes(). - Documented isAnnotated(). Issue: SPR-11514
1 parent 8853107 commit ba84458

File tree

4 files changed

+135
-59
lines changed

4 files changed

+135
-59
lines changed

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

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,16 @@ public class AnnotatedElementUtils {
4545
* <em>present</em> on the annotation (of the specified
4646
* {@code annotationType}) on the supplied {@link AnnotatedElement}.
4747
*
48-
* <p>This method also finds all meta-annotations in the annotation
49-
* hierarchy above the specified annotation.
48+
* <p>This method finds all meta-annotations in the annotation hierarchy
49+
* above the specified annotation.
5050
*
5151
* @param element the annotated element; never {@code null}
5252
* @param annotationType the annotation type on which to find
5353
* meta-annotations; never {@code null}
5454
* @return the names of all meta-annotations present on the annotation,
5555
* or {@code null} if not found
56+
* @see #getMetaAnnotationTypes(AnnotatedElement, String)
57+
* @see #hasMetaAnnotationTypes
5658
*/
5759
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element,
5860
Class<? extends Annotation> annotationType) {
@@ -65,47 +67,66 @@ public static Set<String> getMetaAnnotationTypes(AnnotatedElement element,
6567
* <em>present</em> on the annotation (of the specified
6668
* {@code annotationType}) on the supplied {@link AnnotatedElement}.
6769
*
68-
* <p>This method also finds all meta-annotations in the annotation
69-
* hierarchy above the specified annotation.
70+
* <p>This method finds all meta-annotations in the annotation hierarchy
71+
* above the specified annotation.
7072
*
7173
* @param element the annotated element; never {@code null}
7274
* @param annotationType the fully qualified class name of the annotation
7375
* type on which to find meta-annotations; never {@code null} or empty
7476
* @return the names of all meta-annotations present on the annotation,
7577
* or {@code null} if not found
78+
* @see #getMetaAnnotationTypes(AnnotatedElement, Class)
79+
* @see #hasMetaAnnotationTypes
7680
*/
7781
public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
7882
Assert.notNull(element, "AnnotatedElement must not be null");
7983
Assert.hasText(annotationType, "annotationType must not be null or empty");
8084

8185
final Set<String> types = new LinkedHashSet<String>();
8286

83-
processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Object>() {
84-
@Override
85-
public Object process(Annotation annotation, int metaDepth) {
86-
if (metaDepth > 0) {
87-
types.add(annotation.annotationType().getName());
88-
}
89-
return null;
87+
try {
88+
Annotation annotation = getAnnotation(element, annotationType);
89+
if (annotation != null) {
90+
processWithGetSemantics(annotation.annotationType(), annotationType, new SimpleAnnotationProcessor<Object>() {
91+
92+
@Override
93+
public Object process(Annotation annotation, int metaDepth) {
94+
types.add(annotation.annotationType().getName());
95+
return null;
96+
}
97+
}, new HashSet<AnnotatedElement>(), 1);
9098
}
91-
});
99+
}
100+
catch (Throwable ex) {
101+
throw new IllegalStateException("Failed to introspect annotations on " + element, ex);
102+
}
92103

93104
return (types.isEmpty() ? null : types);
94105
}
95106

96107
/**
108+
* Determine if the supplied {@link AnnotatedElement} is annotated with
109+
* a <em>composed annotation</em> that is meta-annotated with an
110+
* annotation of the specified {@code annotationType}.
111+
*
112+
* <p>This method finds all meta-annotations in the annotation hierarchy
113+
* above the specified element.
114+
*
97115
* @param element the annotated element; never {@code null}
98-
* @param annotationType the fully qualified class name of the annotation
116+
* @param annotationType the fully qualified class name of the meta-annotation
99117
* type to find; never {@code null} or empty
118+
* @return {@code true} if a matching meta-annotation is present
119+
* @see #getMetaAnnotationTypes
100120
*/
101-
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationType) {
121+
public static boolean hasMetaAnnotationTypes(AnnotatedElement element, final String annotationType) {
102122
Assert.notNull(element, "AnnotatedElement must not be null");
103123
Assert.hasText(annotationType, "annotationType must not be null or empty");
104124

105125
return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Boolean>() {
106126
@Override
107127
public Boolean process(Annotation annotation, int metaDepth) {
108-
if (metaDepth > 0) {
128+
boolean found = annotation.annotationType().getName().equals(annotationType);
129+
if (found && (metaDepth > 0)) {
109130
return Boolean.TRUE;
110131
}
111132
return null;
@@ -114,18 +135,27 @@ public Boolean process(Annotation annotation, int metaDepth) {
114135
}
115136

116137
/**
138+
* Determine if an annotation of the specified {@code annotationType}
139+
* is <em>present</em> on the supplied {@link AnnotatedElement} or
140+
* within the annotation hierarchy above the specified element.
141+
*
142+
* <p>If this method returns {@code true}, then {@link #getAnnotationAttributes}
143+
* will return a non-null value.
144+
*
117145
* @param element the annotated element; never {@code null}
118146
* @param annotationType the fully qualified class name of the annotation
119147
* type to find; never {@code null} or empty
148+
* @return {@code true} if a matching annotation is present
120149
*/
121-
public static boolean isAnnotated(AnnotatedElement element, String annotationType) {
150+
public static boolean isAnnotated(AnnotatedElement element, final String annotationType) {
122151
Assert.notNull(element, "AnnotatedElement must not be null");
123152
Assert.hasText(annotationType, "annotationType must not be null or empty");
124153

125154
return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor<Boolean>() {
126155
@Override
127156
public Boolean process(Annotation annotation, int metaDepth) {
128-
return Boolean.TRUE;
157+
boolean found = annotation.annotationType().getName().equals(annotationType);
158+
return (found ? Boolean.TRUE : null);
129159
}
130160
}));
131161
}
@@ -167,7 +197,7 @@ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement elem
167197
*/
168198
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType,
169199
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
170-
return processWithGetSemantics(element, annotationType, new MergeAnnotationAttributesProcessor(
200+
return processWithGetSemantics(element, annotationType, new MergeAnnotationAttributesProcessor(annotationType,
171201
classValuesAsString, nestedAnnotationsAsMap));
172202
}
173203

@@ -269,7 +299,7 @@ private static AnnotationAttributes findAnnotationAttributes(AnnotatedElement el
269299

270300
return processWithFindSemantics(element, annotationType, searchOnInterfaces, searchOnSuperclasses,
271301
searchOnMethodsInInterfaces, searchOnMethodsInSuperclasses, new MergeAnnotationAttributesProcessor(
272-
classValuesAsString, nestedAnnotationsAsMap));
302+
annotationType, classValuesAsString, nestedAnnotationsAsMap));
273303
}
274304

275305
/**
@@ -376,29 +406,20 @@ private static <T> T processWithGetSemantics(AnnotatedElement element, String an
376406

377407
// Search in local annotations
378408
for (Annotation annotation : annotations) {
379-
// TODO Add check for !isInJavaLangAnnotationPackage()
380-
// if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
381-
// && (annotation.annotationType().getName().equals(annotationType) ||
382-
// metaDepth > 0)) {
409+
// TODO Test for !AnnotationUtils.isInJavaLangAnnotationPackage(annotation)
383410
if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) {
384411
T result = processor.process(annotation, metaDepth);
385412
if (result != null) {
386413
return result;
387414
}
388-
result = processWithGetSemantics(annotation.annotationType(), annotationType, processor,
389-
visited, metaDepth + 1);
390-
if (result != null) {
391-
processor.postProcess(annotation, result);
392-
return result;
393-
}
394415
}
395416
}
396417

397418
// Search in meta annotations on local annotations
398419
for (Annotation annotation : annotations) {
399420
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
400421
T result = processWithGetSemantics(annotation.annotationType(), annotationType, processor,
401-
visited, metaDepth);
422+
visited, metaDepth + 1);
402423
if (result != null) {
403424
processor.postProcess(annotation, result);
404425
return result;
@@ -492,13 +513,6 @@ private static <T> T processWithFindSemantics(AnnotatedElement element, String a
492513
if (result != null) {
493514
return result;
494515
}
495-
result = processWithFindSemantics(annotation.annotationType(), annotationType,
496-
searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces,
497-
searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1);
498-
if (result != null) {
499-
processor.postProcess(annotation, result);
500-
return result;
501-
}
502516
}
503517
}
504518

@@ -507,7 +521,7 @@ private static <T> T processWithFindSemantics(AnnotatedElement element, String a
507521
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
508522
T result = processWithFindSemantics(annotation.annotationType(), annotationType,
509523
searchOnInterfaces, searchOnSuperclasses, searchOnMethodsInInterfaces,
510-
searchOnMethodsInSuperclasses, processor, visited, metaDepth);
524+
searchOnMethodsInSuperclasses, processor, visited, metaDepth + 1);
511525
if (result != null) {
512526
processor.postProcess(annotation, result);
513527
return result;
@@ -636,6 +650,14 @@ private static <T> T searchOnInterfaces(Method method, String annotationType, bo
636650
return null;
637651
}
638652

653+
private static Annotation getAnnotation(AnnotatedElement element, String annotationType) {
654+
for (Annotation annotation : element.getAnnotations()) {
655+
if (annotation.annotationType().getName().equals(annotationType)) {
656+
return annotation;
657+
}
658+
}
659+
return null;
660+
}
639661

640662
/**
641663
* Callback interface that is used to process a target annotation (or
@@ -707,18 +729,21 @@ public final void postProcess(Annotation annotation, T result) {
707729
*/
708730
private static class MergeAnnotationAttributesProcessor implements Processor<AnnotationAttributes> {
709731

732+
private final String annotationType;
710733
private final boolean classValuesAsString;
711734
private final boolean nestedAnnotationsAsMap;
712735

713736

714-
MergeAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
737+
MergeAnnotationAttributesProcessor(String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
738+
this.annotationType = annotationType;
715739
this.classValuesAsString = classValuesAsString;
716740
this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
717741
}
718742

719743
@Override
720744
public AnnotationAttributes process(Annotation annotation, int metaDepth) {
721-
return AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap);
745+
boolean found = annotation.annotationType().getName().equals(annotationType);
746+
return (!found ? null : AnnotationUtils.getAnnotationAttributes(annotation, classValuesAsString, nestedAnnotationsAsMap));
722747
}
723748

724749
@Override

spring-core/src/main/java/org/springframework/core/type/filter/AnnotationTypeFilter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -28,7 +28,7 @@
2828
* A simple filter which matches classes with a given annotation,
2929
* checking inherited annotations as well.
3030
*
31-
* <p>The matching logic mirrors that of {@code Class.isAnnotationPresent()}.
31+
* <p>The matching logic mirrors that of {@link java.lang.Class#isAnnotationPresent(Class)}.
3232
*
3333
* @author Mark Fisher
3434
* @author Ramnivas Laddad

spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,33 @@ public void getMetaAnnotationTypesOnClassWithMetaDepth1() {
6262

6363
@Test
6464
public void getMetaAnnotationTypesOnClassWithMetaDepth2() {
65-
Set<String> names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class,
66-
ComposedTransactionalComponent.class);
65+
Set<String> names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class);
6766
assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class, Retention.class, Documented.class, Target.class, Inherited.class), names);
6867
}
6968

69+
@Test
70+
public void hasMetaAnnotationTypesOnNonAnnotatedClass() {
71+
assertFalse(hasMetaAnnotationTypes(NonAnnotatedClass.class, Transactional.class.getName()));
72+
}
73+
74+
@Test
75+
public void hasMetaAnnotationTypesOnClassWithMetaDepth0() {
76+
assertFalse(hasMetaAnnotationTypes(TransactionalComponentClass.class, TransactionalComponent.class.getName()));
77+
}
78+
79+
@Test
80+
public void hasMetaAnnotationTypesOnClassWithMetaDepth1() {
81+
assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, Transactional.class.getName()));
82+
assertTrue(hasMetaAnnotationTypes(TransactionalComponentClass.class, Component.class.getName()));
83+
}
84+
85+
@Test
86+
public void hasMetaAnnotationTypesOnClassWithMetaDepth2() {
87+
assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Transactional.class.getName()));
88+
assertTrue(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, Component.class.getName()));
89+
assertFalse(hasMetaAnnotationTypes(ComposedTransactionalComponentClass.class, ComposedTransactionalComponent.class.getName()));
90+
}
91+
7092
@Test
7193
public void getAllAnnotationAttributesOnClassWithLocalAnnotation() {
7294
MultiValueMap<String, Object> attributes = getAllAnnotationAttributes(TxConfig.class,
@@ -277,6 +299,14 @@ public void findAnnotationAttributesFromBridgeMethod() throws NoSuchMethodExcept
277299
assertNotNull("Should find @Order on StringGenericParameter.getFor() bridge method", attributes);
278300
}
279301

302+
/** @since 4.2 */
303+
@Test
304+
public void findAnnotationAttributesOnClassWithMetaAndLocalTxConfig() {
305+
AnnotationAttributes attributes = findAnnotationAttributes(MetaAndLocalTxConfigClass.class, Transactional.class);
306+
assertNotNull("Should find @Transactional on MetaAndLocalTxConfigClass", attributes);
307+
assertEquals("TX qualifier for MetaAndLocalTxConfigClass.", "localTxMgr", attributes.getString("qualifier"));
308+
}
309+
280310

281311
// -------------------------------------------------------------------------
282312

@@ -315,6 +345,8 @@ static class MetaCycleAnnotatedClass {
315345

316346
String value() default "";
317347

348+
String qualifier() default "transactionManager";
349+
318350
boolean readOnly() default false;
319351
}
320352

@@ -333,6 +365,13 @@ static class MetaCycleAnnotatedClass {
333365
@interface Composed2 {
334366
}
335367

368+
@Transactional
369+
@Retention(RetentionPolicy.RUNTIME)
370+
@interface TxComposedWithOverride {
371+
372+
String qualifier() default "txMgr";
373+
}
374+
336375
@Transactional("TxComposed1")
337376
@Retention(RetentionPolicy.RUNTIME)
338377
@interface TxComposed1 {
@@ -354,6 +393,14 @@ static class MetaCycleAnnotatedClass {
354393
@interface ComposedTransactionalComponent {
355394
}
356395

396+
@TxComposedWithOverride
397+
// Override default "txMgr" from @TxComposedWithOverride with "localTxMgr"
398+
@Transactional(qualifier = "localTxMgr")
399+
@Retention(RetentionPolicy.RUNTIME)
400+
@Target(ElementType.TYPE)
401+
@interface MetaAndLocalTxConfig {
402+
}
403+
357404
// -------------------------------------------------------------------------
358405

359406
static class NonAnnotatedClass {
@@ -389,6 +436,10 @@ static class SubClassWithInheritedComposedAnnotation extends ClassWithInheritedC
389436
static class SubSubClassWithInheritedComposedAnnotation extends SubClassWithInheritedComposedAnnotation {
390437
}
391438

439+
@MetaAndLocalTxConfig
440+
static class MetaAndLocalTxConfigClass {
441+
}
442+
392443
@Transactional("TxConfig")
393444
static class TxConfig {
394445
}

0 commit comments

Comments
 (0)