Always invoke ConfigurationBeanNameGenerator

Previously, if a ConfigurationBeanNameGenerator is used to parse
configuration classes, it is not invoked for `Bean` methods that provide
a specific bean name. This doesn't give a chance to an implementation to
tune such a bean, if required.

This commit updates the signature of ConfigurationBeanNameGenerator to
provide the identified bean name on the `@Bean` declaration,if any, and
to always invoke it.

Closes gh-35505
This commit is contained in:
Stéphane Nicoll
2025-10-01 10:34:38 +02:00
parent 969517bce1
commit 741107969a
6 changed files with 329 additions and 26 deletions
@@ -45,17 +45,15 @@ abstract class BeanAnnotationHelper {
public static String determineBeanNameFor(Method beanMethod, ConfigurableBeanFactory beanFactory) {
String beanName = retrieveBeanNameFor(beanMethod);
if (!beanName.isEmpty()) {
return beanName;
if (beanFactory.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR)
instanceof ConfigurationBeanNameGenerator cbng) {
return cbng.deriveBeanName(MethodMetadata.introspect(beanMethod), (!beanName.isEmpty() ? beanName : null));
}
return (beanFactory.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR)
instanceof ConfigurationBeanNameGenerator cbng ?
cbng.deriveBeanName(MethodMetadata.introspect(beanMethod)) : beanMethod.getName());
return determineBeanNameFrom(beanName, beanMethod);
}
public static String determineBeanNameFor(Method beanMethod) {
String beanName = retrieveBeanNameFor(beanMethod);
return (!beanName.isEmpty() ? beanName : beanMethod.getName());
return determineBeanNameFrom(retrieveBeanNameFor(beanMethod), beanMethod);
}
private static String retrieveBeanNameFor(Method beanMethod) {
@@ -77,6 +75,10 @@ abstract class BeanAnnotationHelper {
return beanName;
}
private static String determineBeanNameFrom(String derivedBeanName, Method beanMethod) {
return (!derivedBeanName.isEmpty() ? derivedBeanName : beanMethod.getName());
}
public static boolean isScopedProxy(Method beanMethod) {
Boolean scopedProxy = scopedProxyCache.get(beanMethod);
if (scopedProxy == null) {
@@ -88,4 +90,9 @@ abstract class BeanAnnotationHelper {
return scopedProxy;
}
static void clearCaches() {
scopedProxyCache.clear();
beanNameCache.clear();
}
}
@@ -16,6 +16,8 @@
package org.springframework.context.annotation;
import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.type.MethodMetadata;
@@ -23,10 +25,10 @@ import org.springframework.core.type.MethodMetadata;
* Extended variant of {@link BeanNameGenerator} for
* {@link Configuration @Configuration} class purposes, not only covering
* bean name generation for component and configuration classes themselves
* but also for {@link Bean @Bean} methods without a {@link Bean#name() name}
* attribute specified on the annotation itself.
* but also for {@link Bean @Bean} methods.
*
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 7.0
* @see AnnotationConfigApplicationContext#setBeanNameGenerator
* @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR
@@ -35,10 +37,11 @@ public interface ConfigurationBeanNameGenerator extends BeanNameGenerator {
/**
* Derive a default bean name for the given {@link Bean @Bean} method,
* in the absence of a {@link Bean#name() name} attribute specified.
* providing the {@link Bean#name() name} attribute specified.
* @param beanMethod the method metadata for the {@link Bean @Bean} method
* @param beanName the {@link Bean#name() name} attribute or {@code null} if non is specified
* @return the default bean name to use
*/
String deriveBeanName(MethodMetadata beanMethod);
String deriveBeanName(MethodMetadata beanMethod, @Nullable String beanName);
}
@@ -197,22 +197,16 @@ class ConfigurationClassBeanDefinitionReader {
// Consider name and any aliases.
String[] explicitNames = bean.getStringArray("name");
String beanName;
String localBeanName;
if (explicitNames.length > 0 && StringUtils.hasText(explicitNames[0])) {
beanName = explicitNames[0];
localBeanName = beanName;
String beanName = (explicitNames.length > 0 && StringUtils.hasText(explicitNames[0])) ? explicitNames[0] : null;
String localBeanName = defaultBeanName(beanName, methodName);
beanName = (this.importBeanNameGenerator instanceof ConfigurationBeanNameGenerator cbng ?
cbng.deriveBeanName(metadata, beanName) : defaultBeanName(beanName, methodName));
if (explicitNames.length > 0) {
// Register aliases even when overridden below.
for (int i = 1; i < explicitNames.length; i++) {
this.registry.registerAlias(beanName, explicitNames[i]);
}
}
else {
// Default bean name derived from method name.
beanName = (this.importBeanNameGenerator instanceof ConfigurationBeanNameGenerator cbng ?
cbng.deriveBeanName(metadata) : methodName);
localBeanName = methodName;
}
ConfigurationClassBeanDefinition beanDef =
new ConfigurationClassBeanDefinition(configClass, metadata, localBeanName);
@@ -306,6 +300,10 @@ class ConfigurationClassBeanDefinitionReader {
this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
private static String defaultBeanName(@Nullable String beanName, String methodName) {
return (beanName != null ? beanName : methodName);
}
@SuppressWarnings("NullAway") // Reflection
private boolean isOverriddenByExistingDefinition(
BeanMethod beanMethod, String beanName, ConfigurationClassBeanDefinition newBeanDef) {
@@ -16,6 +16,8 @@
package org.springframework.context.annotation;
import org.jspecify.annotations.Nullable;
import org.springframework.core.type.MethodMetadata;
/**
@@ -23,8 +25,8 @@ import org.springframework.core.type.MethodMetadata;
* {@link Configuration @Configuration} class purposes, not only enforcing
* fully-qualified names for component and configuration classes themselves
* but also fully-qualified default bean names ("className.methodName") for
* {@link Bean @Bean} methods. This only affects methods without an explicit
* {@link Bean#name() name} attribute specified.
* {@link Bean @Bean} methods. By default, this only affects methods without
* an explicit {@link Bean#name() name} attribute specified.
*
* <p>This provides an alternative to the default bean name generation for
* {@code @Bean} methods (which uses the plain method name), primarily for use
@@ -54,8 +56,8 @@ public class FullyQualifiedConfigurationBeanNameGenerator extends FullyQualified
@Override
public String deriveBeanName(MethodMetadata beanMethod) {
return beanMethod.getDeclaringClassName() + "." + beanMethod.getMethodName();
public String deriveBeanName(MethodMetadata beanMethod, @Nullable String beanName) {
return (beanName != null ? beanName : beanMethod.getDeclaringClassName() + "." + beanMethod.getMethodName());
}
}