Skip to content

Commit

Permalink
Improve warning for unexpected use of value attribute as @⁠Component …
Browse files Browse the repository at this point in the history
…name

Prior to this commit, if a String 'value' attribute of an annotation
was annotated with @⁠AliasFor and explicitly configured to alias an
attribute other than @⁠Component.value, the value was still used as the
@⁠Component name, but the warning message that was logged stated that
the 'value' attribute should be annotated with
@⁠AliasFor(annotation=Component.class). However, it is not possible to
annotate an annotation attribute twice with @⁠AliasFor.

To address that, this commit revises the logic in
AnnotationBeanNameGenerator so that it issues a log message similar to
the following in such scenarios.

WARN o.s.c.a.AnnotationBeanNameGenerator - Although the 'value'
attribute in @⁠example.MyStereotype declares @⁠AliasFor for an
attribute other than @⁠Component's 'value' attribute, the value is
still used as the @⁠Component name based on convention. As of Spring
Framework 7.0, such a 'value' attribute will no longer be used as the
@⁠Component name.

See gh-34346
Closes gh-34317

(cherry picked from commit 17a94fb)
  • Loading branch information
sbrannen committed Feb 10, 2025
1 parent a5b9e66 commit a68b728
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2025 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.
Expand All @@ -17,6 +17,7 @@
package org.springframework.context.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
Expand All @@ -33,6 +34,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.AliasFor;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotation.Adapt;
Expand All @@ -41,6 +43,7 @@
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

/**
Expand Down Expand Up @@ -147,16 +150,26 @@ protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotat
Set<String> metaAnnotationTypes = this.metaAnnotationTypesCache.computeIfAbsent(annotationType,
key -> getMetaAnnotationTypes(mergedAnnotation));
if (isStereotypeWithNameValue(annotationType, metaAnnotationTypes, attributes)) {
Object value = attributes.get("value");
Object value = attributes.get(MergedAnnotation.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));
if (hasExplicitlyAliasedValueAttribute(mergedAnnotation.getType())) {
logger.warn("""
Although the 'value' attribute in @%s declares @AliasFor for an attribute \
other than @Component's 'value' attribute, the value is still used as the \
@Component name based on convention. As of Spring Framework 7.0, such a \
'value' attribute will no longer be used as the @Component name."""
.formatted(annotationType));
}
else {
logger.warn("""
Support for convention-based @Component 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 " +
Expand Down Expand Up @@ -224,7 +237,7 @@ protected boolean isStereotypeWithNameValue(String annotationType,
annotationType.equals("jakarta.inject.Named") ||
annotationType.equals("javax.inject.Named");

return (isStereotype && attributes.containsKey("value"));
return (isStereotype && attributes.containsKey(MergedAnnotation.VALUE));
}

/**
Expand Down Expand Up @@ -255,4 +268,14 @@ protected String buildDefaultBeanName(BeanDefinition definition) {
return StringUtils.uncapitalizeAsProperty(shortClassName);
}

/**
* Determine if the supplied annotation type declares a {@code value()} attribute
* with an explicit alias configured via {@link AliasFor @AliasFor}.
* @since 6.2.3
*/
private static boolean hasExplicitlyAliasedValueAttribute(Class<? extends Annotation> annotationType) {
Method valueAttribute = ReflectionUtils.findMethod(annotationType, MergedAnnotation.VALUE);
return (valueAttribute != null && valueAttribute.isAnnotationPresent(AliasFor.class));
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
Expand Down Expand Up @@ -168,6 +168,25 @@ void generateBeanNameFromSubStereotypeAnnotationWithStringArrayValueAndExplicitC
assertGeneratedName(RestControllerAdviceClass.class, "myRestControllerAdvice");
}

@Test // gh-34317
void generateBeanNameFromStereotypeAnnotationWithStringValueAsExplicitAliasForMetaAnnotationOtherThanComponent() {
// As of Spring Framework 6.2, "enigma" is incorrectly used as the @Component name.
// As of Spring Framework 7.0, the generated name will be "annotationBeanNameGeneratorTests.StereotypeWithoutExplicitName".
assertGeneratedName(StereotypeWithoutExplicitName.class, "enigma");
}

@Test // gh-34317
void generateBeanNameFromStereotypeAnnotationWithStringValueAndExplicitAliasForComponentNameWithBlankName() {
// As of Spring Framework 6.2, "enigma" is incorrectly used as the @Component name.
// As of Spring Framework 7.0, the generated name will be "annotationBeanNameGeneratorTests.StereotypeWithGeneratedName".
assertGeneratedName(StereotypeWithGeneratedName.class, "enigma");
}

@Test // gh-34317
void generateBeanNameFromStereotypeAnnotationWithStringValueAndExplicitAliasForComponentName() {
assertGeneratedName(StereotypeWithExplicitName.class, "explicitName");
}


private void assertGeneratedName(Class<?> clazz, String expectedName) {
BeanDefinition bd = annotatedBeanDef(clazz);
Expand Down Expand Up @@ -319,7 +338,6 @@ static class ComposedControllerAnnotationWithStringValue {
String[] basePackages() default {};
}


@TestControllerAdvice(basePackages = "com.example", name = "myControllerAdvice")
static class ControllerAdviceClass {
}
Expand All @@ -328,4 +346,56 @@ static class ControllerAdviceClass {
static class RestControllerAdviceClass {
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
@interface MetaAnnotationWithStringAttribute {

String attribute() default "";
}

/**
* Custom stereotype annotation which has a {@code String value} attribute that
* is explicitly declared as an alias for an attribute in a meta-annotation
* other than {@link Component @Component}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component
@MetaAnnotationWithStringAttribute
@interface MyStereotype {

@AliasFor(annotation = MetaAnnotationWithStringAttribute.class, attribute = "attribute")
String value() default "";
}

@MyStereotype("enigma")
static class StereotypeWithoutExplicitName {
}

/**
* Custom stereotype annotation which is identical to {@link MyStereotype @MyStereotype}
* except that it has a {@link #name} attribute that is an explicit alias for
* {@link Component#value}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component
@MetaAnnotationWithStringAttribute
@interface MyNamedStereotype {

@AliasFor(annotation = MetaAnnotationWithStringAttribute.class, attribute = "attribute")
String value() default "";

@AliasFor(annotation = Component.class, attribute = "value")
String name() default "";
}

@MyNamedStereotype(value = "enigma", name ="explicitName")
static class StereotypeWithExplicitName {
}

@MyNamedStereotype(value = "enigma")
static class StereotypeWithGeneratedName {
}

}

0 comments on commit a68b728

Please sign in to comment.