mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 08:24:13 +00:00
Retain source declaration order in AnnotatedTypeMetadata on Java 24+
Prior to this commit, ClassFileAnnotationDelegate created MergedAnnotations from a HashSet, which resulted in a non-deterministic iteration order and lost the original source declaration order of the annotations. To address that, this commit revises ClassFileAnnotationDelegate to create MergedAnnotations from a List. In addition, this commit updates all related tests to use the containsExactly() assertion instead of containsExactlyInAnyOrder() to ensure we consistently adhere to the "source declaration order" requirement. Closes gh-36598
This commit is contained in:
@@ -453,6 +453,8 @@ public interface MergedAnnotations extends Iterable<MergedAnnotation<Annotation>
|
||||
* <p>The provided annotations must all be
|
||||
* {@link MergedAnnotation#isDirectlyPresent() directly present} and must have
|
||||
* an {@link MergedAnnotation#getAggregateIndex() aggregate index} of {@code 0}.
|
||||
* In addition, the provided collection must retain the source declaration order
|
||||
* of the annotations — for example, a {@link java.util.List}.
|
||||
* <p>The resulting {@code MergedAnnotations} instance will contain both the
|
||||
* provided annotations and any meta-annotations that can be read using
|
||||
* reflection.
|
||||
|
||||
+3
-3
@@ -26,7 +26,6 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -43,6 +42,7 @@ import org.springframework.util.ClassUtils;
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @author Juergen Hoeller
|
||||
* @author Sam Brannen
|
||||
* @since 7.0
|
||||
*/
|
||||
abstract class ClassFileAnnotationDelegate {
|
||||
@@ -50,11 +50,11 @@ abstract class ClassFileAnnotationDelegate {
|
||||
static MergedAnnotations createMergedAnnotations(
|
||||
String className, RuntimeVisibleAnnotationsAttribute annotationAttribute, @Nullable ClassLoader classLoader) {
|
||||
|
||||
Set<MergedAnnotation<?>> annotations = annotationAttribute.annotations()
|
||||
List<MergedAnnotation<?>> annotations = annotationAttribute.annotations()
|
||||
.stream()
|
||||
.map(ann -> createMergedAnnotation(className, ann, classLoader))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
.collect(Collectors.toList());
|
||||
return MergedAnnotations.of(annotations);
|
||||
}
|
||||
|
||||
|
||||
+14
-6
@@ -261,7 +261,9 @@ public abstract class AbstractAnnotationMetadataTests {
|
||||
assertThat(get(WithDirectAnnotations.class).getAnnotations().stream())
|
||||
.filteredOn(MergedAnnotation::isDirectlyPresent)
|
||||
.extracting(a -> a.getType().getName())
|
||||
.containsExactlyInAnyOrder(DirectAnnotation1.class.getName(), DirectAnnotation2.class.getName());
|
||||
// We do not use containsExactlyInAnyOrder(), because annotations
|
||||
// must be returned in source declaration order.
|
||||
.containsExactly(DirectAnnotation1.class.getName(), DirectAnnotation2.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -290,8 +292,10 @@ public abstract class AbstractAnnotationMetadataTests {
|
||||
MultiValueMap<String, Object> attributes =
|
||||
get(WithMetaAnnotationAttributes.class).getAllAnnotationAttributes(AnnotationAttributes.class.getName());
|
||||
assertThat(attributes).containsOnlyKeys("name", "size");
|
||||
assertThat(attributes.get("name")).containsExactlyInAnyOrder("m1", "m2");
|
||||
assertThat(attributes.get("size")).containsExactlyInAnyOrder(1, 2);
|
||||
// We do not use containsExactlyInAnyOrder(), because annotations
|
||||
// must be returned in source declaration order.
|
||||
assertThat(attributes.get("name")).containsExactly("m1", "m2");
|
||||
assertThat(attributes.get("size")).containsExactly(1, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -335,15 +339,19 @@ public abstract class AbstractAnnotationMetadataTests {
|
||||
@Test
|
||||
void getAnnotationTypesReturnsDirectAnnotations() {
|
||||
AnnotationMetadata metadata = get(WithDirectAnnotations.class);
|
||||
assertThat(metadata.getAnnotationTypes()).containsExactlyInAnyOrder(
|
||||
DirectAnnotation1.class.getName(), DirectAnnotation2.class.getName());
|
||||
// We do not use containsExactlyInAnyOrder(), because annotations
|
||||
// must be returned in source declaration order.
|
||||
assertThat(metadata.getAnnotationTypes())
|
||||
.containsExactly(DirectAnnotation1.class.getName(), DirectAnnotation2.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMetaAnnotationTypesReturnsMetaAnnotations() {
|
||||
AnnotationMetadata metadata = get(WithMetaAnnotations.class);
|
||||
// We do not use containsExactlyInAnyOrder(), because annotations
|
||||
// must be returned in source declaration order.
|
||||
assertThat(metadata.getMetaAnnotationTypes(MetaAnnotationRoot.class.getName()))
|
||||
.containsExactlyInAnyOrder(MetaAnnotation1.class.getName(), MetaAnnotation2.class.getName());
|
||||
.containsExactly(MetaAnnotation1.class.getName(), MetaAnnotation2.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+7
-3
@@ -224,7 +224,9 @@ public abstract class AbstractMethodMetadataTests {
|
||||
Stream<Class<?>> types = metadata.getAnnotations().stream()
|
||||
.filter(MergedAnnotation::isDirectlyPresent)
|
||||
.map(MergedAnnotation::getType);
|
||||
assertThat(types).containsExactlyInAnyOrder(Tag.class, DirectAnnotation.class);
|
||||
// We do not use containsExactlyInAnyOrder(), because annotations
|
||||
// must be returned in source declaration order.
|
||||
assertThat(types).containsExactly(Tag.class, DirectAnnotation.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -254,8 +256,10 @@ public abstract class AbstractMethodMetadataTests {
|
||||
MultiValueMap<String, Object> attributes = getTagged(WithMetaAnnotationAttributes.class)
|
||||
.getAllAnnotationAttributes(AnnotationAttributes.class.getName());
|
||||
assertThat(attributes).containsOnlyKeys("name", "size");
|
||||
assertThat(attributes.get("name")).containsExactlyInAnyOrder("m1", "m2");
|
||||
assertThat(attributes.get("size")).containsExactlyInAnyOrder(1, 2);
|
||||
// We do not use containsExactlyInAnyOrder(), because annotations
|
||||
// must be returned in source declaration order.
|
||||
assertThat(attributes.get("name")).containsExactly("m1", "m2");
|
||||
assertThat(attributes.get("size")).containsExactly(1, 2);
|
||||
}
|
||||
|
||||
@Test // gh-24375
|
||||
|
||||
+3
-1
@@ -405,7 +405,9 @@ class AnnotationMetadataTests {
|
||||
assertThat(metadata.hasAnnotation(SpecialAttr.class.getName())).isTrue();
|
||||
|
||||
assertThat(metadata.hasAnnotation(NamedComposedAnnotation.class.getName())).isTrue();
|
||||
assertThat(metadata.getAnnotationTypes()).containsExactlyInAnyOrder(
|
||||
// We do not use containsExactlyInAnyOrder(), because annotations
|
||||
// must be returned in source declaration order.
|
||||
assertThat(metadata.getAnnotationTypes()).containsExactly(
|
||||
Component.class.getName(), Scope.class.getName(),
|
||||
SpecialAttr.class.getName(), DirectAnnotation.class.getName(),
|
||||
MetaMetaAnnotation.class.getName(), EnumSubclasses.class.getName(),
|
||||
|
||||
+13
-5
@@ -57,7 +57,9 @@ class InheritedAnnotationsAnnotationMetadataTests {
|
||||
|
||||
@Test
|
||||
void getAnnotationTypes() {
|
||||
assertThat(standardMetadata.getAnnotationTypes()).containsExactlyInAnyOrder(
|
||||
// We do not use containsExactlyInAnyOrder(), because annotations
|
||||
// must be returned in source declaration order.
|
||||
assertThat(standardMetadata.getAnnotationTypes()).containsExactly(
|
||||
NamedAnnotation3.class.getName(),
|
||||
InheritedComposedAnnotation.class.getName());
|
||||
|
||||
@@ -81,11 +83,14 @@ class InheritedAnnotationsAnnotationMetadataTests {
|
||||
Set<String> metaAnnotationTypes;
|
||||
|
||||
metaAnnotationTypes = standardMetadata.getMetaAnnotationTypes(InheritedComposedAnnotation.class.getName());
|
||||
assertThat(metaAnnotationTypes).containsExactlyInAnyOrder(
|
||||
MetaAnnotation.class.getName(),
|
||||
// We do not use containsExactlyInAnyOrder(), because annotations
|
||||
// must be returned in source declaration order, with local annotations
|
||||
// before meta-annotations.
|
||||
assertThat(metaAnnotationTypes).containsExactly(
|
||||
NamedAnnotation1.class.getName(),
|
||||
NamedAnnotation2.class.getName(),
|
||||
NamedAnnotation3.class.getName());
|
||||
NamedAnnotation3.class.getName(),
|
||||
MetaAnnotation.class.getName());
|
||||
|
||||
metaAnnotationTypes = asmMetadata.getMetaAnnotationTypes(InheritedComposedAnnotation.class.getName());
|
||||
assertThat(metaAnnotationTypes).isEmpty();
|
||||
@@ -138,7 +143,10 @@ class InheritedAnnotationsAnnotationMetadataTests {
|
||||
|
||||
annotationAttributes = standardMetadata.getAllAnnotationAttributes(NamedAnnotation3.class.getName());
|
||||
assertThat(annotationAttributes).containsKey("name");
|
||||
assertThat(annotationAttributes.get("name")).containsExactlyInAnyOrder("name 3", "local");
|
||||
// We do not use containsExactlyInAnyOrder(), because annotations
|
||||
// must be returned in source declaration order, with local annotations
|
||||
// before meta-annotations.
|
||||
assertThat(annotationAttributes.get("name")).containsExactly("local", "name 3");
|
||||
|
||||
annotationAttributes = asmMetadata.getAllAnnotationAttributes(NamedAnnotation1.class.getName());
|
||||
assertThat(annotationAttributes).isNull();
|
||||
|
||||
Reference in New Issue
Block a user