mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Introduce ConfigurationBeanNameGenerator for @Bean-annotated methods
Includes FullyQualifiedConfigurationBeanNameGenerator implementation. Closes gh-33448
This commit is contained in:
+19
-2
@@ -19,8 +19,10 @@ package org.springframework.context.annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
/**
|
||||
@@ -41,11 +43,26 @@ abstract class BeanAnnotationHelper {
|
||||
return AnnotatedElementUtils.hasAnnotation(method, Bean.class);
|
||||
}
|
||||
|
||||
public static String determineBeanNameFor(Method beanMethod, ConfigurableBeanFactory beanFactory) {
|
||||
String beanName = retrieveBeanNameFor(beanMethod);
|
||||
if (!beanName.isEmpty()) {
|
||||
return beanName;
|
||||
}
|
||||
return (beanFactory.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR)
|
||||
instanceof ConfigurationBeanNameGenerator cbng ?
|
||||
cbng.deriveBeanName(MethodMetadata.introspect(beanMethod)) : beanMethod.getName());
|
||||
}
|
||||
|
||||
public static String determineBeanNameFor(Method beanMethod) {
|
||||
String beanName = retrieveBeanNameFor(beanMethod);
|
||||
return (!beanName.isEmpty() ? beanName : beanMethod.getName());
|
||||
}
|
||||
|
||||
private static String retrieveBeanNameFor(Method beanMethod) {
|
||||
String beanName = beanNameCache.get(beanMethod);
|
||||
if (beanName == null) {
|
||||
// By default, the bean name is the name of the @Bean-annotated method
|
||||
beanName = beanMethod.getName();
|
||||
// By default, the bean name is empty (indicating a name to be derived from the method name)
|
||||
beanName = "";
|
||||
// Check to see if the user has explicitly set a custom bean name...
|
||||
AnnotationAttributes bean =
|
||||
AnnotatedElementUtils.findMergedAnnotationAttributes(beanMethod, Bean.class, false, false);
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
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.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0
|
||||
* @see AnnotationConfigApplicationContext#setBeanNameGenerator
|
||||
* @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR
|
||||
*/
|
||||
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.
|
||||
* @param beanMethod the method metadata for the {@link Bean @Bean} method
|
||||
* @return the default bean name to use
|
||||
*/
|
||||
String deriveBeanName(MethodMetadata beanMethod);
|
||||
|
||||
}
|
||||
+42
-31
@@ -18,10 +18,7 @@ package org.springframework.context.annotation;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -199,16 +196,30 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
Assert.state(bean != null, "No @Bean annotation attributes");
|
||||
|
||||
// Consider name and any aliases
|
||||
List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
|
||||
String beanName = (!names.isEmpty() ? names.remove(0) : methodName);
|
||||
|
||||
// Register aliases even when overridden
|
||||
for (String alias : names) {
|
||||
this.registry.registerAlias(beanName, alias);
|
||||
String[] explicitNames = bean.getStringArray("name");
|
||||
String beanName;
|
||||
String localBeanName;
|
||||
if (explicitNames.length > 0 && StringUtils.hasText(explicitNames[0])) {
|
||||
beanName = explicitNames[0];
|
||||
localBeanName = beanName;
|
||||
// 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);
|
||||
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
|
||||
|
||||
// Has this effectively been overridden before (for example, via XML)?
|
||||
if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
|
||||
if (isOverriddenByExistingDefinition(beanMethod, beanName, beanDef)) {
|
||||
if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {
|
||||
throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
|
||||
beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() +
|
||||
@@ -217,9 +228,6 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
|
||||
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
|
||||
|
||||
if (metadata.isStatic()) {
|
||||
// static @Bean method
|
||||
if (configClass.getMetadata() instanceof StandardAnnotationMetadata sam) {
|
||||
@@ -288,7 +296,7 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
new BeanDefinitionHolder(beanDef, beanName), this.registry,
|
||||
proxyMode == ScopedProxyMode.TARGET_CLASS);
|
||||
beanDefToRegister = new ConfigurationClassBeanDefinition(
|
||||
(RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata, beanName);
|
||||
(RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata, localBeanName);
|
||||
}
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
@@ -299,7 +307,9 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullAway") // Reflection
|
||||
protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String beanName) {
|
||||
private boolean isOverriddenByExistingDefinition(
|
||||
BeanMethod beanMethod, String beanName, ConfigurationClassBeanDefinition newBeanDef) {
|
||||
|
||||
if (!this.registry.containsBeanDefinition(beanName)) {
|
||||
return false;
|
||||
}
|
||||
@@ -320,9 +330,7 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
configClass.getMetadata().getAnnotationAttributes(Configuration.class.getName());
|
||||
if ((attributes != null && (Boolean) attributes.get("enforceUniqueMethods")) ||
|
||||
!this.registry.isBeanDefinitionOverridable(beanName)) {
|
||||
throw new BeanDefinitionOverrideException(beanName,
|
||||
new ConfigurationClassBeanDefinition(configClass, beanMethod.getMetadata(), beanName),
|
||||
existingBeanDef,
|
||||
throw new BeanDefinitionOverrideException(beanName, newBeanDef, existingBeanDef,
|
||||
"@Bean method override with same bean name but different method name: " + existingBeanDef);
|
||||
}
|
||||
return true;
|
||||
@@ -400,17 +408,20 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
});
|
||||
}
|
||||
|
||||
private void loadBeanDefinitionsFromImportBeanDefinitionRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
|
||||
private void loadBeanDefinitionsFromImportBeanDefinitionRegistrars(
|
||||
Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
|
||||
|
||||
registrars.forEach((registrar, metadata) ->
|
||||
registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
|
||||
}
|
||||
|
||||
private void loadBeanDefinitionsFromBeanRegistrars(Map<String, BeanRegistrar> registrars) {
|
||||
Assert.isInstanceOf(ListableBeanFactory.class, this.registry,
|
||||
"Cannot support bean registrars since " + this.registry.getClass().getName() +
|
||||
" does not implement BeanDefinitionRegistry");
|
||||
registrars.values().forEach(registrar -> registrar.register(new BeanRegistryAdapter(this.registry,
|
||||
(ListableBeanFactory) this.registry, this.environment, registrar.getClass()), this.environment));
|
||||
if (!(this.registry instanceof ListableBeanFactory beanFactory)) {
|
||||
throw new IllegalStateException("Cannot support bean registrars since " +
|
||||
this.registry.getClass().getName() + " does not implement ListableBeanFactory");
|
||||
}
|
||||
registrars.values().forEach(registrar -> registrar.register(new BeanRegistryAdapter(
|
||||
this.registry, beanFactory, this.environment, registrar.getClass()), this.environment));
|
||||
}
|
||||
|
||||
|
||||
@@ -427,32 +438,32 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
|
||||
private final MethodMetadata factoryMethodMetadata;
|
||||
|
||||
private final String derivedBeanName;
|
||||
private final String localBeanName;
|
||||
|
||||
public ConfigurationClassBeanDefinition(
|
||||
ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) {
|
||||
ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String localBeanName) {
|
||||
|
||||
this.annotationMetadata = configClass.getMetadata();
|
||||
this.factoryMethodMetadata = beanMethodMetadata;
|
||||
this.derivedBeanName = derivedBeanName;
|
||||
this.localBeanName = localBeanName;
|
||||
setResource(configClass.getResource());
|
||||
setLenientConstructorResolution(false);
|
||||
}
|
||||
|
||||
public ConfigurationClassBeanDefinition(RootBeanDefinition original,
|
||||
ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) {
|
||||
ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String localBeanName) {
|
||||
|
||||
super(original);
|
||||
this.annotationMetadata = configClass.getMetadata();
|
||||
this.factoryMethodMetadata = beanMethodMetadata;
|
||||
this.derivedBeanName = derivedBeanName;
|
||||
this.localBeanName = localBeanName;
|
||||
}
|
||||
|
||||
private ConfigurationClassBeanDefinition(ConfigurationClassBeanDefinition original) {
|
||||
super(original);
|
||||
this.annotationMetadata = original.annotationMetadata;
|
||||
this.factoryMethodMetadata = original.factoryMethodMetadata;
|
||||
this.derivedBeanName = original.derivedBeanName;
|
||||
this.localBeanName = original.localBeanName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -468,7 +479,7 @@ class ConfigurationClassBeanDefinitionReader {
|
||||
@Override
|
||||
public boolean isFactoryMethod(Method candidate) {
|
||||
return (super.isFactoryMethod(candidate) && BeanAnnotationHelper.isBeanAnnotated(candidate) &&
|
||||
BeanAnnotationHelper.determineBeanNameFor(candidate).equals(this.derivedBeanName));
|
||||
BeanAnnotationHelper.determineBeanNameFor(candidate).equals(this.localBeanName));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -352,7 +352,7 @@ class ConfigurationClassEnhancer {
|
||||
MethodProxy cglibMethodProxy) throws Throwable {
|
||||
|
||||
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
|
||||
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
|
||||
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod, beanFactory);
|
||||
|
||||
// Determine whether this bean is a scoped-proxy
|
||||
if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
|
||||
@@ -455,7 +455,7 @@ class ConfigurationClassEnhancer {
|
||||
}
|
||||
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
|
||||
if (currentlyInvoked != null) {
|
||||
String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked);
|
||||
String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked, beanFactory);
|
||||
beanFactory.registerDependentBean(beanName, outerBeanName);
|
||||
}
|
||||
return beanInstance;
|
||||
|
||||
+14
-6
@@ -408,12 +408,20 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
|
||||
SingletonBeanRegistry singletonRegistry = null;
|
||||
if (registry instanceof SingletonBeanRegistry sbr) {
|
||||
singletonRegistry = sbr;
|
||||
if (!this.localBeanNameGeneratorSet) {
|
||||
BeanNameGenerator generator = (BeanNameGenerator) singletonRegistry.getSingleton(
|
||||
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
|
||||
if (generator != null) {
|
||||
this.componentScanBeanNameGenerator = generator;
|
||||
this.importBeanNameGenerator = generator;
|
||||
BeanNameGenerator configurationGenerator = (BeanNameGenerator) singletonRegistry.getSingleton(
|
||||
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
|
||||
if (configurationGenerator != null) {
|
||||
if (this.localBeanNameGeneratorSet) {
|
||||
if (configurationGenerator instanceof ConfigurationBeanNameGenerator &
|
||||
configurationGenerator != this.importBeanNameGenerator) {
|
||||
throw new IllegalStateException("Context-level ConfigurationBeanNameGenerator [" +
|
||||
configurationGenerator + "] must not be overridden with processor-level generator [" +
|
||||
this.importBeanNameGenerator + "]");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.componentScanBeanNameGenerator = configurationGenerator;
|
||||
this.importBeanNameGenerator = configurationGenerator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -28,7 +28,8 @@ import org.springframework.util.Assert;
|
||||
* <p>Favor this bean naming strategy over {@code AnnotationBeanNameGenerator} 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).
|
||||
* different packages). If you need such conflict avoidance for {@link Bean @Bean}
|
||||
* methods as well, consider {@link FullyQualifiedConfigurationBeanNameGenerator}.
|
||||
*
|
||||
* <p>Note that an instance of this class is used by default for configuration-level
|
||||
* import purposes; whereas, the default for component scanning purposes is a plain
|
||||
@@ -39,6 +40,7 @@ import org.springframework.util.Assert;
|
||||
* @since 5.2.3
|
||||
* @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
|
||||
* @see AnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedConfigurationBeanNameGenerator
|
||||
* @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR
|
||||
*/
|
||||
public class FullyQualifiedAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator {
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import org.springframework.core.type.MethodMetadata;
|
||||
|
||||
/**
|
||||
* Extended variant of {@link FullyQualifiedAnnotationBeanNameGenerator} for
|
||||
* {@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.
|
||||
*
|
||||
* <p>This provides an alternative to the default bean name generation for
|
||||
* {@code @Bean} methods (which uses the plain method name), primarily for use
|
||||
* in large applications with potential bean name overlaps. Favor this bean
|
||||
* naming strategy over {@code FullyQualifiedAnnotationBeanNameGenerator} if
|
||||
* you expect such naming conflicts for {@code @Bean} methods, as long as the
|
||||
* application does not depend on {@code @Bean} method names as bean names.
|
||||
* Where the name does matter, make sure to declare {@code @Bean("myBeanName")}
|
||||
* in such a scenario, even if it repeats the method name as the bean name.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0
|
||||
* @see AnnotationBeanNameGenerator
|
||||
* @see FullyQualifiedAnnotationBeanNameGenerator
|
||||
* @see AnnotationConfigApplicationContext#setBeanNameGenerator
|
||||
* @see AnnotationConfigUtils#CONFIGURATION_BEAN_NAME_GENERATOR
|
||||
*/
|
||||
public class FullyQualifiedConfigurationBeanNameGenerator extends FullyQualifiedAnnotationBeanNameGenerator
|
||||
implements ConfigurationBeanNameGenerator {
|
||||
|
||||
/**
|
||||
* A convenient constant for a default {@code FullyQualifiedConfigurationBeanNameGenerator}
|
||||
* instance, as used for configuration-level import purposes.
|
||||
*/
|
||||
public static final FullyQualifiedConfigurationBeanNameGenerator INSTANCE =
|
||||
new FullyQualifiedConfigurationBeanNameGenerator();
|
||||
|
||||
|
||||
@Override
|
||||
public String deriveBeanName(MethodMetadata beanMethod) {
|
||||
return beanMethod.getDeclaringClassName() + "." + beanMethod.getMethodName();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user