Browse Source

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
pull/31129/head
Sam Brannen 1 year ago
parent
commit
bfd918d16c
  1. 38
      framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc
  2. 24
      spring-context/src/main/java/org/springframework/context/annotation/AnnotationBeanNameGenerator.java
  3. 30
      spring-context/src/main/java/org/springframework/stereotype/Component.java
  4. 4
      spring-context/src/test/java/example/profilescan/DevComponent.java
  5. 4
      spring-context/src/test/java/example/scannable/CustomStereotype.java
  6. 7
      spring-context/src/test/java/org/springframework/context/annotation/AnnotationBeanNameGeneratorTests.java
  7. 8
      spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationMetaAnnotationTests.java

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

@ -27,7 +27,7 @@ use these features. @@ -27,7 +27,7 @@ use these features.
== `@Component` and Further Stereotype Annotations
The `@Repository` annotation is a marker for any class that fulfills the role or
stereotype of a repository (also known as Data Access Object or DAO). Among the uses
_stereotype_ of a repository (also known as Data Access Object or DAO). Among the uses
of this marker is the automatic translation of exceptions, as described in
xref:data-access/orm/general.adoc#orm-exception-translation[Exception Translation].
@ -39,7 +39,7 @@ layers, respectively). Therefore, you can annotate your component classes with @@ -39,7 +39,7 @@ layers, respectively). Therefore, you can annotate your component classes with
`@Component`, but, by annotating them with `@Repository`, `@Service`, or `@Controller`
instead, your classes are more properly suited for processing by tools or associating
with aspects. For example, these stereotype annotations make ideal targets for
pointcuts. `@Repository`, `@Service`, and `@Controller` can also
pointcuts. `@Repository`, `@Service`, and `@Controller` may also
carry additional semantics in future releases of the Spring Framework. Thus, if you are
choosing between using `@Component` or `@Service` for your service layer, `@Service` is
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 @@ -664,24 +664,36 @@ analogous to how the container selects between multiple `@Autowired` constructor
== Naming Autodetected Components
When a component is autodetected as part of the scanning process, its bean name is
generated by the `BeanNameGenerator` strategy known to that scanner. By default, any
Spring stereotype annotation (`@Component`, `@Repository`, `@Service`, `@Controller`,
`@Configuration`, and so forth) that contains a non-empty `value` attribute provides that
value as the name to the corresponding bean definition.
generated by the `BeanNameGenerator` strategy known to that scanner.
By default, the `AnnotationBeanNameGenerator` is used. For Spring
xref:core/beans/classpath-scanning.adoc#beans-stereotype-annotations[stereotype annotations],
if you supply a name via the the annotation's `value` attribute that name will be used as
the name in the corresponding bean definition. This convention also applies when the
following JSR-250 and JSR-330 annotations are used instead of Spring stereotype
annotations: `@jakarta.annotation.ManagedBean`, `@javax.annotation.ManagedBean`,
`@jakarta.inject.Named`, and `@javax.inject.Named`.
[NOTE]
====
As of Spring Framework 6.1, the name of the annotation attribute that is used to specify
the bean name is no longer required to be `value`. Custom stereotype annotations can
declare an attribute with a different name (such as `name`) and annotate that attribute
with `@AliasFor(annotation = Component.class, attribute = "value")`. See the source code
declaration of `Repository#value()` and `ControllerAdvice#name()` for concrete examples.
declaration of `ControllerAdvice#name()` for a concrete example.
[WARNING]
====
As of Spring Framework 6.1, support for convention-based stereotype names is deprecated
and will be removed in a future version of the framework. Consequently, custom stereotype
annotations must use `@AliasFor` to declare an explicit alias for the `value` attribute
in `@Component`. See the source code declaration of `Repository#value()` and
`ControllerAdvice#name()` for concrete examples.
====
If such an annotation contains no name `value` or for any other detected component
(such as those discovered by custom filters), the default bean name generator returns
the uncapitalized non-qualified class name. For example, if the following component
classes were detected, the names would be `myMovieLister` and `movieFinderImpl`:
If an explicit bean name cannot be derived from such an annotation or for any other
detected component (such as those discovered by custom filters), the default bean name
generator returns the uncapitalized non-qualified class name. For example, if the
following component classes were detected, the names would be `myMovieLister` and
`movieFinderImpl`.
[tabs]
======

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

@ -22,6 +22,9 @@ import java.util.Map; @@ -22,6 +22,9 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@ -77,6 +80,18 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator { @@ -77,6 +80,18 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";
/**
* Set used to track which stereotype annotations have already been checked
* to see if they use a convention-based override for the {@code value}
* attribute in {@code @Component}.
* @since 6.1
* @see #determineBeanNameFromAnnotation(AnnotatedBeanDefinition)
*/
private static final Set<String> conventionBasedStereotypeCheckCache = ConcurrentHashMap.newKeySet();
private final Log logger = LogFactory.getLog(AnnotationBeanNameGenerator.class);
private final Map<String, Set<String>> metaAnnotationTypesCache = new ConcurrentHashMap<>();
@ -117,6 +132,15 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator { @@ -117,6 +132,15 @@ public class AnnotationBeanNameGenerator implements BeanNameGenerator {
if (isStereotypeWithNameValue(annotationType, metaAnnotationTypes, attributes)) {
Object value = attributes.get("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 (beanName != null && !currentName.equals(beanName)) {
throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
"component names: '" + beanName + "' versus '" + currentName + "'");

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

@ -23,19 +23,35 @@ import java.lang.annotation.RetentionPolicy; @@ -23,19 +23,35 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that an annotated class is a "component".
* Such classes are considered as candidates for auto-detection
* Indicates that the annotated class is a <em>component</em>.
*
* <p>Such classes are considered as candidates for auto-detection
* when using annotation-based configuration and classpath scanning.
*
* <p>A component may optionally specify a logical component name via the
* {@link #value value} attribute of this annotation.
*
* <p>Other class-level annotations may be considered as identifying
* 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.
* {@link org.aspectj.lang.annotation.Aspect @Aspect} annotation. Note, however,
* that the {@code @Aspect} annotation does not automatically make a class
* eligible for classpath scanning.
*
* <p>Any annotation meta-annotated with {@code @Component} is considered a
* <em>stereotype</em> annotation which makes the annotated class eligible for
* classpath scanning. For example, {@link Service @Service},
* {@link Controller @Controller}, and {@link Repository @Repository} are
* stereotype annotations. Stereotype annotations may also support configuration
* of a logical component name by overriding the {@link #value} attribute of this
* annotation via {@link org.springframework.core.annotation.AliasFor @AliasFor}.
*
* <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()} and
* <p>As of Spring Framework 6.1, support for configuring the name of a stereotype
* component by convention (i.e., via a {@code String value()} attribute without
* {@code @AliasFor}) is deprecated and will be removed in a future version of the
* framework. Consequently, custom stereotype annotations must use {@code @AliasFor}
* to declare an explicit alias for this annotation's {@link #value} attribute.
* See the source code declaration of {@link Repository#value()} and
* {@link org.springframework.web.bind.annotation.ControllerAdvice#name()
* ControllerAdvice.name()} for concrete examples.
*

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy; @@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
@Retention(RetentionPolicy.RUNTIME)
@ -32,6 +33,7 @@ public @interface DevComponent { @@ -32,6 +33,7 @@ public @interface DevComponent {
String PROFILE_NAME = "dev";
@AliasFor(annotation = Component.class)
String value() default "";
}

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

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 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.
@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy; @@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Scope;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Service;
/**
@ -33,6 +34,7 @@ import org.springframework.stereotype.Service; @@ -33,6 +34,7 @@ import org.springframework.stereotype.Service;
@Scope("prototype")
public @interface CustomStereotype {
@AliasFor(annotation = Service.class)
String value() default "thoreau";
}

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

@ -210,12 +210,16 @@ class AnnotationBeanNameGeneratorTests { @@ -210,12 +210,16 @@ class AnnotationBeanNameGeneratorTests {
@Retention(RetentionPolicy.RUNTIME)
@Component
@interface ConventionBasedComponent1 {
// This intentionally convention-based. Please do not add @AliasFor.
// See gh-31093.
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Component
@interface ConventionBasedComponent2 {
// This intentionally convention-based. Please do not add @AliasFor.
// See gh-31093.
String value() default "";
}
@ -256,7 +260,8 @@ class AnnotationBeanNameGeneratorTests { @@ -256,7 +260,8 @@ class AnnotationBeanNameGeneratorTests {
@Target(ElementType.TYPE)
@Controller
@interface TestRestController {
// This intentionally convention-based. Please do not add @AliasFor.
// See gh-31093.
String value() default "";
}

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

@ -1,5 +1,5 @@ @@ -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.
@ -25,6 +25,7 @@ import org.springframework.beans.testfixture.beans.TestBean; @@ -25,6 +25,7 @@ import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
import static org.assertj.core.api.Assertions.assertThat;
@ -62,9 +63,12 @@ class ConfigurationMetaAnnotationTests { @@ -62,9 +63,12 @@ class ConfigurationMetaAnnotationTests {
}
@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Configuration
@interface TestConfiguration {
@AliasFor(annotation = Configuration.class)
String value() default "";
}
}

Loading…
Cancel
Save