From 013ec6abdb65c23d3e1be2dc6141eac2130d0503 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 24 Oct 2019 11:54:42 -0700 Subject: [PATCH] Support customer repeatable containers in type map Update `AnnotationTypeMappings` so that a custom `RepeatableContainers` instances can be used. Prior to this commit, only standard repeatables were used when reading the annotations. This works in most situations, but causes regressions for some `AnnotationUtils` methods. Fixed gh-23856 --- .../annotation/AnnotationTypeMappings.java | 50 ++++++++++++++++--- .../annotation/TypeMappedAnnotations.java | 2 +- .../core/annotation/AnnotationUtilsTests.java | 2 - 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java index 2f732df792f..e6fee398763 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationTypeMappings.java @@ -47,15 +47,22 @@ final class AnnotationTypeMappings { private static final IntrospectionFailureLogger failureLogger = IntrospectionFailureLogger.DEBUG; - private static final Map cache = new ConcurrentReferenceHashMap<>(); + private static final Map standardRepeatablesCache = new ConcurrentReferenceHashMap<>(); + private static final Map noRepeatablesCache = new ConcurrentReferenceHashMap<>(); + + + private final RepeatableContainers repeatableContainers; private final AnnotationFilter filter; private final List mappings; - private AnnotationTypeMappings(AnnotationFilter filter, Class annotationType) { + private AnnotationTypeMappings(RepeatableContainers repeatableContainers, + AnnotationFilter filter, Class annotationType) { + + this.repeatableContainers = repeatableContainers; this.filter = filter; this.mappings = new ArrayList<>(); addAllMappings(annotationType); @@ -80,7 +87,7 @@ final class AnnotationTypeMappings { if (!isMappable(source, metaAnnotation)) { continue; } - Annotation[] repeatedAnnotations = RepeatableContainers.standardRepeatables() + Annotation[] repeatedAnnotations = this.repeatableContainers .findRepeatedAnnotations(metaAnnotation); if (repeatedAnnotations != null) { for (Annotation repeatedAnnotation : repeatedAnnotations) { @@ -178,11 +185,37 @@ final class AnnotationTypeMappings { static AnnotationTypeMappings forAnnotationType( Class annotationType, AnnotationFilter annotationFilter) { - return cache.computeIfAbsent(annotationFilter, Cache::new).get(annotationType); + return forAnnotationType(annotationType, + RepeatableContainers.standardRepeatables(), annotationFilter); + } + + /** + * Create {@link AnnotationTypeMappings} for the specified annotation type. + * @param annotationType the source annotation type + * @param annotationFilter the annotation filter used to limit which + * annotations are considered + * @return type mappings for the annotation type + */ + static AnnotationTypeMappings forAnnotationType( + Class annotationType, + RepeatableContainers repeatableContainers, + AnnotationFilter annotationFilter) { + + if (repeatableContainers == RepeatableContainers.standardRepeatables()) { + return standardRepeatablesCache.computeIfAbsent(annotationFilter, + key -> new Cache(repeatableContainers, key)).get(annotationType); + } + if (repeatableContainers == RepeatableContainers.none()) { + return noRepeatablesCache.computeIfAbsent(annotationFilter, + key -> new Cache(repeatableContainers, key)).get(annotationType); + } + return new AnnotationTypeMappings(repeatableContainers, annotationFilter, + annotationType); } static void clearCache() { - cache.clear(); + standardRepeatablesCache.clear(); + noRepeatablesCache.clear(); } @@ -191,6 +224,8 @@ final class AnnotationTypeMappings { */ private static class Cache { + private final RepeatableContainers repeatableContainers; + private final AnnotationFilter filter; private final Map, AnnotationTypeMappings> mappings; @@ -199,7 +234,8 @@ final class AnnotationTypeMappings { * Create a cache instance with the specified filter. * @param filter the annotation filter */ - Cache(AnnotationFilter filter) { + Cache(RepeatableContainers repeatableContainers, AnnotationFilter filter) { + this.repeatableContainers = repeatableContainers; this.filter = filter; this.mappings = new ConcurrentReferenceHashMap<>(); } @@ -214,7 +250,7 @@ final class AnnotationTypeMappings { } AnnotationTypeMappings createMappings(Class annotationType) { - return new AnnotationTypeMappings(this.filter, annotationType); + return new AnnotationTypeMappings(this.repeatableContainers, this.filter, annotationType); } } 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 8bbbe050075..ba1ae30a00f 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 @@ -413,7 +413,7 @@ final class TypeMappedAnnotations implements MergedAnnotations { return doWithAnnotations(type, aggregateIndex, source, repeatedAnnotations); } AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType( - annotation.annotationType(), annotationFilter); + annotation.annotationType(), repeatableContainers, annotationFilter); for (int i = 0; i < mappings.size(); i++) { AnnotationTypeMapping mapping = mappings.get(i); if (isMappingForType(mapping, annotationFilter, this.requiredType)) { diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index f74a0693a06..461b300a38b 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -34,7 +34,6 @@ import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.core.Ordered; @@ -974,7 +973,6 @@ class AnnotationUtilsTests { } @Test // gh-23856 - @Disabled("disabled until gh-23856 is resolved") void findAnnotationFindsRepeatableContainerOnComposedAnnotationMetaAnnotatedWithRepeatableAnnotationsOnMethod() throws NoSuchMethodException { Method method = getClass().getDeclaredMethod("methodWithComposedAnnotationMetaAnnotatedWithRepeatableAnnotations"); MyRepeatableContainer annotation = AnnotationUtils.findAnnotation(method, MyRepeatableContainer.class);