Accept non-generic type match as a fallback

DefaultListableBeanFactory performs a fallback check for autowire candidates now, which GenericTypeAwareAutowireCandidateResolver implements to accept raw type matches if the target class has unresolvable type variables. Full generic matches are still preferred; the BeanFactory will only start looking for fallback matches if the first pass led to an empty result.

Issue: SPR-10993
Issue: SPR-11004
This commit is contained in:
Juergen Hoeller
2013-10-25 17:52:38 +02:00
parent 02f9b713b0
commit 0851766738
8 changed files with 332 additions and 122 deletions

View File

@@ -212,6 +212,29 @@ public class DependencyDescriptor implements Serializable {
ResolvableType.forMethodParameter(this.methodParameter));
}
/**
* Return whether a fallback match is allowed.
* <p>This is {@code false} by default but may be overridden to return {@code true} in order
* to suggest to a {@link org.springframework.beans.factory.support.AutowireCandidateResolver}
* that a fallback match is acceptable as well.
*/
public boolean fallbackMatchAllowed() {
return false;
}
/**
* Return a variant of this descriptor that is intended for a fallback match.
* @see #fallbackMatchAllowed()
*/
public DependencyDescriptor forFallbackMatch() {
return new DependencyDescriptor(this) {
@Override
public boolean fallbackMatchAllowed() {
return true;
}
};
}
/**
* Initialize parameter name discovery for the underlying method parameter, if any.
* <p>This method does not actually try to retrieve the parameter name at
@@ -241,7 +264,8 @@ public class DependencyDescriptor implements Serializable {
if (this.nestingLevel > 1) {
Type type = this.field.getGenericType();
if (type instanceof ParameterizedType) {
Type arg = ((ParameterizedType) type).getActualTypeArguments()[0];
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
Type arg = args[args.length - 1];
if (arg instanceof Class) {
return (Class) arg;
}

View File

@@ -979,6 +979,14 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
result.put(candidateName, getBean(candidateName));
}
}
if (result.isEmpty()) {
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
for (String candidateName : candidateNames) {
if (!candidateName.equals(beanName) && isAutowireCandidate(candidateName, fallbackDescriptor)) {
result.put(candidateName, getBean(candidateName));
}
}
}
return result;
}

View File

@@ -57,24 +57,28 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid
// if explicitly false, do not proceed with any other checks
return false;
}
return (descriptor == null || checkGenericTypeMatch(bdHolder, descriptor.getResolvableType()));
return (descriptor == null || checkGenericTypeMatch(bdHolder, descriptor));
}
/**
* Match the given dependency type with its generic type information
* against the given candidate bean definition.
*/
protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, ResolvableType dependencyType) {
if (dependencyType.getType() instanceof Class) {
protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
ResolvableType dependencyType = descriptor.getResolvableType();
if (!dependencyType.hasGenerics()) {
// No generic type -> we know it's a Class type-match, so no need to check again.
return true;
}
RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();
ResolvableType targetType = null;
if (bd.getResolvedFactoryMethod() != null) {
RootBeanDefinition rbd = null;
if (bdHolder.getBeanDefinition() instanceof RootBeanDefinition) {
rbd = (RootBeanDefinition) bdHolder.getBeanDefinition();
}
if (rbd != null && rbd.getResolvedFactoryMethod() != 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(bd.getResolvedFactoryMethod());
targetType = ResolvableType.forMethodReturnType(rbd.getResolvedFactoryMethod());
}
if (targetType == null) {
// Regular case: straight bean instance, with BeanFactory available.
@@ -86,14 +90,20 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid
}
// Fallback: no BeanFactory set, or no type resolvable through it
// -> best-effort match against the target class if applicable.
if (targetType == null && bd.hasBeanClass() && bd.getFactoryMethodName() == null) {
Class<?> beanClass = bd.getBeanClass();
if (targetType == null && rbd != null && rbd.hasBeanClass() && rbd.getFactoryMethodName() == null) {
Class<?> beanClass = rbd.getBeanClass();
if (!FactoryBean.class.isAssignableFrom(beanClass)) {
targetType = ResolvableType.forClass(ClassUtils.getUserClass(beanClass));
}
}
}
return (targetType == null || dependencyType.isAssignableFrom(targetType));
if (targetType == null) {
return true;
}
if (descriptor.fallbackMatchAllowed() && targetType.hasUnresolvableGenerics()) {
return descriptor.getDependencyType().isAssignableFrom(targetType.getRawClass());
}
return dependencyType.isAssignableFrom(targetType);
}

View File

@@ -1305,6 +1305,130 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertSame(ir, bean.integerRepositoryMap.get("integerRepo"));
}
@Test
public void testGenericsBasedConstructorInjectionWithNonTypedTarget() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
GenericRepository gr = new GenericRepository();
bf.registerSingleton("genericRepo", gr);
RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean");
assertSame(gr, bean.stringRepository);
assertSame(gr, bean.integerRepository);
assertSame(1, bean.stringRepositoryArray.length);
assertSame(1, bean.integerRepositoryArray.length);
assertSame(gr, bean.stringRepositoryArray[0]);
assertSame(gr, bean.integerRepositoryArray[0]);
assertSame(1, bean.stringRepositoryList.size());
assertSame(1, bean.integerRepositoryList.size());
assertSame(gr, bean.stringRepositoryList.get(0));
assertSame(gr, bean.integerRepositoryList.get(0));
assertSame(1, bean.stringRepositoryMap.size());
assertSame(1, bean.integerRepositoryMap.size());
assertSame(gr, bean.stringRepositoryMap.get("genericRepo"));
assertSame(gr, bean.integerRepositoryMap.get("genericRepo"));
}
@Test
public void testGenericsBasedConstructorInjectionWithNonGenericTarget() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
SimpleRepository ngr = new SimpleRepository();
bf.registerSingleton("simpleRepo", ngr);
RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean");
assertSame(ngr, bean.stringRepository);
assertSame(ngr, bean.integerRepository);
assertSame(1, bean.stringRepositoryArray.length);
assertSame(1, bean.integerRepositoryArray.length);
assertSame(ngr, bean.stringRepositoryArray[0]);
assertSame(ngr, bean.integerRepositoryArray[0]);
assertSame(1, bean.stringRepositoryList.size());
assertSame(1, bean.integerRepositoryList.size());
assertSame(ngr, bean.stringRepositoryList.get(0));
assertSame(ngr, bean.integerRepositoryList.get(0));
assertSame(1, bean.stringRepositoryMap.size());
assertSame(1, bean.integerRepositoryMap.size());
assertSame(ngr, bean.stringRepositoryMap.get("simpleRepo"));
assertSame(ngr, bean.integerRepositoryMap.get("simpleRepo"));
}
@Test
public void testGenericsBasedConstructorInjectionWithMixedTargets() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
StringRepository sr = new StringRepository();
bf.registerSingleton("stringRepo", sr);
GenericRepository gr = new GenericRepositorySubclass();
bf.registerSingleton("genericRepo", gr);
RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean");
assertSame(sr, bean.stringRepository);
assertSame(gr, bean.integerRepository);
assertSame(1, bean.stringRepositoryArray.length);
assertSame(1, bean.integerRepositoryArray.length);
assertSame(sr, bean.stringRepositoryArray[0]);
assertSame(gr, bean.integerRepositoryArray[0]);
assertSame(1, bean.stringRepositoryList.size());
assertSame(1, bean.integerRepositoryList.size());
assertSame(sr, bean.stringRepositoryList.get(0));
assertSame(gr, bean.integerRepositoryList.get(0));
assertSame(1, bean.stringRepositoryMap.size());
assertSame(1, bean.integerRepositoryMap.size());
assertSame(sr, bean.stringRepositoryMap.get("stringRepo"));
assertSame(gr, bean.integerRepositoryMap.get("genericRepo"));
}
@Test
public void testGenericsBasedConstructorInjectionWithMixedTargetsIncludingNonGeneric() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver());
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(bf);
bf.addBeanPostProcessor(bpp);
RootBeanDefinition bd = new RootBeanDefinition(RepositoryConstructorInjectionBean.class);
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
bf.registerBeanDefinition("annotatedBean", bd);
StringRepository sr = new StringRepository();
bf.registerSingleton("stringRepo", sr);
SimpleRepository ngr = new SimpleRepositorySubclass();
bf.registerSingleton("simpleRepo", ngr);
RepositoryConstructorInjectionBean bean = (RepositoryConstructorInjectionBean) bf.getBean("annotatedBean");
assertSame(sr, bean.stringRepository);
assertSame(ngr, bean.integerRepository);
assertSame(1, bean.stringRepositoryArray.length);
assertSame(1, bean.integerRepositoryArray.length);
assertSame(sr, bean.stringRepositoryArray[0]);
assertSame(ngr, bean.integerRepositoryArray[0]);
assertSame(1, bean.stringRepositoryList.size());
assertSame(1, bean.integerRepositoryList.size());
assertSame(sr, bean.stringRepositoryList.get(0));
assertSame(ngr, bean.integerRepositoryList.get(0));
assertSame(1, bean.stringRepositoryMap.size());
assertSame(1, bean.integerRepositoryMap.size());
assertSame(sr, bean.stringRepositoryMap.get("stringRepo"));
assertSame(ngr, bean.integerRepositoryMap.get("simpleRepo"));
}
public static class ResourceInjectionBean {
@@ -1859,6 +1983,18 @@ public class AutowiredAnnotationBeanPostProcessorTests {
public static class IntegerRepository implements Repository<Integer> {
}
public static class GenericRepository<T> implements Repository<T> {
}
public static class GenericRepositorySubclass extends GenericRepository {
}
public static class SimpleRepository implements Repository {
}
public static class SimpleRepositorySubclass extends SimpleRepository {
}
public static class RepositoryFieldInjectionBean {