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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user