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:
Juergen Hoeller
2019-03-12 17:17:24 +01:00
parent a35adc6ea6
commit 40c62139ae
10 changed files with 271 additions and 142 deletions

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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