AnnotationMetadataReadingVisitor passes metaAnnotationMap into getMergedAnnotationAttributes algorithm, for finding out about applicable overrides
Issue: SPR-11649
This commit is contained in:
@@ -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 {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user