From 793cab4f8093186ff345f67ada4ca73990da28d7 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 6 Mar 2014 14:13:33 +0100 Subject: [PATCH] Allow generics-based injection in case of nested unresolved type variable Issue: SPR-11471 --- ...wiredAnnotationBeanPostProcessorTests.java | 116 ++++++++++++++++++ .../springframework/core/ResolvableType.java | 7 +- 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 9e1e9eb49e..1996f3ba83 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -1647,6 +1647,59 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertSame(ngr, bean.integerRepositoryMap.get("simpleRepo")); } + @Test + public void testGenericsBasedInjectionIntoMatchingTypeVariable() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(GenericInterface1Impl.class); + bd.setFactoryMethodName("create"); + bf.registerBeanDefinition("bean1", bd); + bf.registerBeanDefinition("bean2", new RootBeanDefinition(GenericInterface2Impl.class)); + + GenericInterface1Impl bean1 = (GenericInterface1Impl) bf.getBean("bean1"); + GenericInterface2Impl bean2 = (GenericInterface2Impl) bf.getBean("bean2"); + assertSame(bean2, bean1.gi2); + } + + @Test + public void testGenericsBasedInjectionIntoUnresolvedTypeVariable() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(GenericInterface1Impl.class); + bd.setFactoryMethodName("createPlain"); + bf.registerBeanDefinition("bean1", bd); + bf.registerBeanDefinition("bean2", new RootBeanDefinition(GenericInterface2Impl.class)); + + GenericInterface1Impl bean1 = (GenericInterface1Impl) bf.getBean("bean1"); + GenericInterface2Impl bean2 = (GenericInterface2Impl) bf.getBean("bean2"); + assertSame(bean2, bean1.gi2); + } + + @Test + public void testGenericsBasedInjectionIntoTypeVariableSelectingBestMatch() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new QualifierAnnotationAutowireCandidateResolver()); + AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); + bpp.setBeanFactory(bf); + bf.addBeanPostProcessor(bpp); + RootBeanDefinition bd = new RootBeanDefinition(GenericInterface1Impl.class); + bd.setFactoryMethodName("create"); + bf.registerBeanDefinition("bean1", bd); + bf.registerBeanDefinition("bean2", new RootBeanDefinition(GenericInterface2Impl.class)); + bf.registerBeanDefinition("bean2a", new RootBeanDefinition(ReallyGenericInterface2Impl.class)); + bf.registerBeanDefinition("bean2b", new RootBeanDefinition(PlainGenericInterface2Impl.class)); + + GenericInterface1Impl bean1 = (GenericInterface1Impl) bf.getBean("bean1"); + GenericInterface2Impl bean2 = (GenericInterface2Impl) bf.getBean("bean2"); + assertSame(bean2, bean1.gi2); + } + public static class ResourceInjectionBean { @@ -2534,4 +2587,67 @@ public class AutowiredAnnotationBeanPostProcessorTests { } } + + public interface GenericInterface1 { + + public String doSomethingGeneric(T o); + } + + + public static class GenericInterface1Impl implements GenericInterface1{ + + @Autowired + private GenericInterface2 gi2; + + @Override + public String doSomethingGeneric(T o) { + return gi2.doSomethingMoreGeneric(o) + "_somethingGeneric_" + o; + } + + public static GenericInterface1 create(){ + return new StringGenericInterface1Impl(); + } + + public static GenericInterface1 createPlain(){ + return new GenericInterface1Impl(); + } + } + + + public static class StringGenericInterface1Impl extends GenericInterface1Impl { + } + + + public interface GenericInterface2 { + + public String doSomethingMoreGeneric(K o); + } + + + public static class GenericInterface2Impl implements GenericInterface2{ + + @Override + public String doSomethingMoreGeneric(String o) { + return "somethingMoreGeneric_" + o; + } + } + + + public static class ReallyGenericInterface2Impl implements GenericInterface2 { + + @Override + public String doSomethingMoreGeneric(Object o) { + return "somethingMoreGeneric_" + o; + } + } + + + public static class PlainGenericInterface2Impl implements GenericInterface2{ + + @Override + public String doSomethingMoreGeneric(Object o) { + return "somethingMoreGeneric_" + o; + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index a27513e982..241de0ddcf 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -220,6 +220,7 @@ public final class ResolvableType implements Serializable { } // Main assignability check about to follow + boolean exactMatch = checkingGeneric; boolean checkGenerics = true; Class ourResolved = null; if (this.type instanceof TypeVariable) { @@ -241,6 +242,10 @@ public final class ResolvableType implements Serializable { } } } + if (ourResolved == null) { + // Unresolved type variable, potentially nested -> never insist on exact match + exactMatch = false; + } } if (ourResolved == null) { ourResolved = resolve(Object.class); @@ -249,7 +254,7 @@ public final class ResolvableType implements Serializable { // We need an exact type match for generics // List is not assignable from List - if (checkingGeneric ? !ourResolved.equals(typeResolved) : !ClassUtils.isAssignable(ourResolved, typeResolved)) { + if (exactMatch ? !ourResolved.equals(typeResolved) : !ClassUtils.isAssignable(ourResolved, typeResolved)) { return false; }