Support repeatable annotation containers with multiple attributes
Prior to this commit, there was a bug in the implementation of StandardRepeatableContainers.computeRepeatedAnnotationsMethod() which has existed since Spring Framework 5.2 (when StandardRepeatableContainers was introduced). Specifically, StandardRepeatableContainers ignored any repeatable container annotation if it declared attributes other than `value()`. However, Java permits any number of attributes in a repeatable container annotation. In addition, the changes made in conjunction with gh-20279 made the bug in StandardRepeatableContainers apparent when using the getMergedRepeatableAnnotations() or findMergedRepeatableAnnotations() method in AnnotatedElementUtils, resulting in regressions for the behavior of those two methods. This commit fixes the regressions and bug by altering the logic in StandardRepeatableContainers.computeRepeatedAnnotationsMethod() so that it explicitly looks for the `value()` method and ignores any other methods declared in a repeatable container annotation candidate. Closes gh-29685
This commit is contained in:
@@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Repeatable;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
@@ -77,6 +78,7 @@ import static org.springframework.core.annotation.AnnotationUtilsTests.asArray;
|
||||
* @see AnnotationUtilsTests
|
||||
* @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests
|
||||
* @see ComposedRepeatableAnnotationsTests
|
||||
* @see NestedRepeatableAnnotationsTests
|
||||
*/
|
||||
class AnnotatedElementUtilsTests {
|
||||
|
||||
@@ -908,6 +910,31 @@ class AnnotatedElementUtilsTests {
|
||||
assertThat(annotation.value()).containsExactly("FromValueAttributeMeta");
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.3.25
|
||||
*/
|
||||
@Test // gh-29685
|
||||
void getMergedRepeatableAnnotationsWithContainerWithMultipleAttributes() {
|
||||
Set<StandardRepeatableWithContainerWithMultipleAttributes> repeatableAnnotations =
|
||||
AnnotatedElementUtils.getMergedRepeatableAnnotations(
|
||||
StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class,
|
||||
StandardRepeatableWithContainerWithMultipleAttributes.class);
|
||||
assertThat(repeatableAnnotations).map(StandardRepeatableWithContainerWithMultipleAttributes::value)
|
||||
.containsExactly("a", "b");
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 5.3.25
|
||||
*/
|
||||
@Test // gh-29685
|
||||
void findMergedRepeatableAnnotationsWithContainerWithMultipleAttributes() {
|
||||
Set<StandardRepeatableWithContainerWithMultipleAttributes> repeatableAnnotations =
|
||||
AnnotatedElementUtils.findMergedRepeatableAnnotations(
|
||||
StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class,
|
||||
StandardRepeatableWithContainerWithMultipleAttributes.class);
|
||||
assertThat(repeatableAnnotations).map(StandardRepeatableWithContainerWithMultipleAttributes::value)
|
||||
.containsExactly("a", "b");
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -1567,4 +1594,24 @@ class AnnotatedElementUtilsTests {
|
||||
static class ValueAttributeMetaMetaClass {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface StandardContainerWithMultipleAttributes {
|
||||
|
||||
StandardRepeatableWithContainerWithMultipleAttributes[] value();
|
||||
|
||||
String name() default "";
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Repeatable(StandardContainerWithMultipleAttributes.class)
|
||||
@interface StandardRepeatableWithContainerWithMultipleAttributes {
|
||||
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
@StandardRepeatableWithContainerWithMultipleAttributes("a")
|
||||
@StandardRepeatableWithContainerWithMultipleAttributes("b")
|
||||
static class StandardRepeatablesWithContainerWithMultipleAttributesTestCase {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -67,6 +67,15 @@ class RepeatableContainersTests {
|
||||
StandardRepeatablesTestCase.class, StandardContainer.class);
|
||||
assertThat(values).containsExactly("a", "b");
|
||||
}
|
||||
|
||||
@Test
|
||||
void standardRepeatablesWithContainerWithMultipleAttributes() {
|
||||
Object[] values = findRepeatedAnnotationValues(RepeatableContainers.standardRepeatables(),
|
||||
StandardRepeatablesWithContainerWithMultipleAttributesTestCase.class,
|
||||
StandardContainerWithMultipleAttributes.class);
|
||||
assertThat(values).containsExactly("a", "b");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
@@ -247,6 +256,26 @@ class RepeatableContainersTests {
|
||||
static class StandardRepeatablesTestCase {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface StandardContainerWithMultipleAttributes {
|
||||
|
||||
StandardRepeatableWithContainerWithMultipleAttributes[] value();
|
||||
|
||||
String name() default "";
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Repeatable(StandardContainerWithMultipleAttributes.class)
|
||||
@interface StandardRepeatableWithContainerWithMultipleAttributes {
|
||||
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
@StandardRepeatableWithContainerWithMultipleAttributes("a")
|
||||
@StandardRepeatableWithContainerWithMultipleAttributes("b")
|
||||
static class StandardRepeatablesWithContainerWithMultipleAttributesTestCase {
|
||||
}
|
||||
|
||||
@ExplicitContainer({ @ExplicitRepeatable("a"), @ExplicitRepeatable("b") })
|
||||
static class ExplicitRepeatablesTestCase {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user