AnnotationMetadataReadingVisitor passes metaAnnotationMap into getMergedAnnotationAttributes algorithm, for finding out about applicable overrides

Issue: SPR-11649
master
Juergen Hoeller 11 years ago
parent 39e1342302
commit 842a8a851d
  1. 4
      spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java
  2. 39
      spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java
  3. 121
      spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java

@ -56,7 +56,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
/** /**
* Declared as a {@link LinkedMultiValueMap} instead of a {@link MultiValueMap} * Declared as a {@link LinkedMultiValueMap} instead of a {@link MultiValueMap}
* to ensure that the hierarchical ordering of the entries is preserved. * to ensure that the hierarchical ordering of the entries is preserved.
* @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes(LinkedMultiValueMap, String) * @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes
*/ */
protected final LinkedMultiValueMap<String, AnnotationAttributes> attributesMap = new LinkedMultiValueMap<String, AnnotationAttributes>(4); protected final LinkedMultiValueMap<String, AnnotationAttributes> attributesMap = new LinkedMultiValueMap<String, AnnotationAttributes>(4);
@ -125,7 +125,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
@Override @Override
public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) { public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes( AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
this.attributesMap, annotationType); this.attributesMap, this.metaAnnotationMap, annotationType);
return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString); return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
} }

@ -25,9 +25,8 @@ import java.util.Set;
import org.springframework.asm.Type; import org.springframework.asm.Type;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.LinkedMultiValueMap;
/** /**
* Internal utility class used when reading annotations. * 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, * Retrieve the merged attributes of the annotation of the given type,
* from the supplied {@code attributesMap}. * if any, from the supplied {@code attributesMap}.
* <p>Annotation attribute values appearing <em>lower</em> in the annotation * <p>Annotation attribute values appearing <em>lower</em> in the annotation
* hierarchy (i.e., closer to the declaring class) will override those * hierarchy (i.e., closer to the declaring class) will override those
* defined <em>higher</em> in the annotation hierarchy. * defined <em>higher</em> in the annotation hierarchy.
* @param attributesMap the map of annotation attribute lists, keyed by * @param attributesMap the map of annotation attribute lists,
* annotation type name * 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 * @param annotationType the name of the annotation type to look for
* @return the merged annotation attributes; or {@code null} if no matching * @return the merged annotation attributes, or {@code null} if no
* annotation is present in the {@code attributesMap} * matching annotation is present in the {@code attributesMap}
* @since 4.0.3 * @since 4.0.3
*/ */
public static AnnotationAttributes getMergedAnnotationAttributes( public static AnnotationAttributes getMergedAnnotationAttributes(
LinkedMultiValueMap<String, AnnotationAttributes> attributesMap, String annotationType) { LinkedMultiValueMap<String, AnnotationAttributes> attributesMap,
Map<String, Set<String>> metaAnnotationMap, String annotationType) {
// Get the unmerged list of attributes for the target annotation. // Get the unmerged list of attributes for the target annotation.
List<AnnotationAttributes> attributesList = attributesMap.get(annotationType); List<AnnotationAttributes> attributesList = attributesMap.get(annotationType);
@ -140,14 +142,17 @@ abstract class AnnotationReadingVisitorUtils {
for (String currentAnnotationType : annotationTypes) { for (String currentAnnotationType : annotationTypes) {
List<AnnotationAttributes> currentAttributesList = attributesMap.get(currentAnnotationType); List<AnnotationAttributes> currentAttributesList = attributesMap.get(currentAnnotationType);
if (currentAttributesList != null && !currentAttributesList.isEmpty()) { if (currentAttributesList != null && !currentAttributesList.isEmpty()) {
AnnotationAttributes currentAttributes = currentAttributesList.get(0); Set<String> metaAnns = metaAnnotationMap.get(currentAnnotationType);
for (String overridableAttributeName : overridableAttributeNames) { if (metaAnns != null && metaAnns.contains(annotationType)) {
Object value = currentAttributes.get(overridableAttributeName); AnnotationAttributes currentAttributes = currentAttributesList.get(0);
if (value != null) { for (String overridableAttributeName : overridableAttributeNames) {
// Store the value, potentially overriding a value from an Object value = currentAttributes.get(overridableAttributeName);
// attribute of the same name found higher in the annotation if (value != null) {
// hierarchy. // Store the value, potentially overriding a value from an
results.put(overridableAttributeName, value); // attribute of the same name found higher in the annotation
// hierarchy.
results.put(overridableAttributeName, value);
}
} }
} }
} }

@ -28,12 +28,12 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import org.junit.Test; import org.junit.Test;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory; import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -112,23 +112,7 @@ public class AnnotationMetadataTests {
assertMetaAnnotationOverrides(metadata); assertMetaAnnotationOverrides(metadata);
} }
/**
* @param metadata the metadata for {@link ComposedConfigurationWithAttributeOverridesClass}
*/
private void assertMetaAnnotationOverrides(AnnotationMetadata metadata) { 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( AnnotationAttributes attributes = (AnnotationAttributes) metadata.getAnnotationAttributes(
TestComponentScan.class.getName(), false); TestComponentScan.class.getName(), false);
String[] basePackages = attributes.getStringArray("basePackages"); 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) { @Test
MultiValueMap<String, Object> map = metadata.getAllAnnotationAttributes(TestComponentScan.class.getName()); public void multipleAnnotationsWithIdenticalAttributeNamesUsingStandardAnnotationMetadata() {
List<Object> basePackages = map.get("basePackages"); AnnotationMetadata metadata = new StandardAnnotationMetadata(NamedAnnotationsClass.class);
assertThat("length of basePackages list", basePackages.size(), is(1)); assertMultipleAnnotationsWithIdenticalAttributeNames(metadata);
}
// Ideally, the expected base package should be "org.example.componentscan", but /**
// since Spring's annotation processing currently does not support meta-annotation * https://jira.spring.io/browse/SPR-11649
// attribute overrides when searching for "all attributes", the actual value found */
// is "bogus". @Test
String expectedBasePackage = "bogus"; public void multipleAnnotationsWithIdenticalAttributeNamesUsingAnnotationMetadataReadingVisitor() throws Exception {
assertThat("basePackages[0]", ((String[]) basePackages.get(0))[0], is(expectedBasePackage)); MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(NamedAnnotationsClass.class.getName());
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
assertMultipleAnnotationsWithIdenticalAttributeNames(metadata);
}
List<Object> value = map.get("value"); /**
assertThat("length of value list", value.size(), is(1)); * https://jira.spring.io/browse/SPR-11649
assertThat("length of 0th value array", ((String[]) value.get(0)).length, is(0)); */
@Test
public void composedAnnotationWithMetaAnnotationsWithIdenticalAttributeNamesUsingStandardAnnotationMetadata() {
AnnotationMetadata metadata = new StandardAnnotationMetadata(NamedComposedAnnotationClass.class);
assertMultipleAnnotationsWithIdenticalAttributeNames(metadata);
}
List<Object> basePackageClasses = map.get("basePackageClasses"); /**
assertThat("length of basePackageClasses list", basePackageClasses.size(), is(1)); * https://jira.spring.io/browse/SPR-11649
assertThat("length of 0th basePackageClasses array", ((Class<?>[]) basePackageClasses.get(0)).length, is(0)); */
@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) { private void doTestAnnotationInfo(AnnotationMetadata metadata) {
@ -426,4 +443,40 @@ public class AnnotationMetadataTests {
public static class ComposedConfigurationWithAttributeOverridesClass { 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 {
}
} }

Loading…
Cancel
Save