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
(cherry picked from commit e1720d89fc)
This commit is contained in:
@@ -27,7 +27,7 @@ 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.util.LinkedMultiValueMap;
|
||||||
|
|
||||||
import static org.springframework.core.annotation.AnnotationUtils.VALUE;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal utility class used when reading annotations.
|
* Internal utility class used when reading annotations.
|
||||||
@@ -119,12 +119,14 @@ abstract class AnnotationReadingVisitorUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// To start with, we populate the results with all attribute values from the
|
// To start with, we populate the results with a copy of all attribute
|
||||||
// target annotation.
|
// values from the target annotation. A copy is necessary so that we do
|
||||||
AnnotationAttributes results = attributesList.get(0);
|
// not inadvertently mutate the state of the metadata passed to this
|
||||||
|
// method.
|
||||||
|
AnnotationAttributes results = new AnnotationAttributes(attributesList.get(0));
|
||||||
|
|
||||||
Set<String> overridableAttributeNames = new HashSet<String>(results.keySet());
|
Set<String> overridableAttributeNames = new HashSet<String>(results.keySet());
|
||||||
overridableAttributeNames.remove(VALUE);
|
overridableAttributeNames.remove(AnnotationUtils.VALUE);
|
||||||
|
|
||||||
// Since the map is a LinkedMultiValueMap, we depend on the ordering of
|
// Since the map is a LinkedMultiValueMap, we depend on the ordering of
|
||||||
// elements in the map and reverse the order of the keys in order to traverse
|
// elements in the map and reverse the order of the keys in order to traverse
|
||||||
@@ -132,6 +134,9 @@ abstract class AnnotationReadingVisitorUtils {
|
|||||||
List<String> annotationTypes = new ArrayList<String>(attributesMap.keySet());
|
List<String> annotationTypes = new ArrayList<String>(attributesMap.keySet());
|
||||||
Collections.reverse(annotationTypes);
|
Collections.reverse(annotationTypes);
|
||||||
|
|
||||||
|
// No need to revisit the target annotation type:
|
||||||
|
annotationTypes.remove(annotationType);
|
||||||
|
|
||||||
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()) {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ 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.*;
|
||||||
@@ -111,7 +112,23 @@ 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");
|
||||||
@@ -123,6 +140,30 @@ public class AnnotationMetadataTests {
|
|||||||
assertThat("length of basePackageClasses[]", basePackageClasses.length, is(0));
|
assertThat("length of basePackageClasses[]", basePackageClasses.length, is(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param metadata the metadata for {@link ComposedConfigurationWithAttributeOverridesClass}
|
||||||
|
*/
|
||||||
|
private void assertAllAttributesForMetaAnnotationOverrides(AnnotationMetadata metadata) {
|
||||||
|
MultiValueMap<String, Object> map = metadata.getAllAnnotationAttributes(TestComponentScan.class.getName());
|
||||||
|
List<Object> basePackages = map.get("basePackages");
|
||||||
|
assertThat("length of basePackages list", basePackages.size(), is(1));
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
List<Object> 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));
|
||||||
|
|
||||||
|
List<Object> 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));
|
||||||
|
}
|
||||||
|
|
||||||
private void doTestAnnotationInfo(AnnotationMetadata metadata) {
|
private void doTestAnnotationInfo(AnnotationMetadata metadata) {
|
||||||
assertThat(metadata.getClassName(), is(AnnotatedComponent.class.getName()));
|
assertThat(metadata.getClassName(), is(AnnotatedComponent.class.getName()));
|
||||||
assertThat(metadata.isInterface(), is(false));
|
assertThat(metadata.isInterface(), is(false));
|
||||||
@@ -318,8 +359,10 @@ public class AnnotationMetadataTests {
|
|||||||
// SPR-10914
|
// SPR-10914
|
||||||
public static enum SubclassEnum {
|
public static enum SubclassEnum {
|
||||||
FOO {
|
FOO {
|
||||||
|
/* Do not delete! This subclassing is intentional. */
|
||||||
},
|
},
|
||||||
BAR {
|
BAR {
|
||||||
|
/* Do not delete! This subclassing is intentional. */
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user