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
This commit is contained in:
parent
527876d9a0
commit
a0e462581f
|
|
@ -71,7 +71,6 @@ import org.springframework.beans.factory.config.InstantiationAwareBeanPostProces
|
|||
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.TypedStringValue;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
|
|
@ -82,6 +81,7 @@ import org.springframework.util.Assert;
|
|||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -815,46 +815,51 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
* if present to determine the object type. If not present, i.e. the FactoryBean is
|
||||
* declared as a raw type, checks the FactoryBean's {@code getObjectType} method
|
||||
* on a plain instance of the FactoryBean, without bean properties applied yet.
|
||||
* If this doesn't return a type yet, a full creation of the FactoryBean is
|
||||
* used as fallback (through delegation to the superclass's implementation).
|
||||
* If this doesn't return a type yet, and {@code allowInit} is {@code true} a
|
||||
* full creation of the FactoryBean is used as fallback (through delegation to the
|
||||
* superclass's implementation).
|
||||
* <p>The shortcut check for a FactoryBean is only applied in case of a singleton
|
||||
* FactoryBean. If the FactoryBean instance itself is not kept as singleton,
|
||||
* it will be fully created to check the type of its exposed object.
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) {
|
||||
protected ResolvableType getTypeForFactoryBean(String beanName,
|
||||
RootBeanDefinition mbd, boolean allowInit) {
|
||||
|
||||
ResolvableType result = ResolvableType.NONE;
|
||||
|
||||
ResolvableType beanType = mbd.hasBeanClass() ?
|
||||
ResolvableType.forClass(mbd.getBeanClass()) :
|
||||
ResolvableType.NONE;
|
||||
|
||||
// For instance supplied beans try the target type and bean class
|
||||
if (mbd.getInstanceSupplier() != null) {
|
||||
ResolvableType targetType = mbd.targetType;
|
||||
if (targetType != null) {
|
||||
Class<?> result = targetType.as(FactoryBean.class).getGeneric().resolve();
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
result = getFactoryBeanGeneric(mbd.targetType);
|
||||
if (result.resolve() != null) {
|
||||
return result;
|
||||
}
|
||||
if (mbd.hasBeanClass()) {
|
||||
Class<?> result = GenericTypeResolver.resolveTypeArgument(mbd.getBeanClass(), FactoryBean.class);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
result = getFactoryBeanGeneric(beanType);
|
||||
if (result.resolve() != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Consider factory methods
|
||||
String factoryBeanName = mbd.getFactoryBeanName();
|
||||
String factoryMethodName = mbd.getFactoryMethodName();
|
||||
|
||||
// Scan the factory bean methods
|
||||
if (factoryBeanName != null) {
|
||||
if (factoryMethodName != null) {
|
||||
// Try to obtain the FactoryBean's object type from its factory method declaration
|
||||
// without instantiating the containing bean at all.
|
||||
BeanDefinition fbDef = getBeanDefinition(factoryBeanName);
|
||||
if (fbDef instanceof AbstractBeanDefinition) {
|
||||
AbstractBeanDefinition afbDef = (AbstractBeanDefinition) fbDef;
|
||||
if (afbDef.hasBeanClass()) {
|
||||
Class<?> result = getTypeForFactoryBeanFromMethod(afbDef.getBeanClass(), factoryMethodName);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
// Try to obtain the FactoryBean's object type from its factory method
|
||||
// declaration without instantiating the containing bean at all.
|
||||
BeanDefinition factoryBeanDefinition = getBeanDefinition(factoryBeanName);
|
||||
if (factoryBeanDefinition instanceof AbstractBeanDefinition &&
|
||||
((AbstractBeanDefinition) factoryBeanDefinition).hasBeanClass()) {
|
||||
Class<?> factoryBeanClass = ((AbstractBeanDefinition) factoryBeanDefinition).getBeanClass();
|
||||
result = getTypeForFactoryBeanFromMethod(factoryBeanClass, factoryMethodName);
|
||||
if (result.resolve() != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -862,40 +867,44 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
// exit here - we don't want to force the creation of another bean just to
|
||||
// obtain a FactoryBean's object type...
|
||||
if (!isBeanEligibleForMetadataCaching(factoryBeanName)) {
|
||||
return null;
|
||||
return ResolvableType.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
// Let's obtain a shortcut instance for an early getObjectType() call...
|
||||
FactoryBean<?> fb = (mbd.isSingleton() ?
|
||||
getSingletonFactoryBeanForTypeCheck(beanName, mbd) :
|
||||
getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));
|
||||
|
||||
if (fb != null) {
|
||||
// Try to obtain the FactoryBean's object type from this early stage of the instance.
|
||||
Class<?> result = getTypeForFactoryBean(fb);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
// If we're allowed, we can create the factory bean and call getObjectType() early
|
||||
if (allowInit) {
|
||||
FactoryBean<?> factoryBean = (mbd.isSingleton() ?
|
||||
getSingletonFactoryBeanForTypeCheck(beanName, mbd) :
|
||||
getNonSingletonFactoryBeanForTypeCheck(beanName, mbd));
|
||||
if (factoryBean != null) {
|
||||
// Try to obtain the FactoryBean's object type from this early stage of the instance.
|
||||
Class<?> type = getTypeForFactoryBean(factoryBean);
|
||||
if (type != null) {
|
||||
return ResolvableType.forClass(type);
|
||||
}
|
||||
// No type found for shortcut FactoryBean instance:
|
||||
// fall back to full creation of the FactoryBean instance.
|
||||
return super.getTypeForFactoryBean(beanName, mbd);
|
||||
return super.getTypeForFactoryBean(beanName, mbd, allowInit);
|
||||
}
|
||||
}
|
||||
|
||||
if (factoryBeanName == null && mbd.hasBeanClass()) {
|
||||
if (factoryBeanName == null && mbd.hasBeanClass() && factoryMethodName != null) {
|
||||
// No early bean instantiation possible: determine FactoryBean's type from
|
||||
// static factory method signature or from class inheritance hierarchy...
|
||||
if (factoryMethodName != null) {
|
||||
return getTypeForFactoryBeanFromMethod(mbd.getBeanClass(), factoryMethodName);
|
||||
}
|
||||
else {
|
||||
return GenericTypeResolver.resolveTypeArgument(mbd.getBeanClass(), FactoryBean.class);
|
||||
}
|
||||
return getTypeForFactoryBeanFromMethod(mbd.getBeanClass(), factoryMethodName);
|
||||
}
|
||||
result = getFactoryBeanGeneric(beanType);
|
||||
if (result.resolve() != null) {
|
||||
return result;
|
||||
}
|
||||
return ResolvableType.NONE;
|
||||
}
|
||||
|
||||
return null;
|
||||
private ResolvableType getFactoryBeanGeneric(@Nullable ResolvableType type) {
|
||||
if (type == null) {
|
||||
return ResolvableType.NONE;
|
||||
}
|
||||
return type.as(FactoryBean.class).getGeneric();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -905,36 +914,30 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
* @param factoryMethodName the name of the factory method
|
||||
* @return the common {@code FactoryBean} object type, or {@code null} if none
|
||||
*/
|
||||
@Nullable
|
||||
private Class<?> getTypeForFactoryBeanFromMethod(Class<?> beanClass, final String factoryMethodName) {
|
||||
|
||||
/**
|
||||
* Holder used to keep a reference to a {@code Class} value.
|
||||
*/
|
||||
class Holder {
|
||||
|
||||
@Nullable
|
||||
Class<?> value = null;
|
||||
}
|
||||
|
||||
final Holder objectType = new Holder();
|
||||
|
||||
private ResolvableType getTypeForFactoryBeanFromMethod(Class<?> beanClass, String factoryMethodName) {
|
||||
// CGLIB subclass methods hide generic parameters; look at the original user class.
|
||||
Class<?> fbClass = ClassUtils.getUserClass(beanClass);
|
||||
Class<?> factoryBeanClass = ClassUtils.getUserClass(beanClass);
|
||||
FactoryBeanMethodTypeFinder finder = new FactoryBeanMethodTypeFinder(factoryMethodName);
|
||||
ReflectionUtils.doWithMethods(factoryBeanClass, finder, ReflectionUtils.USER_DECLARED_METHODS);
|
||||
return finder.getResult();
|
||||
}
|
||||
|
||||
// Find the given factory method, taking into account that in the case of
|
||||
// @Bean methods, there may be parameters present.
|
||||
ReflectionUtils.doWithMethods(fbClass, method -> {
|
||||
if (method.getName().equals(factoryMethodName) &&
|
||||
FactoryBean.class.isAssignableFrom(method.getReturnType())) {
|
||||
Class<?> currentType = GenericTypeResolver.resolveReturnTypeArgument(method, FactoryBean.class);
|
||||
if (currentType != null) {
|
||||
objectType.value = ClassUtils.determineCommonAncestor(currentType, objectType.value);
|
||||
}
|
||||
}
|
||||
}, ReflectionUtils.USER_DECLARED_METHODS);
|
||||
|
||||
return (objectType.value != null && Object.class != objectType.value ? objectType.value : null);
|
||||
/**
|
||||
* This implementation attempts to query the FactoryBean's generic parameter metadata
|
||||
* if present to determine the object type. If not present, i.e. the FactoryBean is
|
||||
* declared as a raw type, checks the FactoryBean's {@code getObjectType} method
|
||||
* on a plain instance of the FactoryBean, without bean properties applied yet.
|
||||
* If this doesn't return a type yet, a full creation of the FactoryBean is
|
||||
* used as fallback (through delegation to the superclass's implementation).
|
||||
* <p>The shortcut check for a FactoryBean is only applied in case of a singleton
|
||||
* FactoryBean. If the FactoryBean instance itself is not kept as singleton,
|
||||
* it will be fully created to check the type of its exposed object.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
@Nullable
|
||||
protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) {
|
||||
return getTypeForFactoryBean(beanName, mbd, true).resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1983,4 +1986,51 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MethodCallback} used to find {@link FactoryBean} type information.
|
||||
*/
|
||||
private static class FactoryBeanMethodTypeFinder implements MethodCallback {
|
||||
|
||||
private final String factoryMethodName;
|
||||
|
||||
private ResolvableType result = ResolvableType.NONE;
|
||||
|
||||
|
||||
FactoryBeanMethodTypeFinder(String factoryMethodName) {
|
||||
this.factoryMethodName = factoryMethodName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
if (isFactoryBeanMethod(method)) {
|
||||
ResolvableType returnType = ResolvableType.forMethodReturnType(method);
|
||||
ResolvableType candidate = returnType.as(FactoryBean.class).getGeneric();
|
||||
if (this.result == ResolvableType.NONE) {
|
||||
this.result = candidate;
|
||||
}
|
||||
else {
|
||||
Class<?> resolvedResult = this.result.resolve();
|
||||
Class<?> commonAncestor = ClassUtils.determineCommonAncestor(candidate.resolve(), resolvedResult);
|
||||
if (!ObjectUtils.nullSafeEquals(resolvedResult, commonAncestor)) {
|
||||
this.result = ResolvableType.forClass(commonAncestor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFactoryBeanMethod(Method method) {
|
||||
return method.getName().equals(this.factoryMethodName) &&
|
||||
FactoryBean.class.isAssignableFrom(method.getReturnType());
|
||||
}
|
||||
|
||||
|
||||
ResolvableType getResult() {
|
||||
Class<?> resolved = this.result.resolve();
|
||||
boolean foundResult = resolved != null && resolved != Object.class;
|
||||
return (foundResult ? this.result : ResolvableType.NONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ import org.springframework.core.DecoratingClassLoader;
|
|||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
|
@ -488,13 +489,35 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
|
||||
@Override
|
||||
public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException {
|
||||
return isTypeMatch(name, typeToMatch, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal extended variant of {@link #isTypeMatch(String, ResolvableType)}
|
||||
* to check whether the bean with the given name matches the specified type. Allow
|
||||
* additional constraints to be applied to ensure that beans are not created early.
|
||||
* @param name the name of the bean to query
|
||||
* @param typeToMatch the type to match against (as a
|
||||
* {@code ResolvableType})
|
||||
* @return {@code true} if the bean type matches, {@code false} if it
|
||||
* doesn't match or cannot be determined yet
|
||||
* @throws NoSuchBeanDefinitionException if there is no bean with the given
|
||||
* name
|
||||
* @since 5.2
|
||||
* @see #getBean
|
||||
* @see #getType
|
||||
*/
|
||||
boolean isTypeMatch(String name, ResolvableType typeToMatch,
|
||||
boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException {
|
||||
|
||||
String beanName = transformedBeanName(name);
|
||||
boolean isFactoryDereference = BeanFactoryUtils.isFactoryDereference(name);
|
||||
|
||||
// Check manually registered singletons.
|
||||
Object beanInstance = getSingleton(beanName, false);
|
||||
if (beanInstance != null && beanInstance.getClass() != NullBean.class) {
|
||||
if (beanInstance instanceof FactoryBean) {
|
||||
if (!BeanFactoryUtils.isFactoryDereference(name)) {
|
||||
if (!isFactoryDereference) {
|
||||
Class<?> type = getTypeForFactoryBean((FactoryBean<?>) beanInstance);
|
||||
return (type != null && typeToMatch.isAssignableFrom(type));
|
||||
}
|
||||
|
|
@ -502,7 +525,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
return typeToMatch.isInstance(beanInstance);
|
||||
}
|
||||
}
|
||||
else if (!BeanFactoryUtils.isFactoryDereference(name)) {
|
||||
else if (!isFactoryDereference) {
|
||||
if (typeToMatch.isInstance(beanInstance)) {
|
||||
// Direct match for exposed instance?
|
||||
return true;
|
||||
|
|
@ -544,7 +567,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
|
||||
// Retrieve corresponding bean definition.
|
||||
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
|
||||
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
|
||||
|
||||
// Setup the types that we want to match against
|
||||
Class<?> classToMatch = typeToMatch.resolve();
|
||||
if (classToMatch == null) {
|
||||
classToMatch = FactoryBean.class;
|
||||
|
|
@ -552,50 +577,75 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
Class<?>[] typesToMatch = (FactoryBean.class == classToMatch ?
|
||||
new Class<?>[] {classToMatch} : new Class<?>[] {FactoryBean.class, classToMatch});
|
||||
|
||||
// Check decorated bean definition, if any: We assume it'll be easier
|
||||
// to determine the decorated bean's type than the proxy's type.
|
||||
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
|
||||
if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) {
|
||||
RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd);
|
||||
Class<?> targetClass = predictBeanType(dbd.getBeanName(), tbd, typesToMatch);
|
||||
if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) {
|
||||
return typeToMatch.isAssignableFrom(targetClass);
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> beanType = predictBeanType(beanName, mbd, typesToMatch);
|
||||
if (beanType == null) {
|
||||
return false;
|
||||
}
|
||||
// Attempt to predict the bean type
|
||||
Class<?> predictedType = null;
|
||||
|
||||
// Check bean class whether we're dealing with a FactoryBean.
|
||||
if (FactoryBean.class.isAssignableFrom(beanType)) {
|
||||
if (!BeanFactoryUtils.isFactoryDereference(name) && beanInstance == null) {
|
||||
// If it's a FactoryBean, we want to look at what it creates, not the factory class.
|
||||
beanType = getTypeForFactoryBean(beanName, mbd);
|
||||
if (beanType == null) {
|
||||
return false;
|
||||
// We're looking for a regular reference but we're a factory bean that has
|
||||
// a decorated bean definition. The target bean should be the same type
|
||||
// as FactoryBean would ultimately return.
|
||||
if (!isFactoryDereference && dbd != null && isFactoryBean(beanName, mbd)) {
|
||||
// We should only attempt if the user explicitly set lazy-init to true
|
||||
// and we know the merged bean definition is for a factory bean.
|
||||
if (!mbd.isLazyInit() || allowFactoryBeanInit) {
|
||||
RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd);
|
||||
Class<?> targetType = predictBeanType(dbd.getBeanName(), tbd, typesToMatch);
|
||||
if (targetType != null && !FactoryBean.class.isAssignableFrom(targetType)) {
|
||||
predictedType = targetType;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (BeanFactoryUtils.isFactoryDereference(name)) {
|
||||
// Special case: A SmartInstantiationAwareBeanPostProcessor returned a non-FactoryBean
|
||||
// type but we nevertheless are being asked to dereference a FactoryBean...
|
||||
// Let's check the original bean class and proceed with it if it is a FactoryBean.
|
||||
beanType = predictBeanType(beanName, mbd, FactoryBean.class);
|
||||
if (beanType == null || !FactoryBean.class.isAssignableFrom(beanType)) {
|
||||
|
||||
// If we couldn't use the target type, try regular prediction.
|
||||
if (predictedType == null) {
|
||||
predictedType = predictBeanType(beanName, mbd, typesToMatch);
|
||||
if (predictedType == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ResolvableType resolvableType = mbd.targetType;
|
||||
if (resolvableType == null) {
|
||||
resolvableType = mbd.factoryMethodReturnType;
|
||||
// Attempt to get the actual ResolvableType for the bean.
|
||||
ResolvableType beanType = null;
|
||||
|
||||
// If it's a FactoryBean, we want to look at what it creates, not the factory class.
|
||||
if (FactoryBean.class.isAssignableFrom(predictedType)) {
|
||||
if (beanInstance == null && !isFactoryDereference) {
|
||||
beanType = getTypeForFactoryBean(beanName, mbd, allowFactoryBeanInit);
|
||||
predictedType = (beanType != null) ? beanType.resolve() : null;
|
||||
if (predictedType == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resolvableType != null && resolvableType.resolve() == beanType) {
|
||||
return typeToMatch.isAssignableFrom(resolvableType);
|
||||
else if (isFactoryDereference) {
|
||||
// Special case: A SmartInstantiationAwareBeanPostProcessor returned a non-FactoryBean
|
||||
// type but we nevertheless are being asked to dereference a FactoryBean...
|
||||
// Let's check the original bean class and proceed with it if it is a FactoryBean.
|
||||
predictedType = predictBeanType(beanName, mbd, FactoryBean.class);
|
||||
if (predictedType == null || !FactoryBean.class.isAssignableFrom(predictedType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return typeToMatch.isAssignableFrom(beanType);
|
||||
|
||||
// We don't have an exact type but if bean definition target type or the factory
|
||||
// method return type matches the predicted type then we can use that.
|
||||
if (beanType == null) {
|
||||
ResolvableType definedType = mbd.targetType;
|
||||
if (definedType == null) {
|
||||
definedType = mbd.factoryMethodReturnType;
|
||||
}
|
||||
if (definedType != null && definedType.resolve() == predictedType) {
|
||||
beanType = definedType;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a bean type use it so that generics are considered
|
||||
if (beanType != null) {
|
||||
return typeToMatch.isAssignableFrom(beanType);
|
||||
}
|
||||
|
||||
// If we don't have a bean type, fallback to the predicted type
|
||||
return typeToMatch.isAssignableFrom(predictedType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -645,7 +695,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
if (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)) {
|
||||
if (!BeanFactoryUtils.isFactoryDereference(name)) {
|
||||
// If it's a FactoryBean, we want to look at what it creates, not at the factory class.
|
||||
return getTypeForFactoryBean(beanName, mbd);
|
||||
return getTypeForFactoryBean(beanName, mbd, true).resolve();
|
||||
}
|
||||
else {
|
||||
return beanClass;
|
||||
|
|
@ -1551,6 +1601,53 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. 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
|
||||
* 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
|
||||
* subclasses do instantiate the FactoryBean, they should consider trying the
|
||||
* {@code getObjectType} method without fully populating the bean. If this fails, a
|
||||
* full FactoryBean creation as performed by this implementation should be used as
|
||||
* 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
|
||||
* @return the type for the bean if determinable, otherwise {@code ResolvableType.NONE}
|
||||
* @since 5.2
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
|
||||
* @see #getBean(String)
|
||||
*/
|
||||
protected ResolvableType getTypeForFactoryBean(String beanName,
|
||||
RootBeanDefinition mbd, boolean allowInit) {
|
||||
|
||||
if (allowInit && mbd.isSingleton()) {
|
||||
try {
|
||||
FactoryBean<?> factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true);
|
||||
Class<?> objectType = getTypeForFactoryBean(factoryBean);
|
||||
return (objectType != null) ? ResolvableType.forClass(objectType) : ResolvableType.NONE;
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
if (ex.contains(BeanCurrentlyInCreationException.class)) {
|
||||
logger.trace(LogMessage.format("Bean currently in creation on FactoryBean type check: %s", ex));
|
||||
}
|
||||
else if (mbd.isLazyInit()) {
|
||||
logger.trace(LogMessage.format("Bean creation exception on lazy FactoryBean type check: %s", ex));
|
||||
}
|
||||
else {
|
||||
logger.debug(LogMessage.format("Bean creation exception on non-lazy FactoryBean type check: %s", ex));
|
||||
}
|
||||
onSuppressedException(ex);
|
||||
}
|
||||
}
|
||||
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.
|
||||
|
|
@ -1565,35 +1662,12 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
|||
* @return the type for the bean if determinable, or {@code null} otherwise
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
|
||||
* @see #getBean(String)
|
||||
* @deprecated since 5.2 in favor of {@link #getTypeForFactoryBean(String, RootBeanDefinition, boolean)}
|
||||
*/
|
||||
@Nullable
|
||||
@Deprecated
|
||||
protected Class<?> getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) {
|
||||
if (!mbd.isSingleton()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
FactoryBean<?> factoryBean = doGetBean(FACTORY_BEAN_PREFIX + beanName, FactoryBean.class, null, true);
|
||||
return getTypeForFactoryBean(factoryBean);
|
||||
}
|
||||
catch (BeanCreationException ex) {
|
||||
if (ex.contains(BeanCurrentlyInCreationException.class)) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Bean currently in creation on FactoryBean type check: " + ex);
|
||||
}
|
||||
}
|
||||
else if (mbd.isLazyInit()) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Bean creation exception on lazy FactoryBean type check: " + ex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Bean creation exception on non-lazy FactoryBean type check: " + ex);
|
||||
}
|
||||
}
|
||||
onSuppressedException(ex);
|
||||
return null;
|
||||
}
|
||||
return getTypeForFactoryBean(beanName, mbd, true).resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ import org.springframework.core.ResolvableType;
|
|||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
|
@ -514,48 +515,47 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
|||
if (!mbd.isAbstract() && (allowEagerInit ||
|
||||
(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
|
||||
!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
|
||||
// In case of FactoryBean, match object created by FactoryBean.
|
||||
boolean isFactoryBean = isFactoryBean(beanName, mbd);
|
||||
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
|
||||
boolean matchFound =
|
||||
(allowEagerInit || !isFactoryBean ||
|
||||
(dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
|
||||
(includeNonSingletons ||
|
||||
(dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
|
||||
isTypeMatch(beanName, type);
|
||||
if (!matchFound && isFactoryBean) {
|
||||
// In case of FactoryBean, try to match FactoryBean instance itself next.
|
||||
beanName = FACTORY_BEAN_PREFIX + beanName;
|
||||
matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
|
||||
boolean matchFound = false;
|
||||
boolean allowFactoryBeanInit = allowEagerInit || containsSingleton(beanName);
|
||||
boolean isNonLazyDecorated = dbd != null && !mbd.isLazyInit();
|
||||
if (!isFactoryBean) {
|
||||
if (includeNonSingletons || isSingleton(beanName, mbd, dbd)) {
|
||||
matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (includeNonSingletons || isNonLazyDecorated ||
|
||||
(allowFactoryBeanInit && isSingleton(beanName, mbd, dbd))) {
|
||||
matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
|
||||
}
|
||||
if (!matchFound) {
|
||||
// In case of FactoryBean, try to match FactoryBean instance itself next.
|
||||
beanName = FACTORY_BEAN_PREFIX + beanName;
|
||||
matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
|
||||
}
|
||||
}
|
||||
if (matchFound) {
|
||||
result.add(beanName);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (CannotLoadBeanClassException ex) {
|
||||
catch (CannotLoadBeanClassException | BeanDefinitionStoreException ex) {
|
||||
if (allowEagerInit) {
|
||||
throw ex;
|
||||
}
|
||||
// Probably a class name with a placeholder: let's ignore it for type matching purposes.
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Ignoring bean class loading failure for bean '" + beanName + "'", ex);
|
||||
}
|
||||
onSuppressedException(ex);
|
||||
}
|
||||
catch (BeanDefinitionStoreException ex) {
|
||||
if (allowEagerInit) {
|
||||
throw ex;
|
||||
}
|
||||
// Probably some metadata with a placeholder: let's ignore it for type matching purposes.
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Ignoring unresolvable metadata in bean definition '" + beanName + "'", ex);
|
||||
}
|
||||
// Probably a placeholder: let's ignore it for type matching purposes.
|
||||
LogMessage message = (ex instanceof CannotLoadBeanClassException) ?
|
||||
LogMessage.format("Ignoring bean class loading failure for bean '%s'", beanName) :
|
||||
LogMessage.format("Ignoring unresolvable metadata in bean definition '%s'", beanName);
|
||||
logger.trace(message, ex);
|
||||
onSuppressedException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check manually registered singletons too.
|
||||
for (String beanName : this.manualSingletonNames) {
|
||||
try {
|
||||
|
|
@ -576,15 +576,17 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
|||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
// Shouldn't happen - probably a result of circular reference resolution...
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Failed to check manually registered singleton with name '" + beanName + "'", ex);
|
||||
}
|
||||
logger.trace(LogMessage.format("Failed to check manually registered singleton with name '%s'", beanName), ex);
|
||||
}
|
||||
}
|
||||
|
||||
return StringUtils.toStringArray(result);
|
||||
}
|
||||
|
||||
private boolean isSingleton(String beanName, RootBeanDefinition mbd, BeanDefinitionHolder dbd) {
|
||||
return (dbd != null) ? mbd.isSingleton() : isSingleton(beanName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the specified bean would need to be eagerly initialized
|
||||
* in order to determine its type.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.context.annotation;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.AbstractBeanFactory;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Test for {@link AbstractBeanFactory} type inference from
|
||||
* {@link FactoryBean FactoryBeans} defined in the configuration.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class ConfigurationWithFactoryBeanBeanEarlyDeductionTests {
|
||||
|
||||
@Test
|
||||
public void preFreezeDirect() {
|
||||
assertPreFreeze(DirectConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postFreezeDirect() {
|
||||
assertPostFreeze(DirectConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preFreezeGenericMethod() {
|
||||
assertPreFreeze(GenericMethodConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postFreezeGenericMethod() {
|
||||
assertPostFreeze(GenericMethodConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preFreezeGenericClass() {
|
||||
assertPreFreeze(GenericClassConfiguration.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postFreezeGenericClass() {
|
||||
assertPostFreeze(GenericClassConfiguration.class);
|
||||
}
|
||||
|
||||
private void assertPostFreeze(Class<?> configurationClass) {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
configurationClass);
|
||||
assertContainsMyBeanName(context);
|
||||
}
|
||||
|
||||
private void assertPreFreeze(Class<?> configurationClass,
|
||||
BeanFactoryPostProcessor... postProcessors) {
|
||||
NameCollectingBeanFactoryPostProcessor postProcessor = new NameCollectingBeanFactoryPostProcessor();
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
|
||||
Arrays.stream(postProcessors).forEach(context::addBeanFactoryPostProcessor);
|
||||
context.addBeanFactoryPostProcessor(postProcessor);
|
||||
context.register(configurationClass);
|
||||
context.refresh();
|
||||
assertContainsMyBeanName(postProcessor.getNames());
|
||||
}
|
||||
|
||||
private void assertContainsMyBeanName(AnnotationConfigApplicationContext context) {
|
||||
assertContainsMyBeanName(context.getBeanNamesForType(MyBean.class, true, false));
|
||||
}
|
||||
|
||||
private void assertContainsMyBeanName(String[] names) {
|
||||
assertThat(names).containsExactly("myBean");
|
||||
}
|
||||
|
||||
private static class NameCollectingBeanFactoryPostProcessor
|
||||
implements BeanFactoryPostProcessor {
|
||||
|
||||
private String[] names;
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
|
||||
throws BeansException {
|
||||
this.names = beanFactory.getBeanNamesForType(MyBean.class, true, false);
|
||||
}
|
||||
|
||||
public String[] getNames() {
|
||||
return this.names;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class DirectConfiguration {
|
||||
|
||||
@Bean
|
||||
MyBean myBean() {
|
||||
return new MyBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class GenericMethodConfiguration {
|
||||
|
||||
@Bean
|
||||
FactoryBean<MyBean> myBean() {
|
||||
return new TestFactoryBean<>(new MyBean());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class GenericClassConfiguration {
|
||||
|
||||
@Bean
|
||||
MyFactoryBean myBean() {
|
||||
return new MyFactoryBean();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MyBean {
|
||||
}
|
||||
|
||||
static class TestFactoryBean<T> implements FactoryBean<T> {
|
||||
|
||||
private final T instance;
|
||||
|
||||
public TestFactoryBean(T instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getObject() throws Exception {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return this.instance.getClass();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MyFactoryBean extends TestFactoryBean<MyBean> {
|
||||
|
||||
public MyFactoryBean() {
|
||||
super(new MyBean());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue