Proper matching of raw generic types and generically typed factory methods
Also optimized getTypeForFactoryMethod's implementation for non-generic factory methods, and reduced calls to getResolvedFactoryMethod in order to avoid repeated synchronization. Issue: SPR-11034
This commit is contained in:
@@ -222,8 +222,9 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
|
||||
}
|
||||
if (qualifier == null) {
|
||||
Annotation targetAnnotation = null;
|
||||
if (bd.getResolvedFactoryMethod() != null) {
|
||||
targetAnnotation = AnnotationUtils.getAnnotation(bd.getResolvedFactoryMethod(), type);
|
||||
Method resolvedFactoryMethod = bd.getResolvedFactoryMethod();
|
||||
if (resolvedFactoryMethod != null) {
|
||||
targetAnnotation = AnnotationUtils.getAnnotation(resolvedFactoryMethod, type);
|
||||
}
|
||||
if (targetAnnotation == null) {
|
||||
// look for matching annotation on the target class
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.PrivilegedActionException;
|
||||
@@ -587,7 +588,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) {
|
||||
protected Class<?> predictBeanType(String beanName, RootBeanDefinition mbd, Class<?>... typesToMatch) {
|
||||
Class<?> targetType = mbd.getTargetType();
|
||||
if (targetType == null) {
|
||||
targetType = (mbd.getFactoryMethodName() != null ? getTypeForFactoryMethod(beanName, mbd, typesToMatch) :
|
||||
@@ -627,7 +628,12 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
* @return the type for the bean if determinable, or {@code null} else
|
||||
* @see #createBean
|
||||
*/
|
||||
protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition mbd, Class[] typesToMatch) {
|
||||
protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition mbd, Class<?>... typesToMatch) {
|
||||
Class<?> preResolved = mbd.resolvedFactoryMethodReturnType;
|
||||
if (preResolved != null) {
|
||||
return preResolved;
|
||||
}
|
||||
|
||||
Class<?> factoryClass;
|
||||
boolean isStatic = true;
|
||||
|
||||
@@ -652,6 +658,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
|
||||
// If all factory methods have the same return type, return that type.
|
||||
// Can't clearly figure out exact method due to type converting / autowiring!
|
||||
boolean cache = false;
|
||||
int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount();
|
||||
Method[] candidates = ReflectionUtils.getUniqueDeclaredMethods(factoryClass);
|
||||
Set<Class<?>> returnTypes = new HashSet<Class<?>>(1);
|
||||
@@ -659,38 +666,51 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
|
||||
if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic &&
|
||||
factoryMethod.getName().equals(mbd.getFactoryMethodName()) &&
|
||||
factoryMethod.getParameterTypes().length >= minNrOfArgs) {
|
||||
Class<?>[] paramTypes = factoryMethod.getParameterTypes();
|
||||
String[] paramNames = null;
|
||||
ParameterNameDiscoverer pnd = getParameterNameDiscoverer();
|
||||
if (pnd != null) {
|
||||
paramNames = pnd.getParameterNames(factoryMethod);
|
||||
}
|
||||
ConstructorArgumentValues cav = mbd.getConstructorArgumentValues();
|
||||
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders =
|
||||
new HashSet<ConstructorArgumentValues.ValueHolder>(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);
|
||||
TypeVariable<Method>[] declaredTypeVariables = factoryMethod.getTypeParameters();
|
||||
// No declared type variables to inspect, so just process the standard return type.
|
||||
if (declaredTypeVariables.length > 0) {
|
||||
// Fully resolve parameter names and argument values.
|
||||
Class<?>[] paramTypes = factoryMethod.getParameterTypes();
|
||||
String[] paramNames = null;
|
||||
ParameterNameDiscoverer pnd = getParameterNameDiscoverer();
|
||||
if (pnd != null) {
|
||||
paramNames = pnd.getParameterNames(factoryMethod);
|
||||
}
|
||||
if (valueHolder != null) {
|
||||
args[i] = valueHolder.getValue();
|
||||
usedValueHolders.add(valueHolder);
|
||||
ConstructorArgumentValues cav = mbd.getConstructorArgumentValues();
|
||||
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders =
|
||||
new HashSet<ConstructorArgumentValues.ValueHolder>(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(
|
||||
factoryMethod, args, getBeanClassLoader());
|
||||
if (returnType != null) {
|
||||
cache = true;
|
||||
returnTypes.add(returnType);
|
||||
}
|
||||
}
|
||||
Class<?> returnType = AutowireUtils.resolveReturnTypeForFactoryMethod(
|
||||
factoryMethod, args, getBeanClassLoader());
|
||||
if (returnType != null) {
|
||||
returnTypes.add(returnType);
|
||||
else {
|
||||
returnTypes.add(factoryMethod.getReturnType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (returnTypes.size() == 1) {
|
||||
// Clear return type found: all factory methods return same type.
|
||||
return returnTypes.iterator().next();
|
||||
Class<?> result = returnTypes.iterator().next();
|
||||
if (cache) {
|
||||
mbd.resolvedFactoryMethodReturnType = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
// Ambiguous return types found: return null to indicate "not determinable".
|
||||
|
||||
@@ -195,17 +195,7 @@ abstract class AutowireUtils {
|
||||
TypeVariable<Method>[] declaredTypeVariables = method.getTypeParameters();
|
||||
Type genericReturnType = method.getGenericReturnType();
|
||||
Type[] methodArgumentTypes = method.getGenericParameterTypes();
|
||||
|
||||
// No declared type variables to inspect, so just return the standard return type.
|
||||
if (declaredTypeVariables.length == 0) {
|
||||
return method.getReturnType();
|
||||
}
|
||||
|
||||
// The supplied argument list is too short for the method's signature, so
|
||||
// return null, since such a method invocation would fail.
|
||||
if (args.length < methodArgumentTypes.length) {
|
||||
return null;
|
||||
}
|
||||
Assert.isTrue(args.length == methodArgumentTypes.length, "Argument array does not match parameter count");
|
||||
|
||||
// Ensure that the type variable (e.g., T) is declared directly on the method
|
||||
// itself (e.g., via <T>), not on the enclosing class or interface.
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
@@ -66,7 +68,7 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid
|
||||
*/
|
||||
protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
|
||||
ResolvableType dependencyType = descriptor.getResolvableType();
|
||||
if (!dependencyType.hasGenerics()) {
|
||||
if (dependencyType.getType() instanceof Class) {
|
||||
// No generic type -> we know it's a Class type-match, so no need to check again.
|
||||
return true;
|
||||
}
|
||||
@@ -75,10 +77,19 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid
|
||||
if (bdHolder.getBeanDefinition() instanceof RootBeanDefinition) {
|
||||
rbd = (RootBeanDefinition) bdHolder.getBeanDefinition();
|
||||
}
|
||||
if (rbd != null && rbd.getResolvedFactoryMethod() != null) {
|
||||
if (rbd != null && rbd.getFactoryMethodName() != null) {
|
||||
// Should typically be set for any kind of factory method, since the BeanFactory
|
||||
// pre-resolves them before reaching out to the AutowireCandidateResolver...
|
||||
targetType = ResolvableType.forMethodReturnType(rbd.getResolvedFactoryMethod());
|
||||
Class<?> preResolved = rbd.resolvedFactoryMethodReturnType;
|
||||
if (preResolved != null) {
|
||||
targetType = ResolvableType.forClass(preResolved);
|
||||
}
|
||||
else {
|
||||
Method resolvedFactoryMethod = rbd.getResolvedFactoryMethod();
|
||||
if (resolvedFactoryMethod != null) {
|
||||
targetType = ResolvableType.forMethodReturnType(resolvedFactoryMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetType == null) {
|
||||
// Regular case: straight bean instance, with BeanFactory available.
|
||||
|
||||
@@ -71,6 +71,9 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
|
||||
/** Package-visible field for caching the resolved constructor or factory method */
|
||||
Object resolvedConstructorOrFactoryMethod;
|
||||
|
||||
/** Package-visible field for caching the return type of a generically typed factory method */
|
||||
volatile Class<?> resolvedFactoryMethodReturnType;
|
||||
|
||||
/** Package-visible field that marks the constructor arguments as resolved */
|
||||
boolean constructorArgumentsResolved = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user