diff --git a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotations.java b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotations.java
index ddd078b25cf3..3e789889ccac 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotations.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotations.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -410,7 +410,10 @@ private MergedAnnotation process(
Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
if (repeatedAnnotations != null) {
- return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
+ MergedAnnotation result = doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
+ if (result != null) {
+ return result;
+ }
}
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
annotation.annotationType(), repeatableContainers, annotationFilter);
diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsRepeatableAnnotationTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsRepeatableAnnotationTests.java
index 40b1fdf5b8f8..f1a80ee56e73 100644
--- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsRepeatableAnnotationTests.java
+++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsRepeatableAnnotationTests.java
@@ -24,6 +24,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
+import java.util.Arrays;
import java.util.Set;
import java.util.stream.Stream;
@@ -175,7 +176,7 @@ void typeHierarchyWhenOnClassReturnsAnnotations() {
}
@Test
- void typeHierarchyWhenWhenOnSuperclassReturnsAnnotations() {
+ void typeHierarchyWhenOnSuperclassReturnsAnnotations() {
Set annotations = getAnnotations(null, PeteRepeat.class,
SearchStrategy.TYPE_HIERARCHY, SubRepeatableClass.class);
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B",
@@ -240,6 +241,44 @@ void typeHierarchyAnnotationsWithLocalComposedAnnotationWhoseRepeatableMetaAnnot
assertThat(annotationTypes).containsExactly(WithRepeatedMetaAnnotations.class, Noninherited.class, Noninherited.class);
}
+ @Test // gh-32731
+ void searchFindsRepeatableContainerAnnotationAndRepeatedAnnotations() {
+ Class> clazz = StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class;
+
+ // NO RepeatableContainers
+ MergedAnnotations mergedAnnotations = MergedAnnotations.from(clazz, TYPE_HIERARCHY, RepeatableContainers.none());
+ ContainerWithMultipleAttributes container = mergedAnnotations
+ .get(ContainerWithMultipleAttributes.class)
+ .synthesize(MergedAnnotation::isPresent).orElse(null);
+ assertThat(container).as("container").isNotNull();
+ assertThat(container.name()).isEqualTo("enigma");
+ RepeatableWithContainerWithMultipleAttributes[] repeatedAnnotations = container.value();
+ assertThat(Arrays.stream(repeatedAnnotations).map(RepeatableWithContainerWithMultipleAttributes::value))
+ .containsExactly("A", "B");
+ Set set =
+ mergedAnnotations.stream(RepeatableWithContainerWithMultipleAttributes.class)
+ .collect(MergedAnnotationCollectors.toAnnotationSet());
+ // Only finds the locally declared repeated annotation.
+ assertThat(set.stream().map(RepeatableWithContainerWithMultipleAttributes::value))
+ .containsExactly("C");
+
+ // Standard RepeatableContainers
+ mergedAnnotations = MergedAnnotations.from(clazz, TYPE_HIERARCHY, RepeatableContainers.standardRepeatables());
+ container = mergedAnnotations
+ .get(ContainerWithMultipleAttributes.class)
+ .synthesize(MergedAnnotation::isPresent).orElse(null);
+ assertThat(container).as("container").isNotNull();
+ assertThat(container.name()).isEqualTo("enigma");
+ repeatedAnnotations = container.value();
+ assertThat(Arrays.stream(repeatedAnnotations).map(RepeatableWithContainerWithMultipleAttributes::value))
+ .containsExactly("A", "B");
+ set = mergedAnnotations.stream(RepeatableWithContainerWithMultipleAttributes.class)
+ .collect(MergedAnnotationCollectors.toAnnotationSet());
+ // Finds the locally declared repeated annotation plus the 2 in the container.
+ assertThat(set.stream().map(RepeatableWithContainerWithMultipleAttributes::value))
+ .containsExactly("A", "B", "C");
+ }
+
private Set getAnnotations(Class extends Annotation> container,
Class repeatable, SearchStrategy searchStrategy, AnnotatedElement element) {
@@ -449,4 +488,27 @@ static class SubNoninheritedRepeatableClass extends NoninheritedRepeatableClass
static class WithRepeatedMetaAnnotationsClass {
}
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface ContainerWithMultipleAttributes {
+
+ RepeatableWithContainerWithMultipleAttributes[] value();
+
+ String name() default "";
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Repeatable(ContainerWithMultipleAttributes.class)
+ @interface RepeatableWithContainerWithMultipleAttributes {
+
+ String value() default "";
+ }
+
+ @ContainerWithMultipleAttributes(name = "enigma", value = {
+ @RepeatableWithContainerWithMultipleAttributes("A"),
+ @RepeatableWithContainerWithMultipleAttributes("B")
+ })
+ @RepeatableWithContainerWithMultipleAttributes("C")
+ static class StandardRepeatablesWithContainerWithMultipleAttributesTestCase {
+ }
+
}