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 2daf769109a..d2452cf6fb4 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; @@ -756,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)); @@ -822,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); @@ -854,7 +863,22 @@ class BeanFactoryGenericsTests { bf.registerBeanDefinition("myFactoryBeanHolder", new RootBeanDefinition(MyFactoryBeanHolder.class, AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR, false)); - assertThat(bf.getBean(MyFactoryBeanHolder.class).factoryBeans).contains(bf.getBean(MyFactoryBean.class)); + assertThat(bf.getBean(MyFactoryBeanHolder.class).factoryBeans).containsOnly(bf.getBean(MyFactoryBean.class)); + } + + @Test // gh-32489 + void genericMatchingAgainstLazyFactoryBeanClass() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver()); + + RootBeanDefinition bd = new RootBeanDefinition(MyFactoryBean.class); + 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).containsOnly(bf.getBean(MyFactoryBean.class)); } @@ -974,6 +998,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 1eb25d791ec..ad81a380acc 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -307,8 +307,8 @@ class AnnotationConfigApplicationContextTests { assertThat(ObjectUtils.containsElement(context.getBeanNamesForType(BeanC.class), "c")).isTrue(); assertThat(context.getBeansOfType(BeanA.class)).isEmpty(); - assertThat(context.getBeansOfType(BeanB.class).values().iterator().next()).isSameAs(context.getBean(BeanB.class)); - assertThat(context.getBeansOfType(BeanC.class).values().iterator().next()).isSameAs(context.getBean(BeanC.class)); + assertThat(context.getBeansOfType(BeanB.class).values()).singleElement().isSameAs(context.getBean(BeanB.class)); + assertThat(context.getBeansOfType(BeanC.class).values()).singleElement().isSameAs(context.getBean(BeanC.class)); assertThatExceptionOfType(NoSuchBeanDefinitionException.class) .isThrownBy(() -> context.getBeanFactory().resolveNamedBean(BeanA.class)); @@ -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,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(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); } @Test @@ -453,14 +459,17 @@ 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")); } @Test