Skip to content

Commit b0eacd2

Browse files
committed
Support for exposing additional object types in SmartFactoryBean
Closes gh-35101
1 parent 3bf9b0d commit b0eacd2

File tree

9 files changed

+289
-91
lines changed

9 files changed

+289
-91
lines changed

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

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,24 @@
1616

1717
package org.springframework.beans.factory;
1818

19+
import org.jspecify.annotations.Nullable;
20+
1921
/**
2022
* Extension of the {@link FactoryBean} interface. Implementations may
2123
* indicate whether they always return independent instances, for the
2224
* case where their {@link #isSingleton()} implementation returning
2325
* {@code false} does not clearly indicate independent instances.
24-
*
25-
* <p>Plain {@link FactoryBean} implementations which do not implement
26+
* Plain {@link FactoryBean} implementations which do not implement
2627
* this extended interface are simply assumed to always return independent
2728
* instances if their {@link #isSingleton()} implementation returns
2829
* {@code false}; the exposed object is only accessed on demand.
2930
*
31+
* <p>As of 7.0, this interface also allows for exposing additional object
32+
* types for dependency injection through implementing a pair of methods:
33+
* {@link #getObject(Class)} as well as {@link #supportsType(Class)}.
34+
* The primary {@link #getObjectType()} will be exposed for regular access.
35+
* Only if a specific type is requested, additional types are considered.
36+
*
3037
* <p><b>NOTE:</b> This interface is a special purpose interface, mainly for
3138
* internal use within the framework and within collaborating frameworks.
3239
* In general, application-provided FactoryBeans should simply implement
@@ -41,6 +48,42 @@
4148
*/
4249
public interface SmartFactoryBean<T> extends FactoryBean<T> {
4350

51+
/**
52+
* Return an instance of the given type, if supported by this factory.
53+
* <p>By default, this supports the primary type exposed by the factory, as
54+
* indicated by {@link #getObjectType()} and returned by {@link #getObject()}.
55+
* Specific factories may support additional types for dependency injection.
56+
* @param type the requested type
57+
* @return a corresponding instance managed by this factory,
58+
* or {@code null} if none available
59+
* @throws Exception in case of creation errors
60+
* @since 7.0
61+
* @see #getObject()
62+
* @see #supportsType(Class)
63+
*/
64+
@SuppressWarnings("unchecked")
65+
default <S> @Nullable S getObject(Class<S> type) throws Exception{
66+
Class<?> objectType = getObjectType();
67+
return (objectType != null && type.isAssignableFrom(objectType) ? (S) getObject() : null);
68+
}
69+
70+
/**
71+
* Determine whether this factory supports the requested type.
72+
* <p>By default, this supports the primary type exposed by the factory, as
73+
* indicated by {@link #getObjectType()}. Specific factories may support
74+
* additional types for dependency injection.
75+
* @param type the requested type
76+
* @return {@code true} if {@link #getObject(Class)} is able to
77+
* return a corresponding instance, {@code false} otherwise
78+
* @since 7.0
79+
* @see #getObject(Class)
80+
* @see #getObjectType()
81+
*/
82+
default boolean supportsType(Class<?> type) {
83+
Class<?> objectType = getObjectType();
84+
return (objectType != null && type.isAssignableFrom(objectType));
85+
}
86+
4487
/**
4588
* Is the object managed by this factory a prototype? That is,
4689
* will {@link #getObject()} always return an independent instance?

spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import org.springframework.beans.BeansException;
2929
import org.springframework.beans.factory.BeanFactory;
30+
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
3031
import org.springframework.beans.factory.InjectionPoint;
3132
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
3233
import org.springframework.core.MethodParameter;
@@ -210,7 +211,7 @@ public boolean isEager() {
210211
/**
211212
* Resolve the specified bean name, as a candidate result of the matching
212213
* algorithm for this dependency, to a bean instance from the given factory.
213-
* <p>The default implementation calls {@link BeanFactory#getBean(String)}.
214+
* <p>The default implementation calls {@link BeanFactory#getBean(String, Class)}.
214215
* Subclasses may provide additional arguments or other customizations.
215216
* @param beanName the bean name, as a candidate result for this dependency
216217
* @param requiredType the expected type of the bean (as an assertion)
@@ -223,7 +224,14 @@ public boolean isEager() {
223224
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
224225
throws BeansException {
225226

226-
return beanFactory.getBean(beanName);
227+
try {
228+
// Need to provide required type for SmartFactoryBean
229+
return beanFactory.getBean(beanName, requiredType);
230+
}
231+
catch (BeanNotOfRequiredTypeException ex) {
232+
// Probably a null bean...
233+
return beanFactory.getBean(beanName);
234+
}
227235
}
228236

229237

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,15 +1294,15 @@ private BeanWrapper obtainFromSupplier(Supplier<?> supplier, String beanName, Ro
12941294
* @see #obtainFromSupplier
12951295
*/
12961296
@Override
1297-
protected Object getObjectForBeanInstance(
1298-
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
1297+
protected Object getObjectForBeanInstance(Object beanInstance, @Nullable Class<?> requiredType,
1298+
String name, String beanName, @Nullable RootBeanDefinition mbd) {
12991299

13001300
String currentlyCreatedBean = this.currentlyCreatedBean.get();
13011301
if (currentlyCreatedBean != null) {
13021302
registerDependentBean(beanName, currentlyCreatedBean);
13031303
}
13041304

1305-
return super.getObjectForBeanInstance(beanInstance, name, beanName, mbd);
1305+
return super.getObjectForBeanInstance(beanInstance, requiredType, name, beanName, mbd);
13061306
}
13071307

13081308
/**

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ protected <T> T doGetBean(
252252
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
253253
}
254254
}
255-
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
255+
beanInstance = getObjectForBeanInstance(sharedInstance, requiredType, name, beanName, null);
256256
}
257257

258258
else {
@@ -340,7 +340,7 @@ else if (requiredType != null) {
340340
throw ex;
341341
}
342342
});
343-
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
343+
beanInstance = getObjectForBeanInstance(sharedInstance, requiredType, name, beanName, mbd);
344344
}
345345

346346
else if (mbd.isPrototype()) {
@@ -353,7 +353,7 @@ else if (mbd.isPrototype()) {
353353
finally {
354354
afterPrototypeCreation(beanName);
355355
}
356-
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
356+
beanInstance = getObjectForBeanInstance(prototypeInstance, requiredType, name, beanName, mbd);
357357
}
358358

359359
else {
@@ -375,7 +375,7 @@ else if (mbd.isPrototype()) {
375375
afterPrototypeCreation(beanName);
376376
}
377377
});
378-
beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
378+
beanInstance = getObjectForBeanInstance(scopedInstance, requiredType, name, beanName, mbd);
379379
}
380380
catch (IllegalStateException ex) {
381381
throw new ScopeNotActiveException(beanName, scopeName, ex);
@@ -536,6 +536,10 @@ protected boolean isTypeMatch(String name, ResolvableType typeToMatch, boolean a
536536
// Determine target for FactoryBean match if necessary.
537537
if (beanInstance instanceof FactoryBean<?> factoryBean) {
538538
if (!isFactoryDereference) {
539+
if (factoryBean instanceof SmartFactoryBean<?> smartFactoryBean &&
540+
smartFactoryBean.supportsType(typeToMatch.toClass())) {
541+
return true;
542+
}
539543
Class<?> type = getTypeForFactoryBean(factoryBean);
540544
if (type == null) {
541545
return false;
@@ -1835,8 +1839,8 @@ protected boolean hasBeanCreationStarted() {
18351839
* @param mbd the merged bean definition
18361840
* @return the object to expose for the bean
18371841
*/
1838-
protected Object getObjectForBeanInstance(
1839-
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
1842+
protected Object getObjectForBeanInstance(Object beanInstance, @Nullable Class<?> requiredType,
1843+
String name, String beanName, @Nullable RootBeanDefinition mbd) {
18401844

18411845
// Don't let calling code try to dereference the factory if the bean isn't a factory.
18421846
if (BeanFactoryUtils.isFactoryDereference(name)) {
@@ -1873,7 +1877,7 @@ protected Object getObjectForBeanInstance(
18731877
mbd = getMergedLocalBeanDefinition(beanName);
18741878
}
18751879
boolean synthetic = (mbd != null && mbd.isSynthetic());
1876-
object = getObjectFromFactoryBean(factoryBean, beanName, !synthetic);
1880+
object = getObjectFromFactoryBean(factoryBean, requiredType, beanName, !synthetic);
18771881
}
18781882
return object;
18791883
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,8 @@ else if (value instanceof String[] values) {
401401
Object innerBean = this.beanFactory.createBean(actualInnerBeanName, mbd, null);
402402
if (innerBean instanceof FactoryBean<?> factoryBean) {
403403
boolean synthetic = mbd.isSynthetic();
404-
innerBean = this.beanFactory.getObjectFromFactoryBean(factoryBean, actualInnerBeanName, !synthetic);
404+
innerBean = this.beanFactory.getObjectFromFactoryBean(
405+
factoryBean, null, actualInnerBeanName, !synthetic);
405406
}
406407
if (innerBean instanceof NullBean) {
407408
innerBean = null;

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,11 +730,14 @@ public <T> Map<String, T> getBeansOfType(
730730
Map<String, T> result = CollectionUtils.newLinkedHashMap(beanNames.length);
731731
for (String beanName : beanNames) {
732732
try {
733-
Object beanInstance = getBean(beanName);
733+
Object beanInstance = (type != null ? getBean(beanName, type) : getBean(beanName));
734734
if (!(beanInstance instanceof NullBean)) {
735735
result.put(beanName, (T) beanInstance);
736736
}
737737
}
738+
catch (BeanNotOfRequiredTypeException ex) {
739+
// Ignore - probably a NullBean
740+
}
738741
catch (BeanCreationException ex) {
739742
Throwable rootCause = ex.getMostSpecificCause();
740743
if (rootCause instanceof BeanCurrentlyInCreationException bce) {

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
2727
import org.springframework.beans.factory.FactoryBean;
2828
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
29+
import org.springframework.beans.factory.SmartFactoryBean;
2930
import org.springframework.core.AttributeAccessor;
3031
import org.springframework.core.ResolvableType;
3132

@@ -115,7 +116,9 @@ ResolvableType getFactoryBeanGeneric(@Nullable ResolvableType type) {
115116
* @throws BeanCreationException if FactoryBean object creation failed
116117
* @see org.springframework.beans.factory.FactoryBean#getObject()
117118
*/
118-
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
119+
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, @Nullable Class<?> requiredType,
120+
String beanName, boolean shouldPostProcess) {
121+
119122
if (factory.isSingleton() && containsSingleton(beanName)) {
120123
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
121124
boolean locked;
@@ -127,12 +130,14 @@ protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanNam
127130
locked = (lockFlag && this.singletonLock.tryLock());
128131
}
129132
try {
130-
Object object = this.factoryBeanObjectCache.get(beanName);
133+
// A SmartFactoryBean may return multiple object types -> do not cache.
134+
boolean smart = (factory instanceof SmartFactoryBean<?>);
135+
Object object = (!smart ? this.factoryBeanObjectCache.get(beanName) : null);
131136
if (object == null) {
132-
object = doGetObjectFromFactoryBean(factory, beanName);
137+
object = doGetObjectFromFactoryBean(factory, requiredType, beanName);
133138
// Only post-process and store if not put there already during getObject() call above
134139
// (for example, because of circular reference processing triggered by custom getBean calls)
135-
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
140+
Object alreadyThere = (!smart ? this.factoryBeanObjectCache.get(beanName) : null);
136141
if (alreadyThere != null) {
137142
object = alreadyThere;
138143
}
@@ -158,7 +163,7 @@ protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanNam
158163
}
159164
}
160165
}
161-
if (containsSingleton(beanName)) {
166+
if (!smart && containsSingleton(beanName)) {
162167
this.factoryBeanObjectCache.put(beanName, object);
163168
}
164169
}
@@ -172,7 +177,7 @@ protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanNam
172177
}
173178
}
174179
else {
175-
Object object = doGetObjectFromFactoryBean(factory, beanName);
180+
Object object = doGetObjectFromFactoryBean(factory, requiredType, beanName);
176181
if (shouldPostProcess) {
177182
try {
178183
object = postProcessObjectFromFactoryBean(object, beanName);
@@ -193,10 +198,13 @@ protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanNam
193198
* @throws BeanCreationException if FactoryBean object creation failed
194199
* @see org.springframework.beans.factory.FactoryBean#getObject()
195200
*/
196-
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
201+
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, @Nullable Class<?> requiredType, String beanName)
202+
throws BeanCreationException {
203+
197204
Object object;
198205
try {
199-
object = factory.getObject();
206+
object = (requiredType != null && factory instanceof SmartFactoryBean<?> smartFactoryBean ?
207+
smartFactoryBean.getObject(requiredType) : factory.getObject());
200208
}
201209
catch (FactoryBeanNotInitializedException ex) {
202210
throw new BeanCurrentlyInCreationException(beanName, ex.toString());

0 commit comments

Comments
 (0)