diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java index b67fdb6e54..12041b3686 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java @@ -66,11 +66,15 @@ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolve * @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate() * @see AbstractBeanDefinition#isDefaultCandidate() */ + @SuppressWarnings("unchecked") public static Map resolveAutowireCandidates(ConfigurableListableBeanFactory lbf, Class type) { Map candidates = new LinkedHashMap<>(); for (String beanName : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(lbf, type)) { if (AutowireUtils.isAutowireCandidate(lbf, beanName)) { - candidates.put(beanName, lbf.getBean(beanName, type)); + Object beanInstance = lbf.getBean(beanName); + if (!(beanInstance instanceof NullBean)) { + candidates.put(beanName, (T) beanInstance); + } } } return candidates; 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 aed293304a..b432e50b38 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 @@ -1770,6 +1770,10 @@ class AutowiredAnnotationBeanPostProcessorTests { parent.registerBeanDefinition("testBean4", tb4); bf.setParentBeanFactory(parent); + RootBeanDefinition tb5 = new RootBeanDefinition(NullFactoryMethods.class); + tb5.setFactoryMethodName("createTestBean"); + bf.registerBeanDefinition("testBean5", tb5); + ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class); assertThat(bean.streamTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class)); @@ -1789,7 +1793,7 @@ class AutowiredAnnotationBeanPostProcessorTests { Map typeMatches = BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, TestBean.class); assertThat(typeMatches.remove("testBean3")).isNotNull(); Map candidates = SimpleAutowireCandidateResolver.resolveAutowireCandidates(bf, TestBean.class); - assertThat(candidates).containsExactlyEntriesOf(candidates); + assertThat(candidates).containsExactlyEntriesOf(typeMatches); } @Test diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java index a8e6e51cb1..f4c960b923 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/GenericConversionService.java @@ -341,6 +341,13 @@ public class GenericConversionService implements ConfigurableConversionService { conditionalConverter.matches(sourceType, targetType); } + public boolean matchesFallback(TypeDescriptor sourceType, TypeDescriptor targetType) { + return (this.typeInfo.getTargetType() == targetType.getObjectType() && + this.targetType.hasUnresolvableGenerics() && + (!(this.converter instanceof ConditionalConverter conditionalConverter) || + conditionalConverter.matches(sourceType, targetType))); + } + @Override public @Nullable Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { @@ -610,12 +617,20 @@ public class GenericConversionService implements ConfigurableConversionService { } public @Nullable GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) { + // Look for proper match among all converters (taking full generics into account) for (GenericConverter converter : this.converters) { if (!(converter instanceof ConditionalGenericConverter genericConverter) || genericConverter.matches(sourceType, targetType)) { return converter; } } + // Fallback to pre-6.2.3 behavior: accept Class match for unresolvable generics + for (GenericConverter converter : this.converters) { + if (converter instanceof ConverterAdapter converterAdapter && + converterAdapter.matchesFallback(sourceType, targetType)) { + return converter; + } + } return null; } diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java index 87fac1f6bc..dbe84aaa1f 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/GenericConversionServiceTests.java @@ -581,6 +581,18 @@ class GenericConversionServiceTests { assertThat(bList).allMatch(e -> e instanceof BRaw); } + @Test + void stringToListOfMapConverterWithFallbackMatch() { + conversionService.addConverter(new StringToListOfMapConverter()); + + List> result = (List>) conversionService.convert("foo", + TypeDescriptor.valueOf(String.class), + TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(Map.class)) + ); + + assertThat("foo").isEqualTo(result.get(0).get("bar")); + } + @ExampleAnnotation(active = true) public String annotatedString; @@ -969,4 +981,13 @@ class GenericConversionServiceTests { } } + + private static class StringToListOfMapConverter implements Converter>> { + + @Override + public List> convert(String source) { + return List.of(Map.of("bar", source)); + } + } + }