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
This commit is contained in:
Phillip Webb 2019-07-31 18:31:11 +01:00
parent 6eca9e7cc9
commit 89d150d398
2 changed files with 48 additions and 8 deletions

View File

@ -858,9 +858,16 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
// Try to obtain the FactoryBean's object type from its factory method // Try to obtain the FactoryBean's object type from its factory method
// declaration without instantiating the containing bean at all. // declaration without instantiating the containing bean at all.
BeanDefinition factoryBeanDefinition = getBeanDefinition(factoryBeanName); BeanDefinition factoryBeanDefinition = getBeanDefinition(factoryBeanName);
if (factoryBeanDefinition instanceof AbstractBeanDefinition && Class<?> factoryBeanClass = null;
((AbstractBeanDefinition) factoryBeanDefinition).hasBeanClass()) { if (factoryBeanDefinition instanceof AbstractBeanDefinition
Class<?> factoryBeanClass = ((AbstractBeanDefinition) factoryBeanDefinition).getBeanClass(); && ((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); result = getTypeForFactoryBeanFromMethod(factoryBeanClass, factoryMethodName);
if (result.resolve() != null) { if (result.resolve() != null) {
return result; return result;

View File

@ -28,6 +28,8 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanFactory; import org.springframework.beans.factory.support.AbstractBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry; 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 org.springframework.core.type.AnnotationMetadata;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -80,6 +82,32 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
assertPostFreeze(AttributeClassConfiguration.class); 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) { private void assertPostFreeze(Class<?> configurationClass) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configurationClass); configurationClass);
@ -90,11 +118,16 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
BeanFactoryPostProcessor... postProcessors) { BeanFactoryPostProcessor... postProcessors) {
NameCollectingBeanFactoryPostProcessor postProcessor = new NameCollectingBeanFactoryPostProcessor(); NameCollectingBeanFactoryPostProcessor postProcessor = new NameCollectingBeanFactoryPostProcessor();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
Arrays.stream(postProcessors).forEach(context::addBeanFactoryPostProcessor); try {
context.addBeanFactoryPostProcessor(postProcessor); Arrays.stream(postProcessors).forEach(context::addBeanFactoryPostProcessor);
context.register(configurationClass); context.addBeanFactoryPostProcessor(postProcessor);
context.refresh(); context.register(configurationClass);
assertContainsMyBeanName(postProcessor.getNames()); context.refresh();
assertContainsMyBeanName(postProcessor.getNames());
}
finally {
context.close();
}
} }
private void assertContainsMyBeanName(AnnotationConfigApplicationContext context) { private void assertContainsMyBeanName(AnnotationConfigApplicationContext context) {