From a199654a62abfb7fa7b0d415b7ac023197d0e9da Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 11 Sep 2023 17:15:58 +0200 Subject: [PATCH] Use MergedAnnotations API in AnnotationBeanNameGenerator where feasible AnnotationBeanNameGenerator was written before the introduction of the MergedAnnotations API and therefore heavily relies on the AnnotationMetadata abstraction and various helper methods for ASM compatibility. However, recent work on determineBeanNameFromAnnotation() has made it apparent that we should use the MergedAnnotations API directly in AnnotationBeanNameGenerator where feasible in order to avoid unnecessary, repeated iterations/streams over the same annotation metadata. Closes gh-31203 --- .../AnnotationBeanNameGenerator.java | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java index cb78bf95c7..22a0191d14 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java @@ -16,11 +16,15 @@ package org.springframework.context.annotation; +import java.lang.annotation.Annotation; import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -31,6 +35,8 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotation.Adapt; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.type.AnnotationMetadata; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -89,12 +95,15 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator { */ private static final Set conventionBasedStereotypeCheckCache = ConcurrentHashMap.newKeySet(); + private static final Adapt[] ADAPTATIONS = Adapt.values(false, true); + private final Log logger = LogFactory.getLog(AnnotationBeanNameGenerator.class); private final Map> metaAnnotationTypesCache = new ConcurrentHashMap<>(); + @Override public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) { if (definition instanceof AnnotatedBeanDefinition annotatedBeanDefinition) { @@ -122,24 +131,32 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator { return beanName; } - for (String annotationType : metadata.getAnnotationTypes()) { - AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, annotationType); - if (attributes != null) { - Set metaAnnotationTypes = this.metaAnnotationTypesCache.computeIfAbsent(annotationType, key -> { - Set result = metadata.getMetaAnnotationTypes(key); - return (result.isEmpty() ? Collections.emptySet() : result); - }); + // List of annotations directly present on the class we're searching on. + // MergedAnnotation implementations do not implement equals()/hashCode(), + // so we use a List and a 'visited' Set below. + List> mergedAnnotations = metadata.getAnnotations().stream() + .filter(MergedAnnotation::isDirectlyPresent) + .toList(); + + Set visited = new HashSet<>(); + + for (MergedAnnotation mergedAnnotation : mergedAnnotations) { + AnnotationAttributes attributes = mergedAnnotation.asAnnotationAttributes(ADAPTATIONS); + if (visited.add(attributes)) { + String annotationType = mergedAnnotation.getType().getName(); + Set metaAnnotationTypes = this.metaAnnotationTypesCache.computeIfAbsent(annotationType, + key -> getMetaAnnotationTypes(mergedAnnotation)); if (isStereotypeWithNameValue(annotationType, metaAnnotationTypes, attributes)) { Object value = attributes.get("value"); if (value instanceof String currentName && !currentName.isBlank()) { if (conventionBasedStereotypeCheckCache.add(annotationType) && metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) && logger.isWarnEnabled()) { logger.warn(""" - Support for convention-based stereotype names is deprecated and will \ - be removed in a future version of the framework. Please annotate the \ - 'value' attribute in @%s with @AliasFor(annotation=Component.class) \ - to declare an explicit alias for @Component's 'value' attribute.""" - .formatted(annotationType)); + Support for convention-based stereotype names is deprecated and will \ + be removed in a future version of the framework. Please annotate the \ + 'value' attribute in @%s with @AliasFor(annotation=Component.class) \ + to declare an explicit alias for @Component's 'value' attribute.""" + .formatted(annotationType)); } if (beanName != null && !currentName.equals(beanName)) { throw new IllegalStateException("Stereotype annotations suggest inconsistent " + @@ -153,6 +170,13 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator { return beanName; } + private Set getMetaAnnotationTypes(MergedAnnotation mergedAnnotation) { + Set result = MergedAnnotations.from(mergedAnnotation.getType()).stream() + .map(metaAnnotation -> metaAnnotation.getType().getName()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + return (result.isEmpty() ? Collections.emptySet() : result); + } + /** * Get the explicit bean name for the underlying class, as configured via * {@link org.springframework.stereotype.Component @Component} and taking into