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 d32a2910d33..75966d98ee2 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 @@ -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. @@ -32,6 +32,7 @@ import org.springframework.util.MultiValueMap; * * @author Phillip Webb * @author Juergen Hoeller + * @author Sam Brannen * @since 4.0 */ public class AnnotatedElementUtils { @@ -159,7 +160,7 @@ public class AnnotatedElementUtils { for (Annotation annotation : element.getAnnotations()) { if (annotation.annotationType().getName().equals(annotationType) || depth > 0) { T result = processor.process(annotation, depth); - if (result != null) { + if (result != null) { return result; } result = doProcess(annotation.annotationType(), annotationType, processor, visited, depth + 1); @@ -170,10 +171,12 @@ public class AnnotatedElementUtils { } } for (Annotation annotation : element.getAnnotations()) { - T result = doProcess(annotation.annotationType(), annotationType, processor, visited, depth); - if (result != null) { - processor.postProcess(annotation, result); - return result; + if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) { + T result = doProcess(annotation.annotationType(), annotationType, processor, visited, depth); + if (result != null) { + processor.postProcess(annotation, result); + return result; + } } } } 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 03c6f377355..d3e51b7c639 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 @@ -289,7 +289,7 @@ public abstract class AnnotationUtils { } } for (Annotation ann : clazz.getAnnotations()) { - if (visited.add(ann)) { + if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) { annotation = findAnnotation(ann.annotationType(), annotationType, visited); if (annotation != null) { return annotation; @@ -422,6 +422,18 @@ public abstract class AnnotationUtils { return (clazz.isAnnotationPresent(annotationType) && !isAnnotationDeclaredLocally(annotationType, clazz)); } + /** + * Determine if the supplied {@link Annotation} is defined in the + * {@code java.lang.annotation} package. + * + * @param annotation the annotation to check; never {@code null} + * @return {@code true} if the annotation is in the {@code java.lang.annotation} package + */ + public static boolean isInJavaLangAnnotationPackage(Annotation annotation) { + Assert.notNull(annotation, "Annotation must not be null"); + return annotation.annotationType().getName().startsWith("java.lang.annotation"); + } + /** * Retrieve the given annotation's attributes as a Map, preserving all attribute types * as-is. @@ -631,7 +643,7 @@ public abstract class AnnotationUtils { else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, annotation.annotationType())) { result.addAll(Arrays.asList(getValue(annotation))); } - else { + else if (!isInJavaLangAnnotationPackage(annotation)) { process(annotation.annotationType()); } } 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 0c359da919c..4b8df68c962 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 @@ -16,9 +16,12 @@ package org.springframework.core.annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.junit.Test; @@ -32,6 +35,13 @@ import static org.junit.Assert.*; */ public class AnnotatedElementUtilsTests { + @Test + public void getAnnotationAttributesOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() { + AnnotationAttributes attributes = AnnotatedElementUtils.getAnnotationAttributes(MetaCycleAnnotatedClass.class, + Transactional.class.getName()); + assertNull("Should not find annotation attributes for @Transactional on MetaCycleAnnotatedClass", attributes); + } + @Test public void getAnnotationAttributesFavorsInheritedAnnotationsOverMoreLocallyDeclaredComposedAnnotations() { AnnotationAttributes attributes = AnnotatedElementUtils.getAnnotationAttributes( @@ -61,7 +71,34 @@ public class AnnotatedElementUtilsTests { // ------------------------------------------------------------------------- + @MetaCycle3 + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @Documented + @interface MetaCycle1 { + } + + @MetaCycle1 + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @Documented + @interface MetaCycle2 { + } + + @MetaCycle2 + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented + @interface MetaCycle3 { + } + + @MetaCycle3 + static class MetaCycleAnnotatedClass { + } + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented @Inherited @interface Transactional { @@ -70,12 +107,16 @@ public class AnnotatedElementUtilsTests { @Transactional @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented @Inherited @interface Composed1 { } @Transactional(readOnly = true) @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented @interface Composed2 { } 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 c4a7a696c28..e613a619314 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 @@ -362,6 +362,13 @@ public class AnnotationUtilsTests { assertNotNull(order); } + @Test + public void findRepeatableAnnotationOnComposedAnnotation() { + Repeatable repeatable = findAnnotation(MyRepeatableMeta.class, Repeatable.class); + assertNotNull(repeatable); + assertEquals(MyRepeatableContainer.class, repeatable.value()); + } + @Test public void getRepeatableFromMethod() throws Exception { Method method = InterfaceWithRepeated.class.getMethod("foo"); diff --git a/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java b/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java index 24e3c8fbdd5..df4598ff9df 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/MetaAnnotationUtils.java @@ -123,7 +123,7 @@ abstract class MetaAnnotationUtils { // Declared on a composed annotation (i.e., as a meta-annotation)? for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { - if (visited.add(composedAnnotation)) { + if (!isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { AnnotationDescriptor descriptor = findAnnotationDescriptor(composedAnnotation.annotationType(), visited, annotationType); if (descriptor != null) { @@ -210,7 +210,7 @@ abstract class MetaAnnotationUtils { // Declared on a composed annotation (i.e., as a meta-annotation)? for (Annotation composedAnnotation : clazz.getDeclaredAnnotations()) { - if (visited.add(composedAnnotation)) { + if (!isInJavaLangAnnotationPackage(composedAnnotation) && visited.add(composedAnnotation)) { UntypedAnnotationDescriptor descriptor = findAnnotationDescriptorForTypes( composedAnnotation.annotationType(), visited, annotationTypes); if (descriptor != null) { diff --git a/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java index 0fb5ad25332..01a81d6456d 100644 --- a/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/MetaAnnotationUtilsTests.java @@ -17,8 +17,11 @@ package org.springframework.test.context; 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; import org.junit.Test; import org.springframework.core.annotation.Order; @@ -393,42 +396,58 @@ public class MetaAnnotationUtilsTests { @Component(value = "meta1") @Order @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented static @interface Meta1 { } @Component(value = "meta2") @Transactional @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented static @interface Meta2 { } @Meta2 @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented @interface MetaMeta { } @MetaMeta @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented @interface MetaMetaMeta { } @MetaCycle3 @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @Documented @interface MetaCycle1 { } @MetaCycle1 @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + @Documented @interface MetaCycle2 { } @MetaCycle2 @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented @interface MetaCycle3 { } @ContextConfiguration @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Documented static @interface MetaConfig { static class DevConfig {