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
This commit is contained in:
parent
2d377155ab
commit
bfd918d16c
|
@ -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
|
|||
`@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
|
|||
== 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]
|
||||
======
|
||||
|
|
|
@ -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 {
|
|||
|
||||
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 {
|
|||
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 + "'");
|
||||
|
|
|
@ -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 —
|
||||
* 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>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>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, 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.
|
||||
*
|
||||
|
|
|
@ -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;
|
|||
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 {
|
|||
|
||||
String PROFILE_NAME = "dev";
|
||||
|
||||
@AliasFor(annotation = Component.class)
|
||||
String value() default "";
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
|||
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;
|
|||
@Scope("prototype")
|
||||
public @interface CustomStereotype {
|
||||
|
||||
@AliasFor(annotation = Service.class)
|
||||
String value() default "thoreau";
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
@Target(ElementType.TYPE)
|
||||
@Controller
|
||||
@interface TestRestController {
|
||||
|
||||
// This intentionally convention-based. Please do not add @AliasFor.
|
||||
// See gh-31093.
|
||||
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.
|
||||
|
@ -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 {
|
|||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Configuration
|
||||
@interface TestConfiguration {
|
||||
|
||||
@AliasFor(annotation = Configuration.class)
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue