Skip to content

Commit e1720d8

Browse files
committed
Don't mutate annotation metadata when merging attrs
Prior to this commit, invoking the getMergedAnnotationAttributes() method in AnnotationReadingVisitorUtils resulted in mutation of the internal state of the ASM-based annotation metadata supplied to the method. This commit fixes this issue by making a copy of the original AnnotationAttributes for the target annotation before merging attribute values from the meta-annotation hierarchy. This commit also introduces a slight performance improvement by avoiding duplicate processing of the attributes of the target annotation. Issue: SPR-11710
1 parent a0b6175 commit e1720d8

File tree

2 files changed

+53
-5
lines changed

2 files changed

+53
-5
lines changed

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import org.springframework.core.annotation.AnnotationAttributes;
2828
import org.springframework.util.LinkedMultiValueMap;
2929

30-
import static org.springframework.core.annotation.AnnotationUtils.VALUE;
30+
import org.springframework.core.annotation.AnnotationUtils;
3131

3232
/**
3333
* Internal utility class used when reading annotations.
@@ -119,19 +119,24 @@ public static AnnotationAttributes getMergedAnnotationAttributes(
119119
return null;
120120
}
121121

122-
// To start with, we populate the results with all attribute values from the
123-
// target annotation.
124-
AnnotationAttributes results = attributesList.get(0);
122+
// To start with, we populate the results with a copy of all attribute
123+
// values from the target annotation. A copy is necessary so that we do
124+
// not inadvertently mutate the state of the metadata passed to this
125+
// method.
126+
AnnotationAttributes results = new AnnotationAttributes(attributesList.get(0));
125127

126128
Set<String> overridableAttributeNames = new HashSet<String>(results.keySet());
127-
overridableAttributeNames.remove(VALUE);
129+
overridableAttributeNames.remove(AnnotationUtils.VALUE);
128130

129131
// Since the map is a LinkedMultiValueMap, we depend on the ordering of
130132
// elements in the map and reverse the order of the keys in order to traverse
131133
// "down" the annotation hierarchy.
132134
List<String> annotationTypes = new ArrayList<String>(attributesMap.keySet());
133135
Collections.reverse(annotationTypes);
134136

137+
// No need to revisit the target annotation type:
138+
annotationTypes.remove(annotationType);
139+
135140
for (String currentAnnotationType : annotationTypes) {
136141
List<AnnotationAttributes> currentAttributesList = attributesMap.get(currentAnnotationType);
137142
if (currentAttributesList != null && !currentAttributesList.isEmpty()) {

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.core.type.classreading.MetadataReaderFactory;
3434
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
3535
import org.springframework.stereotype.Component;
36+
import org.springframework.util.MultiValueMap;
3637

3738
import static org.hamcrest.CoreMatchers.*;
3839
import static org.junit.Assert.*;
@@ -111,7 +112,23 @@ public void metaAnnotationOverridesUsingAnnotationMetadataReadingVisitor() throw
111112
assertMetaAnnotationOverrides(metadata);
112113
}
113114

115+
/**
116+
* @param metadata the metadata for {@link ComposedConfigurationWithAttributeOverridesClass}
117+
*/
114118
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) {
115132
AnnotationAttributes attributes = (AnnotationAttributes) metadata.getAnnotationAttributes(
116133
TestComponentScan.class.getName(), false);
117134
String[] basePackages = attributes.getStringArray("basePackages");
@@ -123,6 +140,30 @@ private void assertMetaAnnotationOverrides(AnnotationMetadata metadata) {
123140
assertThat("length of basePackageClasses[]", basePackageClasses.length, is(0));
124141
}
125142

143+
/**
144+
* @param metadata the metadata for {@link ComposedConfigurationWithAttributeOverridesClass}
145+
*/
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));
150+
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));
157+
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));
161+
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));
165+
}
166+
126167
private void doTestAnnotationInfo(AnnotationMetadata metadata) {
127168
assertThat(metadata.getClassName(), is(AnnotatedComponent.class.getName()));
128169
assertThat(metadata.isInterface(), is(false));
@@ -318,8 +359,10 @@ public static enum SomeEnum {
318359
// SPR-10914
319360
public static enum SubclassEnum {
320361
FOO {
362+
/* Do not delete! This subclassing is intentional. */
321363
},
322364
BAR {
365+
/* Do not delete! This subclassing is intentional. */
323366
};
324367
}
325368

0 commit comments

Comments
 (0)