Browse Source

Introduce ConfigurationBeanNameGenerator for @Bean-annotated methods

Includes FullyQualifiedConfigurationBeanNameGenerator implementation.

Closes gh-33448
pull/35196/head
Juergen Hoeller 5 months ago
parent
commit
1145054971
  1. 21
      spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java
  2. 44
      spring-context/src/main/java/org/springframework/context/annotation/ConfigurationBeanNameGenerator.java
  3. 73
      spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
  4. 4
      spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java
  5. 20
      spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java
  6. 4
      spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedAnnotationBeanNameGenerator.java
  7. 61
      spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.java
  8. 34
      spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java
  9. 14
      spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java
  10. 5
      spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java

21
spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java

@ -19,8 +19,10 @@ package org.springframework.context.annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.MethodMetadata;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;
/** /**
@ -41,11 +43,26 @@ abstract class BeanAnnotationHelper {
return AnnotatedElementUtils.hasAnnotation(method, Bean.class); 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) { 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); String beanName = beanNameCache.get(beanMethod);
if (beanName == null) { if (beanName == null) {
// By default, the bean name is the name of the @Bean-annotated method // By default, the bean name is empty (indicating a name to be derived from the method name)
beanName = beanMethod.getName(); beanName = "";
// Check to see if the user has explicitly set a custom bean name... // Check to see if the user has explicitly set a custom bean name...
AnnotationAttributes bean = AnnotationAttributes bean =
AnnotatedElementUtils.findMergedAnnotationAttributes(beanMethod, Bean.class, false, false); AnnotatedElementUtils.findMergedAnnotationAttributes(beanMethod, Bean.class, false, false);

44
spring-context/src/main/java/org/springframework/context/annotation/ConfigurationBeanNameGenerator.java

@ -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);
}

73
spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java

@ -18,10 +18,7 @@ package org.springframework.context.annotation;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -199,16 +196,30 @@ class ConfigurationClassBeanDefinitionReader {
Assert.state(bean != null, "No @Bean annotation attributes"); Assert.state(bean != null, "No @Bean annotation attributes");
// Consider name and any aliases // Consider name and any aliases
List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name"))); String[] explicitNames = bean.getStringArray("name");
String beanName = (!names.isEmpty() ? names.remove(0) : methodName); String beanName;
String localBeanName;
// Register aliases even when overridden if (explicitNames.length > 0 && StringUtils.hasText(explicitNames[0])) {
for (String alias : names) { beanName = explicitNames[0];
this.registry.registerAlias(beanName, alias); 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)? // 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())) { if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {
throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(), throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() + beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() +
@ -217,9 +228,6 @@ class ConfigurationClassBeanDefinitionReader {
return; return;
} }
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
if (metadata.isStatic()) { if (metadata.isStatic()) {
// static @Bean method // static @Bean method
if (configClass.getMetadata() instanceof StandardAnnotationMetadata sam) { if (configClass.getMetadata() instanceof StandardAnnotationMetadata sam) {
@ -288,7 +296,7 @@ class ConfigurationClassBeanDefinitionReader {
new BeanDefinitionHolder(beanDef, beanName), this.registry, new BeanDefinitionHolder(beanDef, beanName), this.registry,
proxyMode == ScopedProxyMode.TARGET_CLASS); proxyMode == ScopedProxyMode.TARGET_CLASS);
beanDefToRegister = new ConfigurationClassBeanDefinition( beanDefToRegister = new ConfigurationClassBeanDefinition(
(RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata, beanName); (RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata, localBeanName);
} }
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
@ -299,7 +307,9 @@ class ConfigurationClassBeanDefinitionReader {
} }
@SuppressWarnings("NullAway") // Reflection @SuppressWarnings("NullAway") // Reflection
protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String beanName) { private boolean isOverriddenByExistingDefinition(
BeanMethod beanMethod, String beanName, ConfigurationClassBeanDefinition newBeanDef) {
if (!this.registry.containsBeanDefinition(beanName)) { if (!this.registry.containsBeanDefinition(beanName)) {
return false; return false;
} }
@ -320,9 +330,7 @@ class ConfigurationClassBeanDefinitionReader {
configClass.getMetadata().getAnnotationAttributes(Configuration.class.getName()); configClass.getMetadata().getAnnotationAttributes(Configuration.class.getName());
if ((attributes != null && (Boolean) attributes.get("enforceUniqueMethods")) || if ((attributes != null && (Boolean) attributes.get("enforceUniqueMethods")) ||
!this.registry.isBeanDefinitionOverridable(beanName)) { !this.registry.isBeanDefinitionOverridable(beanName)) {
throw new BeanDefinitionOverrideException(beanName, throw new BeanDefinitionOverrideException(beanName, newBeanDef, existingBeanDef,
new ConfigurationClassBeanDefinition(configClass, beanMethod.getMetadata(), beanName),
existingBeanDef,
"@Bean method override with same bean name but different method name: " + existingBeanDef); "@Bean method override with same bean name but different method name: " + existingBeanDef);
} }
return true; 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) -> registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator)); registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
} }
private void loadBeanDefinitionsFromBeanRegistrars(Map<String, BeanRegistrar> registrars) { private void loadBeanDefinitionsFromBeanRegistrars(Map<String, BeanRegistrar> registrars) {
Assert.isInstanceOf(ListableBeanFactory.class, this.registry, if (!(this.registry instanceof ListableBeanFactory beanFactory)) {
"Cannot support bean registrars since " + this.registry.getClass().getName() + throw new IllegalStateException("Cannot support bean registrars since " +
" does not implement BeanDefinitionRegistry"); this.registry.getClass().getName() + " does not implement ListableBeanFactory");
registrars.values().forEach(registrar -> registrar.register(new BeanRegistryAdapter(this.registry, }
(ListableBeanFactory) this.registry, this.environment, registrar.getClass()), this.environment)); 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 MethodMetadata factoryMethodMetadata;
private final String derivedBeanName; private final String localBeanName;
public ConfigurationClassBeanDefinition( public ConfigurationClassBeanDefinition(
ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) { ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String localBeanName) {
this.annotationMetadata = configClass.getMetadata(); this.annotationMetadata = configClass.getMetadata();
this.factoryMethodMetadata = beanMethodMetadata; this.factoryMethodMetadata = beanMethodMetadata;
this.derivedBeanName = derivedBeanName; this.localBeanName = localBeanName;
setResource(configClass.getResource()); setResource(configClass.getResource());
setLenientConstructorResolution(false); setLenientConstructorResolution(false);
} }
public ConfigurationClassBeanDefinition(RootBeanDefinition original, public ConfigurationClassBeanDefinition(RootBeanDefinition original,
ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String derivedBeanName) { ConfigurationClass configClass, MethodMetadata beanMethodMetadata, String localBeanName) {
super(original); super(original);
this.annotationMetadata = configClass.getMetadata(); this.annotationMetadata = configClass.getMetadata();
this.factoryMethodMetadata = beanMethodMetadata; this.factoryMethodMetadata = beanMethodMetadata;
this.derivedBeanName = derivedBeanName; this.localBeanName = localBeanName;
} }
private ConfigurationClassBeanDefinition(ConfigurationClassBeanDefinition original) { private ConfigurationClassBeanDefinition(ConfigurationClassBeanDefinition original) {
super(original); super(original);
this.annotationMetadata = original.annotationMetadata; this.annotationMetadata = original.annotationMetadata;
this.factoryMethodMetadata = original.factoryMethodMetadata; this.factoryMethodMetadata = original.factoryMethodMetadata;
this.derivedBeanName = original.derivedBeanName; this.localBeanName = original.localBeanName;
} }
@Override @Override
@ -468,7 +479,7 @@ class ConfigurationClassBeanDefinitionReader {
@Override @Override
public boolean isFactoryMethod(Method candidate) { public boolean isFactoryMethod(Method candidate) {
return (super.isFactoryMethod(candidate) && BeanAnnotationHelper.isBeanAnnotated(candidate) && return (super.isFactoryMethod(candidate) && BeanAnnotationHelper.isBeanAnnotated(candidate) &&
BeanAnnotationHelper.determineBeanNameFor(candidate).equals(this.derivedBeanName)); BeanAnnotationHelper.determineBeanNameFor(candidate).equals(this.localBeanName));
} }
@Override @Override

4
spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java

@ -352,7 +352,7 @@ class ConfigurationClassEnhancer {
MethodProxy cglibMethodProxy) throws Throwable { MethodProxy cglibMethodProxy) throws Throwable {
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance); ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod); String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod, beanFactory);
// Determine whether this bean is a scoped-proxy // Determine whether this bean is a scoped-proxy
if (BeanAnnotationHelper.isScopedProxy(beanMethod)) { if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
@ -455,7 +455,7 @@ class ConfigurationClassEnhancer {
} }
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod(); Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
if (currentlyInvoked != null) { if (currentlyInvoked != null) {
String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked); String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked, beanFactory);
beanFactory.registerDependentBean(beanName, outerBeanName); beanFactory.registerDependentBean(beanName, outerBeanName);
} }
return beanInstance; return beanInstance;

20
spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java

@ -408,12 +408,20 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
SingletonBeanRegistry singletonRegistry = null; SingletonBeanRegistry singletonRegistry = null;
if (registry instanceof SingletonBeanRegistry sbr) { if (registry instanceof SingletonBeanRegistry sbr) {
singletonRegistry = sbr; singletonRegistry = sbr;
if (!this.localBeanNameGeneratorSet) { BeanNameGenerator configurationGenerator = (BeanNameGenerator) singletonRegistry.getSingleton(
BeanNameGenerator generator = (BeanNameGenerator) singletonRegistry.getSingleton( AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR); if (configurationGenerator != null) {
if (generator != null) { if (this.localBeanNameGeneratorSet) {
this.componentScanBeanNameGenerator = generator; if (configurationGenerator instanceof ConfigurationBeanNameGenerator &
this.importBeanNameGenerator = generator; 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;
} }
} }
} }

4
spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedAnnotationBeanNameGenerator.java

@ -28,7 +28,8 @@ import org.springframework.util.Assert;
* <p>Favor this bean naming strategy over {@code AnnotationBeanNameGenerator} if * <p>Favor this bean naming strategy over {@code AnnotationBeanNameGenerator} if
* you run into naming conflicts due to multiple autodetected components having the * 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 * 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 * <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 * 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 * @since 5.2.3
* @see org.springframework.beans.factory.support.DefaultBeanNameGenerator * @see org.springframework.beans.factory.support.DefaultBeanNameGenerator
* @see AnnotationBeanNameGenerator * @see AnnotationBeanNameGenerator
* @see FullyQualifiedConfigurationBeanNameGenerator
* @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR * @see ConfigurationClassPostProcessor#IMPORT_BEAN_NAME_GENERATOR
*/ */
public class FullyQualifiedAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator { public class FullyQualifiedAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator {

61
spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.java

@ -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();
}
}

34
spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java

@ -67,6 +67,21 @@ class AnnotationConfigApplicationContextTests {
assertThat(beans).hasSize(1); assertThat(beans).hasSize(1);
} }
@Test
void scanAndRefreshWithFullyQualifiedBeanNames() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.setBeanNameGenerator(FullyQualifiedConfigurationBeanNameGenerator.INSTANCE);
context.scan("org.springframework.context.annotation6");
context.refresh();
context.getBean(ConfigForScanning.class.getName());
context.getBean(ConfigForScanning.class.getName() + ".testBean"); // contributed by ConfigForScanning
context.getBean(ComponentForScanning.class.getName());
context.getBean(Jsr330NamedForScanning.class.getName());
Map<String, Object> beans = context.getBeansWithAnnotation(Configuration.class);
assertThat(beans).hasSize(1);
}
@Test @Test
void registerAndRefresh() { void registerAndRefresh() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@ -74,7 +89,22 @@ class AnnotationConfigApplicationContextTests {
context.refresh(); context.refresh();
context.getBean("testBean"); context.getBean("testBean");
context.getBean("name"); assertThat(context.getBean("name")).isEqualTo("foo");
assertThat(context.getBean("prefixName")).isEqualTo("barfoo");
Map<String, Object> beans = context.getBeansWithAnnotation(Configuration.class);
assertThat(beans).hasSize(2);
}
@Test
void registerAndRefreshWithFullyQualifiedBeanNames() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.setBeanNameGenerator(FullyQualifiedConfigurationBeanNameGenerator.INSTANCE);
context.register(Config.class, NameConfig.class);
context.refresh();
context.getBean(Config.class.getName() + ".testBean");
assertThat(context.getBean(NameConfig.class.getName() + ".name")).isEqualTo("foo");
assertThat(context.getBean(NameConfig.class.getName() + ".prefixName")).isEqualTo("barfoo");
Map<String, Object> beans = context.getBeansWithAnnotation(Configuration.class); Map<String, Object> beans = context.getBeansWithAnnotation(Configuration.class);
assertThat(beans).hasSize(2); assertThat(beans).hasSize(2);
} }
@ -598,6 +628,8 @@ class AnnotationConfigApplicationContextTests {
static class NameConfig { static class NameConfig {
@Bean String name() { return "foo"; } @Bean String name() { return "foo"; }
@Bean(autowireCandidate = false) String prefixName() { return "bar" + name(); }
} }
@Configuration @Configuration

14
spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java

@ -16,6 +16,8 @@
package org.springframework.core.type; package org.springframework.core.type;
import java.lang.reflect.Method;
/** /**
* Interface that defines abstract access to the annotations of a specific * Interface that defines abstract access to the annotations of a specific
* method, in a form that does not require that method's class to be loaded yet. * method, in a form that does not require that method's class to be loaded yet.
@ -71,4 +73,16 @@ public interface MethodMetadata extends AnnotatedTypeMetadata {
*/ */
boolean isOverridable(); boolean isOverridable();
/**
* Factory method to create a new {@link MethodMetadata} instance
* for the given method using standard reflection.
* @param method the method to introspect
* @return a new {@link MethodMetadata} instance
* @since 7.0
*/
static MethodMetadata introspect(Method method) {
return StandardMethodMetadata.from(method);
}
} }

5
spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java

@ -166,4 +166,9 @@ public class StandardMethodMetadata implements MethodMetadata {
return this.introspectedMethod.toString(); return this.introspectedMethod.toString();
} }
static MethodMetadata from(Method introspectedMethod) {
return new StandardMethodMetadata(introspectedMethod, true);
}
} }

Loading…
Cancel
Save