提交 bfd918d1 编写于 作者: S Sam Brannen

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
上级 2d377155
...@@ -27,7 +27,7 @@ use these features. ...@@ -27,7 +27,7 @@ use these features.
== `@Component` and Further Stereotype Annotations == `@Component` and Further Stereotype Annotations
The `@Repository` annotation is a marker for any class that fulfills the role or 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 of this marker is the automatic translation of exceptions, as described in
xref:data-access/orm/general.adoc#orm-exception-translation[Exception Translation]. 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` `@Component`, but, by annotating them with `@Repository`, `@Service`, or `@Controller`
instead, your classes are more properly suited for processing by tools or associating instead, your classes are more properly suited for processing by tools or associating
with aspects. For example, these stereotype annotations make ideal targets for 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 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 choosing between using `@Component` or `@Service` for your service layer, `@Service` is
clearly the better choice. Similarly, as stated earlier, `@Repository` is already 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 == Naming Autodetected Components
When a component is autodetected as part of the scanning process, its bean name is 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 generated by the `BeanNameGenerator` strategy known to that scanner.
Spring stereotype annotation (`@Component`, `@Repository`, `@Service`, `@Controller`,
`@Configuration`, and so forth) that contains a non-empty `value` attribute provides that By default, the `AnnotationBeanNameGenerator` is used. For Spring
value as the name to the corresponding bean definition. 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 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 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 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 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 If an explicit bean name cannot be derived from such an annotation or for any other
(such as those discovered by custom filters), the default bean name generator returns detected component (such as those discovered by custom filters), the default bean name
the uncapitalized non-qualified class name. For example, if the following component generator returns the uncapitalized non-qualified class name. For example, if the
classes were detected, the names would be `myMovieLister` and `movieFinderImpl`: following component classes were detected, the names would be `myMovieLister` and
`movieFinderImpl`.
[tabs] [tabs]
====== ======
......
...@@ -22,6 +22,9 @@ import java.util.Map; ...@@ -22,6 +22,9 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; 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.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; 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"; 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<>(); 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)) { if (isStereotypeWithNameValue(annotationType, metaAnnotationTypes, attributes)) {
Object value = attributes.get("value"); Object value = attributes.get("value");
if (value instanceof String currentName && !currentName.isBlank()) { 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)) { if (beanName != null && !currentName.equals(beanName)) {
throw new IllegalStateException("Stereotype annotations suggest inconsistent " + throw new IllegalStateException("Stereotype annotations suggest inconsistent " +
"component names: '" + beanName + "' versus '" + currentName + "'"); "component names: '" + beanName + "' versus '" + currentName + "'");
......
...@@ -23,19 +23,35 @@ import java.lang.annotation.RetentionPolicy; ...@@ -23,19 +23,35 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Indicates that an annotated class is a "component". * Indicates that the annotated class is a <em>component</em>.
* Such classes are considered as candidates for auto-detection *
* <p>Such classes are considered as candidates for auto-detection
* when using annotation-based configuration and classpath scanning. * 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 * <p>Other class-level annotations may be considered as identifying
* a component as well, typically a special kind of component &mdash; * a component as well, typically a special kind of component &mdash;
* for example, the {@link Repository @Repository} annotation or AspectJ's * 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 * <p>As of Spring Framework 6.1, support for configuring the name of a stereotype
* use {@link org.springframework.core.annotation.AliasFor @AliasFor} to declare * component by convention (i.e., via a {@code String value()} attribute without
* an explicit alias for this annotation's {@link #value} attribute. See the * {@code @AliasFor}) is deprecated and will be removed in a future version of the
* source code declaration of {@link Repository#value()} and * 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() * {@link org.springframework.web.bind.annotation.ControllerAdvice#name()
* ControllerAdvice.name()} for concrete examples. * ControllerAdvice.name()} for concrete examples.
* *
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.lang.annotation.Target;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
...@@ -32,6 +33,7 @@ public @interface DevComponent { ...@@ -32,6 +33,7 @@ public @interface DevComponent {
String PROFILE_NAME = "dev"; String PROFILE_NAME = "dev";
@AliasFor(annotation = Component.class)
String value() default ""; String value() default "";
} }
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.lang.annotation.Target;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Service; 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") @Scope("prototype")
public @interface CustomStereotype { public @interface CustomStereotype {
@AliasFor(annotation = Service.class)
String value() default "thoreau"; String value() default "thoreau";
} }
...@@ -210,12 +210,16 @@ class AnnotationBeanNameGeneratorTests { ...@@ -210,12 +210,16 @@ class AnnotationBeanNameGeneratorTests {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Component @Component
@interface ConventionBasedComponent1 { @interface ConventionBasedComponent1 {
// This intentionally convention-based. Please do not add @AliasFor.
// See gh-31093.
String value() default ""; String value() default "";
} }
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Component @Component
@interface ConventionBasedComponent2 { @interface ConventionBasedComponent2 {
// This intentionally convention-based. Please do not add @AliasFor.
// See gh-31093.
String value() default ""; String value() default "";
} }
...@@ -256,7 +260,8 @@ class AnnotationBeanNameGeneratorTests { ...@@ -256,7 +260,8 @@ class AnnotationBeanNameGeneratorTests {
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Controller @Controller
@interface TestRestController { @interface TestRestController {
// This intentionally convention-based. Please do not add @AliasFor.
// See gh-31093.
String value() default ""; String value() default "";
} }
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -62,9 +63,12 @@ class ConfigurationMetaAnnotationTests { ...@@ -62,9 +63,12 @@ class ConfigurationMetaAnnotationTests {
} }
@Configuration
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Configuration
@interface TestConfiguration { @interface TestConfiguration {
@AliasFor(annotation = Configuration.class)
String value() default ""; String value() default "";
} }
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册