Skip to content

Commit 42a35ef

Browse files
committed
AnnotationMetadataReadingVisitor passes metaAnnotationMap into getMergedAnnotationAttributes algorithm, for finding out about applicable overrides
Issue: SPR-11649 (cherry picked from commit 842a8a8)
1 parent 21fd681 commit 42a35ef

File tree

3 files changed

+112
-53
lines changed

3 files changed

+112
-53
lines changed

spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationMetadataReadingVisitor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisito
5656
/**
5757
* Declared as a {@link LinkedMultiValueMap} instead of a {@link MultiValueMap}
5858
* to ensure that the hierarchical ordering of the entries is preserved.
59-
* @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes(LinkedMultiValueMap, String)
59+
* @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes
6060
*/
6161
protected final LinkedMultiValueMap<String, AnnotationAttributes> attributesMap = new LinkedMultiValueMap<String, AnnotationAttributes>(4);
6262

@@ -125,7 +125,7 @@ public AnnotationAttributes getAnnotationAttributes(String annotationType) {
125125
@Override
126126
public AnnotationAttributes getAnnotationAttributes(String annotationType, boolean classValuesAsString) {
127127
AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
128-
this.attributesMap, annotationType);
128+
this.attributesMap, this.metaAnnotationMap, annotationType);
129129
return AnnotationReadingVisitorUtils.convertClassValues(this.classLoader, raw, classValuesAsString);
130130
}
131131

spring-core/src/main/java/org/springframework/core/type/classreading/AnnotationReadingVisitorUtils.java

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@
2525

2626
import org.springframework.asm.Type;
2727
import org.springframework.core.annotation.AnnotationAttributes;
28-
import org.springframework.util.LinkedMultiValueMap;
29-
3028
import org.springframework.core.annotation.AnnotationUtils;
29+
import org.springframework.util.LinkedMultiValueMap;
3130

3231
/**
3332
* Internal utility class used when reading annotations.
@@ -98,20 +97,23 @@ else if (value instanceof Class[]) {
9897
}
9998

10099
/**
101-
* Retrieve the merged attributes of the annotation of the given type, if any,
102-
* from the supplied {@code attributesMap}.
100+
* Retrieve the merged attributes of the annotation of the given type,
101+
* if any, from the supplied {@code attributesMap}.
103102
* <p>Annotation attribute values appearing <em>lower</em> in the annotation
104103
* hierarchy (i.e., closer to the declaring class) will override those
105104
* defined <em>higher</em> in the annotation hierarchy.
106-
* @param attributesMap the map of annotation attribute lists, keyed by
107-
* annotation type name
105+
* @param attributesMap the map of annotation attribute lists,
106+
* keyed by annotation type name
107+
* @param metaAnnotationMap the map of meta annotation relationships,
108+
* keyed by annotation type name
108109
* @param annotationType the name of the annotation type to look for
109-
* @return the merged annotation attributes; or {@code null} if no matching
110-
* annotation is present in the {@code attributesMap}
110+
* @return the merged annotation attributes, or {@code null} if no
111+
* matching annotation is present in the {@code attributesMap}
111112
* @since 4.0.3
112113
*/
113114
public static AnnotationAttributes getMergedAnnotationAttributes(
114-
LinkedMultiValueMap<String, AnnotationAttributes> attributesMap, String annotationType) {
115+
LinkedMultiValueMap<String, AnnotationAttributes> attributesMap,
116+
Map<String, Set<String>> metaAnnotationMap, String annotationType) {
115117

116118
// Get the unmerged list of attributes for the target annotation.
117119
List<AnnotationAttributes> attributesList = attributesMap.get(annotationType);
@@ -140,14 +142,17 @@ public static AnnotationAttributes getMergedAnnotationAttributes(
140142
for (String currentAnnotationType : annotationTypes) {
141143
List<AnnotationAttributes> currentAttributesList = attributesMap.get(currentAnnotationType);
142144
if (currentAttributesList != null && !currentAttributesList.isEmpty()) {
143-
AnnotationAttributes currentAttributes = currentAttributesList.get(0);
144-
for (String overridableAttributeName : overridableAttributeNames) {
145-
Object value = currentAttributes.get(overridableAttributeName);
146-
if (value != null) {
147-
// Store the value, potentially overriding a value from an
148-
// attribute of the same name found higher in the annotation
149-
// hierarchy.
150-
results.put(overridableAttributeName, value);
145+
Set<String> metaAnns = metaAnnotationMap.get(currentAnnotationType);
146+
if (metaAnns != null && metaAnns.contains(annotationType)) {
147+
AnnotationAttributes currentAttributes = currentAttributesList.get(0);
148+
for (String overridableAttributeName : overridableAttributeNames) {
149+
Object value = currentAttributes.get(overridableAttributeName);
150+
if (value != null) {
151+
// Store the value, potentially overriding a value from an
152+
// attribute of the same name found higher in the annotation
153+
// hierarchy.
154+
results.put(overridableAttributeName, value);
155+
}
151156
}
152157
}
153158
}

spring-core/src/test/java/org/springframework/core/type/AnnotationMetadataTests.java

Lines changed: 88 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828
import java.util.Set;
2929

3030
import org.junit.Test;
31+
3132
import org.springframework.core.annotation.AnnotationAttributes;
3233
import org.springframework.core.type.classreading.MetadataReader;
3334
import org.springframework.core.type.classreading.MetadataReaderFactory;
3435
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
3536
import org.springframework.stereotype.Component;
36-
import org.springframework.util.MultiValueMap;
3737

3838
import static org.hamcrest.CoreMatchers.*;
3939
import static org.junit.Assert.*;
@@ -112,23 +112,7 @@ public void metaAnnotationOverridesUsingAnnotationMetadataReadingVisitor() throw
112112
assertMetaAnnotationOverrides(metadata);
113113
}
114114

115-
/**
116-
* @param metadata the metadata for {@link ComposedConfigurationWithAttributeOverridesClass}
117-
*/
118115
private void assertMetaAnnotationOverrides(AnnotationMetadata metadata) {
119-
assertAllAttributesForMetaAnnotationOverrides(metadata);
120-
assertAttributesForMetaAnnotationOverrides(metadata);
121-
122-
// SPR-11710: Invoke a 2nd time after invoking getAnnotationAttributes() in order
123-
// to ensure that getMergedAnnotationAttributes() in AnnotationReadingVisitorUtils
124-
// does not mutate the state of the metadata.
125-
assertAllAttributesForMetaAnnotationOverrides(metadata);
126-
}
127-
128-
/**
129-
* @param metadata the metadata for {@link ComposedConfigurationWithAttributeOverridesClass}
130-
*/
131-
private void assertAttributesForMetaAnnotationOverrides(AnnotationMetadata metadata) {
132116
AnnotationAttributes attributes = (AnnotationAttributes) metadata.getAnnotationAttributes(
133117
TestComponentScan.class.getName(), false);
134118
String[] basePackages = attributes.getStringArray("basePackages");
@@ -141,27 +125,60 @@ private void assertAttributesForMetaAnnotationOverrides(AnnotationMetadata metad
141125
}
142126

143127
/**
144-
* @param metadata the metadata for {@link ComposedConfigurationWithAttributeOverridesClass}
128+
* https://jira.spring.io/browse/SPR-11649
145129
*/
146-
private void assertAllAttributesForMetaAnnotationOverrides(AnnotationMetadata metadata) {
147-
MultiValueMap<String, Object> map = metadata.getAllAnnotationAttributes(TestComponentScan.class.getName());
148-
List<Object> basePackages = map.get("basePackages");
149-
assertThat("length of basePackages list", basePackages.size(), is(1));
130+
@Test
131+
public void multipleAnnotationsWithIdenticalAttributeNamesUsingStandardAnnotationMetadata() {
132+
AnnotationMetadata metadata = new StandardAnnotationMetadata(NamedAnnotationsClass.class);
133+
assertMultipleAnnotationsWithIdenticalAttributeNames(metadata);
134+
}
150135

151-
// Ideally, the expected base package should be "org.example.componentscan", but
152-
// since Spring's annotation processing currently does not support meta-annotation
153-
// attribute overrides when searching for "all attributes", the actual value found
154-
// is "bogus".
155-
String expectedBasePackage = "bogus";
156-
assertThat("basePackages[0]", ((String[]) basePackages.get(0))[0], is(expectedBasePackage));
136+
/**
137+
* https://jira.spring.io/browse/SPR-11649
138+
*/
139+
@Test
140+
public void multipleAnnotationsWithIdenticalAttributeNamesUsingAnnotationMetadataReadingVisitor() throws Exception {
141+
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
142+
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(NamedAnnotationsClass.class.getName());
143+
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
144+
assertMultipleAnnotationsWithIdenticalAttributeNames(metadata);
145+
}
157146

158-
List<Object> value = map.get("value");
159-
assertThat("length of value list", value.size(), is(1));
160-
assertThat("length of 0th value array", ((String[]) value.get(0)).length, is(0));
147+
/**
148+
* https://jira.spring.io/browse/SPR-11649
149+
*/
150+
@Test
151+
public void composedAnnotationWithMetaAnnotationsWithIdenticalAttributeNamesUsingStandardAnnotationMetadata() {
152+
AnnotationMetadata metadata = new StandardAnnotationMetadata(NamedComposedAnnotationClass.class);
153+
assertMultipleAnnotationsWithIdenticalAttributeNames(metadata);
154+
}
161155

162-
List<Object> basePackageClasses = map.get("basePackageClasses");
163-
assertThat("length of basePackageClasses list", basePackageClasses.size(), is(1));
164-
assertThat("length of 0th basePackageClasses array", ((Class<?>[]) basePackageClasses.get(0)).length, is(0));
156+
/**
157+
* https://jira.spring.io/browse/SPR-11649
158+
*/
159+
@Test
160+
public void composedAnnotationWithMetaAnnotationsWithIdenticalAttributeNamesUsingAnnotationMetadataReadingVisitor() throws Exception {
161+
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
162+
MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(NamedComposedAnnotationClass.class.getName());
163+
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
164+
assertMultipleAnnotationsWithIdenticalAttributeNames(metadata);
165+
}
166+
167+
private void assertMultipleAnnotationsWithIdenticalAttributeNames(AnnotationMetadata metadata) {
168+
AnnotationAttributes attributes1 = (AnnotationAttributes) metadata.getAnnotationAttributes(
169+
NamedAnnotation1.class.getName(), false);
170+
String name1 = attributes1.getString("name");
171+
assertThat("name of NamedAnnotation1", name1, is("name 1"));
172+
173+
AnnotationAttributes attributes2 = (AnnotationAttributes) metadata.getAnnotationAttributes(
174+
NamedAnnotation2.class.getName(), false);
175+
String name2 = attributes2.getString("name");
176+
assertThat("name of NamedAnnotation2", name2, is("name 2"));
177+
178+
AnnotationAttributes attributes3 = (AnnotationAttributes) metadata.getAnnotationAttributes(
179+
NamedAnnotation3.class.getName(), false);
180+
String name3 = attributes3.getString("name");
181+
assertThat("name of NamedAnnotation3", name3, is("name 3"));
165182
}
166183

167184
private void doTestAnnotationInfo(AnnotationMetadata metadata) {
@@ -389,6 +406,7 @@ public void meta() {
389406
}
390407
}
391408

409+
@SuppressWarnings({ "serial" })
392410
private static class AnnotatedComponentSubClass extends AnnotatedComponent {
393411

394412
}
@@ -425,4 +443,40 @@ private static class AnnotatedComponentSubClass extends AnnotatedComponent {
425443
public static class ComposedConfigurationWithAttributeOverridesClass {
426444
}
427445

446+
@Retention(RetentionPolicy.RUNTIME)
447+
@Target(ElementType.TYPE)
448+
public static @interface NamedAnnotation1 {
449+
String name() default "";
450+
}
451+
452+
@Retention(RetentionPolicy.RUNTIME)
453+
@Target(ElementType.TYPE)
454+
public static @interface NamedAnnotation2 {
455+
String name() default "";
456+
}
457+
458+
@Retention(RetentionPolicy.RUNTIME)
459+
@Target(ElementType.TYPE)
460+
public static @interface NamedAnnotation3 {
461+
String name() default "";
462+
}
463+
464+
@NamedAnnotation1(name = "name 1")
465+
@NamedAnnotation2(name = "name 2")
466+
@NamedAnnotation3(name = "name 3")
467+
public static class NamedAnnotationsClass {
468+
}
469+
470+
@NamedAnnotation1(name = "name 1")
471+
@NamedAnnotation2(name = "name 2")
472+
@NamedAnnotation3(name = "name 3")
473+
@Retention(RetentionPolicy.RUNTIME)
474+
@Target(ElementType.TYPE)
475+
public static @interface NamedComposedAnnotation {
476+
}
477+
478+
@NamedComposedAnnotation
479+
public static class NamedComposedAnnotationClass {
480+
}
481+
428482
}

0 commit comments

Comments
 (0)