From 5e7630469c7b665dc272f820de9def70adb6fe56 Mon Sep 17 00:00:00 2001 From: Chengang Guan <115277968+guanchengang@users.noreply.github.com> Date: Wed, 30 Sep 2026 23:41:07 +0800 Subject: [PATCH] Fix container detection in RepeatableContainers.standardRepeatables() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit StandardRepeatableContainers.computeRepeatedAnnotationsMethod() previously treated any annotation as a container annotation if it merely declared a `value` attribute returning an array of a @⁠Repeatable annotated type, without verifying that the repeatable annotation actually references the candidate container via @⁠Repeatable.value(). Consequently, an unrelated annotation that happened to have the same shape as the official container (e.g. a "fake" container with a `value` attribute returning the same repeatable type) was incorrectly recognized as a container. This commit aligns the detection logic with the Java language semantics by additionally asserting that the container type declared in @⁠Repeatable.value() is identical to the candidate annotation type. Closes gh-37326 Signed-off-by: Chengang Guan --- .../core/annotation/RepeatableContainers.java | 8 +++++--- .../annotation/RepeatableContainersTests.java | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java b/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java index b8462d8f379..d569d318132 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java @@ -274,9 +274,11 @@ public abstract class RepeatableContainers { Class returnType = method.getReturnType(); if (returnType.isArray()) { Class componentType = returnType.componentType(); - if (Annotation.class.isAssignableFrom(componentType) && - componentType.isAnnotationPresent(Repeatable.class)) { - return method; + if (Annotation.class.isAssignableFrom(componentType)) { + Repeatable repeatable = componentType.getAnnotation(Repeatable.class); + if (repeatable != null && repeatable.value() == annotationType) { + return method; + } } } } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java b/spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java index d4c886e4de2..712017f00c8 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java @@ -37,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException * * @author Phillip Webb * @author Sam Brannen + * @author Chengang Guan */ class RepeatableContainersTests { @@ -79,6 +80,13 @@ class RepeatableContainersTests { assertThat(values).containsExactly("a", "b"); } + @Test + void standardRepeatablesWhenContainerNotReferencedByRepeatableReturnsNull() { + Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(), + FakeRepeatablesTestCase.class, FakeStandardContainer.class); + assertThat(values).isNull(); + } + } @Nested @@ -211,6 +219,12 @@ class RepeatableContainersTests { StandardRepeatable[] value(); } + @Retention(RetentionPolicy.RUNTIME) + @interface FakeStandardContainer { + + StandardRepeatable[] value(); + } + @Retention(RetentionPolicy.RUNTIME) @Repeatable(StandardContainer.class) @interface StandardRepeatable { @@ -263,4 +277,8 @@ class RepeatableContainersTests { static class ExplicitRepeatablesTestCase { } + @FakeStandardContainer({ @StandardRepeatable("a"), @StandardRepeatable("b") }) + static class FakeRepeatablesTestCase { + } + }