Support FactoryBean bean definition attributes

Update `getTypeForFactoryBean` detection so that a bean definition
attribute can be used to supply the result. This commit allows projects
such as Spring Data to provide the result that would be supplied by
`getObjectType` early so that we don't need to initialize the
`FactoryBean` unnecessarily.

Closes gh-23338
This commit is contained in:
Phillip Webb
2019-07-23 08:21:42 +01:00
committed by Juergen Hoeller
parent a0e462581f
commit 71a5308c78
4 changed files with 98 additions and 5 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@@ -16,6 +16,7 @@
package org.springframework.beans.factory;
import org.springframework.core.AttributeAccessor;
import org.springframework.lang.Nullable;
/**
@@ -58,6 +59,16 @@ import org.springframework.lang.Nullable;
*/
public interface FactoryBean<T> {
/**
* The name of an attribute that can be
* {@link AttributeAccessor#setAttribute set} on a
* {@link org.springframework.beans.factory.config.BeanDefinition} so that
* factory beans can signal their object type when it can't be deduced from
* the factory bean class.
*/
public static final String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
/**
* Return an instance (possibly shared or independent) of the object
* managed by this factory.

View File

@@ -826,7 +826,11 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
protected ResolvableType getTypeForFactoryBean(String beanName,
RootBeanDefinition mbd, boolean allowInit) {
ResolvableType result = ResolvableType.NONE;
// Check it the the bean definition itself has defined the type with an attribute
ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);
if (result != ResolvableType.NONE) {
return result;
}
ResolvableType beanType = mbd.hasBeanClass() ?
ResolvableType.forClass(mbd.getBeanClass()) :

View File

@@ -65,6 +65,7 @@ import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.beans.factory.config.Scope;
import org.springframework.core.AttributeAccessor;
import org.springframework.core.DecoratingClassLoader;
import org.springframework.core.NamedThreadLocal;
import org.springframework.core.ResolvableType;
@@ -1607,7 +1608,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
* already. Implementations are only allowed to instantiate the factory bean if
* {@code allowInit} is {@code true}, otherwise they should try to determine the
* result through other means.
* <p>If {@code allowInit} is {@code true}, the default implementation will create
* <p>If no {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} if set on the bean definition
* and {@code allowInit} is {@code true}, the default implementation will create
* the FactoryBean via {@code getBean} to call its {@code getObjectType} method.
* Subclasses are encouraged to optimize this, typically by inspecting the generic
* signature of the factory bean class or the factory method that creates it. If
@@ -1617,7 +1619,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
* fallback.
* @param beanName the name of the bean
* @param mbd the merged bean definition for the bean
* @param allowInit if initialization of the bean is permitted
* @param allowInit if initialization of the FactoryBean is permitted
* @return the type for the bean if determinable, otherwise {@code ResolvableType.NONE}
* @since 5.2
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
@@ -1626,6 +1628,11 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
protected ResolvableType getTypeForFactoryBean(String beanName,
RootBeanDefinition mbd, boolean allowInit) {
ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);
if (result != ResolvableType.NONE) {
return result;
}
if (allowInit && mbd.isSingleton()) {
try {
FactoryBean<?> factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true);
@@ -1648,6 +1655,25 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
return ResolvableType.NONE;
}
/**
* Determine the bean type for a FactoryBean by inspecting its attributes for a
* {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} value.
* @param attributes the attributes to inspect
* @return a {@link ResolvableType} extracted from the attributes or
* {@code ResolvableType.NONE}
* @since 5.2
*/
ResolvableType getTypeForFactoryBeanFromAttributes(AttributeAccessor attributes) {
Object attribute = attributes.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
if (attribute instanceof ResolvableType) {
return (ResolvableType) attribute;
}
if (attribute instanceof Class) {
return ResolvableType.forClass((Class<?>) attribute);
}
return ResolvableType.NONE;
}
/**
* Determine the bean type for the given FactoryBean definition, as far as possible.
* Only called if there is no singleton instance registered for the target bean already.

View File

@@ -22,9 +22,13 @@ import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
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.core.type.AnnotationMetadata;
import static org.assertj.core.api.Assertions.assertThat;
@@ -66,6 +70,16 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
assertPostFreeze(GenericClassConfiguration.class);
}
@Test
public void preFreezeAttribute() {
assertPreFreeze(AttributeClassConfiguration.class);
}
@Test
public void postFreezeAttribute() {
assertPostFreeze(AttributeClassConfiguration.class);
}
private void assertPostFreeze(Class<?> configurationClass) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configurationClass);
@@ -138,7 +152,29 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
}
static class MyBean {
@Configuration
@Import(AttributeClassRegistrar.class)
static class AttributeClassConfiguration {
}
static class AttributeClassRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(
RawWithAbstractObjectTypeFactoryBean.class).getBeanDefinition();
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, MyBean.class);
registry.registerBeanDefinition("myBean", definition);
}
}
abstract static class AbstractMyBean {
}
static class MyBean extends AbstractMyBean {
}
static class TestFactoryBean<T> implements FactoryBean<T> {
@@ -169,4 +205,20 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
}
static class RawWithAbstractObjectTypeFactoryBean implements FactoryBean<Object> {
private final Object object = new MyBean();
@Override
public Object getObject() throws Exception {
return object;
}
@Override
public Class<?> getObjectType() {
return MyBean.class;
}
}
}