Look up @Component stereotype names using @AliasFor semantics

Although gh-20615 introduced the use of @AliasFor for @Component(value) in the built-in
stereotype annotations (@Service, @Controller, @Repository, @Configuration, and
@RestController), prior to this commit the framework did not actually rely on @AliasFor
support when looking up a component name via stereotype annotations. Rather, the
framework had custom annotation parsing logic in
AnnotationBeanNameGenerator#determineBeanNameFromAnnotation() which effectively ignored
explicit annotation attribute overrides configured via @AliasFor.

This commit revises AnnotationBeanNameGenerator#determineBeanNameFromAnnotation() so that
it first looks up @Component stereotype names using @AliasFor semantics before falling
back to the "convention-based" component name lookup strategy.

Consequently, the name of the annotation attribute that is used to specify the bean name
is no longer required to be `value`, and custom stereotype annotations can now declare an
attribute with a different name (such as `name`) and annotate that attribute with
`@AliasFor(annotation = Component.class, attribute = "value")`.

Closes gh-31089
This commit is contained in:
Sam Brannen
2023-08-22 13:10:52 +02:00
parent d189e169cc
commit ff104b6de0
8 changed files with 171 additions and 26 deletions

View File

@@ -17,6 +17,7 @@
package org.springframework.context.annotation;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -26,6 +27,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
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.type.AnnotationMetadata;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -100,6 +102,12 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotatedDef) {
AnnotationMetadata metadata = annotatedDef.getMetadata();
Set<String> annotationTypes = metadata.getAnnotationTypes();
String explicitBeanName = getExplicitBeanName(metadata);
if (explicitBeanName != null) {
return explicitBeanName;
}
String beanName = null;
for (String annotationType : annotationTypes) {
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, annotationType);
@@ -123,6 +131,36 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
return beanName;
}
/**
* Get the explicit bean name for the underlying class, as configured via
* {@link org.springframework.stereotype.Component @Component} and taking into
* account {@link org.springframework.core.annotation.AliasFor @AliasFor}
* semantics for annotation attribute overrides for {@code @Component}'s
* {@code value} attribute.
* @param metadata the {@link AnnotationMetadata} for the underlying class
* @return the explicit bean name, or {@code null} if not found
* @since 6.1
* @see org.springframework.stereotype.Component#value()
*/
@Nullable
private String getExplicitBeanName(AnnotationMetadata metadata) {
List<String> names = metadata.getAnnotations().stream(COMPONENT_ANNOTATION_CLASSNAME)
.map(annotation -> annotation.getString(MergedAnnotation.VALUE))
.filter(StringUtils::hasText)
.map(String::trim)
.distinct()
.toList();
if (names.size() == 1) {
return names.get(0);
}
if (names.size() > 1) {
throw new IllegalStateException(
"Stereotype annotations suggest inconsistent component names: " + names);
}
return null;
}
/**
* Check whether the given annotation is a stereotype that is allowed
* to suggest a component name through its {@code value()} attribute.
@@ -134,8 +172,7 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
protected boolean isStereotypeWithNameValue(String annotationType,
Set<String> metaAnnotationTypes, @Nullable Map<String, Object> attributes) {
boolean isStereotype = annotationType.equals(COMPONENT_ANNOTATION_CLASSNAME) ||
metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) ||
boolean isStereotype = metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) ||
annotationType.equals("jakarta.annotation.ManagedBean") ||
annotationType.equals("javax.annotation.ManagedBean") ||
annotationType.equals("jakarta.inject.Named") ||

View File

@@ -434,6 +434,7 @@ public @interface Configuration {
* {@link AnnotationConfigApplicationContext}. If the {@code @Configuration} class
* is registered as a traditional XML bean definition, the name/id of the bean
* element will take precedence.
* <p>Alias for {@link Component#value}.
* @return the explicit component name, if any (or empty String otherwise)
* @see AnnotationBeanNameGenerator
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,11 +28,17 @@ import java.lang.annotation.Target;
* when using annotation-based configuration and classpath scanning.
*
* <p>Other class-level annotations may be considered as identifying
* a component as well, typically a special kind of component:
* e.g. the {@link Repository @Repository} annotation or AspectJ's
* a component as well, typically a special kind of component &mdash;
* for example, the {@link Repository @Repository} annotation or AspectJ's
* {@link org.aspectj.lang.annotation.Aspect @Aspect} annotation.
*
* <p>As of Spring Framework 6.1, custom component stereotype annotations should
* use {@link org.springframework.core.annotation.AliasFor @AliasFor} to declare
* an explicit alias for this annotation's {@link #value} attribute. See the
* source code declaration of {@link Repository#value()} for a concrete example.
*
* @author Mark Fisher
* @author Sam Brannen
* @since 2.5
* @see Repository
* @see Service
@@ -47,7 +53,7 @@ public @interface Component {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* to be turned into a Spring bean name in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
String value() default "";

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,9 +46,7 @@ import org.springframework.core.annotation.AliasFor;
public @interface Controller {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* Alias for {@link Component#value}.
*/
@AliasFor(annotation = Component.class)
String value() default "";

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,9 +43,8 @@ import org.springframework.core.annotation.AliasFor;
* to its role in the overall application architecture for the purpose of tooling,
* aspects, etc.
*
* <p>As of Spring 2.5, this annotation also serves as a specialization of
* {@link Component @Component}, allowing for implementation classes to be autodetected
* through classpath scanning.
* <p>This annotation also serves as a specialization of {@link Component @Component},
* allowing for implementation classes to be autodetected through classpath scanning.
*
* @author Rod Johnson
* @author Juergen Hoeller
@@ -62,9 +61,7 @@ import org.springframework.core.annotation.AliasFor;
public @interface Repository {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* Alias for {@link Component#value}.
*/
@AliasFor(annotation = Component.class)
String value() default "";

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,9 +48,7 @@ import org.springframework.core.annotation.AliasFor;
public @interface Service {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
* Alias for {@link Component#value}.
*/
@AliasFor(annotation = Component.class)
String value() default "";