diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 7fc37015d6b..7844bfc2670 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -41,16 +41,46 @@ import org.springframework.util.MultiValueMap; public class AnnotatedElementUtils { /** + * Get the fully qualified class names of all meta-annotation types + * present on the annotation (of the specified + * {@code annotationType}) on the supplied {@link AnnotatedElement}. + * + *

This method also finds all meta-annotations in the annotation + * hierarchy above the specified annotation. + * + * @param element the annotated element; never {@code null} + * @param annotationType the annotation type on which to find + * meta-annotations; never {@code null} + * @return the names of all meta-annotations present on the annotation, + * or {@code null} if not found + */ + public static Set getMetaAnnotationTypes(AnnotatedElement element, + Class annotationType) { + Assert.notNull(annotationType, "annotationType must not be null"); + return getMetaAnnotationTypes(element, annotationType.getName()); + } + + /** + * Get the fully qualified class names of all meta-annotation types + * present on the annotation (of the specified + * {@code annotationType}) on the supplied {@link AnnotatedElement}. + * + *

This method also finds all meta-annotations in the annotation + * hierarchy above the specified annotation. + * * @param element the annotated element; never {@code null} * @param annotationType the fully qualified class name of the annotation - * type to find; never {@code null} or empty + * type on which to find meta-annotations; never {@code null} or empty + * @return the names of all meta-annotations present on the annotation, + * or {@code null} if not found */ public static Set getMetaAnnotationTypes(AnnotatedElement element, String annotationType) { Assert.notNull(element, "AnnotatedElement must not be null"); Assert.hasText(annotationType, "annotationType must not be null or empty"); final Set types = new LinkedHashSet(); - processWithGetSemantics(element, annotationType, new Processor() { + + processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { @Override public Object process(Annotation annotation, int metaDepth) { if (metaDepth > 0) { @@ -58,10 +88,8 @@ public class AnnotatedElementUtils { } return null; } - @Override - public void postProcess(Annotation annotation, Object result) { - } }); + return (types.isEmpty() ? null : types); } @@ -74,7 +102,7 @@ public class AnnotatedElementUtils { Assert.notNull(element, "AnnotatedElement must not be null"); Assert.hasText(annotationType, "annotationType must not be null or empty"); - return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new Processor() { + return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { @Override public Boolean process(Annotation annotation, int metaDepth) { if (metaDepth > 0) { @@ -82,9 +110,6 @@ public class AnnotatedElementUtils { } return null; } - @Override - public void postProcess(Annotation annotation, Boolean result) { - } })); } @@ -97,14 +122,11 @@ public class AnnotatedElementUtils { Assert.notNull(element, "AnnotatedElement must not be null"); Assert.hasText(annotationType, "annotationType must not be null or empty"); - return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new Processor() { + return Boolean.TRUE.equals(processWithGetSemantics(element, annotationType, new SimpleAnnotationProcessor() { @Override public Boolean process(Annotation annotation, int metaDepth) { return Boolean.TRUE; } - @Override - public void postProcess(Annotation annotation, Boolean result) { - } })); } @@ -145,7 +167,6 @@ public class AnnotatedElementUtils { */ public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, String annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - return processWithGetSemantics(element, annotationType, new MergeAnnotationAttributesProcessor( classValuesAsString, nestedAnnotationsAsMap)); } @@ -326,6 +347,10 @@ public class AnnotatedElementUtils { // Search in local annotations for (Annotation annotation : annotations) { + // TODO Add check for !isInJavaLangAnnotationPackage() + // if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) + // && (annotation.annotationType().getName().equals(annotationType) || + // metaDepth > 0)) { if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) { T result = processor.process(annotation, metaDepth); if (result != null) { @@ -432,7 +457,8 @@ public class AnnotatedElementUtils { // Search in local annotations for (Annotation annotation : annotations) { - if (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0) { + if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) + && (annotation.annotationType().getName().equals(annotationType) || metaDepth > 0)) { T result = processor.process(annotation, metaDepth); if (result != null) { return result; @@ -616,9 +642,9 @@ public class AnnotatedElementUtils { * Post-process the result returned by the {@link #process} method. * *

The {@code annotation} supplied to this method is an annotation - * that is present in the annotation hierarchy, above the initial - * {@link AnnotatedElement} but below a target annotation found by - * the search algorithm. + * that is present in the annotation hierarchy, between the initial + * {@link AnnotatedElement} and a target annotation found by the + * search algorithm. * * @param annotation the annotation to post-process * @param result the result to post-process @@ -626,6 +652,22 @@ public class AnnotatedElementUtils { void postProcess(Annotation annotation, T result); } + /** + * {@link Processor} that only {@linkplain #process processes} annotations + * and does not {@link #postProcess} results. + * @since 4.2 + */ + private abstract static class SimpleAnnotationProcessor implements Processor { + + /** + * No-op. + */ + @Override + public final void postProcess(Annotation annotation, T result) { + /* no-op */ + } + } + private static class MergeAnnotationAttributesProcessor implements Processor { private final boolean classValuesAsString; diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java index 74a2fa4e0c0..11fa7e68d51 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java @@ -24,10 +24,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; import org.junit.Ignore; import org.junit.Test; +import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import static org.junit.Assert.*; @@ -42,6 +45,28 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.*; */ public class AnnotatedElementUtilsTests { + private Set names(Class... classes) { + return Arrays.stream(classes).map(clazz -> clazz.getName()).collect(Collectors.toSet()); + } + + @Test + public void getMetaAnnotationTypesOnNonAnnotatedClass() { + assertNull(getMetaAnnotationTypes(NonAnnotatedClass.class, TransactionalComponent.class)); + } + + @Test + public void getMetaAnnotationTypesOnClassWithMetaDepth1() { + Set names = getMetaAnnotationTypes(TransactionalComponentClass.class, TransactionalComponent.class); + assertEquals(names(Transactional.class, Component.class, Retention.class, Documented.class, Target.class, Inherited.class), names); + } + + @Test + public void getMetaAnnotationTypesOnClassWithMetaDepth2() { + Set names = getMetaAnnotationTypes(ComposedTransactionalComponentClass.class, + ComposedTransactionalComponent.class); + assertEquals(names(TransactionalComponent.class, Transactional.class, Component.class, Retention.class, Documented.class, Target.class, Inherited.class), names); + } + @Test public void getAllAnnotationAttributesOnClassWithLocalAnnotation() { MultiValueMap attributes = getAllAnnotationAttributes(TxConfig.class, @@ -318,8 +343,30 @@ public class AnnotatedElementUtilsTests { @interface TxComposed2 { } + @Transactional + @Component + @Retention(RetentionPolicy.RUNTIME) + @interface TransactionalComponent { + } + + @TransactionalComponent + @Retention(RetentionPolicy.RUNTIME) + @interface ComposedTransactionalComponent { + } + // ------------------------------------------------------------------------- + static class NonAnnotatedClass { + } + + @TransactionalComponent + static class TransactionalComponentClass { + } + + @ComposedTransactionalComponent + static class ComposedTransactionalComponentClass { + } + @Transactional static class ClassWithInheritedAnnotation { }