Skip to content

Commit b78d371

Browse files
committed
Merge branch '6.2.x'
2 parents b0a1a11 + 17a94fb commit b78d371

File tree

2 files changed

+106
-13
lines changed

2 files changed

+106
-13
lines changed

spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java

+31-8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.context.annotation;
1818

1919
import java.lang.annotation.Annotation;
20+
import java.lang.reflect.Method;
2021
import java.util.Collections;
2122
import java.util.HashSet;
2223
import java.util.LinkedHashSet;
@@ -34,13 +35,15 @@
3435
import org.springframework.beans.factory.config.BeanDefinition;
3536
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3637
import org.springframework.beans.factory.support.BeanNameGenerator;
38+
import org.springframework.core.annotation.AliasFor;
3739
import org.springframework.core.annotation.AnnotationAttributes;
3840
import org.springframework.core.annotation.MergedAnnotation;
3941
import org.springframework.core.annotation.MergedAnnotation.Adapt;
4042
import org.springframework.core.annotation.MergedAnnotations;
4143
import org.springframework.core.type.AnnotationMetadata;
4244
import org.springframework.util.Assert;
4345
import org.springframework.util.ClassUtils;
46+
import org.springframework.util.ReflectionUtils;
4447
import org.springframework.util.StringUtils;
4548

4649
/**
@@ -143,16 +146,26 @@ public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry
143146
Set<String> metaAnnotationTypes = this.metaAnnotationTypesCache.computeIfAbsent(annotationType,
144147
key -> getMetaAnnotationTypes(mergedAnnotation));
145148
if (isStereotypeWithNameValue(annotationType, metaAnnotationTypes, attributes)) {
146-
Object value = attributes.get("value");
149+
Object value = attributes.get(MergedAnnotation.VALUE);
147150
if (value instanceof String currentName && !currentName.isBlank()) {
148151
if (conventionBasedStereotypeCheckCache.add(annotationType) &&
149152
metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) && logger.isWarnEnabled()) {
150-
logger.warn("""
151-
Support for convention-based stereotype names is deprecated and will \
152-
be removed in a future version of the framework. Please annotate the \
153-
'value' attribute in @%s with @AliasFor(annotation=Component.class) \
154-
to declare an explicit alias for @Component's 'value' attribute."""
155-
.formatted(annotationType));
153+
if (hasExplicitlyAliasedValueAttribute(mergedAnnotation.getType())) {
154+
logger.warn("""
155+
Although the 'value' attribute in @%s declares @AliasFor for an attribute \
156+
other than @Component's 'value' attribute, the value is still used as the \
157+
@Component name based on convention. As of Spring Framework 7.0, such a \
158+
'value' attribute will no longer be used as the @Component name."""
159+
.formatted(annotationType));
160+
}
161+
else {
162+
logger.warn("""
163+
Support for convention-based @Component names is deprecated and will \
164+
be removed in a future version of the framework. Please annotate the \
165+
'value' attribute in @%s with @AliasFor(annotation=Component.class) \
166+
to declare an explicit alias for @Component's 'value' attribute."""
167+
.formatted(annotationType));
168+
}
156169
}
157170
if (beanName != null && !currentName.equals(beanName)) {
158171
throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
@@ -216,7 +229,7 @@ protected boolean isStereotypeWithNameValue(String annotationType,
216229
boolean isStereotype = metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) ||
217230
annotationType.equals("jakarta.inject.Named");
218231

219-
return (isStereotype && attributes.containsKey("value"));
232+
return (isStereotype && attributes.containsKey(MergedAnnotation.VALUE));
220233
}
221234

222235
/**
@@ -247,4 +260,14 @@ protected String buildDefaultBeanName(BeanDefinition definition) {
247260
return StringUtils.uncapitalizeAsProperty(shortClassName);
248261
}
249262

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

spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java

+75-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -150,6 +150,25 @@ void generateBeanNameFromSubStereotypeAnnotationWithStringArrayValueAndExplicitC
150150
assertGeneratedName(RestControllerAdviceClass.class, "myRestControllerAdvice");
151151
}
152152

153+
@Test // gh-34317
154+
void generateBeanNameFromStereotypeAnnotationWithStringValueAsExplicitAliasForMetaAnnotationOtherThanComponent() {
155+
// As of Spring Framework 6.2, "enigma" is incorrectly used as the @Component name.
156+
// As of Spring Framework 7.0, the generated name will be "annotationBeanNameGeneratorTests.StereotypeWithoutExplicitName".
157+
assertGeneratedName(StereotypeWithoutExplicitName.class, "enigma");
158+
}
159+
160+
@Test // gh-34317
161+
void generateBeanNameFromStereotypeAnnotationWithStringValueAndExplicitAliasForComponentNameWithBlankName() {
162+
// As of Spring Framework 6.2, "enigma" is incorrectly used as the @Component name.
163+
// As of Spring Framework 7.0, the generated name will be "annotationBeanNameGeneratorTests.StereotypeWithGeneratedName".
164+
assertGeneratedName(StereotypeWithGeneratedName.class, "enigma");
165+
}
166+
167+
@Test // gh-34317
168+
void generateBeanNameFromStereotypeAnnotationWithStringValueAndExplicitAliasForComponentName() {
169+
assertGeneratedName(StereotypeWithExplicitName.class, "explicitName");
170+
}
171+
153172

154173
private void assertGeneratedName(Class<?> clazz, String expectedName) {
155174
BeanDefinition bd = annotatedBeanDef(clazz);
@@ -192,15 +211,15 @@ static class ComponentWithMultipleConflictingNames {
192211
@Retention(RetentionPolicy.RUNTIME)
193212
@Component
194213
@interface ConventionBasedComponent1 {
195-
// This intentionally convention-based. Please do not add @AliasFor.
214+
// This is intentionally convention-based. Please do not add @AliasFor.
196215
// See gh-31093.
197216
String value() default "";
198217
}
199218

200219
@Retention(RetentionPolicy.RUNTIME)
201220
@Component
202221
@interface ConventionBasedComponent2 {
203-
// This intentionally convention-based. Please do not add @AliasFor.
222+
// This is intentionally convention-based. Please do not add @AliasFor.
204223
// See gh-31093.
205224
String value() default "";
206225
}
@@ -242,7 +261,7 @@ private static class ComponentFromNonStringMeta {
242261
@Target(ElementType.TYPE)
243262
@Controller
244263
@interface TestRestController {
245-
// This intentionally convention-based. Please do not add @AliasFor.
264+
// This is intentionally convention-based. Please do not add @AliasFor.
246265
// See gh-31093.
247266
String value() default "";
248267
}
@@ -301,7 +320,6 @@ static class ComposedControllerAnnotationWithStringValue {
301320
String[] basePackages() default {};
302321
}
303322

304-
305323
@TestControllerAdvice(basePackages = "com.example", name = "myControllerAdvice")
306324
static class ControllerAdviceClass {
307325
}
@@ -310,4 +328,56 @@ static class ControllerAdviceClass {
310328
static class RestControllerAdviceClass {
311329
}
312330

331+
@Retention(RetentionPolicy.RUNTIME)
332+
@Target(ElementType.ANNOTATION_TYPE)
333+
@interface MetaAnnotationWithStringAttribute {
334+
335+
String attribute() default "";
336+
}
337+
338+
/**
339+
* Custom stereotype annotation which has a {@code String value} attribute that
340+
* is explicitly declared as an alias for an attribute in a meta-annotation
341+
* other than {@link Component @Component}.
342+
*/
343+
@Retention(RetentionPolicy.RUNTIME)
344+
@Target(ElementType.TYPE)
345+
@Component
346+
@MetaAnnotationWithStringAttribute
347+
@interface MyStereotype {
348+
349+
@AliasFor(annotation = MetaAnnotationWithStringAttribute.class, attribute = "attribute")
350+
String value() default "";
351+
}
352+
353+
@MyStereotype("enigma")
354+
static class StereotypeWithoutExplicitName {
355+
}
356+
357+
/**
358+
* Custom stereotype annotation which is identical to {@link MyStereotype @MyStereotype}
359+
* except that it has a {@link #name} attribute that is an explicit alias for
360+
* {@link Component#value}.
361+
*/
362+
@Retention(RetentionPolicy.RUNTIME)
363+
@Target(ElementType.TYPE)
364+
@Component
365+
@MetaAnnotationWithStringAttribute
366+
@interface MyNamedStereotype {
367+
368+
@AliasFor(annotation = MetaAnnotationWithStringAttribute.class, attribute = "attribute")
369+
String value() default "";
370+
371+
@AliasFor(annotation = Component.class, attribute = "value")
372+
String name() default "";
373+
}
374+
375+
@MyNamedStereotype(value = "enigma", name ="explicitName")
376+
static class StereotypeWithExplicitName {
377+
}
378+
379+
@MyNamedStereotype(value = "enigma")
380+
static class StereotypeWithGeneratedName {
381+
}
382+
313383
}

0 commit comments

Comments
 (0)