From 1145054971ccc8bfd0814a45df63bc9647d0cba0 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 13 Jul 2025 16:08:05 +0200 Subject: [PATCH] Introduce ConfigurationBeanNameGenerator for @Bean-annotated methods Includes FullyQualifiedConfigurationBeanNameGenerator implementation. Closes gh-33448 --- .../annotation/BeanAnnotationHelper.java | 21 +++++- .../ConfigurationBeanNameGenerator.java | 44 +++++++++++ ...onfigurationClassBeanDefinitionReader.java | 73 +++++++++++-------- .../ConfigurationClassEnhancer.java | 4 +- .../ConfigurationClassPostProcessor.java | 20 +++-- ...yQualifiedAnnotationBeanNameGenerator.java | 4 +- ...alifiedConfigurationBeanNameGenerator.java | 61 ++++++++++++++++ ...notationConfigApplicationContextTests.java | 34 ++++++++- .../core/type/MethodMetadata.java | 14 ++++ .../core/type/StandardMethodMetadata.java | 5 ++ 10 files changed, 237 insertions(+), 43 deletions(-) create mode 100644 spring-context/src/main/java/org/springframework/context/annotation/ConfigurationBeanNameGenerator.java create mode 100644 spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.java diff --git a/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java b/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java index 109c4699d53..4985d68c6e8 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/BeanAnnotationHelper.java +++ b/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.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); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationBeanNameGenerator.java new file mode 100644 index 00000000000..52f51affd43 --- /dev/null +++ b/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); + +} diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index ff2ee5be1f3..711fa5cb06a 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/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.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 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 registrars) { + private void loadBeanDefinitionsFromImportBeanDefinitionRegistrars( + Map registrars) { + registrars.forEach((registrar, metadata) -> registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator)); } private void loadBeanDefinitionsFromBeanRegistrars(Map 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 diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index 67296856d47..6ae194adfb2 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -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; 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 dda2c8377b3..91812cc9e0e 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 @@ -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; } } } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedAnnotationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedAnnotationBeanNameGenerator.java index 473bd06bc6e..13bb9768112 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedAnnotationBeanNameGenerator.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedAnnotationBeanNameGenerator.java @@ -28,7 +28,8 @@ import org.springframework.util.Assert; *

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}. * *

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 { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.java b/spring-context/src/main/java/org/springframework/context/annotation/FullyQualifiedConfigurationBeanNameGenerator.java new file mode 100644 index 00000000000..677d74dc11c --- /dev/null +++ b/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. + * + *

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(); + } + +} diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java index cd3bc2f8846..1864d8899e3 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java @@ -67,6 +67,21 @@ class AnnotationConfigApplicationContextTests { 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 beans = context.getBeansWithAnnotation(Configuration.class); + assertThat(beans).hasSize(1); + } + @Test void registerAndRefresh() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @@ -74,7 +89,22 @@ class AnnotationConfigApplicationContextTests { context.refresh(); context.getBean("testBean"); - context.getBean("name"); + assertThat(context.getBean("name")).isEqualTo("foo"); + assertThat(context.getBean("prefixName")).isEqualTo("barfoo"); + Map 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 beans = context.getBeansWithAnnotation(Configuration.class); assertThat(beans).hasSize(2); } @@ -598,6 +628,8 @@ class AnnotationConfigApplicationContextTests { static class NameConfig { @Bean String name() { return "foo"; } + + @Bean(autowireCandidate = false) String prefixName() { return "bar" + name(); } } @Configuration diff --git a/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java index 17d7874461d..a62ed09dfe1 100644 --- a/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/MethodMetadata.java @@ -16,6 +16,8 @@ package org.springframework.core.type; +import java.lang.reflect.Method; + /** * 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. @@ -71,4 +73,16 @@ public interface MethodMetadata extends AnnotatedTypeMetadata { */ 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); + } + } diff --git a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java index 49505574099..4a099c384a1 100644 --- a/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java +++ b/spring-core/src/main/java/org/springframework/core/type/StandardMethodMetadata.java @@ -166,4 +166,9 @@ public class StandardMethodMetadata implements MethodMetadata { return this.introspectedMethod.toString(); } + + static MethodMetadata from(Method introspectedMethod) { + return new StandardMethodMetadata(introspectedMethod, true); + } + }