Include repeatable annotation container in MergedAnnotations results
A bug has existed in Spring's MergedAnnotations support since it was introduced in Spring Framework 5.2. Specifically, if the MergedAnnotations API is used to search for annotations with "standard repeatable annotation" support enabled (which is the default), it's possible to search for a repeatable annotation but not for the repeatable annotation's container annotation. The reason is that MergedAnnotationFinder.process(Object, int, Object, Annotation) does not process the container annotation and instead only processes the "contained" annotations, which prevents a container annotation from being included in search results. In #29685, we fixed a bug that prevented the MergedAnnotations support from recognizing an annotation as a container if the container annotation declares attributes other than the required `value` attribute. As a consequence of that bug fix, since Spring Framework 5.3.25, the MergedAnnotations infrastructure considers such an annotation a container, and due to the aforementioned bug the container is no longer processed, which results in a regression in behavior for annotation searches for such a container annotation. This commit addresses the original bug as well as the regression by processing container annotations in addition to the contained repeatable annotations. See gh-29685 Closes gh-32731
This commit is contained in:
parent
abcc1dfc6c
commit
4baad16437
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -418,7 +418,10 @@ final class TypeMappedAnnotations implements MergedAnnotations {
|
||||||
|
|
||||||
Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
|
Annotation[] repeatedAnnotations = repeatableContainers.findRepeatedAnnotations(annotation);
|
||||||
if (repeatedAnnotations != null) {
|
if (repeatedAnnotations != null) {
|
||||||
return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
|
MergedAnnotation<A> result = doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations);
|
||||||
|
if (result != null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
|
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
|
||||||
annotation.annotationType(), repeatableContainers, annotationFilter);
|
annotation.annotationType(), repeatableContainers, annotationFilter);
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
|
@ -168,7 +169,7 @@ class MergedAnnotationsRepeatableAnnotationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void typeHierarchyWhenWhenOnSuperclassReturnsAnnotations() {
|
void typeHierarchyWhenOnSuperclassReturnsAnnotations() {
|
||||||
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
|
Set<PeteRepeat> annotations = getAnnotations(null, PeteRepeat.class,
|
||||||
TYPE_HIERARCHY, SubRepeatableClass.class);
|
TYPE_HIERARCHY, SubRepeatableClass.class);
|
||||||
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B", "C");
|
assertThat(annotations.stream().map(PeteRepeat::value)).containsExactly("A", "B", "C");
|
||||||
|
|
@ -226,6 +227,44 @@ class MergedAnnotationsRepeatableAnnotationTests {
|
||||||
assertThat(annotationTypes).containsExactly(WithRepeatedMetaAnnotations.class, Noninherited.class, Noninherited.class);
|
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<RepeatableWithContainerWithMultipleAttributes> 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 <A extends Annotation> Set<A> getAnnotations(Class<? extends Annotation> container,
|
private <A extends Annotation> Set<A> getAnnotations(Class<? extends Annotation> container,
|
||||||
Class<A> repeatable, SearchStrategy searchStrategy, AnnotatedElement element) {
|
Class<A> repeatable, SearchStrategy searchStrategy, AnnotatedElement element) {
|
||||||
|
|
||||||
|
|
@ -420,4 +459,27 @@ class MergedAnnotationsRepeatableAnnotationTests {
|
||||||
static class WithRepeatedMetaAnnotationsClass {
|
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 {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue