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:
@@ -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") ||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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 —
|
||||
* 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 "";
|
||||
|
||||
@@ -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 "";
|
||||
|
||||
@@ -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 "";
|
||||
|
||||
@@ -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 "";
|
||||
|
||||
Reference in New Issue
Block a user