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:
Juergen Hoeller 2013-10-27 00:35:36 +02:00
parent 88fe2e9b00
commit a6b0261000
8 changed files with 194 additions and 54 deletions

View File

@ -222,8 +222,9 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
} }
if (qualifier == null) { if (qualifier == null) {
Annotation targetAnnotation = null; Annotation targetAnnotation = null;
if (bd.getResolvedFactoryMethod() != null) { Method resolvedFactoryMethod = bd.getResolvedFactoryMethod();
targetAnnotation = AnnotationUtils.getAnnotation(bd.getResolvedFactoryMethod(), type); if (resolvedFactoryMethod != null) {
targetAnnotation = AnnotationUtils.getAnnotation(resolvedFactoryMethod, type);
} }
if (targetAnnotation == null) { if (targetAnnotation == null) {
// look for matching annotation on the target class // look for matching annotation on the target class

View File

@ -21,6 +21,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.TypeVariable;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.security.PrivilegedActionException; import java.security.PrivilegedActionException;
@ -587,7 +588,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
} }
@Override @Override
protected Class<?> predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { protected Class<?> predictBeanType(String beanName, RootBeanDefinition mbd, Class<?>... typesToMatch) {
Class<?> targetType = mbd.getTargetType(); Class<?> targetType = mbd.getTargetType();
if (targetType == null) { if (targetType == null) {
targetType = (mbd.getFactoryMethodName() != null ? getTypeForFactoryMethod(beanName, mbd, typesToMatch) : 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 * @return the type for the bean if determinable, or {@code null} else
* @see #createBean * @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; Class<?> factoryClass;
boolean isStatic = true; 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. // If all factory methods have the same return type, return that type.
// Can't clearly figure out exact method due to type converting / autowiring! // Can't clearly figure out exact method due to type converting / autowiring!
boolean cache = false;
int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount(); int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount();
Method[] candidates = ReflectionUtils.getUniqueDeclaredMethods(factoryClass); Method[] candidates = ReflectionUtils.getUniqueDeclaredMethods(factoryClass);
Set<Class<?>> returnTypes = new HashSet<Class<?>>(1); Set<Class<?>> returnTypes = new HashSet<Class<?>>(1);
@ -659,38 +666,51 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic && if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic &&
factoryMethod.getName().equals(mbd.getFactoryMethodName()) && factoryMethod.getName().equals(mbd.getFactoryMethodName()) &&
factoryMethod.getParameterTypes().length >= minNrOfArgs) { factoryMethod.getParameterTypes().length >= minNrOfArgs) {
Class<?>[] paramTypes = factoryMethod.getParameterTypes(); TypeVariable<Method>[] declaredTypeVariables = factoryMethod.getTypeParameters();
String[] paramNames = null; // No declared type variables to inspect, so just process the standard return type.
ParameterNameDiscoverer pnd = getParameterNameDiscoverer(); if (declaredTypeVariables.length > 0) {
if (pnd != null) { // Fully resolve parameter names and argument values.
paramNames = pnd.getParameterNames(factoryMethod); Class<?>[] paramTypes = factoryMethod.getParameterTypes();
} String[] paramNames = null;
ConstructorArgumentValues cav = mbd.getConstructorArgumentValues(); ParameterNameDiscoverer pnd = getParameterNameDiscoverer();
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = if (pnd != null) {
new HashSet<ConstructorArgumentValues.ValueHolder>(paramTypes.length); paramNames = pnd.getParameterNames(factoryMethod);
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) { ConstructorArgumentValues cav = mbd.getConstructorArgumentValues();
args[i] = valueHolder.getValue(); Set<ConstructorArgumentValues.ValueHolder> usedValueHolders =
usedValueHolders.add(valueHolder); 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( else {
factoryMethod, args, getBeanClassLoader()); returnTypes.add(factoryMethod.getReturnType());
if (returnType != null) {
returnTypes.add(returnType);
} }
} }
} }
if (returnTypes.size() == 1) { if (returnTypes.size() == 1) {
// Clear return type found: all factory methods return same type. // 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 { else {
// Ambiguous return types found: return null to indicate "not determinable". // Ambiguous return types found: return null to indicate "not determinable".

View File

@ -195,17 +195,7 @@ abstract class AutowireUtils {
TypeVariable<Method>[] declaredTypeVariables = method.getTypeParameters(); TypeVariable<Method>[] declaredTypeVariables = method.getTypeParameters();
Type genericReturnType = method.getGenericReturnType(); Type genericReturnType = method.getGenericReturnType();
Type[] methodArgumentTypes = method.getGenericParameterTypes(); Type[] methodArgumentTypes = method.getGenericParameterTypes();
Assert.isTrue(args.length == methodArgumentTypes.length, "Argument array does not match parameter count");
// 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;
}
// Ensure that the type variable (e.g., T) is declared directly on the method // 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. // itself (e.g., via <T>), not on the enclosing class or interface.

View File

@ -16,6 +16,8 @@
package org.springframework.beans.factory.support; package org.springframework.beans.factory.support;
import java.lang.reflect.Method;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
@ -66,7 +68,7 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid
*/ */
protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) { protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
ResolvableType dependencyType = descriptor.getResolvableType(); 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. // No generic type -> we know it's a Class type-match, so no need to check again.
return true; return true;
} }
@ -75,10 +77,19 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid
if (bdHolder.getBeanDefinition() instanceof RootBeanDefinition) { if (bdHolder.getBeanDefinition() instanceof RootBeanDefinition) {
rbd = (RootBeanDefinition) bdHolder.getBeanDefinition(); 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 // Should typically be set for any kind of factory method, since the BeanFactory
// pre-resolves them before reaching out to the AutowireCandidateResolver... // 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) { if (targetType == null) {
// Regular case: straight bean instance, with BeanFactory available. // Regular case: straight bean instance, with BeanFactory available.

View File

@ -71,6 +71,9 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
/** Package-visible field for caching the resolved constructor or factory method */ /** Package-visible field for caching the resolved constructor or factory method */
Object resolvedConstructorOrFactoryMethod; 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 */ /** Package-visible field that marks the constructor arguments as resolved */
boolean constructorArgumentsResolved = false; boolean constructorArgumentsResolved = false;

View File

@ -21,6 +21,9 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -1209,6 +1212,81 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertSame(ir, bean.integerRepositoryMap.get("integerRepo")); assertSame(ir, bean.integerRepositoryMap.get("integerRepo"));
} }
@Test
public void testGenericsBasedFieldInjectionWithQualifiers() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
RootBeanDefinition bd = new RootBeanDefinition(RepositoryFieldInjectionBeanWithQualifiers.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
StringRepository sr = new StringRepository();
bf.registerSingleton("stringRepo", sr);
IntegerRepository ir = new IntegerRepository();
bf.registerSingleton("integerRepo", ir);
RepositoryFieldInjectionBeanWithQualifiers bean = (RepositoryFieldInjectionBeanWithQualifiers) bf.getBean("annotatedBean");
assertSame(sr, bean.stringRepository);
assertSame(ir, bean.integerRepository);
assertSame(1, bean.stringRepositoryArray.length);
assertSame(1, bean.integerRepositoryArray.length);
assertSame(sr, bean.stringRepositoryArray[0]);
assertSame(ir, bean.integerRepositoryArray[0]);
assertSame(1, bean.stringRepositoryList.size());
assertSame(1, bean.integerRepositoryList.size());
assertSame(sr, bean.stringRepositoryList.get(0));
assertSame(ir, bean.integerRepositoryList.get(0));
assertSame(1, bean.stringRepositoryMap.size());
assertSame(1, bean.integerRepositoryMap.size());
assertSame(sr, bean.stringRepositoryMap.get("stringRepo"));
assertSame(ir, bean.integerRepositoryMap.get("integerRepo"));
}
@Test
public void testGenericsBasedFieldInjectionWithMocks() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
RootBeanDefinition bd = new RootBeanDefinition(RepositoryFieldInjectionBeanWithQualifiers.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class);
bf.registerBeanDefinition("mocksControl", rbd);
rbd = new RootBeanDefinition();
rbd.setFactoryBeanName("mocksControl");
rbd.setFactoryMethodName("createMock");
rbd.getConstructorArgumentValues().addGenericArgumentValue(Repository.class);
bf.registerBeanDefinition("stringRepo", rbd);
rbd = new RootBeanDefinition();
rbd.setFactoryBeanName("mocksControl");
rbd.setFactoryMethodName("createMock");
rbd.getConstructorArgumentValues().addGenericArgumentValue(Repository.class);
bf.registerBeanDefinition("integerRepo", rbd);
Repository sr = bf.getBean("stringRepo", Repository.class);
Repository ir = bf.getBean("integerRepo", Repository.class);
RepositoryFieldInjectionBeanWithQualifiers bean = (RepositoryFieldInjectionBeanWithQualifiers) bf.getBean("annotatedBean");
assertSame(sr, bean.stringRepository);
assertSame(ir, bean.integerRepository);
assertSame(1, bean.stringRepositoryArray.length);
assertSame(1, bean.integerRepositoryArray.length);
assertSame(sr, bean.stringRepositoryArray[0]);
assertSame(ir, bean.integerRepositoryArray[0]);
assertSame(1, bean.stringRepositoryList.size());
assertSame(1, bean.integerRepositoryList.size());
assertSame(sr, bean.stringRepositoryList.get(0));
assertSame(ir, bean.integerRepositoryList.get(0));
assertSame(1, bean.stringRepositoryMap.size());
assertSame(1, bean.integerRepositoryMap.size());
assertSame(sr, bean.stringRepositoryMap.get("stringRepo"));
assertSame(ir, bean.integerRepositoryMap.get("integerRepo"));
}
@Test @Test
public void testGenericsBasedMethodInjection() { public void testGenericsBasedMethodInjection() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
@ -2057,6 +2135,34 @@ public class AutowiredAnnotationBeanPostProcessorTests {
} }
public static class RepositoryFieldInjectionBeanWithQualifiers {
@Autowired @Qualifier("stringRepo")
public Repository<?> stringRepository;
@Autowired @Qualifier("integerRepo")
public Repository integerRepository;
@Autowired @Qualifier("stringRepo")
public Repository<?>[] stringRepositoryArray;
@Autowired @Qualifier("integerRepo")
public Repository[] integerRepositoryArray;
@Autowired @Qualifier("stringRepo")
public List<Repository> stringRepositoryList;
@Autowired @Qualifier("integerRepo")
public List<Repository<?>> integerRepositoryList;
@Autowired @Qualifier("stringRepo")
public Map<String, Repository<?>> stringRepositoryMap;
@Autowired @Qualifier("integerRepo")
public Map<String, Repository> integerRepositoryMap;
}
public static class RepositoryMethodInjectionBean { public static class RepositoryMethodInjectionBean {
public Repository<String> stringRepository; public Repository<String> stringRepository;
@ -2216,4 +2322,22 @@ public class AutowiredAnnotationBeanPostProcessorTests {
} }
} }
/**
* Pseudo-implementation of EasyMock's {@code MocksControl} class.
*/
public static class MocksControl {
@SuppressWarnings("unchecked")
public <T> T createMock(Class<T> toMock) {
return (T) Proxy.newProxyInstance(AutowiredAnnotationBeanPostProcessorTests.class.getClassLoader(), new Class<?>[]{toMock},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
throw new UnsupportedOperationException("mocked!");
}
});
}
}
} }

View File

@ -49,9 +49,6 @@ public class AutowireUtilsTests {
Method createNamedProxyWithDifferentTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", Method createNamedProxyWithDifferentTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy",
new Class[] { String.class, Object.class }); new Class[] { String.class, Object.class });
// one argument to few
assertNull(
AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[]{"enigma"}, getClass().getClassLoader()));
assertEquals(Long.class, assertEquals(Long.class,
AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L }, getClass().getClassLoader())); AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L }, getClass().getClassLoader()));

View File

@ -701,7 +701,6 @@ public class BeanFactoryGenericsTests {
rbd.setFactoryBeanName("mocksControl"); rbd.setFactoryBeanName("mocksControl");
rbd.setFactoryMethodName("createMock"); rbd.setFactoryMethodName("createMock");
rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class);
bf.registerBeanDefinition("mock", rbd); bf.registerBeanDefinition("mock", rbd);
Map<String, Runnable> beans = bf.getBeansOfType(Runnable.class); Map<String, Runnable> beans = bf.getBeansOfType(Runnable.class);
@ -719,7 +718,6 @@ public class BeanFactoryGenericsTests {
rbd.setFactoryBeanName("mocksControl"); rbd.setFactoryBeanName("mocksControl");
rbd.setFactoryMethodName("createMock"); rbd.setFactoryMethodName("createMock");
rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class.getName()); rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class.getName());
bf.registerBeanDefinition("mock", rbd); bf.registerBeanDefinition("mock", rbd);
Map<String, Runnable> beans = bf.getBeansOfType(Runnable.class); Map<String, Runnable> beans = bf.getBeansOfType(Runnable.class);
@ -737,7 +735,6 @@ public class BeanFactoryGenericsTests {
rbd.setFactoryBeanName("mocksControl"); rbd.setFactoryBeanName("mocksControl");
rbd.setFactoryMethodName("createMock"); rbd.setFactoryMethodName("createMock");
rbd.getConstructorArgumentValues().addIndexedArgumentValue(0, Runnable.class); rbd.getConstructorArgumentValues().addIndexedArgumentValue(0, Runnable.class);
bf.registerBeanDefinition("mock", rbd); bf.registerBeanDefinition("mock", rbd);
Map<String, Runnable> beans = bf.getBeansOfType(Runnable.class); Map<String, Runnable> beans = bf.getBeansOfType(Runnable.class);
@ -788,6 +785,7 @@ public class BeanFactoryGenericsTests {
} }
} }
/** /**
* Pseudo-implementation of EasyMock's {@code MocksControl} class. * Pseudo-implementation of EasyMock's {@code MocksControl} class.
*/ */
@ -795,14 +793,10 @@ public class BeanFactoryGenericsTests {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T createMock(Class<T> toMock) { public <T> T createMock(Class<T> toMock) {
return (T) Proxy.newProxyInstance(BeanFactoryGenericsTests.class.getClassLoader(), new Class<?>[] {toMock},
return (T) Proxy.newProxyInstance( new InvocationHandler() {
BeanFactoryGenericsTests.class.getClassLoader(),
new Class[] { toMock }, new InvocationHandler() {
@Override @Override
public Object invoke(Object proxy, Method method, Object[] args) public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
throws Throwable {
throw new UnsupportedOperationException("mocked!"); throw new UnsupportedOperationException("mocked!");
} }
}); });