Merge branch '7.0.x'

This commit is contained in:
Sam Brannen
2026-04-04 17:21:16 +02:00
10 changed files with 55 additions and 30 deletions
@@ -623,7 +623,7 @@ public interface MergedAnnotation<A extends Annotation> {
* @param annotationType the annotation type
* @param attributes the annotation attributes or {@code null} if just default
* values should be used
* @return a {@link MergedAnnotation} instance for the annotation and attributes
* @return a {@link MergedAnnotation} instance for the annotation type and attributes
*/
static <A extends Annotation> MergedAnnotation<A> of(
@Nullable ClassLoader classLoader, @Nullable Object source,
@@ -446,15 +446,17 @@ public interface MergedAnnotations extends Iterable<MergedAnnotation<Annotation>
}
/**
* Create a new {@link MergedAnnotations} instance from the specified
* collection of directly present annotations. This method allows a
* {@code MergedAnnotations} instance to be created from annotations that
* are not necessarily loaded using reflection. The provided annotations
* must all be {@link MergedAnnotation#isDirectlyPresent() directly present}
* and must have an {@link MergedAnnotation#getAggregateIndex() aggregate
* index} of {@code 0}.
* Create a new {@link MergedAnnotations} instance from the provided
* collection of annotations.
* <p>This method allows a {@code MergedAnnotations} instance to be created
* from annotations that are not necessarily loaded using reflection.
* <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 &mdash; for example, a {@link java.util.List}.
* <p>The resulting {@code MergedAnnotations} instance will contain both the
* specified annotations and any meta-annotations that can be read using
* provided annotations and any meta-annotations that can be read using
* reflection.
* @param annotations the annotations to include
* @return a {@code MergedAnnotations} instance containing the annotations
@@ -22,7 +22,8 @@ import org.springframework.core.type.ClassMetadata;
/**
* Simple facade for accessing class metadata,
* as read by an ASM {@link org.springframework.asm.ClassReader}.
* as read by an ASM {@link org.springframework.asm.ClassReader} or the
* {@code java.lang.classfile.ClassFile} API on Java 24 and higher.
*
* @author Juergen Hoeller
* @since 2.5
@@ -22,7 +22,7 @@ import org.springframework.core.io.ResourceLoader;
/**
* Internal delegate for instantiating {@link MetadataReaderFactory} implementations.
* For JDK < 24, the {@link SimpleMetadataReaderFactory} is being used.
* For JDK < 24, the {@link SimpleMetadataReaderFactory} is used.
*
* @author Brian Clozel
* @since 7.0
@@ -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);
}
@@ -22,7 +22,7 @@ import org.springframework.core.io.ResourceLoader;
/**
* Internal delegate for instantiating {@link MetadataReaderFactory} implementations.
* For JDK >= 24, the {@link ClassFileMetadataReaderFactory} is being used.
* For JDK >= 24, the {@link ClassFileMetadataReaderFactory} is used.
*
* @author Brian Clozel
* @since 7.0
@@ -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
@@ -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
@@ -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(),
@@ -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();