Skip to content

Commit a0e4625

Browse files
philwebbjhoeller
authored andcommitted
Consider generics for predicting FactoryBean types
Update the `FactoryBean` type prediction logic (primarily in the `DefaultListableBeanFactory`) so that generic type information is considered when calling `getBeanNamesForType` on a non-frozen configuration. Calling `getBeanNamesForType` with `allowEagerInit` disabled will now detect `FactoryBean` variants as long as generic type information is available in either the class or the factory method return type. Closes gh-23338
1 parent 527876d commit a0e4625

File tree

4 files changed

+458
-160
lines changed

4 files changed

+458
-160
lines changed

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

Lines changed: 126 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
7272
import org.springframework.beans.factory.config.TypedStringValue;
7373
import org.springframework.core.DefaultParameterNameDiscoverer;
74-
import org.springframework.core.GenericTypeResolver;
7574
import org.springframework.core.MethodParameter;
7675
import org.springframework.core.NamedThreadLocal;
7776
import org.springframework.core.ParameterNameDiscoverer;
@@ -82,6 +81,7 @@
8281
import org.springframework.util.ClassUtils;
8382
import org.springframework.util.ObjectUtils;
8483
import org.springframework.util.ReflectionUtils;
84+
import org.springframework.util.ReflectionUtils.MethodCallback;
8585
import org.springframework.util.StringUtils;
8686

8787
/**
@@ -815,87 +815,96 @@ protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition m
815815
* if present to determine the object type. If not present, i.e. the FactoryBean is
816816
* declared as a raw type, checks the FactoryBean's {@code getObjectType} method
817817
* on a plain instance of the FactoryBean, without bean properties applied yet.
818-
* If this doesn't return a type yet, a full creation of the FactoryBean is
819-
* used as fallback (through delegation to the superclass's implementation).
818+
* If this doesn't return a type yet, and {@code allowInit} is {@code true} a
819+
* full creation of the FactoryBean is used as fallback (through delegation to the
820+
* superclass's implementation).
820821
* <p>The shortcut check for a FactoryBean is only applied in case of a singleton
821822
* FactoryBean. If the FactoryBean instance itself is not kept as singleton,
822823
* it will be fully created to check the type of its exposed object.
823824
*/
824825
@Override
825-
@Nullable
826-
protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) {
826+
protected ResolvableType getTypeForFactoryBean(String beanName,
827+
RootBeanDefinition mbd, boolean allowInit) {
828+
829+
ResolvableType result = ResolvableType.NONE;
830+
831+
ResolvableType beanType = mbd.hasBeanClass() ?
832+
ResolvableType.forClass(mbd.getBeanClass()) :
833+
ResolvableType.NONE;
834+
835+
// For instance supplied beans try the target type and bean class
827836
if (mbd.getInstanceSupplier() != null) {
828-
ResolvableType targetType = mbd.targetType;
829-
if (targetType != null) {
830-
Class<?> result = targetType.as(FactoryBean.class).getGeneric().resolve();
831-
if (result != null) {
832-
return result;
833-
}
837+
result = getFactoryBeanGeneric(mbd.targetType);
838+
if (result.resolve() != null) {
839+
return result;
834840
}
835-
if (mbd.hasBeanClass()) {
836-
Class<?> result = GenericTypeResolver.resolveTypeArgument(mbd.getBeanClass(), FactoryBean.class);
837-
if (result != null) {
838-
return result;
839-
}
841+
result = getFactoryBeanGeneric(beanType);
842+
if (result.resolve() != null) {
843+
return result;
840844
}
841845
}
842846

847+
// Consider factory methods
843848
String factoryBeanName = mbd.getFactoryBeanName();
844849
String factoryMethodName = mbd.getFactoryMethodName();
845850

851+
// Scan the factory bean methods
846852
if (factoryBeanName != null) {
847853
if (factoryMethodName != null) {
848-
// Try to obtain the FactoryBean's object type from its factory method declaration
849-
// without instantiating the containing bean at all.
850-
BeanDefinition fbDef = getBeanDefinition(factoryBeanName);
851-
if (fbDef instanceof AbstractBeanDefinition) {
852-
AbstractBeanDefinition afbDef = (AbstractBeanDefinition) fbDef;
853-
if (afbDef.hasBeanClass()) {
854-
Class<?> result = getTypeForFactoryBeanFromMethod(afbDef.getBeanClass(), factoryMethodName);
855-
if (result != null) {
856-
return result;
857-
}
854+
// Try to obtain the FactoryBean's object type from its factory method
855+
// declaration without instantiating the containing bean at all.
856+
BeanDefinition factoryBeanDefinition = getBeanDefinition(factoryBeanName);
857+
if (factoryBeanDefinition instanceof AbstractBeanDefinition &&
858+
((AbstractBeanDefinition) factoryBeanDefinition).hasBeanClass()) {
859+
Class<?> factoryBeanClass = ((AbstractBeanDefinition) factoryBeanDefinition).getBeanClass();
860+
result = getTypeForFactoryBeanFromMethod(factoryBeanClass, factoryMethodName);
861+
if (result.resolve() != null) {
862+
return result;
858863
}
859864
}
860865
}
861866
// If not resolvable above and the referenced factory bean doesn't exist yet,
862867
// exit here - we don't want to force the creation of another bean just to
863868
// obtain a FactoryBean's object type...
864869
if (!isBeanEligibleForMetadataCaching(factoryBeanName)) {
865-
return null;
870+
return ResolvableType.NONE;
866871
}
867872
}
868873

869-
// Let's obtain a shortcut instance for an early getObjectType() call...
870-
FactoryBean<?> fb = (mbd.isSingleton() ?
871-
getSingletonFactoryBeanForTypeCheck(beanName, mbd) :
872-
getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));
873-
874-
if (fb != null) {
875-
// Try to obtain the FactoryBean's object type from this early stage of the instance.
876-
Class<?> result = getTypeForFactoryBean(fb);
877-
if (result != null) {
878-
return result;
879-
}
880-
else {
874+
// If we're allowed, we can create the factory bean and call getObjectType() early
875+
if (allowInit) {
876+
FactoryBean<?> factoryBean = (mbd.isSingleton() ?
877+
getSingletonFactoryBeanForTypeCheck(beanName, mbd) :
878+
getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));
879+
if (factoryBean != null) {
880+
// Try to obtain the FactoryBean's object type from this early stage of the instance.
881+
Class<?> type = getTypeForFactoryBean(factoryBean);
882+
if (type != null) {
883+
return ResolvableType.forClass(type);
884+
}
881885
// No type found for shortcut FactoryBean instance:
882886
// fall back to full creation of the FactoryBean instance.
883-
return super.getTypeForFactoryBean(beanName, mbd);
887+
return super.getTypeForFactoryBean(beanName, mbd, allowInit);
884888
}
885889
}
886890

887-
if (factoryBeanName == null && mbd.hasBeanClass()) {
891+
if (factoryBeanName == null && mbd.hasBeanClass() && factoryMethodName != null) {
888892
// No early bean instantiation possible: determine FactoryBean's type from
889893
// static factory method signature or from class inheritance hierarchy...
890-
if (factoryMethodName != null) {
891-
return getTypeForFactoryBeanFromMethod(mbd.getBeanClass(), factoryMethodName);
892-
}
893-
else {
894-
return GenericTypeResolver.resolveTypeArgument(mbd.getBeanClass(), FactoryBean.class);
895-
}
894+
return getTypeForFactoryBeanFromMethod(mbd.getBeanClass(), factoryMethodName);
895+
}
896+
result = getFactoryBeanGeneric(beanType);
897+
if (result.resolve() != null) {
898+
return result;
896899
}
900+
return ResolvableType.NONE;
901+
}
897902

898-
return null;
903+
private ResolvableType getFactoryBeanGeneric(@Nullable ResolvableType type) {
904+
if (type == null) {
905+
return ResolvableType.NONE;
906+
}
907+
return type.as(FactoryBean.class).getGeneric();
899908
}
900909

901910
/**
@@ -905,36 +914,30 @@ protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd
905914
* @param factoryMethodName the name of the factory method
906915
* @return the common {@code FactoryBean} object type, or {@code null} if none
907916
*/
908-
@Nullable
909-
private Class<?> getTypeForFactoryBeanFromMethod(Class<?> beanClass, final String factoryMethodName) {
910-
911-
/**
912-
* Holder used to keep a reference to a {@code Class} value.
913-
*/
914-
class Holder {
915-
916-
@Nullable
917-
Class<?> value = null;
918-
}
919-
920-
final Holder objectType = new Holder();
921-
917+
private ResolvableType getTypeForFactoryBeanFromMethod(Class<?> beanClass, String factoryMethodName) {
922918
// CGLIB subclass methods hide generic parameters; look at the original user class.
923-
Class<?> fbClass = ClassUtils.getUserClass(beanClass);
924-
925-
// Find the given factory method, taking into account that in the case of
926-
// @Bean methods, there may be parameters present.
927-
ReflectionUtils.doWithMethods(fbClass, method -> {
928-
if (method.getName().equals(factoryMethodName) &&
929-
FactoryBean.class.isAssignableFrom(method.getReturnType())) {
930-
Class<?> currentType = GenericTypeResolver.resolveReturnTypeArgument(method, FactoryBean.class);
931-
if (currentType != null) {
932-
objectType.value = ClassUtils.determineCommonAncestor(currentType, objectType.value);
933-
}
934-
}
935-
}, ReflectionUtils.USER_DECLARED_METHODS);
919+
Class<?> factoryBeanClass = ClassUtils.getUserClass(beanClass);
920+
FactoryBeanMethodTypeFinder finder = new FactoryBeanMethodTypeFinder(factoryMethodName);
921+
ReflectionUtils.doWithMethods(factoryBeanClass, finder, ReflectionUtils.USER_DECLARED_METHODS);
922+
return finder.getResult();
923+
}
936924

937-
return (objectType.value != null && Object.class != objectType.value ? objectType.value : null);
925+
/**
926+
* This implementation attempts to query the FactoryBean's generic parameter metadata
927+
* if present to determine the object type. If not present, i.e. the FactoryBean is
928+
* declared as a raw type, checks the FactoryBean's {@code getObjectType} method
929+
* on a plain instance of the FactoryBean, without bean properties applied yet.
930+
* If this doesn't return a type yet, a full creation of the FactoryBean is
931+
* used as fallback (through delegation to the superclass's implementation).
932+
* <p>The shortcut check for a FactoryBean is only applied in case of a singleton
933+
* FactoryBean. If the FactoryBean instance itself is not kept as singleton,
934+
* it will be fully created to check the type of its exposed object.
935+
*/
936+
@Override
937+
@Deprecated
938+
@Nullable
939+
protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) {
940+
return getTypeForFactoryBean(beanName, mbd, true).resolve();
938941
}
939942

940943
/**
@@ -1983,4 +1986,51 @@ public String getDependencyName() {
19831986
}
19841987
}
19851988

1989+
/**
1990+
* {@link MethodCallback} used to find {@link FactoryBean} type information.
1991+
*/
1992+
private static class FactoryBeanMethodTypeFinder implements MethodCallback {
1993+
1994+
private final String factoryMethodName;
1995+
1996+
private ResolvableType result = ResolvableType.NONE;
1997+
1998+
1999+
FactoryBeanMethodTypeFinder(String factoryMethodName) {
2000+
this.factoryMethodName = factoryMethodName;
2001+
}
2002+
2003+
2004+
@Override
2005+
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
2006+
if (isFactoryBeanMethod(method)) {
2007+
ResolvableType returnType = ResolvableType.forMethodReturnType(method);
2008+
ResolvableType candidate = returnType.as(FactoryBean.class).getGeneric();
2009+
if (this.result == ResolvableType.NONE) {
2010+
this.result = candidate;
2011+
}
2012+
else {
2013+
Class<?> resolvedResult = this.result.resolve();
2014+
Class<?> commonAncestor = ClassUtils.determineCommonAncestor(candidate.resolve(), resolvedResult);
2015+
if (!ObjectUtils.nullSafeEquals(resolvedResult, commonAncestor)) {
2016+
this.result = ResolvableType.forClass(commonAncestor);
2017+
}
2018+
}
2019+
}
2020+
}
2021+
2022+
private boolean isFactoryBeanMethod(Method method) {
2023+
return method.getName().equals(this.factoryMethodName) &&
2024+
FactoryBean.class.isAssignableFrom(method.getReturnType());
2025+
}
2026+
2027+
2028+
ResolvableType getResult() {
2029+
Class<?> resolved = this.result.resolve();
2030+
boolean foundResult = resolved != null && resolved != Object.class;
2031+
return (foundResult ? this.result : ResolvableType.NONE);
2032+
}
2033+
2034+
}
2035+
19862036
}

0 commit comments

Comments
 (0)