Skip to content

Commit 71a5308

Browse files
philwebbjhoeller
authored andcommitted
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
1 parent a0e4625 commit 71a5308

File tree

4 files changed

+98
-5
lines changed

4 files changed

+98
-5
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/FactoryBean.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.beans.factory;
1818

19+
import org.springframework.core.AttributeAccessor;
1920
import org.springframework.lang.Nullable;
2021

2122
/**
@@ -58,6 +59,16 @@
5859
*/
5960
public interface FactoryBean<T> {
6061

62+
/**
63+
* The name of an attribute that can be
64+
* {@link AttributeAccessor#setAttribute set} on a
65+
* {@link org.springframework.beans.factory.config.BeanDefinition} so that
66+
* factory beans can signal their object type when it can't be deduced from
67+
* the factory bean class.
68+
*/
69+
public static final String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
70+
71+
6172
/**
6273
* Return an instance (possibly shared or independent) of the object
6374
* managed by this factory.

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -826,7 +826,11 @@ protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition m
826826
protected ResolvableType getTypeForFactoryBean(String beanName,
827827
RootBeanDefinition mbd, boolean allowInit) {
828828

829-
ResolvableType result = ResolvableType.NONE;
829+
// Check it the the bean definition itself has defined the type with an attribute
830+
ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);
831+
if (result != ResolvableType.NONE) {
832+
return result;
833+
}
830834

831835
ResolvableType beanType = mbd.hasBeanClass() ?
832836
ResolvableType.forClass(mbd.getBeanClass()) :

spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
6666
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
6767
import org.springframework.beans.factory.config.Scope;
68+
import org.springframework.core.AttributeAccessor;
6869
import org.springframework.core.DecoratingClassLoader;
6970
import org.springframework.core.NamedThreadLocal;
7071
import org.springframework.core.ResolvableType;
@@ -1607,7 +1608,8 @@ protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) {
16071608
* already. Implementations are only allowed to instantiate the factory bean if
16081609
* {@code allowInit} is {@code true}, otherwise they should try to determine the
16091610
* result through other means.
1610-
* <p>If {@code allowInit} is {@code true}, the default implementation will create
1611+
* <p>If no {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} if set on the bean definition
1612+
* and {@code allowInit} is {@code true}, the default implementation will create
16111613
* the FactoryBean via {@code getBean} to call its {@code getObjectType} method.
16121614
* Subclasses are encouraged to optimize this, typically by inspecting the generic
16131615
* signature of the factory bean class or the factory method that creates it. If
@@ -1617,7 +1619,7 @@ protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) {
16171619
* fallback.
16181620
* @param beanName the name of the bean
16191621
* @param mbd the merged bean definition for the bean
1620-
* @param allowInit if initialization of the bean is permitted
1622+
* @param allowInit if initialization of the FactoryBean is permitted
16211623
* @return the type for the bean if determinable, otherwise {@code ResolvableType.NONE}
16221624
* @since 5.2
16231625
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
@@ -1626,6 +1628,11 @@ protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) {
16261628
protected ResolvableType getTypeForFactoryBean(String beanName,
16271629
RootBeanDefinition mbd, boolean allowInit) {
16281630

1631+
ResolvableType result = getTypeForFactoryBeanFromAttributes(mbd);
1632+
if (result != ResolvableType.NONE) {
1633+
return result;
1634+
}
1635+
16291636
if (allowInit && mbd.isSingleton()) {
16301637
try {
16311638
FactoryBean<?> factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true);
@@ -1648,6 +1655,25 @@ else if (mbd.isLazyInit()) {
16481655
return ResolvableType.NONE;
16491656
}
16501657

1658+
/**
1659+
* Determine the bean type for a FactoryBean by inspecting its attributes for a
1660+
* {@link FactoryBean#OBJECT_TYPE_ATTRIBUTE} value.
1661+
* @param attributes the attributes to inspect
1662+
* @return a {@link ResolvableType} extracted from the attributes or
1663+
* {@code ResolvableType.NONE}
1664+
* @since 5.2
1665+
*/
1666+
ResolvableType getTypeForFactoryBeanFromAttributes(AttributeAccessor attributes) {
1667+
Object attribute = attributes.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
1668+
if (attribute instanceof ResolvableType) {
1669+
return (ResolvableType) attribute;
1670+
}
1671+
if (attribute instanceof Class) {
1672+
return ResolvableType.forClass((Class<?>) attribute);
1673+
}
1674+
return ResolvableType.NONE;
1675+
}
1676+
16511677
/**
16521678
* Determine the bean type for the given FactoryBean definition, as far as possible.
16531679
* Only called if there is no singleton instance registered for the target bean already.

spring-context/src/test/java/org/springframework/context/annotation/ConfigurationWithFactoryBeanBeanEarlyDeductionTests.java

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@
2222

2323
import org.springframework.beans.BeansException;
2424
import org.springframework.beans.factory.FactoryBean;
25+
import org.springframework.beans.factory.config.BeanDefinition;
2526
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
2627
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2728
import org.springframework.beans.factory.support.AbstractBeanFactory;
29+
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
30+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
31+
import org.springframework.core.type.AnnotationMetadata;
2832

2933
import static org.assertj.core.api.Assertions.assertThat;
3034

@@ -66,6 +70,16 @@ public void postFreezeGenericClass() {
6670
assertPostFreeze(GenericClassConfiguration.class);
6771
}
6872

73+
@Test
74+
public void preFreezeAttribute() {
75+
assertPreFreeze(AttributeClassConfiguration.class);
76+
}
77+
78+
@Test
79+
public void postFreezeAttribute() {
80+
assertPostFreeze(AttributeClassConfiguration.class);
81+
}
82+
6983
private void assertPostFreeze(Class<?> configurationClass) {
7084
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
7185
configurationClass);
@@ -138,7 +152,29 @@ MyFactoryBean myBean() {
138152

139153
}
140154

141-
static class MyBean {
155+
@Configuration
156+
@Import(AttributeClassRegistrar.class)
157+
static class AttributeClassConfiguration {
158+
159+
}
160+
161+
static class AttributeClassRegistrar implements ImportBeanDefinitionRegistrar {
162+
163+
@Override
164+
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
165+
BeanDefinitionRegistry registry) {
166+
BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(
167+
RawWithAbstractObjectTypeFactoryBean.class).getBeanDefinition();
168+
definition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, MyBean.class);
169+
registry.registerBeanDefinition("myBean", definition);
170+
}
171+
172+
}
173+
174+
abstract static class AbstractMyBean {
175+
}
176+
177+
static class MyBean extends AbstractMyBean {
142178
}
143179

144180
static class TestFactoryBean<T> implements FactoryBean<T> {
@@ -169,4 +205,20 @@ public MyFactoryBean() {
169205

170206
}
171207

208+
static class RawWithAbstractObjectTypeFactoryBean implements FactoryBean<Object> {
209+
210+
private final Object object = new MyBean();
211+
212+
@Override
213+
public Object getObject() throws Exception {
214+
return object;
215+
}
216+
217+
@Override
218+
public Class<?> getObjectType() {
219+
return MyBean.class;
220+
}
221+
222+
}
223+
172224
}

0 commit comments

Comments
 (0)