diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/FactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/FactoryBean.java index b4b847373f..0c806cef7b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/FactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/FactoryBean.java @@ -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 { + /** + * 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. 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 8602dd9360..e266067738 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 @@ -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()) : diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 1eff7b50ff..5a1b92c75c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -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. - *

If {@code allowInit} is {@code true}, the default implementation will create + *

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. 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 8c88c19a10..24c7cfb558 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 @@ -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 implements FactoryBean { @@ -169,4 +205,20 @@ public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests { } + static class RawWithAbstractObjectTypeFactoryBean implements FactoryBean { + + private final Object object = new MyBean(); + + @Override + public Object getObject() throws Exception { + return object; + } + + @Override + public Class getObjectType() { + return MyBean.class; + } + + } + }