Skip to content

Commit bfd918d

Browse files
committed
Deprecate convention-based @component stereotype names in favor of @AliasFor
When use of the deprecated feature is detected, a WARNING log message will be generated analogous to the following. WARN o.s.c.a.AnnotationBeanNameGenerator - 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 @org.springframework.context.annotation.AnnotationBeanNameGeneratorTests$ConventionBasedComponent1 with @AliasFor(annotation=Component.class) to declare an explicit alias for @component's 'value' attribute. See gh-31089 Closes gh-31093
1 parent 2d37715 commit bfd918d

File tree

7 files changed

+90
-25
lines changed

7 files changed

+90
-25
lines changed

framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc

+25-13
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use these features.
2727
== `@Component` and Further Stereotype Annotations
2828

2929
The `@Repository` annotation is a marker for any class that fulfills the role or
30-
stereotype of a repository (also known as Data Access Object or DAO). Among the uses
30+
_stereotype_ of a repository (also known as Data Access Object or DAO). Among the uses
3131
of this marker is the automatic translation of exceptions, as described in
3232
xref:data-access/orm/general.adoc#orm-exception-translation[Exception Translation].
3333

@@ -39,7 +39,7 @@ layers, respectively). Therefore, you can annotate your component classes with
3939
`@Component`, but, by annotating them with `@Repository`, `@Service`, or `@Controller`
4040
instead, your classes are more properly suited for processing by tools or associating
4141
with aspects. For example, these stereotype annotations make ideal targets for
42-
pointcuts. `@Repository`, `@Service`, and `@Controller` can also
42+
pointcuts. `@Repository`, `@Service`, and `@Controller` may also
4343
carry additional semantics in future releases of the Spring Framework. Thus, if you are
4444
choosing between using `@Component` or `@Service` for your service layer, `@Service` is
4545
clearly the better choice. Similarly, as stated earlier, `@Repository` is already
@@ -664,24 +664,36 @@ analogous to how the container selects between multiple `@Autowired` constructor
664664
== Naming Autodetected Components
665665

666666
When a component is autodetected as part of the scanning process, its bean name is
667-
generated by the `BeanNameGenerator` strategy known to that scanner. By default, any
668-
Spring stereotype annotation (`@Component`, `@Repository`, `@Service`, `@Controller`,
669-
`@Configuration`, and so forth) that contains a non-empty `value` attribute provides that
670-
value as the name to the corresponding bean definition.
667+
generated by the `BeanNameGenerator` strategy known to that scanner.
668+
669+
By default, the `AnnotationBeanNameGenerator` is used. For Spring
670+
xref:core/beans/classpath-scanning.adoc#beans-stereotype-annotations[stereotype annotations],
671+
if you supply a name via the the annotation's `value` attribute that name will be used as
672+
the name in the corresponding bean definition. This convention also applies when the
673+
following JSR-250 and JSR-330 annotations are used instead of Spring stereotype
674+
annotations: `@jakarta.annotation.ManagedBean`, `@javax.annotation.ManagedBean`,
675+
`@jakarta.inject.Named`, and `@javax.inject.Named`.
671676

672-
[NOTE]
673-
====
674677
As of Spring Framework 6.1, the name of the annotation attribute that is used to specify
675678
the bean name is no longer required to be `value`. Custom stereotype annotations can
676679
declare an attribute with a different name (such as `name`) and annotate that attribute
677680
with `@AliasFor(annotation = Component.class, attribute = "value")`. See the source code
678-
declaration of `Repository#value()` and `ControllerAdvice#name()` for concrete examples.
681+
declaration of `ControllerAdvice#name()` for a concrete example.
682+
683+
[WARNING]
684+
====
685+
As of Spring Framework 6.1, support for convention-based stereotype names is deprecated
686+
and will be removed in a future version of the framework. Consequently, custom stereotype
687+
annotations must use `@AliasFor` to declare an explicit alias for the `value` attribute
688+
in `@Component`. See the source code declaration of `Repository#value()` and
689+
`ControllerAdvice#name()` for concrete examples.
679690
====
680691

681-
If such an annotation contains no name `value` or for any other detected component
682-
(such as those discovered by custom filters), the default bean name generator returns
683-
the uncapitalized non-qualified class name. For example, if the following component
684-
classes were detected, the names would be `myMovieLister` and `movieFinderImpl`:
692+
If an explicit bean name cannot be derived from such an annotation or for any other
693+
detected component (such as those discovered by custom filters), the default bean name
694+
generator returns the uncapitalized non-qualified class name. For example, if the
695+
following component classes were detected, the names would be `myMovieLister` and
696+
`movieFinderImpl`.
685697

686698
[tabs]
687699
======

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

+24
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
import java.util.Set;
2323
import java.util.concurrent.ConcurrentHashMap;
2424

25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
27+
2528
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
2629
import org.springframework.beans.factory.config.BeanDefinition;
2730
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -77,6 +80,18 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
7780

7881
private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";
7982

83+
/**
84+
* Set used to track which stereotype annotations have already been checked
85+
* to see if they use a convention-based override for the {@code value}
86+
* attribute in {@code @Component}.
87+
* @since 6.1
88+
* @see #determineBeanNameFromAnnotation(AnnotatedBeanDefinition)
89+
*/
90+
private static final Set<String> conventionBasedStereotypeCheckCache = ConcurrentHashMap.newKeySet();
91+
92+
93+
private final Log logger = LogFactory.getLog(AnnotationBeanNameGenerator.class);
94+
8095
private final Map<String, Set<String>> metaAnnotationTypesCache = new ConcurrentHashMap<>();
8196

8297

@@ -117,6 +132,15 @@ protected String determineBeanNameFromAnnotation(AnnotatedBeanDefinition annotat
117132
if (isStereotypeWithNameValue(annotationType, metaAnnotationTypes, attributes)) {
118133
Object value = attributes.get("value");
119134
if (value instanceof String currentName && !currentName.isBlank()) {
135+
if (conventionBasedStereotypeCheckCache.add(annotationType) &&
136+
metaAnnotationTypes.contains(COMPONENT_ANNOTATION_CLASSNAME) && logger.isWarnEnabled()) {
137+
logger.warn("""
138+
Support for convention-based stereotype names is deprecated and will \
139+
be removed in a future version of the framework. Please annotate the \
140+
'value' attribute in @%s with @AliasFor(annotation=Component.class) \
141+
to declare an explicit alias for @Component's 'value' attribute."""
142+
.formatted(annotationType));
143+
}
120144
if (beanName != null && !currentName.equals(beanName)) {
121145
throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
122146
"component names: '" + beanName + "' versus '" + currentName + "'");

spring-context/src/main/java/org/springframework/stereotype/Component.java

+23-7
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,35 @@
2323
import java.lang.annotation.Target;
2424

2525
/**
26-
* Indicates that an annotated class is a "component".
27-
* Such classes are considered as candidates for auto-detection
26+
* Indicates that the annotated class is a <em>component</em>.
27+
*
28+
* <p>Such classes are considered as candidates for auto-detection
2829
* when using annotation-based configuration and classpath scanning.
2930
*
31+
* <p>A component may optionally specify a logical component name via the
32+
* {@link #value value} attribute of this annotation.
33+
*
3034
* <p>Other class-level annotations may be considered as identifying
3135
* a component as well, typically a special kind of component &mdash;
3236
* for example, the {@link Repository @Repository} annotation or AspectJ's
33-
* {@link org.aspectj.lang.annotation.Aspect @Aspect} annotation.
37+
* {@link org.aspectj.lang.annotation.Aspect @Aspect} annotation. Note, however,
38+
* that the {@code @Aspect} annotation does not automatically make a class
39+
* eligible for classpath scanning.
40+
*
41+
* <p>Any annotation meta-annotated with {@code @Component} is considered a
42+
* <em>stereotype</em> annotation which makes the annotated class eligible for
43+
* classpath scanning. For example, {@link Service @Service},
44+
* {@link Controller @Controller}, and {@link Repository @Repository} are
45+
* stereotype annotations. Stereotype annotations may also support configuration
46+
* of a logical component name by overriding the {@link #value} attribute of this
47+
* annotation via {@link org.springframework.core.annotation.AliasFor @AliasFor}.
3448
*
35-
* <p>As of Spring Framework 6.1, custom component stereotype annotations should
36-
* use {@link org.springframework.core.annotation.AliasFor @AliasFor} to declare
37-
* an explicit alias for this annotation's {@link #value} attribute. See the
38-
* source code declaration of {@link Repository#value()} and
49+
* <p>As of Spring Framework 6.1, support for configuring the name of a stereotype
50+
* component by convention (i.e., via a {@code String value()} attribute without
51+
* {@code @AliasFor}) is deprecated and will be removed in a future version of the
52+
* framework. Consequently, custom stereotype annotations must use {@code @AliasFor}
53+
* to declare an explicit alias for this annotation's {@link #value} attribute.
54+
* See the source code declaration of {@link Repository#value()} and
3955
* {@link org.springframework.web.bind.annotation.ControllerAdvice#name()
4056
* ControllerAdvice.name()} for concrete examples.
4157
*

spring-context/src/test/java/example/profilescan/DevComponent.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -22,6 +22,7 @@
2222
import java.lang.annotation.Target;
2323

2424
import org.springframework.context.annotation.Profile;
25+
import org.springframework.core.annotation.AliasFor;
2526
import org.springframework.stereotype.Component;
2627

2728
@Retention(RetentionPolicy.RUNTIME)
@@ -32,6 +33,7 @@
3233

3334
String PROFILE_NAME = "dev";
3435

36+
@AliasFor(annotation = Component.class)
3537
String value() default "";
3638

3739
}

spring-context/src/test/java/example/scannable/CustomStereotype.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -22,6 +22,7 @@
2222
import java.lang.annotation.Target;
2323

2424
import org.springframework.context.annotation.Scope;
25+
import org.springframework.core.annotation.AliasFor;
2526
import org.springframework.stereotype.Service;
2627

2728
/**
@@ -33,6 +34,7 @@
3334
@Scope("prototype")
3435
public @interface CustomStereotype {
3536

37+
@AliasFor(annotation = Service.class)
3638
String value() default "thoreau";
3739

3840
}

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,16 @@ static class ComponentWithMultipleConflictingNames {
210210
@Retention(RetentionPolicy.RUNTIME)
211211
@Component
212212
@interface ConventionBasedComponent1 {
213+
// This intentionally convention-based. Please do not add @AliasFor.
214+
// See gh-31093.
213215
String value() default "";
214216
}
215217

216218
@Retention(RetentionPolicy.RUNTIME)
217219
@Component
218220
@interface ConventionBasedComponent2 {
221+
// This intentionally convention-based. Please do not add @AliasFor.
222+
// See gh-31093.
219223
String value() default "";
220224
}
221225

@@ -256,7 +260,8 @@ private static class ComponentFromNonStringMeta {
256260
@Target(ElementType.TYPE)
257261
@Controller
258262
@interface TestRestController {
259-
263+
// This intentionally convention-based. Please do not add @AliasFor.
264+
// See gh-31093.
260265
String value() default "";
261266
}
262267

spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationMetaAnnotationTests.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -25,6 +25,7 @@
2525
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2626
import org.springframework.context.annotation.Bean;
2727
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.core.annotation.AliasFor;
2829

2930
import static org.assertj.core.api.Assertions.assertThat;
3031

@@ -62,9 +63,12 @@ TestBean b() {
6263
}
6364

6465

65-
@Configuration
6666
@Retention(RetentionPolicy.RUNTIME)
67+
@Configuration
6768
@interface TestConfiguration {
69+
70+
@AliasFor(annotation = Configuration.class)
6871
String value() default "";
6972
}
73+
7074
}

0 commit comments

Comments
 (0)