From f2889b1b43c264ed49d81e831c3959ca49ad7435 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 8 Apr 2024 22:39:29 +0200 Subject: [PATCH] Consistent support for generic FactoryBean type matching Closes gh-32590 See gh-32489 --- .../factory/support/AbstractBeanFactory.java | 83 ++-- ...ricTypeAwareAutowireCandidateResolver.java | 34 +- .../support/BeanFactoryGenericsTests.java | 426 ++++++++++-------- ...notationConfigApplicationContextTests.java | 32 +- .../context/annotation/Gh32489Tests.java | 17 +- 5 files changed, 342 insertions(+), 250 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 10b182a5b37..86bca0ef495 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -525,42 +525,73 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp // Check manually registered singletons. Object beanInstance = getSingleton(beanName, false); if (beanInstance != null && beanInstance.getClass() != NullBean.class) { + + // Determine target for FactoryBean match if necessary. if (beanInstance instanceof FactoryBean factoryBean) { if (!isFactoryDereference) { Class type = getTypeForFactoryBean(factoryBean); - return (type != null && typeToMatch.isAssignableFrom(type)); - } - else { - return typeToMatch.isInstance(beanInstance); - } - } - else if (!isFactoryDereference) { - if (typeToMatch.isInstance(beanInstance)) { - // Direct match for exposed instance? - return true; - } - else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) { - // Generics potentially only match on the target class, not on the proxy... - RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); - Class targetType = mbd.getTargetType(); - if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance)) { - // Check raw class match as well, making sure it's exposed on the proxy. - Class classToMatch = typeToMatch.resolve(); - if (classToMatch != null && !classToMatch.isInstance(beanInstance)) { + if (type == null) { + return false; + } + if (typeToMatch.isAssignableFrom(type)) { + return true; + } + else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) { + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + ResolvableType targetType = mbd.targetType; + if (targetType == null) { + targetType = mbd.factoryMethodReturnType; + } + if (targetType == null) { return false; } - if (typeToMatch.isAssignableFrom(targetType)) { - return true; + Class targetClass = targetType.resolve(); + if (targetClass != null && FactoryBean.class.isAssignableFrom(targetClass)) { + Class classToMatch = typeToMatch.resolve(); + if (classToMatch != null && !FactoryBean.class.isAssignableFrom(classToMatch) && + !classToMatch.isAssignableFrom(targetType.toClass())) { + return typeToMatch.isAssignableFrom(targetType.getGeneric()); + } + } + else { + return typeToMatch.isAssignableFrom(targetType); } } - ResolvableType resolvableType = mbd.targetType; - if (resolvableType == null) { - resolvableType = mbd.factoryMethodReturnType; + return false; + } + } + else if (isFactoryDereference) { + return false; + } + + // Actual matching against bean instance... + if (typeToMatch.isInstance(beanInstance)) { + // Direct match for exposed instance? + return true; + } + else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) { + // Generics potentially only match on the target class, not on the proxy... + RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + Class targetType = mbd.getTargetType(); + if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance)) { + // Check raw class match as well, making sure it's exposed on the proxy. + Class classToMatch = typeToMatch.resolve(); + if (classToMatch != null && !classToMatch.isInstance(beanInstance)) { + return false; } - return (resolvableType != null && typeToMatch.isAssignableFrom(resolvableType)); + if (typeToMatch.isAssignableFrom(targetType)) { + return true; + } + } + ResolvableType resolvableType = mbd.targetType; + if (resolvableType == null) { + resolvableType = mbd.factoryMethodReturnType; } + return (resolvableType != null && typeToMatch.isAssignableFrom(resolvableType)); + } + else { + return false; } - return false; } else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) { // null instance registered diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java index 72ab84d4f8e..7d367cfd71a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java @@ -102,23 +102,6 @@ public class GenericTypeAwareAutowireCandidateResolver extends SimpleAutowireCan } } } - else { - // Pre-existing target type: In case of a generic FactoryBean type, - // unwrap nested generic type when matching a non-FactoryBean type. - Class resolvedClass = targetType.resolve(); - if (resolvedClass != null && FactoryBean.class.isAssignableFrom(resolvedClass)) { - Class typeToBeMatched = dependencyType.resolve(); - if (typeToBeMatched != null && !FactoryBean.class.isAssignableFrom(typeToBeMatched) && - !typeToBeMatched.isAssignableFrom(resolvedClass)) { - targetType = targetType.getGeneric(); - if (descriptor.fallbackMatchAllowed()) { - // Matching the Class-based type determination for FactoryBean - // objects in the lazy-determination getType code path below. - targetType = ResolvableType.forClass(targetType.resolve()); - } - } - } - } } if (targetType == null) { @@ -145,6 +128,23 @@ public class GenericTypeAwareAutowireCandidateResolver extends SimpleAutowireCan if (cacheType) { rbd.targetType = targetType; } + + // Pre-declared target type: In case of a generic FactoryBean type, + // unwrap nested generic type when matching a non-FactoryBean type. + Class targetClass = targetType.resolve(); + if (targetClass != null && FactoryBean.class.isAssignableFrom(targetClass)) { + Class classToMatch = dependencyType.resolve(); + if (classToMatch != null && !FactoryBean.class.isAssignableFrom(classToMatch) && + !classToMatch.isAssignableFrom(targetClass)) { + targetType = targetType.getGeneric(); + if (descriptor.fallbackMatchAllowed()) { + // Matching the Class-based type determination for FactoryBean + // objects in the lazy-determination getType code path above. + targetType = ResolvableType.forClass(targetType.resolve()); + } + } + } + if (descriptor.fallbackMatchAllowed() && (targetType.hasUnresolvableGenerics() || targetType.resolve() == Properties.class)) { // Fallback matches allow unresolvable generics, e.g. plain HashMap to Map; diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java index 13bf0a9275f..116e88587ef 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java @@ -29,6 +29,8 @@ import java.util.Map; import java.util.Set; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mockito; import org.springframework.beans.factory.BeanCreationException; @@ -64,27 +66,25 @@ class BeanFactoryGenericsTests { @Test void genericSetProperty() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.getPropertyValues().add("integerSet", Set.of("4", "5")); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.getPropertyValues().add("integerSet", Set.of("4", "5")); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5); } @Test void genericListProperty() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); List input = List.of("http://localhost:8080", "http://localhost:9090"); - rbd.getPropertyValues().add("resourceList", input); + bd.getPropertyValues().add("resourceList", input); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getResourceList()) .containsExactly(new UrlResource("http://localhost:8080"), new UrlResource("http://localhost:9090")); } @@ -95,11 +95,11 @@ class BeanFactoryGenericsTests { bf.registerSingleton("resource1", new UrlResource("http://localhost:8080")); bf.registerSingleton("resource2", new UrlResource("http://localhost:9090")); - RootBeanDefinition rbd = new RootBeanDefinition(GenericIntegerBean.class); - rbd.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE); - bf.registerBeanDefinition("genericBean", rbd); - GenericIntegerBean gb = (GenericIntegerBean) bf.getBean("genericBean"); + RootBeanDefinition bd = new RootBeanDefinition(GenericIntegerBean.class); + bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE); + bf.registerBeanDefinition("genericBean", bd); + GenericIntegerBean gb = (GenericIntegerBean) bf.getBean("genericBean"); assertThat(gb.getResourceList()) .containsExactly(new UrlResource("http://localhost:8080"), new UrlResource("http://localhost:9090")); } @@ -107,43 +107,42 @@ class BeanFactoryGenericsTests { @Test void genericListPropertyWithInvalidElementType() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericIntegerBean.class); - rbd.getPropertyValues().add("testBeanList", List.of(1)); + RootBeanDefinition bd = new RootBeanDefinition(GenericIntegerBean.class); + bd.getPropertyValues().add("testBeanList", List.of(1)); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() -> bf.getBean("genericBean")) - .withMessageContaining("genericBean") - .withMessageContaining("testBeanList[0]") - .withMessageContaining(TestBean.class.getName()) - .withMessageContaining("Integer"); + .withMessageContaining("genericBean") + .withMessageContaining("testBeanList[0]") + .withMessageContaining(TestBean.class.getName()) + .withMessageContaining("Integer"); } @Test void genericListPropertyWithOptionalAutowiring() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE); - bf.registerBeanDefinition("genericBean", rbd); - GenericBean gb = (GenericBean) bf.getBean("genericBean"); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE); + bf.registerBeanDefinition("genericBean", bd); + GenericBean gb = (GenericBean) bf.getBean("genericBean"); assertThat(gb.getResourceList()).isNull(); } @Test void genericMapProperty() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); Map input = Map.of( "4", "5", "6", "7"); - rbd.getPropertyValues().add("shortMap", input); + bd.getPropertyValues().add("shortMap", input); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); } @@ -153,22 +152,21 @@ class BeanFactoryGenericsTests { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); - GenericBean gb = (GenericBean) bf.getBean("listOfArrays"); + GenericBean gb = (GenericBean) bf.getBean("listOfArrays"); assertThat(gb.getListOfArrays()).containsExactly(new String[] {"value1", "value2"}); } @Test void genericSetConstructor() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); Set input = Set.of("4", "5"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input); + bd.getConstructorArgumentValues().addGenericArgumentValue(input); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5); } @@ -178,11 +176,11 @@ class BeanFactoryGenericsTests { bf.registerSingleton("integer1", 4); bf.registerSingleton("integer2", 5); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); - bf.registerBeanDefinition("genericBean", rbd); - GenericBean gb = (GenericBean) bf.getBean("genericBean"); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); + bf.registerBeanDefinition("genericBean", bd); + GenericBean gb = (GenericBean) bf.getBean("genericBean"); assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5); } @@ -190,27 +188,26 @@ class BeanFactoryGenericsTests { void genericSetConstructorWithOptionalAutowiring() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); - bf.registerBeanDefinition("genericBean", rbd); - GenericBean gb = (GenericBean) bf.getBean("genericBean"); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); + bf.registerBeanDefinition("genericBean", bd); + GenericBean gb = (GenericBean) bf.getBean("genericBean"); assertThat(gb.getIntegerSet()).isNull(); } @Test void genericSetListConstructor() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); Set input1 = Set.of("4", "5"); List input2 = List.of("http://localhost:8080", "http://localhost:9090"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input1); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input2); + bd.getConstructorArgumentValues().addGenericArgumentValue(input1); + bd.getConstructorArgumentValues().addGenericArgumentValue(input2); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5); assertThat(gb.getResourceList()) .containsExactly(new UrlResource("http://localhost:8080"), new UrlResource("http://localhost:9090")); @@ -224,11 +221,11 @@ class BeanFactoryGenericsTests { bf.registerSingleton("resource1", new UrlResource("http://localhost:8080")); bf.registerSingleton("resource2", new UrlResource("http://localhost:9090")); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); - bf.registerBeanDefinition("genericBean", rbd); - GenericBean gb = (GenericBean) bf.getBean("genericBean"); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); + bf.registerBeanDefinition("genericBean", bd); + GenericBean gb = (GenericBean) bf.getBean("genericBean"); assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5); assertThat(gb.getResourceList()) .containsExactly(new UrlResource("http://localhost:8080"), new UrlResource("http://localhost:9090")); @@ -240,11 +237,11 @@ class BeanFactoryGenericsTests { bf.registerSingleton("resource1", new UrlResource("http://localhost:8080")); bf.registerSingleton("resource2", new UrlResource("http://localhost:9090")); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); - bf.registerBeanDefinition("genericBean", rbd); - GenericBean gb = (GenericBean) bf.getBean("genericBean"); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); + bf.registerBeanDefinition("genericBean", bd); + GenericBean gb = (GenericBean) bf.getBean("genericBean"); assertThat(gb.getIntegerSet()).isNull(); assertThat(gb.getResourceList()).isNull(); } @@ -252,18 +249,17 @@ class BeanFactoryGenericsTests { @Test void genericSetMapConstructor() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); Set input1 = Set.of("4", "5"); Map input2 = Map.of( "4", "5", "6", "7"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input1); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input2); + bd.getConstructorArgumentValues().addGenericArgumentValue(input1); + bd.getConstructorArgumentValues().addGenericArgumentValue(input2); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5); assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); @@ -272,17 +268,16 @@ class BeanFactoryGenericsTests { @Test void genericMapResourceConstructor() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); Map input = Map.of( "4", "5", "6", "7"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input); - rbd.getConstructorArgumentValues().addGenericArgumentValue("http://localhost:8080"); + bd.getConstructorArgumentValues().addGenericArgumentValue(input); + bd.getConstructorArgumentValues().addGenericArgumentValue("http://localhost:8080"); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); assertThat(gb.getResourceList()).containsExactly(new UrlResource("http://localhost:8080")); @@ -291,20 +286,19 @@ class BeanFactoryGenericsTests { @Test void genericMapMapConstructor() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); Map input1 = Map.of( "1", "0", "2", "3"); Map input2 = Map.of( "4", "5", "6", "7"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input1); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input2); + bd.getConstructorArgumentValues().addGenericArgumentValue(input1); + bd.getConstructorArgumentValues().addGenericArgumentValue(input2); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getShortMap()).isNotSameAs(gb.getPlainMap()); assertThat(gb.getPlainMap()).hasSize(2); assertThat(gb.getPlainMap().get("1")).isEqualTo("0"); @@ -317,17 +311,16 @@ class BeanFactoryGenericsTests { @Test void genericMapMapConstructorWithSameRefAndConversion() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); Map input = Map.of( "1", "0", "2", "3"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input); + bd.getConstructorArgumentValues().addGenericArgumentValue(input); + bd.getConstructorArgumentValues().addGenericArgumentValue(input); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getShortMap()).isNotSameAs(gb.getPlainMap()); assertThat(gb.getPlainMap()).hasSize(2); assertThat(gb.getPlainMap().get("1")).isEqualTo("0"); @@ -340,17 +333,16 @@ class BeanFactoryGenericsTests { @Test void genericMapMapConstructorWithSameRefAndNoConversion() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); Map input = new HashMap<>(); input.put((short) 1, 0); input.put((short) 2, 3); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input); + bd.getConstructorArgumentValues().addGenericArgumentValue(input); + bd.getConstructorArgumentValues().addGenericArgumentValue(input); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getShortMap()).isSameAs(gb.getPlainMap()); assertThat(gb.getShortMap()).hasSize(2); assertThat(gb.getShortMap().get(Short.valueOf("1"))).isEqualTo(0); @@ -360,16 +352,15 @@ class BeanFactoryGenericsTests { @Test void genericMapWithKeyTypeConstructor() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); Map input = Map.of( "4", "5", "6", "7"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input); + bd.getConstructorArgumentValues().addGenericArgumentValue(input); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getLongMap().get(4L)).isEqualTo("5"); assertThat(gb.getLongMap().get(6L)).isEqualTo("7"); } @@ -377,18 +368,18 @@ class BeanFactoryGenericsTests { @Test void genericMapWithCollectionValueConstructor() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.addPropertyEditorRegistrar(registry -> registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false))); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); + bf.addPropertyEditorRegistrar(registry -> + registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false))); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); Map> input = Map.of( "1", Set.of(1), "2", List.of(Boolean.TRUE)); - rbd.getConstructorArgumentValues().addGenericArgumentValue(Boolean.TRUE); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input); + bd.getConstructorArgumentValues().addGenericArgumentValue(Boolean.TRUE); + bd.getConstructorArgumentValues().addGenericArgumentValue(input); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getCollectionMap().get(1)).isInstanceOf(Set.class); assertThat(gb.getCollectionMap().get(2)).isInstanceOf(List.class); } @@ -396,32 +387,30 @@ class BeanFactoryGenericsTests { @Test void genericSetFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.setFactoryMethodName("createInstance"); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.setFactoryMethodName("createInstance"); Set input = Set.of("4", "5"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input); + bd.getConstructorArgumentValues().addGenericArgumentValue(input); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5); } @Test void genericSetListFactoryMethod() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.setFactoryMethodName("createInstance"); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.setFactoryMethodName("createInstance"); Set input1 = Set.of("4", "5"); List input2 = List.of("http://localhost:8080", "http://localhost:9090"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input1); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input2); + bd.getConstructorArgumentValues().addGenericArgumentValue(input1); + bd.getConstructorArgumentValues().addGenericArgumentValue(input2); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5); assertThat(gb.getResourceList()) .containsExactly(new UrlResource("http://localhost:8080"), new UrlResource("http://localhost:9090")); @@ -430,19 +419,18 @@ class BeanFactoryGenericsTests { @Test void genericSetMapFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.setFactoryMethodName("createInstance"); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.setFactoryMethodName("createInstance"); Set input1 = Set.of("4", "5"); Map input2 = Map.of( "4", "5", "6", "7"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input1); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input2); + bd.getConstructorArgumentValues().addGenericArgumentValue(input1); + bd.getConstructorArgumentValues().addGenericArgumentValue(input2); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getIntegerSet()).containsExactlyInAnyOrder(4, 5); assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); @@ -451,18 +439,17 @@ class BeanFactoryGenericsTests { @Test void genericMapResourceFactoryMethod() throws Exception { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.setFactoryMethodName("createInstance"); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.setFactoryMethodName("createInstance"); Map input = Map.of( "4", "5", "6", "7"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input); - rbd.getConstructorArgumentValues().addGenericArgumentValue("http://localhost:8080"); + bd.getConstructorArgumentValues().addGenericArgumentValue(input); + bd.getConstructorArgumentValues().addGenericArgumentValue("http://localhost:8080"); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); assertThat(gb.getShortMap().get(Short.valueOf("6"))).isEqualTo(7); assertThat(gb.getResourceList()).containsExactly(new UrlResource("http://localhost:8080")); @@ -471,21 +458,20 @@ class BeanFactoryGenericsTests { @Test void genericMapMapFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.setFactoryMethodName("createInstance"); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.setFactoryMethodName("createInstance"); Map input1 = Map.of( "1", "0", "2", "3"); Map input2 = Map.of( "4", "5", "6", "7"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input1); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input2); + bd.getConstructorArgumentValues().addGenericArgumentValue(input1); + bd.getConstructorArgumentValues().addGenericArgumentValue(input2); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getPlainMap().get("1")).isEqualTo("0"); assertThat(gb.getPlainMap().get("2")).isEqualTo("3"); assertThat(gb.getShortMap().get(Short.valueOf("4"))).isEqualTo(5); @@ -495,17 +481,16 @@ class BeanFactoryGenericsTests { @Test void genericMapWithKeyTypeFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.setFactoryMethodName("createInstance"); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.setFactoryMethodName("createInstance"); Map input = Map.of( "4", "5", "6", "7"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input); + bd.getConstructorArgumentValues().addGenericArgumentValue(input); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getLongMap().get(Long.valueOf("4"))).isEqualTo("5"); assertThat(gb.getLongMap().get(Long.valueOf("6"))).isEqualTo("7"); } @@ -513,19 +498,19 @@ class BeanFactoryGenericsTests { @Test void genericMapWithCollectionValueFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.addPropertyEditorRegistrar(registry -> registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false))); - RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class); - rbd.setFactoryMethodName("createInstance"); + bf.addPropertyEditorRegistrar(registry -> + registry.registerCustomEditor(Number.class, new CustomNumberEditor(Integer.class, false))); + RootBeanDefinition bd = new RootBeanDefinition(GenericBean.class); + bd.setFactoryMethodName("createInstance"); Map> input = Map.of( "1", Set.of(1), "2", List.of(Boolean.TRUE)); - rbd.getConstructorArgumentValues().addGenericArgumentValue(Boolean.TRUE); - rbd.getConstructorArgumentValues().addGenericArgumentValue(input); + bd.getConstructorArgumentValues().addGenericArgumentValue(Boolean.TRUE); + bd.getConstructorArgumentValues().addGenericArgumentValue(input); + bf.registerBeanDefinition("genericBean", bd); - bf.registerBeanDefinition("genericBean", rbd); GenericBean gb = (GenericBean) bf.getBean("genericBean"); - assertThat(gb.getCollectionMap().get(1)).isInstanceOf(Set.class); assertThat(gb.getCollectionMap().get(2)).isInstanceOf(List.class); } @@ -535,6 +520,7 @@ class BeanFactoryGenericsTests { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); + NamedUrlList list = bf.getBean("list", NamedUrlList.class); assertThat(list).containsExactly(new URL("http://localhost:8080")); } @@ -544,6 +530,7 @@ class BeanFactoryGenericsTests { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); + NamedUrlSet set = bf.getBean("set", NamedUrlSet.class); assertThat(set).containsExactly(new URL("http://localhost:8080")); } @@ -553,6 +540,7 @@ class BeanFactoryGenericsTests { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); + NamedUrlMap map = bf.getBean("map", NamedUrlMap.class); assertThat(map).containsExactly(entry(10, new URL("http://localhost:8080"))); } @@ -562,6 +550,7 @@ class BeanFactoryGenericsTests { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); + GenericIntegerBean gb = (GenericIntegerBean) bf.getBean("integerBean"); assertThat(gb.getGenericProperty()).isEqualTo(10); assertThat(gb.getGenericListProperty()).containsExactly(20, 30); @@ -572,6 +561,7 @@ class BeanFactoryGenericsTests { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); + GenericSetOfIntegerBean gb = (GenericSetOfIntegerBean) bf.getBean("setOfIntegerBean"); assertThat(gb.getGenericProperty()).singleElement().isEqualTo(10); assertThat(gb.getGenericListProperty()).satisfiesExactly( @@ -584,6 +574,7 @@ class BeanFactoryGenericsTests { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); new XmlBeanDefinitionReader(bf).loadBeanDefinitions( new ClassPathResource("genericBeanTests.xml", getClass())); + UrlSet urlSet = bf.getBean("setBean", UrlSet.class); assertThat(urlSet).containsExactly(new URL("https://www.springframework.org")); } @@ -600,27 +591,27 @@ class BeanFactoryGenericsTests { */ @Test void parameterizedStaticFactoryMethod() { - RootBeanDefinition rbd = new RootBeanDefinition(getClass()); - rbd.setFactoryMethodName("createMockitoMock"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); + RootBeanDefinition bd = new RootBeanDefinition(getClass()); + bd.setFactoryMethodName("createMockitoMock"); + bd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); - assertRunnableMockFactory(rbd); + assertRunnableMockFactory(bd); } @Test void parameterizedStaticFactoryMethodWithWrappedClassName() { - RootBeanDefinition rbd = new RootBeanDefinition(); - rbd.setBeanClassName(getClass().getName()); - rbd.setFactoryMethodName("createMockitoMock"); + RootBeanDefinition bd = new RootBeanDefinition(); + bd.setBeanClassName(getClass().getName()); + bd.setFactoryMethodName("createMockitoMock"); // TypedStringValue is used as an equivalent to an XML-defined argument String - rbd.getConstructorArgumentValues().addGenericArgumentValue(new TypedStringValue(Runnable.class.getName())); + bd.getConstructorArgumentValues().addGenericArgumentValue(new TypedStringValue(Runnable.class.getName())); - assertRunnableMockFactory(rbd); + assertRunnableMockFactory(bd); } - private void assertRunnableMockFactory(RootBeanDefinition rbd) { + private void assertRunnableMockFactory(RootBeanDefinition bd) { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - bf.registerBeanDefinition("mock", rbd); + bf.registerBeanDefinition("mock", bd); assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue(); assertThat(bf.getType("mock")).isEqualTo(Runnable.class); @@ -643,14 +634,14 @@ class BeanFactoryGenericsTests { void parameterizedInstanceFactoryMethod() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); - bf.registerBeanDefinition("mocksControl", rbd); + RootBeanDefinition bd1 = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", bd1); - rbd = new RootBeanDefinition(); - rbd.setFactoryBeanName("mocksControl"); - rbd.setFactoryMethodName("createMock"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); - bf.registerBeanDefinition("mock", rbd); + RootBeanDefinition bd2 = new RootBeanDefinition(); + bd2.setFactoryBeanName("mocksControl"); + bd2.setFactoryMethodName("createMock"); + bd2.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); + bf.registerBeanDefinition("mock", bd2); assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue(); assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue(); @@ -664,14 +655,14 @@ class BeanFactoryGenericsTests { void parameterizedInstanceFactoryMethodWithNonResolvedClassName() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); - bf.registerBeanDefinition("mocksControl", rbd); + RootBeanDefinition bd1 = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", bd1); - rbd = new RootBeanDefinition(); - rbd.setFactoryBeanName("mocksControl"); - rbd.setFactoryMethodName("createMock"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class.getName()); - bf.registerBeanDefinition("mock", rbd); + RootBeanDefinition bd2 = new RootBeanDefinition(); + bd2.setFactoryBeanName("mocksControl"); + bd2.setFactoryMethodName("createMock"); + bd2.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class.getName()); + bf.registerBeanDefinition("mock", bd2); assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue(); assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue(); @@ -685,14 +676,14 @@ class BeanFactoryGenericsTests { void parameterizedInstanceFactoryMethodWithInvalidClassName() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); - bf.registerBeanDefinition("mocksControl", rbd); + RootBeanDefinition bd1 = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", bd1); - rbd = new RootBeanDefinition(); - rbd.setFactoryBeanName("mocksControl"); - rbd.setFactoryMethodName("createMock"); - rbd.getConstructorArgumentValues().addGenericArgumentValue("x"); - bf.registerBeanDefinition("mock", rbd); + RootBeanDefinition rbd2 = new RootBeanDefinition(); + rbd2.setFactoryBeanName("mocksControl"); + rbd2.setFactoryMethodName("createMock"); + rbd2.getConstructorArgumentValues().addGenericArgumentValue("x"); + bf.registerBeanDefinition("mock", rbd2); assertThat(bf.isTypeMatch("mock", Runnable.class)).isFalse(); assertThat(bf.isTypeMatch("mock", Runnable.class)).isFalse(); @@ -706,14 +697,14 @@ class BeanFactoryGenericsTests { void parameterizedInstanceFactoryMethodWithIndexedArgument() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); - RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); - bf.registerBeanDefinition("mocksControl", rbd); + RootBeanDefinition bd1 = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", bd1); - rbd = new RootBeanDefinition(); - rbd.setFactoryBeanName("mocksControl"); - rbd.setFactoryMethodName("createMock"); - rbd.getConstructorArgumentValues().addIndexedArgumentValue(0, Runnable.class); - bf.registerBeanDefinition("mock", rbd); + RootBeanDefinition bd2 = new RootBeanDefinition(); + bd2.setFactoryBeanName("mocksControl"); + bd2.setFactoryMethodName("createMock"); + bd2.getConstructorArgumentValues().addIndexedArgumentValue(0, Runnable.class); + bf.registerBeanDefinition("mock", bd2); assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue(); assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue(); @@ -728,14 +719,14 @@ class BeanFactoryGenericsTests { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.setTempClassLoader(new OverridingClassLoader(getClass().getClassLoader())); - RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); - bf.registerBeanDefinition("mocksControl", rbd); + RootBeanDefinition bd1 = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", bd1); - rbd = new RootBeanDefinition(); - rbd.setFactoryBeanName("mocksControl"); - rbd.setFactoryMethodName("createMock"); - rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); - bf.registerBeanDefinition("mock", rbd); + RootBeanDefinition bd2 = new RootBeanDefinition(); + bd2.setFactoryBeanName("mocksControl"); + bd2.setFactoryMethodName("createMock"); + bd2.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); + bf.registerBeanDefinition("mock", bd2); assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue(); assertThat(bf.isTypeMatch("mock", Runnable.class)).isTrue(); @@ -767,25 +758,31 @@ class BeanFactoryGenericsTests { assertThat(floatStoreNames).isEmpty(); } - @Test - void genericMatchingWithFullTypeDifferentiation() { + @ParameterizedTest + @ValueSource(classes = {NumberStoreFactory.class, NumberStoreFactoryBeans.class}) + void genericMatchingWithFullTypeDifferentiation(Class factoryClass) { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver()); - RootBeanDefinition bd1 = new RootBeanDefinition(NumberStoreFactory.class); + RootBeanDefinition bd1 = new RootBeanDefinition(factoryClass); bd1.setFactoryMethodName("newDoubleStore"); bf.registerBeanDefinition("store1", bd1); - RootBeanDefinition bd2 = new RootBeanDefinition(NumberStoreFactory.class); + RootBeanDefinition bd2 = new RootBeanDefinition(factoryClass); bd2.setFactoryMethodName("newFloatStore"); bf.registerBeanDefinition("store2", bd2); - bf.registerBeanDefinition("numberBean", - new RootBeanDefinition(NumberBean.class, RootBeanDefinition.AUTOWIRE_CONSTRUCTOR, false)); + RootBeanDefinition bd3 = new RootBeanDefinition(NumberBean.class); + bd3.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + bd3.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR); + bf.registerBeanDefinition("numberBean", bd3); - NumberBean nb = bf.getBean(NumberBean.class); NumberStore store1 = bf.getBean("store1", NumberStore.class); - assertThat(nb.getDoubleStore()).isSameAs(store1); NumberStore store2 = bf.getBean("store2", NumberStore.class); + NumberBean nb = bf.getBean(NumberBean.class); + assertThat(nb.getDoubleStore()).isSameAs(store1); + assertThat(nb.getFloatStore()).isSameAs(store2); + nb = bf.getBean(NumberBean.class); + assertThat(nb.getDoubleStore()).isSameAs(store1); assertThat(nb.getFloatStore()).isSameAs(store2); String[] numberStoreNames = bf.getBeanNamesForType(ResolvableType.forClass(NumberStore.class)); @@ -833,16 +830,17 @@ class BeanFactoryGenericsTests { assertThat(floatStoreProvider.orderedStream()).singleElement().isEqualTo(store2); } - @Test - void genericMatchingWithUnresolvedOrderedStream() { + @ParameterizedTest + @ValueSource(classes = {NumberStoreFactory.class, NumberStoreFactoryBeans.class}) + void genericMatchingWithUnresolvedOrderedStream(Class factoryClass) { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver()); - RootBeanDefinition bd1 = new RootBeanDefinition(NumberStoreFactory.class); + RootBeanDefinition bd1 = new RootBeanDefinition(factoryClass); bd1.setFactoryMethodName("newDoubleStore"); bf.registerBeanDefinition("store1", bd1); - RootBeanDefinition bd2 = new RootBeanDefinition(NumberStoreFactory.class); + RootBeanDefinition bd2 = new RootBeanDefinition(factoryClass); bd2.setFactoryMethodName("newFloatStore"); bf.registerBeanDefinition("store2", bd2); @@ -856,16 +854,36 @@ class BeanFactoryGenericsTests { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver()); + RootBeanDefinition bd = new RootBeanDefinition(MyFactoryBean.class); + bd.setTargetType(ResolvableType.forClassWithGenerics(MyFactoryBean.class, String.class)); + bf.registerBeanDefinition("myFactoryBean", bd); + bf.registerBeanDefinition("myFactoryBeanHolder", + new RootBeanDefinition(MyFactoryBeanHolder.class, AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR, false)); + + assertThat(bf.getBean(MyFactoryBeanHolder.class).factoryBeans).containsOnly(bf.getBean(MyFactoryBean.class)); + assertThat(bf.getBeanProvider(MyGenericInterfaceForFactoryBeans.class)).containsOnly(bf.getBean(MyFactoryBean.class)); + assertThat(bf.getBeanProvider(bd.getResolvableType())).containsOnly(bf.getBean(MyFactoryBean.class)); + } + + @Test // gh-32489 + void genericMatchingAgainstLazyFactoryBeanClass() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver()); + RootBeanDefinition bd = new RootBeanDefinition(MyFactoryBean.class); // Replicate org.springframework.data.repository.config.RepositoryConfigurationDelegate#registerRepositoriesIn // behavior of setting targetType, required to hit other branch in // org.springframework.beans.factory.support.GenericTypeAwareAutowireCandidateResolver.checkGenericTypeMatch bd.setTargetType(ResolvableType.forClassWithGenerics(MyFactoryBean.class, String.class)); + bd.setLazyInit(true); bf.registerBeanDefinition("myFactoryBean", bd); bf.registerBeanDefinition("myFactoryBeanHolder", new RootBeanDefinition(MyFactoryBeanHolder.class, AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR, false)); - assertThat(bf.getBean(MyFactoryBeanHolder.class).factoryBeans).contains(bf.getBean(MyFactoryBean.class)); + assertThat(bf.getBeanProvider(bd.getResolvableType())).containsOnly(bf.getBean(MyFactoryBean.class)); + assertThat(bf.getBeanProvider(MyGenericInterfaceForFactoryBeans.class)).containsOnly(bf.getBean(MyFactoryBean.class)); + assertThat(bf.getBean(MyFactoryBeanHolder.class).factoryBeans).containsOnly(bf.getBean(MyFactoryBean.class)); + assertThat(bf.getBeanProvider(bd.getResolvableType())).containsOnly(bf.getBean(MyFactoryBean.class)); } @@ -985,6 +1003,38 @@ class BeanFactoryGenericsTests { } + public static class NumberStoreFactoryBeans { + + @Order(1) + public static FactoryBean> newDoubleStore() { + return new FactoryBean<>() { + @Override + public NumberStore getObject() { + return new DoubleStore(); + } + @Override + public Class getObjectType() { + return DoubleStore.class; + } + }; + } + + @Order(0) + public static FactoryBean> newFloatStore() { + return new FactoryBean<>() { + @Override + public NumberStore getObject() { + return new FloatStore(); + } + @Override + public Class getObjectType() { + return FloatStore.class; + } + }; + } + } + + public interface MyGenericInterfaceForFactoryBeans { } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java index 564acf1ca40..80f174db288 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java @@ -409,15 +409,17 @@ class AnnotationConfigApplicationContextTests { bd2.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class, ResolvableType.forClassWithGenerics(GenericHolder.class, Integer.class))); bd2.setLazyInit(true); context.registerBeanDefinition("fb2", bd2); - context.registerBeanDefinition("ip", new RootBeanDefinition(FactoryBeanInjectionPoints.class)); + RootBeanDefinition bd3 = new RootBeanDefinition(FactoryBeanInjectionPoints.class); + bd3.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + context.registerBeanDefinition("ip", bd3); context.refresh(); + assertThat(context.getBean("ip", FactoryBeanInjectionPoints.class).factoryBean).isSameAs(context.getBean("&fb1")); + assertThat(context.getBean("ip", FactoryBeanInjectionPoints.class).factoryResult).isSameAs(context.getBean("fb1")); assertThat(context.getType("&fb1")).isEqualTo(GenericHolderFactoryBean.class); assertThat(context.getType("fb1")).isEqualTo(GenericHolder.class); assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(2); assertThat(context.getBeanNamesForType(GenericHolderFactoryBean.class)).hasSize(1); - assertThat(context.getBean("ip", FactoryBeanInjectionPoints.class).factoryBean).isSameAs(context.getBean("&fb1")); - assertThat(context.getBean("ip", FactoryBeanInjectionPoints.class).factoryResult).isSameAs(context.getBean("fb1")); } @Test @@ -425,7 +427,7 @@ class AnnotationConfigApplicationContextTests { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); RootBeanDefinition bd1 = new RootBeanDefinition(); bd1.setBeanClass(GenericHolderFactoryBean.class); - bd1.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class, ResolvableType.forClassWithGenerics(GenericHolder.class, Object.class))); + bd1.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class, ResolvableType.forClassWithGenerics(GenericHolder.class, String.class))); bd1.setLazyInit(true); context.registerBeanDefinition("fb1", bd1); RootBeanDefinition bd2 = new RootBeanDefinition(); @@ -433,13 +435,19 @@ class AnnotationConfigApplicationContextTests { bd2.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class, ResolvableType.forClassWithGenerics(GenericHolder.class, Integer.class))); bd2.setLazyInit(true); context.registerBeanDefinition("fb2", bd2); - context.registerBeanDefinition("ip", new RootBeanDefinition(FactoryResultInjectionPoint.class)); + RootBeanDefinition bd3 = new RootBeanDefinition(FactoryBeanInjectionPoints.class); + bd3.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + context.registerBeanDefinition("ip", bd3); context.refresh(); + assertThat(context.getBean("ip", FactoryResultInjectionPoint.class).factoryResult).isSameAs(context.getBean("fb1")); + assertThat(context.getBean("ip", FactoryResultInjectionPoint.class).factoryResult).isSameAs(context.getBean("fb1")); assertThat(context.getType("&fb1")).isEqualTo(GenericHolderFactoryBean.class); assertThat(context.getType("fb1")).isEqualTo(GenericHolder.class); assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(2); - assertThat(context.getBean("ip", FactoryResultInjectionPoint.class).factoryResult).isSameAs(context.getBean("fb1")); + assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(2); + assertThat(context.getBeanProvider(ResolvableType.forClassWithGenerics(GenericHolder.class, String.class))) + .containsOnly(context.getBean("fb1")); } @Test @@ -453,14 +461,19 @@ class AnnotationConfigApplicationContextTests { bd2.setBeanClass(UntypedFactoryBean.class); bd2.setTargetType(ResolvableType.forClassWithGenerics(GenericHolder.class, Integer.class)); context.registerBeanDefinition("fb2", bd2); - context.registerBeanDefinition("ip", new RootBeanDefinition(FactoryResultInjectionPoint.class)); + RootBeanDefinition bd3 = new RootBeanDefinition(FactoryResultInjectionPoint.class); + bd3.setScope(RootBeanDefinition.SCOPE_PROTOTYPE); + context.registerBeanDefinition("ip", bd3); context.refresh(); + assertThat(context.getBean("ip", FactoryResultInjectionPoint.class).factoryResult).isSameAs(context.getBean("fb1")); + assertThat(context.getBean("ip", FactoryResultInjectionPoint.class).factoryResult).isSameAs(context.getBean("fb1")); assertThat(context.getType("&fb1")).isEqualTo(GenericHolderFactoryBean.class); assertThat(context.getType("fb1")).isEqualTo(GenericHolder.class); assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(2); assertThat(context.getBeanNamesForType(GenericHolderFactoryBean.class)).hasSize(1); - assertThat(context.getBean("ip", FactoryResultInjectionPoint.class).factoryResult).isSameAs(context.getBean("fb1")); + assertThat(context.getBeanProvider(ResolvableType.forClassWithGenerics(GenericHolder.class, String.class))) + .containsOnly(context.getBean("fb1")); } @Test @@ -480,6 +493,9 @@ class AnnotationConfigApplicationContextTests { assertThat(context.getType("fb")).isEqualTo(String.class); assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(1); assertThat(context.getBeanNamesForType(TypedFactoryBean.class)).hasSize(1); + assertThat(context.getBeanProvider(String.class)).containsOnly(context.getBean("fb", String.class)); + assertThat(context.getBeanProvider(ResolvableType.forClassWithGenerics(FactoryBean.class, String.class))) + .containsOnly(context.getBean("&fb")); } @Test diff --git a/spring-context/src/test/java/org/springframework/context/annotation/Gh32489Tests.java b/spring-context/src/test/java/org/springframework/context/annotation/Gh32489Tests.java index 4151f397461..ea91c188ecd 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/Gh32489Tests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/Gh32489Tests.java @@ -18,7 +18,6 @@ package org.springframework.context.annotation; import java.util.List; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.BeanUtils; @@ -67,8 +66,7 @@ public class Gh32489Tests { } @Test - @Disabled - void resolveFactoryBeanWithMatchingGeneric() { + void provideFactoryBeanWithMatchingGenerics() { try (AnnotationConfigApplicationContext context = prepareContext()) { context.refresh(); ResolvableType requiredType = ResolvableType.forClassWithGenerics(SimpleRepositoryFactoryBean.class, @@ -78,7 +76,7 @@ public class Gh32489Tests { } @Test - void resolveFactoryBeanWithFirstNonMatchingGeneric() { + void provideFactoryBeanWithFirstNonMatchingGenerics() { try (AnnotationConfigApplicationContext context = prepareContext()) { context.refresh(); ResolvableType requiredType = ResolvableType.forClassWithGenerics(SimpleRepositoryFactoryBean.class, @@ -88,7 +86,7 @@ public class Gh32489Tests { } @Test - void resolveFactoryBeanWithSecondNonMatchingGeneric() { + void provideFactoryBeanWithSecondNonMatchingGenerics() { try (AnnotationConfigApplicationContext context = prepareContext()) { context.refresh(); ResolvableType requiredType = ResolvableType.forClassWithGenerics(SimpleRepositoryFactoryBean.class, @@ -98,7 +96,7 @@ public class Gh32489Tests { } @Test - void resolveFactoryBeanTargetTypeWithMatchingGeneric() { + void provideFactoryBeanTargetTypeWithMatchingGenerics() { try (AnnotationConfigApplicationContext context = prepareContext()) { context.refresh(); ResolvableType requiredType = ResolvableType.forClassWithGenerics(Repository.class, @@ -109,7 +107,7 @@ public class Gh32489Tests { } @Test - void resolveFactoryBeanTargetTypeWithFirstNonMatchingGeneric() { + void provideFactoryBeanTargetTypeWithFirstNonMatchingGenerics() { try (AnnotationConfigApplicationContext context = prepareContext()) { context.refresh(); ResolvableType requiredType = ResolvableType.forClassWithGenerics(Repository.class, @@ -119,7 +117,7 @@ public class Gh32489Tests { } @Test - void resolveFactoryBeanTargetTypeWithSecondNonMatchingGeneric() { + void provideFactoryBeanTargetTypeWithSecondNonMatchingGenerics() { try (AnnotationConfigApplicationContext context = prepareContext()) { context.refresh(); ResolvableType requiredType = ResolvableType.forClassWithGenerics(Repository.class, @@ -143,21 +141,18 @@ public class Gh32489Tests { @Autowired List> repositoryFactoryies; - } static class RepositoryFactoriesInformationHolder { @Autowired List> repositoryFactoresInformation; - } static class RepositoryFactoryHolder { @Autowired SimpleRepositoryFactoryBean repositoryFactory; - } static class SimpleRepositoryFactoryBean extends RepositoryFactoryBeanSupport {