diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 8250a3dacdda717f982715e890e5421085b1ad13..237385466876d3f8c1d2f7106f4d100f0f7d0533 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -240,52 +240,68 @@ public abstract class AnnotationUtils { } /** - * Find a single {@link Annotation} of {@code annotationType} from the supplied - * {@link Class}, traversing its annotations, interfaces, and superclasses if - * no annotation can be found on the given class itself. + * Find a single {@link Annotation} of {@code annotationType} on the + * supplied {@link Class}, traversing its interfaces, annotations, and + * superclasses if the annotation is not present on the given class + * itself. *

This method explicitly handles class-level annotations which are not - * declared as {@link java.lang.annotation.Inherited inherited} as well - * as meta-annotations and annotations on interfaces. + * declared as {@link java.lang.annotation.Inherited inherited} as well + * as meta-annotations and annotations on interfaces. *

The algorithm operates as follows: *

    - *
  1. Search for an annotation on the given class and return it if found. - *
  2. Recursively search through all interfaces that the given class - * declares, returning the annotation from the first matching candidate, if any. - *
  3. Recursively search through all annotations that the given class - * declares, returning the annotation from the first matching candidate, if any. - *
  4. Proceed with introspection of the superclass hierarchy of the given - * class by returning to step #1 with the superclass as the class to look for - * annotations on. + *
  5. Search for the annotation on the given class and return it if found. + *
  6. Recursively search through all interfaces that the given class declares. + *
  7. Recursively search through all annotations that the given class declares. + *
  8. Recursively search through the superclass hierarchy of the given class. *
+ *

Note: in this context, the term recursively means that the search + * process continues by returning to step #1 with the current interface, + * annotation, or superclass as the class to look for annotations on. * @param clazz the class to look for annotations on - * @param annotationType the annotation class to look for - * @return the annotation found, or {@code null} if none found + * @param annotationType the type of annotation to look for + * @return the annotation if found, or {@code null} if not found */ public static A findAnnotation(Class clazz, Class annotationType) { + return findAnnotation(clazz, annotationType, new HashSet()); + } + + /** + * Perform the search algorithm for {@link #findAnnotation(Class, Class)}, + * avoiding endless recursion by tracking which annotations have already + * been visited. + * @param clazz the class to look for annotations on + * @param annotationType the type of annotation to look for + * @param visitedAnnotations the set of annotations that have already been visited + * @return the annotation if found, or {@code null} if not found + */ + private static A findAnnotation(Class clazz, Class annotationType, + Set visitedAnnotations) { Assert.notNull(clazz, "Class must not be null"); + A annotation = clazz.getAnnotation(annotationType); if (annotation != null) { return annotation; } for (Class ifc : clazz.getInterfaces()) { - annotation = findAnnotation(ifc, annotationType); + annotation = findAnnotation(ifc, annotationType, visitedAnnotations); if (annotation != null) { return annotation; } } - if (!Annotation.class.isAssignableFrom(clazz)) { - for (Annotation ann : clazz.getAnnotations()) { - annotation = findAnnotation(ann.annotationType(), annotationType); + for (Annotation ann : clazz.getAnnotations()) { + if (!visitedAnnotations.contains(ann)) { + visitedAnnotations.add(ann); + annotation = findAnnotation(ann.annotationType(), annotationType, visitedAnnotations); if (annotation != null) { return annotation; } } } - Class superClass = clazz.getSuperclass(); - if (superClass == null || superClass.equals(Object.class)) { + Class superclass = clazz.getSuperclass(); + if (superclass == null || superclass.equals(Object.class)) { return null; } - return findAnnotation(superClass, annotationType); + return findAnnotation(superclass, annotationType, visitedAnnotations); } /** diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index aa74fb54bfdb708852cd9989f48672e661537f40..4623f648624f20423698440ccd7e48212cc0078d 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -111,6 +111,33 @@ public class AnnotationUtilsTests { assertEquals("meta1", component.value()); } + @Test + public void findAnnotationOnMetaMetaAnnotatedClass() { + Component component = AnnotationUtils.findAnnotation(MetaMetaAnnotatedClass.class, Component.class); + assertNotNull("Should find meta-annotation on composed annotation on class", component); + assertEquals("meta2", component.value()); + } + + @Test + public void findAnnotationOnMetaMetaMetaAnnotatedClass() { + Component component = AnnotationUtils.findAnnotation(MetaMetaMetaAnnotatedClass.class, Component.class); + assertNotNull("Should find meta-annotation on meta-annotation on composed annotation on class", component); + assertEquals("meta2", component.value()); + } + + @Test + public void findAnnotationOnAnnotatedClassWithMissingTargetMetaAnnotation() { + // TransactionalClass is NOT annotated or meta-annotated with @Component + Component component = AnnotationUtils.findAnnotation(TransactionalClass.class, Component.class); + assertNull("Should not find @Component on TransactionalClass", component); + } + + @Test + public void findAnnotationOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() { + Component component = AnnotationUtils.findAnnotation(MetaCycleAnnotatedClass.class, Component.class); + assertNull("Should not find @Component on MetaCycleAnnotatedClass", component); + } + @Test public void testFindAnnotationDeclaringClass() throws Exception { // no class-level annotation @@ -335,6 +362,31 @@ public class AnnotationUtilsTests { @interface Meta2 { } + @Meta2 + @Retention(RetentionPolicy.RUNTIME) + @interface MetaMeta { + } + + @MetaMeta + @Retention(RetentionPolicy.RUNTIME) + @interface MetaMetaMeta { + } + + @MetaCycle3 + @Retention(RetentionPolicy.RUNTIME) + @interface MetaCycle1 { + } + + @MetaCycle1 + @Retention(RetentionPolicy.RUNTIME) + @interface MetaCycle2 { + } + + @MetaCycle2 + @Retention(RetentionPolicy.RUNTIME) + @interface MetaCycle3 { + } + @Meta1 static interface InterfaceWithMetaAnnotation { } @@ -343,6 +395,17 @@ public class AnnotationUtilsTests { static class ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface implements InterfaceWithMetaAnnotation { } + @MetaMeta + static class MetaMetaAnnotatedClass { + } + + @MetaMetaMeta + static class MetaMetaMetaAnnotatedClass { + } + + @MetaCycle3 + static class MetaCycleAnnotatedClass { + } public static interface AnnotatedInterface {