Early resolution of unique factory methods in configuration classes
Includes consistent bean class resolution in the enhancement step as well as general reflection optimizations for user-declared methods. Closes gh-22420
This commit is contained in:
@@ -706,96 +706,100 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
return cachedReturnType.resolve();
|
||||
}
|
||||
|
||||
Class<?> factoryClass;
|
||||
boolean isStatic = true;
|
||||
|
||||
String factoryBeanName = mbd.getFactoryBeanName();
|
||||
if (factoryBeanName != null) {
|
||||
if (factoryBeanName.equals(beanName)) {
|
||||
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
|
||||
"factory-bean reference points back to the same bean definition");
|
||||
}
|
||||
// Check declared factory method return type on factory class.
|
||||
factoryClass = getType(factoryBeanName);
|
||||
isStatic = false;
|
||||
}
|
||||
else {
|
||||
// Check declared factory method return type on bean class.
|
||||
factoryClass = resolveBeanClass(mbd, beanName, typesToMatch);
|
||||
}
|
||||
|
||||
if (factoryClass == null) {
|
||||
return null;
|
||||
}
|
||||
factoryClass = ClassUtils.getUserClass(factoryClass);
|
||||
|
||||
// If all factory methods have the same return type, return that type.
|
||||
// Can't clearly figure out exact method due to type converting / autowiring!
|
||||
Class<?> commonType = null;
|
||||
Method uniqueCandidate = null;
|
||||
int minNrOfArgs =
|
||||
(mbd.hasConstructorArgumentValues() ? mbd.getConstructorArgumentValues().getArgumentCount() : 0);
|
||||
Method[] candidates = this.factoryMethodCandidateCache.computeIfAbsent(
|
||||
factoryClass, ReflectionUtils::getUniqueDeclaredMethods);
|
||||
Method uniqueCandidate = mbd.factoryMethodToIntrospect;
|
||||
|
||||
for (Method candidate : candidates) {
|
||||
if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate) &&
|
||||
candidate.getParameterCount() >= minNrOfArgs) {
|
||||
// Declared type variables to inspect?
|
||||
if (candidate.getTypeParameters().length > 0) {
|
||||
try {
|
||||
// Fully resolve parameter names and argument values.
|
||||
Class<?>[] paramTypes = candidate.getParameterTypes();
|
||||
String[] paramNames = null;
|
||||
ParameterNameDiscoverer pnd = getParameterNameDiscoverer();
|
||||
if (pnd != null) {
|
||||
paramNames = pnd.getParameterNames(candidate);
|
||||
}
|
||||
ConstructorArgumentValues cav = mbd.getConstructorArgumentValues();
|
||||
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length);
|
||||
Object[] args = new Object[paramTypes.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
ConstructorArgumentValues.ValueHolder valueHolder = cav.getArgumentValue(
|
||||
i, paramTypes[i], (paramNames != null ? paramNames[i] : null), usedValueHolders);
|
||||
if (valueHolder == null) {
|
||||
valueHolder = cav.getGenericArgumentValue(null, null, usedValueHolders);
|
||||
if (uniqueCandidate == null) {
|
||||
Class<?> factoryClass;
|
||||
boolean isStatic = true;
|
||||
|
||||
String factoryBeanName = mbd.getFactoryBeanName();
|
||||
if (factoryBeanName != null) {
|
||||
if (factoryBeanName.equals(beanName)) {
|
||||
throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
|
||||
"factory-bean reference points back to the same bean definition");
|
||||
}
|
||||
// Check declared factory method return type on factory class.
|
||||
factoryClass = getType(factoryBeanName);
|
||||
isStatic = false;
|
||||
}
|
||||
else {
|
||||
// Check declared factory method return type on bean class.
|
||||
factoryClass = resolveBeanClass(mbd, beanName, typesToMatch);
|
||||
}
|
||||
|
||||
if (factoryClass == null) {
|
||||
return null;
|
||||
}
|
||||
factoryClass = ClassUtils.getUserClass(factoryClass);
|
||||
|
||||
// If all factory methods have the same return type, return that type.
|
||||
// Can't clearly figure out exact method due to type converting / autowiring!
|
||||
int minNrOfArgs =
|
||||
(mbd.hasConstructorArgumentValues() ? mbd.getConstructorArgumentValues().getArgumentCount() : 0);
|
||||
Method[] candidates = this.factoryMethodCandidateCache.computeIfAbsent(factoryClass,
|
||||
clazz -> ReflectionUtils.getUniqueDeclaredMethods(clazz, ReflectionUtils.USER_DECLARED_METHODS));
|
||||
|
||||
for (Method candidate : candidates) {
|
||||
if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate) &&
|
||||
candidate.getParameterCount() >= minNrOfArgs) {
|
||||
// Declared type variables to inspect?
|
||||
if (candidate.getTypeParameters().length > 0) {
|
||||
try {
|
||||
// Fully resolve parameter names and argument values.
|
||||
Class<?>[] paramTypes = candidate.getParameterTypes();
|
||||
String[] paramNames = null;
|
||||
ParameterNameDiscoverer pnd = getParameterNameDiscoverer();
|
||||
if (pnd != null) {
|
||||
paramNames = pnd.getParameterNames(candidate);
|
||||
}
|
||||
if (valueHolder != null) {
|
||||
args[i] = valueHolder.getValue();
|
||||
usedValueHolders.add(valueHolder);
|
||||
ConstructorArgumentValues cav = mbd.getConstructorArgumentValues();
|
||||
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length);
|
||||
Object[] args = new Object[paramTypes.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
ConstructorArgumentValues.ValueHolder valueHolder = cav.getArgumentValue(
|
||||
i, paramTypes[i], (paramNames != null ? paramNames[i] : null), usedValueHolders);
|
||||
if (valueHolder == null) {
|
||||
valueHolder = cav.getGenericArgumentValue(null, null, usedValueHolders);
|
||||
}
|
||||
if (valueHolder != null) {
|
||||
args[i] = valueHolder.getValue();
|
||||
usedValueHolders.add(valueHolder);
|
||||
}
|
||||
}
|
||||
Class<?> returnType = AutowireUtils.resolveReturnTypeForFactoryMethod(
|
||||
candidate, args, getBeanClassLoader());
|
||||
uniqueCandidate = (commonType == null && returnType == candidate.getReturnType() ?
|
||||
candidate : null);
|
||||
commonType = ClassUtils.determineCommonAncestor(returnType, commonType);
|
||||
if (commonType == null) {
|
||||
// Ambiguous return types found: return null to indicate "not determinable".
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Class<?> returnType = AutowireUtils.resolveReturnTypeForFactoryMethod(
|
||||
candidate, args, getBeanClassLoader());
|
||||
uniqueCandidate = (commonType == null && returnType == candidate.getReturnType() ?
|
||||
candidate : null);
|
||||
commonType = ClassUtils.determineCommonAncestor(returnType, commonType);
|
||||
catch (Throwable ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to resolve generic return type for factory method: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
uniqueCandidate = (commonType == null ? candidate : null);
|
||||
commonType = ClassUtils.determineCommonAncestor(candidate.getReturnType(), commonType);
|
||||
if (commonType == null) {
|
||||
// Ambiguous return types found: return null to indicate "not determinable".
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Failed to resolve generic return type for factory method: " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
uniqueCandidate = (commonType == null ? candidate : null);
|
||||
commonType = ClassUtils.determineCommonAncestor(candidate.getReturnType(), commonType);
|
||||
if (commonType == null) {
|
||||
// Ambiguous return types found: return null to indicate "not determinable".
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mbd.factoryMethodToIntrospect = uniqueCandidate;
|
||||
if (commonType == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
mbd.factoryMethodToIntrospect = uniqueCandidate;
|
||||
if (commonType == null) {
|
||||
return null;
|
||||
}
|
||||
// Common return type found: all factory methods return same type. For a non-parameterized
|
||||
// unique candidate, cache the full type declaration context of the target factory method.
|
||||
cachedReturnType = (uniqueCandidate != null ?
|
||||
@@ -926,7 +930,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
objectType.value = ClassUtils.determineCommonAncestor(currentType, objectType.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, ReflectionUtils.USER_DECLARED_METHODS);
|
||||
|
||||
return (objectType.value != null && Object.class != objectType.value ? objectType.value : null);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -26,6 +26,7 @@ import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
@@ -436,11 +437,22 @@ class ConstructorResolver {
|
||||
// Try all methods with this name to see if they match the given arguments.
|
||||
factoryClass = ClassUtils.getUserClass(factoryClass);
|
||||
|
||||
Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);
|
||||
List<Method> candidateList = new ArrayList<>();
|
||||
for (Method candidate : rawCandidates) {
|
||||
if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {
|
||||
candidateList.add(candidate);
|
||||
List<Method> candidateList = null;
|
||||
if (mbd.isFactoryMethodUnique) {
|
||||
if (factoryMethodToUse == null) {
|
||||
factoryMethodToUse = mbd.getResolvedFactoryMethod();
|
||||
}
|
||||
if (factoryMethodToUse != null) {
|
||||
candidateList = Collections.singletonList(factoryMethodToUse);
|
||||
}
|
||||
}
|
||||
if (candidateList == null) {
|
||||
candidateList = new ArrayList<>();
|
||||
Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);
|
||||
for (Method candidate : rawCandidates) {
|
||||
if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {
|
||||
candidateList.add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -237,6 +237,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
|
||||
this.allowCaching = original.allowCaching;
|
||||
this.isFactoryMethodUnique = original.isFactoryMethodUnique;
|
||||
this.targetType = original.targetType;
|
||||
this.factoryMethodToIntrospect = original.factoryMethodToIntrospect;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -361,6 +362,16 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
|
||||
this.isFactoryMethodUnique = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a factory method name that refers to an overloaded method.
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setNonUniqueFactoryMethodName(String name) {
|
||||
Assert.hasText(name, "Factory method name must not be empty");
|
||||
setFactoryMethodName(name);
|
||||
this.isFactoryMethodUnique = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given candidate qualifies as a factory method.
|
||||
*/
|
||||
@@ -368,6 +379,15 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
|
||||
return candidate.getName().equals(getFactoryMethodName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a resolved Java Method for the factory method on this bean definition.
|
||||
* @param method the resolved factory method, or {@code null} to reset it
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setResolvedFactoryMethod(@Nullable Method method) {
|
||||
this.factoryMethodToIntrospect = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the resolved factory method as a Java Method object, if available.
|
||||
* @return the factory method, or {@code null} if not found or not resolved yet
|
||||
|
||||
Reference in New Issue
Block a user