diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index f45a769477a..20d406a5472 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1357,12 +1357,15 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor); try { + // Step 1: pre-resolved shortcut for single bean match, e.g. from @Autowired Object shortcut = descriptor.resolveShortcut(this); if (shortcut != null) { return shortcut; } Class type = descriptor.getDependencyType(); + + // Step 2: pre-defined value or expression, e.g. from @Value Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { if (value instanceof String strValue) { @@ -1383,13 +1386,20 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str } } + // Step 3a: multiple beans as stream / array / standard collection / plain map Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter); if (multipleBeans != null) { return multipleBeans; } - + // Step 3b: direct bean matches, possibly direct beans of type Collection / Map Map matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { + // Step 3c (fallback): custom Collection / Map declarations for collecting multiple beans + multipleBeans = resolveMultipleBeansFallback(descriptor, beanName, autowiredBeanNames, typeConverter); + if (multipleBeans != null) { + return multipleBeans; + } + // Raise exception if nothing found for required injection point if (isRequired(descriptor)) { raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); } @@ -1399,10 +1409,12 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str String autowiredBeanName; Object instanceCandidate; + // Step 4: determine single candidate if (matchingBeans.size() > 1) { autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); if (autowiredBeanName == null) { - if (isRequired(descriptor) || !indicatesMultipleBeans(type)) { + if (isRequired(descriptor) || !indicatesArrayCollectionOrMap(type)) { + // Raise exception if no clear match found for required injection point return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans); } else { @@ -1421,6 +1433,7 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str instanceCandidate = entry.getValue(); } + // Step 5: validate single result if (autowiredBeanNames != null) { autowiredBeanNames.add(autowiredBeanName); } @@ -1430,6 +1443,7 @@ public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable Str Object result = instanceCandidate; if (result instanceof NullBean) { if (isRequired(descriptor)) { + // Raise exception if null encountered for required injection point raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); } result = null; @@ -1491,63 +1505,92 @@ else if (type.isArray()) { } return result; } - else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { - Class elementType = descriptor.getResolvableType().asCollection().resolveGeneric(); - if (elementType == null) { - return null; - } - Map matchingBeans = findAutowireCandidates(beanName, elementType, - new MultiElementDescriptor(descriptor)); - if (matchingBeans.isEmpty()) { - return null; - } - if (autowiredBeanNames != null) { - autowiredBeanNames.addAll(matchingBeans.keySet()); - } - TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); - Object result = converter.convertIfNecessary(matchingBeans.values(), type); - if (result instanceof List list && list.size() > 1) { - Comparator comparator = adaptDependencyComparator(matchingBeans); - if (comparator != null) { - list.sort(comparator); - } - } - return result; + else if (Collection.class == type || Set.class == type || List.class == type) { + return resolveMultipleBeanCollection(descriptor, beanName, autowiredBeanNames, typeConverter); } else if (Map.class == type) { - ResolvableType mapType = descriptor.getResolvableType().asMap(); - Class keyType = mapType.resolveGeneric(0); - if (String.class != keyType) { - return null; - } - Class valueType = mapType.resolveGeneric(1); - if (valueType == null) { - return null; - } - Map matchingBeans = findAutowireCandidates(beanName, valueType, - new MultiElementDescriptor(descriptor)); - if (matchingBeans.isEmpty()) { - return null; - } - if (autowiredBeanNames != null) { - autowiredBeanNames.addAll(matchingBeans.keySet()); - } - return matchingBeans; + return resolveMultipleBeanMap(descriptor, beanName, autowiredBeanNames, typeConverter); } - else { + return null; + } + + + @Nullable + private Object resolveMultipleBeansFallback(DependencyDescriptor descriptor, @Nullable String beanName, + @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) { + + Class type = descriptor.getDependencyType(); + + if (Collection.class.isAssignableFrom(type) && type.isInterface()) { + return resolveMultipleBeanCollection(descriptor, beanName, autowiredBeanNames, typeConverter); + } + else if (Map.class.isAssignableFrom(type) && type.isInterface()) { + return resolveMultipleBeanMap(descriptor, beanName, autowiredBeanNames, typeConverter); + } + return null; + } + + @Nullable + private Object resolveMultipleBeanCollection(DependencyDescriptor descriptor, @Nullable String beanName, + @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) { + + Class elementType = descriptor.getResolvableType().asCollection().resolveGeneric(); + if (elementType == null) { + return null; + } + Map matchingBeans = findAutowireCandidates(beanName, elementType, + new MultiElementDescriptor(descriptor)); + if (matchingBeans.isEmpty()) { return null; } + if (autowiredBeanNames != null) { + autowiredBeanNames.addAll(matchingBeans.keySet()); + } + TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); + Object result = converter.convertIfNecessary(matchingBeans.values(), descriptor.getDependencyType()); + if (result instanceof List list && list.size() > 1) { + Comparator comparator = adaptDependencyComparator(matchingBeans); + if (comparator != null) { + list.sort(comparator); + } + } + return result; } - private boolean isRequired(DependencyDescriptor descriptor) { - return getAutowireCandidateResolver().isRequired(descriptor); + @Nullable + private Object resolveMultipleBeanMap(DependencyDescriptor descriptor, @Nullable String beanName, + @Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) { + + ResolvableType mapType = descriptor.getResolvableType().asMap(); + Class keyType = mapType.resolveGeneric(0); + if (String.class != keyType) { + return null; + } + Class valueType = mapType.resolveGeneric(1); + if (valueType == null) { + return null; + } + Map matchingBeans = findAutowireCandidates(beanName, valueType, + new MultiElementDescriptor(descriptor)); + if (matchingBeans.isEmpty()) { + return null; + } + if (autowiredBeanNames != null) { + autowiredBeanNames.addAll(matchingBeans.keySet()); + } + TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); + return converter.convertIfNecessary(matchingBeans, descriptor.getDependencyType()); } - private boolean indicatesMultipleBeans(Class type) { + private boolean indicatesArrayCollectionOrMap(Class type) { return (type.isArray() || (type.isInterface() && (Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)))); } + private boolean isRequired(DependencyDescriptor descriptor) { + return getAutowireCandidateResolver().isRequired(descriptor); + } + @Nullable private Comparator adaptDependencyComparator(Map matchingBeans) { Comparator comparator = getDependencyComparator(); @@ -1609,7 +1652,7 @@ protected Map findAutowireCandidates( } } if (result.isEmpty()) { - boolean multiple = indicatesMultipleBeans(requiredType); + boolean multiple = indicatesArrayCollectionOrMap(requiredType); // Consider fallback matches if the first pass failed to find anything... DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch(); for (String candidate : candidateNames) { @@ -1675,7 +1718,7 @@ protected String determineAutowireCandidate(Map candidates, Depe if (priorityCandidate != null) { return priorityCandidate; } - // Fallback + // Fallback: pick directly registered dependency or qualified bean name match for (Map.Entry entry : candidates.entrySet()) { String candidateName = entry.getKey(); Object beanInstance = entry.getValue(); @@ -2094,7 +2137,6 @@ public Object getIfUnique() throws BeansException { public boolean isRequired() { return false; } - @Override @Nullable public Object resolveNotUnique(ResolvableType type, Map matchingBeans) { 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 289863e6d1c..f76705de4af 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 @@ -34,6 +34,8 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; import java.util.concurrent.Callable; import java.util.function.Consumer; import java.util.function.Function; @@ -1403,6 +1405,20 @@ void constructorInjectionWithPlainHashMapAsBean() { assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap")); } + @Test + void constructorInjectionWithSortedMapFallback() { + RootBeanDefinition bd = new RootBeanDefinition(SortedMapConstructorInjectionBean.class); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb1 = new TestBean(); + TestBean tb2 = new TestBean(); + bf.registerSingleton("testBean1", tb1); + bf.registerSingleton("testBean2", tb2); + + SortedMapConstructorInjectionBean bean = bf.getBean("annotatedBean", SortedMapConstructorInjectionBean.class); + assertThat(bean.getTestBeanMap()).containsEntry("testBean1", tb1); + assertThat(bean.getTestBeanMap()).containsEntry("testBean2", tb2); + } + @Test void constructorInjectionWithTypedSetAsBean() { RootBeanDefinition bd = new RootBeanDefinition(SetConstructorInjectionBean.class); @@ -1444,6 +1460,8 @@ void constructorInjectionWithCustomSetAsBean() { RootBeanDefinition tbs = new RootBeanDefinition(CustomCollectionFactoryMethods.class); tbs.setUniqueFactoryMethodName("testBeanSet"); bf.registerBeanDefinition("myTestBeanSet", tbs); + bf.registerSingleton("testBean1", new TestBean()); + bf.registerSingleton("testBean2", new TestBean()); CustomSetConstructorInjectionBean bean = bf.getBean("annotatedBean", CustomSetConstructorInjectionBean.class); assertThat(bean.getTestBeanSet()).isSameAs(bf.getBean("myTestBeanSet")); @@ -1451,6 +1469,19 @@ void constructorInjectionWithCustomSetAsBean() { assertThat(bean.getTestBeanSet()).isSameAs(bf.getBean("myTestBeanSet")); } + @Test + void constructorInjectionWithSortedSetFallback() { + RootBeanDefinition bd = new RootBeanDefinition(SortedSetConstructorInjectionBean.class); + bf.registerBeanDefinition("annotatedBean", bd); + TestBean tb1 = new TestBean(); + TestBean tb2 = new TestBean(); + bf.registerSingleton("testBean1", tb1); + bf.registerSingleton("testBean2", tb2); + + SortedSetConstructorInjectionBean bean = bf.getBean("annotatedBean", SortedSetConstructorInjectionBean.class); + assertThat(bean.getTestBeanSet()).contains(tb1, tb2); + } + @Test void selfReference() { bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectionBean.class)); @@ -3116,6 +3147,21 @@ public Map getTestBeanMap() { } + public static class SortedMapConstructorInjectionBean { + + private SortedMap testBeanMap; + + @Autowired + public SortedMapConstructorInjectionBean(SortedMap testBeanMap) { + this.testBeanMap = testBeanMap; + } + + public SortedMap getTestBeanMap() { + return this.testBeanMap; + } + } + + public static class QualifiedMapConstructorInjectionBean { private Map testBeanMap; @@ -3146,6 +3192,21 @@ public Set getTestBeanSet() { } + public static class SortedSetConstructorInjectionBean { + + private SortedSet testBeanSet; + + @Autowired + public SortedSetConstructorInjectionBean(SortedSet testBeanSet) { + this.testBeanSet = testBeanSet; + } + + public SortedSet getTestBeanSet() { + return this.testBeanSet; + } + } + + public static class SelfInjectionBean { @Autowired