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
This commit is contained in:
Sam Brannen 2023-09-11 17:15:58 +02:00
parent bf5ce82254
commit a199654a62
1 changed files with 36 additions and 12 deletions

View File

@ -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<String> conventionBasedStereotypeCheckCache = ConcurrentHashMap.newKeySet();
private static final Adapt[] ADAPTATIONS = Adapt.values(false, true);
private final Log logger = LogFactory.getLog(AnnotationBeanNameGenerator.class);
private final Map<String, Set<String>> 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<String> metaAnnotationTypes = this.metaAnnotationTypesCache.computeIfAbsent(annotationType, key -> {
Set<String> 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<MergedAnnotation<Annotation>> mergedAnnotations = metadata.getAnnotations().stream()
.filter(MergedAnnotation::isDirectlyPresent)
.toList();
Set<AnnotationAttributes> visited = new HashSet<>();
for (MergedAnnotation<Annotation> mergedAnnotation : mergedAnnotations) {
AnnotationAttributes attributes = mergedAnnotation.asAnnotationAttributes(ADAPTATIONS);
if (visited.add(attributes)) {
String annotationType = mergedAnnotation.getType().getName();
Set<String> 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<String> getMetaAnnotationTypes(MergedAnnotation<Annotation> mergedAnnotation) {
Set<String> 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