From 842a8a851d2de9754b41525099cb597ed8592b02 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 30 Apr 2014 22:51:36 +0200 Subject: [PATCH] AnnotationMetadataReadingVisitor passes metaAnnotationMap into getMergedAnnotationAttributes algorithm, for finding out about applicable overrides Issue: SPR-11649 --- .../AnnotationMetadataReadingVisitor.java | 4 +- .../AnnotationReadingVisitorUtils.java | 39 +++--- .../core/type/AnnotationMetadataTests.java | 121 +++++++++++++----- 3 files changed, 111 insertions(+), 53 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java index a0e2bc6ca8..62ceb04d7c 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java @@ -56,7 +56,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito /** * Declared as a {@link LinkedMultiValueMap} instead of a {@link MultiValueMap} * to ensure that the hierarchical ordering of the entries is preserved. - * @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes(LinkedMultiValueMap, String) + * @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes */ protected final LinkedMultiValueMap attributesMap = new LinkedMultiValueMap(4); @@ -125,7 +125,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito @Override public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) { AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes( - this.attributesMap, annotationType); + this.attributesMap, this.metaAnnotationMap, annotationType); return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString); } diff --git a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java index 4c77a75a5e..2fe25085e8 100644 --- a/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java +++ b/spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java @@ -25,9 +25,8 @@ import java.util.Set; import org.springframework.asm.Type; import org.springframework.core.annotation.AnnotationAttributes; -import org.springframework.util.LinkedMultiValueMap; - import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.LinkedMultiValueMap; /** * Internal utility class used when reading annotations. @@ -98,20 +97,23 @@ abstract class AnnotationReadingVisitorUtils { } /** - * Retrieve the merged attributes of the annotation of the given type, if any, - * from the supplied {@code attributesMap}. + * Retrieve the merged attributes of the annotation of the given type, + * if any, from the supplied {@code attributesMap}. *

Annotation attribute values appearing lower in the annotation * hierarchy (i.e., closer to the declaring class) will override those * defined higher in the annotation hierarchy. - * @param attributesMap the map of annotation attribute lists, keyed by - * annotation type name + * @param attributesMap the map of annotation attribute lists, + * keyed by annotation type name + * @param metaAnnotationMap the map of meta annotation relationships, + * keyed by annotation type name * @param annotationType the name of the annotation type to look for - * @return the merged annotation attributes; or {@code null} if no matching - * annotation is present in the {@code attributesMap} + * @return the merged annotation attributes, or {@code null} if no + * matching annotation is present in the {@code attributesMap} * @since 4.0.3 */ public static AnnotationAttributes getMergedAnnotationAttributes( - LinkedMultiValueMap attributesMap, String annotationType) { + LinkedMultiValueMap attributesMap, + Map> metaAnnotationMap, String annotationType) { // Get the unmerged list of attributes for the target annotation. List attributesList = attributesMap.get(annotationType); @@ -140,14 +142,17 @@ abstract class AnnotationReadingVisitorUtils { for (String currentAnnotationType : annotationTypes) { List currentAttributesList = attributesMap.get(currentAnnotationType); if (currentAttributesList != null && !currentAttributesList.isEmpty()) { - AnnotationAttributes currentAttributes = currentAttributesList.get(0); - for (String overridableAttributeName : overridableAttributeNames) { - Object value = currentAttributes.get(overridableAttributeName); - if (value != null) { - // Store the value, potentially overriding a value from an - // attribute of the same name found higher in the annotation - // hierarchy. - results.put(overridableAttributeName, value); + Set metaAnns = metaAnnotationMap.get(currentAnnotationType); + if (metaAnns != null && metaAnns.contains(annotationType)) { + AnnotationAttributes currentAttributes = currentAttributesList.get(0); + for (String overridableAttributeName : overridableAttributeNames) { + Object value = currentAttributes.get(overridableAttributeName); + if (value != null) { + // Store the value, potentially overriding a value from an + // attribute of the same name found higher in the annotation + // hierarchy. + results.put(overridableAttributeName, value); + } } } } diff --git a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java index 5abc95b292..bfd89f5ba6 100644 --- a/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java +++ b/spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java @@ -28,12 +28,12 @@ import java.util.List; import java.util.Set; import org.junit.Test; + import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.stereotype.Component; -import org.springframework.util.MultiValueMap; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; @@ -112,23 +112,7 @@ public class AnnotationMetadataTests { assertMetaAnnotationOverrides(metadata); } - /** - * @param metadata the metadata for {@link ComposedConfigurationWithAttributeOverridesClass} - */ private void assertMetaAnnotationOverrides(AnnotationMetadata metadata) { - assertAllAttributesForMetaAnnotationOverrides(metadata); - assertAttributesForMetaAnnotationOverrides(metadata); - - // SPR-11710: Invoke a 2nd time after invoking getAnnotationAttributes() in order - // to ensure that getMergedAnnotationAttributes() in AnnotationReadingVisitorUtils - // does not mutate the state of the metadata. - assertAllAttributesForMetaAnnotationOverrides(metadata); - } - - /** - * @param metadata the metadata for {@link ComposedConfigurationWithAttributeOverridesClass} - */ - private void assertAttributesForMetaAnnotationOverrides(AnnotationMetadata metadata) { AnnotationAttributes attributes = (AnnotationAttributes) metadata.getAnnotationAttributes( TestComponentScan.class.getName(), false); String[] basePackages = attributes.getStringArray("basePackages"); @@ -141,27 +125,60 @@ public class AnnotationMetadataTests { } /** - * @param metadata the metadata for {@link ComposedConfigurationWithAttributeOverridesClass} + * https://jira.spring.io/browse/SPR-11649 */ - private void assertAllAttributesForMetaAnnotationOverrides(AnnotationMetadata metadata) { - MultiValueMap map = metadata.getAllAnnotationAttributes(TestComponentScan.class.getName()); - List basePackages = map.get("basePackages"); - assertThat("length of basePackages list", basePackages.size(), is(1)); + @Test + public void multipleAnnotationsWithIdenticalAttributeNamesUsingStandardAnnotationMetadata() { + AnnotationMetadata metadata = new StandardAnnotationMetadata(NamedAnnotationsClass.class); + assertMultipleAnnotationsWithIdenticalAttributeNames(metadata); + } - // Ideally, the expected base package should be "org.example.componentscan", but - // since Spring's annotation processing currently does not support meta-annotation - // attribute overrides when searching for "all attributes", the actual value found - // is "bogus". - String expectedBasePackage = "bogus"; - assertThat("basePackages[0]", ((String[]) basePackages.get(0))[0], is(expectedBasePackage)); + /** + * https://jira.spring.io/browse/SPR-11649 + */ + @Test + public void multipleAnnotationsWithIdenticalAttributeNamesUsingAnnotationMetadataReadingVisitor() throws Exception { + MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); + MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(NamedAnnotationsClass.class.getName()); + AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); + assertMultipleAnnotationsWithIdenticalAttributeNames(metadata); + } - List value = map.get("value"); - assertThat("length of value list", value.size(), is(1)); - assertThat("length of 0th value array", ((String[]) value.get(0)).length, is(0)); + /** + * https://jira.spring.io/browse/SPR-11649 + */ + @Test + public void composedAnnotationWithMetaAnnotationsWithIdenticalAttributeNamesUsingStandardAnnotationMetadata() { + AnnotationMetadata metadata = new StandardAnnotationMetadata(NamedComposedAnnotationClass.class); + assertMultipleAnnotationsWithIdenticalAttributeNames(metadata); + } - List basePackageClasses = map.get("basePackageClasses"); - assertThat("length of basePackageClasses list", basePackageClasses.size(), is(1)); - assertThat("length of 0th basePackageClasses array", ((Class[]) basePackageClasses.get(0)).length, is(0)); + /** + * https://jira.spring.io/browse/SPR-11649 + */ + @Test + public void composedAnnotationWithMetaAnnotationsWithIdenticalAttributeNamesUsingAnnotationMetadataReadingVisitor() throws Exception { + MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory(); + MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(NamedComposedAnnotationClass.class.getName()); + AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); + assertMultipleAnnotationsWithIdenticalAttributeNames(metadata); + } + + private void assertMultipleAnnotationsWithIdenticalAttributeNames(AnnotationMetadata metadata) { + AnnotationAttributes attributes1 = (AnnotationAttributes) metadata.getAnnotationAttributes( + NamedAnnotation1.class.getName(), false); + String name1 = attributes1.getString("name"); + assertThat("name of NamedAnnotation1", name1, is("name 1")); + + AnnotationAttributes attributes2 = (AnnotationAttributes) metadata.getAnnotationAttributes( + NamedAnnotation2.class.getName(), false); + String name2 = attributes2.getString("name"); + assertThat("name of NamedAnnotation2", name2, is("name 2")); + + AnnotationAttributes attributes3 = (AnnotationAttributes) metadata.getAnnotationAttributes( + NamedAnnotation3.class.getName(), false); + String name3 = attributes3.getString("name"); + assertThat("name of NamedAnnotation3", name3, is("name 3")); } private void doTestAnnotationInfo(AnnotationMetadata metadata) { @@ -426,4 +443,40 @@ public class AnnotationMetadataTests { public static class ComposedConfigurationWithAttributeOverridesClass { } + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public static @interface NamedAnnotation1 { + String name() default ""; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public static @interface NamedAnnotation2 { + String name() default ""; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public static @interface NamedAnnotation3 { + String name() default ""; + } + + @NamedAnnotation1(name = "name 1") + @NamedAnnotation2(name = "name 2") + @NamedAnnotation3(name = "name 3") + public static class NamedAnnotationsClass { + } + + @NamedAnnotation1(name = "name 1") + @NamedAnnotation2(name = "name 2") + @NamedAnnotation3(name = "name 3") + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public static @interface NamedComposedAnnotation { + } + + @NamedComposedAnnotation + public static class NamedComposedAnnotationClass { + } + }