From 89d150d3981aaec43bc17f195adfee80786560ab Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 31 Jul 2019 18:31:11 +0100 Subject: [PATCH] Resolve factoryBeanClass if necessary Update `AbstractAutowireCapableBeanFactory.getTypeForFactoryBean` to use fallback to `determineTargetType` if the factory bean definition does not have a resolved class. This is required for the case where a `@Configuration` class is picked up via component scanning and has a bean type that has not yet been resolved. Closes gh-23338 --- .../AbstractAutowireCapableBeanFactory.java | 13 ++++-- ...ithFactoryBeanBeanEarlyDeductionTests.java | 43 ++++++++++++++++--- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index abe0bff6eb..5617b6b985 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -858,9 +858,16 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac // Try to obtain the FactoryBean's object type from its factory method // declaration without instantiating the containing bean at all. BeanDefinition factoryBeanDefinition = getBeanDefinition(factoryBeanName); - if (factoryBeanDefinition instanceof AbstractBeanDefinition && - ((AbstractBeanDefinition) factoryBeanDefinition).hasBeanClass()) { - Class factoryBeanClass = ((AbstractBeanDefinition) factoryBeanDefinition).getBeanClass(); + Class factoryBeanClass = null; + if (factoryBeanDefinition instanceof AbstractBeanDefinition + && ((AbstractBeanDefinition) factoryBeanDefinition).hasBeanClass()) { + factoryBeanClass = ((AbstractBeanDefinition) factoryBeanDefinition).getBeanClass(); + } + else { + RootBeanDefinition fbmbd = getMergedBeanDefinition(factoryBeanName, factoryBeanDefinition); + factoryBeanClass = determineTargetType(factoryBeanName, fbmbd, new Class[] { Object.class }); + } + if (factoryBeanClass != null) { result = getTypeForFactoryBeanFromMethod(factoryBeanClass, factoryMethodName); if (result.resolve() != null) { return result; diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationWithFactoryBeanBeanEarlyDeductionTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationWithFactoryBeanBeanEarlyDeductionTests.java index 24c7cfb558..588d262ca9 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationWithFactoryBeanBeanEarlyDeductionTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationWithFactoryBeanBeanEarlyDeductionTests.java @@ -28,6 +28,8 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.type.AnnotationMetadata; import static org.assertj.core.api.Assertions.assertThat; @@ -80,6 +82,32 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests { assertPostFreeze(AttributeClassConfiguration.class); } + @Test + public void preFreezeUnresolvedGenericFactoryBean() { + // Covers the case where a @Configuration is picked up via component scanning + // and its bean definition only has a String bean class. In such cases + // beanDefinition.hasBeanClass() returns false so we need to actually + // call determineTargetType ourselves + GenericBeanDefinition factoryBeanDefinition = new GenericBeanDefinition(); + factoryBeanDefinition.setBeanClassName(GenericClassConfiguration.class.getName()); + GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); + beanDefinition.setBeanClass(FactoryBean.class); + beanDefinition.setFactoryBeanName("factoryBean"); + beanDefinition.setFactoryMethodName("myBean"); + GenericApplicationContext context = new GenericApplicationContext(); + try { + context.registerBeanDefinition("factoryBean", factoryBeanDefinition); + context.registerBeanDefinition("myBean", beanDefinition); + NameCollectingBeanFactoryPostProcessor postProcessor = new NameCollectingBeanFactoryPostProcessor(); + context.addBeanFactoryPostProcessor(postProcessor); + context.refresh(); + assertContainsMyBeanName(postProcessor.getNames()); + } + finally { + context.close(); + } + } + private void assertPostFreeze(Class configurationClass) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( configurationClass); @@ -90,11 +118,16 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests { BeanFactoryPostProcessor... postProcessors) { NameCollectingBeanFactoryPostProcessor postProcessor = new NameCollectingBeanFactoryPostProcessor(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - Arrays.stream(postProcessors).forEach(context::addBeanFactoryPostProcessor); - context.addBeanFactoryPostProcessor(postProcessor); - context.register(configurationClass); - context.refresh(); - assertContainsMyBeanName(postProcessor.getNames()); + try { + Arrays.stream(postProcessors).forEach(context::addBeanFactoryPostProcessor); + context.addBeanFactoryPostProcessor(postProcessor); + context.register(configurationClass); + context.refresh(); + assertContainsMyBeanName(postProcessor.getNames()); + } + finally { + context.close(); + } } private void assertContainsMyBeanName(AnnotationConfigApplicationContext context) {