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
// 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;

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.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) {