From 04313f062ea8c1202f83ba58fc92e0ca436617f1 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:41:07 +0100 Subject: [PATCH] Improve documentation for FullyQualifiedConfigurationBeanNameGenerator This commit documents FullyQualifiedConfigurationBeanNameGenerator in related Javadoc and in the reference manual. See gh-33448 Closes gh-36455 --- .../pages/core/beans/classpath-scanning.adoc | 36 +++++++++++++------ .../core/beans/java/bean-annotation.adoc | 23 ++++++++---- .../factory/support/BeanNameGenerator.java | 1 + .../AnnotatedBeanDefinitionReader.java | 4 ++- .../AnnotationConfigApplicationContext.java | 10 ++++-- .../context/annotation/Bean.java | 20 +++++++---- .../ClassPathBeanDefinitionScanner.java | 15 ++++---- .../context/annotation/ComponentScan.java | 8 +++-- .../context/annotation/Configuration.java | 2 ++ .../ConfigurationClassPostProcessor.java | 14 +++++--- ...AnnotationConfigWebApplicationContext.java | 18 +++++++--- 11 files changed, 106 insertions(+), 45 deletions(-) diff --git a/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc b/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc index e4e54668448..4e80edb40f3 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/classpath-scanning.adoc @@ -556,18 +556,11 @@ Kotlin:: ====== If you do not want to rely on the default bean-naming strategy, you can provide a custom -bean-naming strategy. First, implement the -{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`] +bean-naming strategy. First, implement either the +{spring-framework-api}/beans/factory/support/BeanNameGenerator.html[`BeanNameGenerator`] or +{spring-framework-api}/context/annotation/ConfigurationBeanNameGenerator.html[`ConfigurationBeanNameGenerator`] interface, and be sure to include a default no-arg constructor. Then, provide the fully -qualified class name when configuring the scanner, as the following example annotation -and bean definition show. - -TIP: If you run into naming conflicts due to multiple autodetected components having the -same non-qualified class name (i.e., classes with identical names but residing in -different packages), you may need to configure a `BeanNameGenerator` that defaults to the -fully qualified class name for the generated bean name. The -`FullyQualifiedAnnotationBeanNameGenerator` located in package -`org.springframework.context.annotation` can be used for such purposes. +qualified class name when configuring the scanner, as the following examples show. [tabs] ====== @@ -602,6 +595,27 @@ Kotlin:: ---- +[TIP] +==== +If you run into naming conflicts due to multiple autodetected components having the same +non-qualified class name (for example, classes with identical names but residing in +different packages), you can configure a `BeanNameGenerator` that defaults to the +fully-qualified class name for the generated bean name. The +`FullyQualifiedAnnotationBeanNameGenerator` can be used for such purposes. + +As of Spring Framework 7.0, if you encounter naming conflicts among `@Bean` methods in +`@Configuration` classes, you can alternatively configure a +`ConfigurationBeanNameGenerator` that generates unique bean names for `@Bean` methods. +The `FullyQualifiedConfigurationBeanNameGenerator` can be used to generate +fully-qualified default bean names for `@Bean` methods without an explicit `name` +attribute — for example, `com.example.MyConfig.myBean` for an `@Bean` method named +`myBean()` declared in `@Configuration` class `com.example.MyConfig`. + +The `FullyQualifiedAnnotationBeanNameGenerator` and +`FullyQualifiedConfigurationBeanNameGenerator` both reside in the +`org.springframework.context.annotation` package. +==== + As a general rule, consider specifying the name with the annotation whenever other components may be making explicit references to it. On the other hand, the auto-generated names are adequate whenever the container is responsible for wiring. diff --git a/framework-docs/modules/ROOT/pages/core/beans/java/bean-annotation.adoc b/framework-docs/modules/ROOT/pages/core/beans/java/bean-annotation.adoc index e0811fc509b..1437b657f5a 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/java/bean-annotation.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/java/bean-annotation.adoc @@ -4,10 +4,10 @@ `@Bean` is a method-level annotation and a direct analog of the XML `` element. The annotation supports some of the attributes offered by ``, such as: +* xref:core/beans/definition.adoc#beans-beanname[name] * xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-initializingbean[init-method] * xref:core/beans/factory-nature.adoc#beans-factory-lifecycle-disposablebean[destroy-method] * xref:core/beans/dependencies/factory-autowire.adoc[autowiring] -* `name`. You can use the `@Bean` annotation in a `@Configuration`-annotated or in a `@Component`-annotated class. @@ -17,9 +17,10 @@ You can use the `@Bean` annotation in a `@Configuration`-annotated or in a == Declaring a Bean To declare a bean, you can annotate a method with the `@Bean` annotation. You use this -method to register a bean definition within an `ApplicationContext` of the type -specified as the method's return value. By default, the bean name is the same as -the method name. The following example shows a `@Bean` method declaration: +method to register a bean definition within an `ApplicationContext` of the type specified +by the method's return type. By default, the bean name is the same as the method name +(unless a different xref:#beans-java-customizing-bean-naming[bean name generator] is +configured). The following example shows a `@Bean` method declaration: [tabs] ====== @@ -126,7 +127,7 @@ Kotlin:: ---- ====== -However, this limits the visibility for advance type prediction to the specified +However, this limits the visibility for advanced type prediction to the specified interface type (`TransferService`). Then, with the full type (`TransferServiceImpl`) known to the container only once the affected singleton bean has been instantiated. Non-lazy singleton beans get instantiated according to their declaration order, @@ -473,8 +474,15 @@ Kotlin:: == Customizing Bean Naming By default, configuration classes use a `@Bean` method's name as the name of the -resulting bean. This functionality can be overridden, however, with the `name` attribute, -as the following example shows: +resulting bean. However, as of Spring Framework 7.0, you can change this default strategy +by configuring a custom +{spring-framework-api}/context/annotation/ConfigurationBeanNameGenerator.html[`ConfigurationBeanNameGenerator`] +when bootstrapping the context or configuring component scanning. For example, +{spring-framework-api}/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.html[`FullyQualifiedConfigurationBeanNameGenerator`] +can be used to generate fully-qualified default bean names for `@Bean` methods without an +explicit `name` attribute. For individual `@Bean` methods, the default or +generator-derived name can be overridden with the `name` attribute, as the following +example shows: [tabs] ====== @@ -505,6 +513,7 @@ Kotlin:: ---- ====== +NOTE: `@Bean("myThing")` is equivalent to `@Bean(name = "myThing")`. [[beans-java-bean-aliasing]] == Bean Aliasing diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanNameGenerator.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanNameGenerator.java index 3da9d393467..aea04016e6e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanNameGenerator.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/BeanNameGenerator.java @@ -23,6 +23,7 @@ import org.springframework.beans.factory.config.BeanDefinition; * * @author Juergen Hoeller * @since 2.0.3 + * @see org.springframework.context.annotation.ConfigurationBeanNameGenerator */ public interface BeanNameGenerator { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java index 180e96f7962..a053cf799e9 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java @@ -109,7 +109,9 @@ public class AnnotatedBeanDefinitionReader { /** * Set the {@code BeanNameGenerator} to use for detected bean classes. - *

The default is a {@link AnnotationBeanNameGenerator}. + *

The default is an {@link AnnotationBeanNameGenerator}. + * @see FullyQualifiedAnnotationBeanNameGenerator + * @see FullyQualifiedConfigurationBeanNameGenerator */ public void setBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) { this.beanNameGenerator = diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java index 8bdf03e34fa..26b09c3b051 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java @@ -118,14 +118,20 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex /** * Provide a custom {@link BeanNameGenerator} for use with {@link AnnotatedBeanDefinitionReader} - * and/or {@link ClassPathBeanDefinitionScanner}, if any. - *

Default is {@link AnnotationBeanNameGenerator}. + * and/or {@link ClassPathBeanDefinitionScanner}. + *

Default is {@code AnnotationBeanNameGenerator}. + *

When processing {@link Configuration @Configuration} classes, a + * {@link ConfigurationBeanNameGenerator} (such as + * {@link FullyQualifiedConfigurationBeanNameGenerator}) also determines the + * default names for {@link Bean @Bean} methods without an explicit {@code name} + * attribute. *

Any call to this method must occur prior to calls to {@link #register(Class...)} * and/or {@link #scan(String...)}. * @see AnnotatedBeanDefinitionReader#setBeanNameGenerator * @see ClassPathBeanDefinitionScanner#setBeanNameGenerator * @see AnnotationBeanNameGenerator * @see FullyQualifiedAnnotationBeanNameGenerator + * @see FullyQualifiedConfigurationBeanNameGenerator */ public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { this.reader.setBeanNameGenerator(beanNameGenerator); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Bean.java b/spring-context/src/main/java/org/springframework/context/annotation/Bean.java index f32df085292..22651d14b04 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Bean.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Bean.java @@ -45,10 +45,12 @@ import org.springframework.core.annotation.AliasFor; * *

While a {@link #name} attribute is available, the default strategy for * determining the name of a bean is to use the name of the {@code @Bean} method. - * This is convenient and intuitive, but if explicit naming is desired, the - * {@code name} attribute (or its alias {@code value}) may be used. Also note - * that {@code name} accepts an array of Strings, allowing for multiple names - * (i.e. a primary bean name plus one or more aliases) for a single bean. + * This default can be overridden by configuring a {@link ConfigurationBeanNameGenerator} + * — for example, {@link FullyQualifiedConfigurationBeanNameGenerator} for + * fully-qualified names. If explicit naming is desired for an individual bean, the + * {@code name} attribute (or its alias {@link #value}) may be used. Also note that + * {@code name} accepts an array of Strings, allowing for multiple names (i.e., a + * primary bean name plus one or more aliases) for a single bean. * *

  * @Bean({"b1", "b2"}) // bean available as 'b1' and 'b2', but not 'myBean'
@@ -237,6 +239,9 @@ import org.springframework.core.annotation.AliasFor;
  * @see org.springframework.stereotype.Component
  * @see org.springframework.beans.factory.annotation.Autowired
  * @see org.springframework.beans.factory.annotation.Value
+ * @see FullyQualifiedConfigurationBeanNameGenerator
+ * @see AnnotationConfigApplicationContext#setBeanNameGenerator
+ * @see ComponentScan#nameGenerator()
  */
 @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
 @Retention(RetentionPolicy.RUNTIME)
@@ -255,10 +260,11 @@ public @interface Bean {
 
 	/**
 	 * The name of this bean, or if several names, a primary bean name plus aliases.
-	 * 

If left unspecified, the name of the bean is the name of the annotated method. - * If specified, the method name is ignored. + *

See the "Bean Names" section in the {@linkplain Bean class-level documentation} + * for details on how the bean name is determined if this attribute is left + * unspecified. *

The bean name and aliases may also be configured via the {@link #value} - * attribute if no other attributes are declared. + * attribute. * @see #value */ @AliasFor("value") diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java index 7c53ba9eba1..bd81ddf8988 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ClassPathBeanDefinitionScanner.java @@ -43,11 +43,11 @@ import org.springframework.util.PatternMatchUtils; * or {@code ApplicationContext}). * *

Candidate classes are detected through configurable type filters. The - * default filters include classes that are annotated with Spring's - * {@link org.springframework.stereotype.Component @Component}, + * default filters include classes that are annotated or meta-annotated with Spring's + * {@link org.springframework.stereotype.Component @Component} annotation, such as the * {@link org.springframework.stereotype.Repository @Repository}, - * {@link org.springframework.stereotype.Service @Service}, or - * {@link org.springframework.stereotype.Controller @Controller} stereotype. + * {@link org.springframework.stereotype.Service @Service}, and + * {@link org.springframework.stereotype.Controller @Controller} stereotypes. * *

Also supports JSR-330's {@link jakarta.inject.Named} annotations, if available. * @@ -204,8 +204,11 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo } /** - * Set the BeanNameGenerator to use for detected bean classes. - *

Default is a {@link AnnotationBeanNameGenerator}. + * Set the {@link BeanNameGenerator} to use for detected bean classes. + *

Default is an {@code AnnotationBeanNameGenerator}. + * @see AnnotationBeanNameGenerator + * @see FullyQualifiedAnnotationBeanNameGenerator + * @see FullyQualifiedConfigurationBeanNameGenerator */ public void setBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) { this.beanNameGenerator = diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java index 621fd708e8b..857d2b5fe8f 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan.java @@ -109,14 +109,18 @@ public @interface ComponentScan { /** * The {@link BeanNameGenerator} class to be used for naming detected components * within the Spring container. - *

The default value of the {@link BeanNameGenerator} interface itself indicates + *

The default value of the {@code BeanNameGenerator} interface itself indicates * that the scanner used to process this {@code @ComponentScan} annotation should * use its inherited bean name generator, for example, the default * {@link AnnotationBeanNameGenerator} or any custom instance supplied to the - * application context at bootstrap time. + * application context at bootstrap time. If a {@link ConfigurationBeanNameGenerator} + * is used (such as {@link FullyQualifiedConfigurationBeanNameGenerator}), it + * also affects the default names for {@link Bean @Bean} methods in + * {@link Configuration @Configuration} classes. * @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator) * @see AnnotationBeanNameGenerator * @see FullyQualifiedAnnotationBeanNameGenerator + * @see FullyQualifiedConfigurationBeanNameGenerator */ Class nameGenerator() default BeanNameGenerator.class; diff --git a/spring-context/src/main/java/org/springframework/context/annotation/Configuration.java b/spring-context/src/main/java/org/springframework/context/annotation/Configuration.java index 3733e2dfebe..95d683ca359 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/Configuration.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/Configuration.java @@ -437,6 +437,8 @@ public @interface Configuration { *

Alias for {@link Component#value}. * @return the explicit component name, if any (or empty String otherwise) * @see AnnotationBeanNameGenerator + * @see FullyQualifiedAnnotationBeanNameGenerator + * @see FullyQualifiedConfigurationBeanNameGenerator */ @AliasFor(annotation = Component.class) String value() default ""; diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 96a75f5f9a7..91e383489ed 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -242,12 +242,15 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo /** * Set the {@link BeanNameGenerator} to be used when triggering component scanning - * from {@link Configuration} classes and when registering {@link Import}'ed - * configuration classes. The default is a standard {@link AnnotationBeanNameGenerator} - * for scanned components (compatible with the default in {@link ClassPathBeanDefinitionScanner}) + * from {@link Configuration @Configuration} classes and when registering + * {@link Import @Import}'ed configuration classes. + *

The default is a standard {@link AnnotationBeanNameGenerator} for scanned + * components (compatible with the default in {@link ClassPathBeanDefinitionScanner}) * and a variant thereof for imported configuration classes (using unique fully-qualified * class names instead of standard component overriding). - *

Note that this strategy does not apply to {@link Bean} methods. + *

If the supplied bean name generator is a {@link ConfigurationBeanNameGenerator} + * (such as {@link FullyQualifiedConfigurationBeanNameGenerator}), it also affects the + * default names for {@link Bean @Bean} methods in configuration classes. *

This setter is typically only appropriate when configuring the post-processor as a * standalone bean definition in XML, for example, not using the dedicated {@code AnnotationConfig*} * application contexts or the {@code } element. Any bean name @@ -255,6 +258,9 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo * @since 3.1.1 * @see AnnotationConfigApplicationContext#setBeanNameGenerator(BeanNameGenerator) * @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR + * @see AnnotationBeanNameGenerator + * @see FullyQualifiedAnnotationBeanNameGenerator + * @see FullyQualifiedConfigurationBeanNameGenerator */ public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) { Assert.notNull(beanNameGenerator, "BeanNameGenerator must not be null"); diff --git a/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java index 837fdbd2688..4479df879f0 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java @@ -56,10 +56,8 @@ import org.springframework.web.context.ContextLoader; * {@code registerBean(...)} methods available in a {@code GenericApplicationContext}. * If you wish to register annotated component classes with a * {@code GenericApplicationContext} in a web environment, you may use a - * {@code GenericWebApplicationContext} with an - * {@link org.springframework.context.annotation.AnnotatedBeanDefinitionReader - * AnnotatedBeanDefinitionReader}. See the Javadoc for {@link GenericWebApplicationContext} - * for details and an example. + * {@code GenericWebApplicationContext} with an {@link AnnotatedBeanDefinitionReader}. + * See the Javadoc for {@link GenericWebApplicationContext} for details and an example. * *

To make use of this application context, the * {@linkplain ContextLoader#CONTEXT_CLASS_PARAM "contextClass"} context-param for @@ -116,9 +114,19 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe /** * Set a custom {@link BeanNameGenerator} for use with {@link AnnotatedBeanDefinitionReader} * and/or {@link ClassPathBeanDefinitionScanner}. - *

Default is {@link org.springframework.context.annotation.AnnotationBeanNameGenerator}. + *

Default is an {@link org.springframework.context.annotation.AnnotationBeanNameGenerator + * AnnotationBeanNameGenerator}. + *

When processing {@link org.springframework.context.annotation.Configuration @Configuration} + * classes, a {@link org.springframework.context.annotation.ConfigurationBeanNameGenerator + * ConfigurationBeanNameGenerator} (such as + * {@link org.springframework.context.annotation.FullyQualifiedConfigurationBeanNameGenerator + * FullyQualifiedConfigurationBeanNameGenerator}) also determines the default + * names for {@link org.springframework.context.annotation.Bean @Bean} methods + * without an explicit {@code name} attribute. * @see AnnotatedBeanDefinitionReader#setBeanNameGenerator * @see ClassPathBeanDefinitionScanner#setBeanNameGenerator + * @see org.springframework.context.annotation.FullyQualifiedAnnotationBeanNameGenerator + * @see org.springframework.context.annotation.FullyQualifiedConfigurationBeanNameGenerator */ public void setBeanNameGenerator(@Nullable BeanNameGenerator beanNameGenerator) { this.beanNameGenerator = beanNameGenerator;