Browse Source

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.

See gh-29685
Closes gh-29686
pull/29935/head
Sam Brannen 3 years ago
parent
commit
5ddc984192
  1. 4
      spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java
  2. 47
      spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java
  3. 29
      spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java

4
spring-core/src/main/java/org/springframework/core/annotation/RepeatableContainers.java

@ -166,8 +166,8 @@ public abstract class RepeatableContainers { @@ -166,8 +166,8 @@ public abstract class RepeatableContainers {
private static Object computeRepeatedAnnotationsMethod(Class<? extends Annotation> annotationType) {
AttributeMethods methods = AttributeMethods.forAnnotationType(annotationType);
if (methods.hasOnlyValueAttribute()) {
Method method = methods.get(0);
Method method = methods.get(MergedAnnotation.VALUE);
if (method != null) {
Class<?> returnType = method.getReturnType();
if (returnType.isArray()) {
Class<?> componentType = returnType.getComponentType();

47
spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java

@ -20,6 +20,7 @@ import java.lang.annotation.Annotation; @@ -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; @@ -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 { @@ -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");
}
// -------------------------------------------------------------------------
@ -1557,4 +1584,24 @@ class AnnotatedElementUtilsTests { @@ -1557,4 +1584,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 {
}
}

29
spring-core/src/test/java/org/springframework/core/annotation/RepeatableContainersTests.java

@ -67,6 +67,15 @@ class RepeatableContainersTests { @@ -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 { @@ -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 {
}

Loading…
Cancel
Save