Revise multiple beans resolution for custom collection types

Closes gh-30022
This commit is contained in:
Juergen Hoeller 2023-08-12 12:48:14 +02:00
parent c65b0a199e
commit 9ede1d07a0
2 changed files with 154 additions and 51 deletions

View File

@ -1357,12 +1357,15 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor); InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try { try {
// Step 1: pre-resolved shortcut for single bean match, e.g. from @Autowired
Object shortcut = descriptor.resolveShortcut(this); Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) { if (shortcut != null) {
return shortcut; return shortcut;
} }
Class<?> type = descriptor.getDependencyType(); Class<?> type = descriptor.getDependencyType();
// Step 2: pre-defined value or expression, e.g. from @Value
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) { if (value != null) {
if (value instanceof String strValue) { if (value instanceof String strValue) {
@ -1383,13 +1386,20 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
} }
} }
// Step 3a: multiple beans as stream / array / standard collection / plain map
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter); Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) { if (multipleBeans != null) {
return multipleBeans; return multipleBeans;
} }
// Step 3b: direct bean matches, possibly direct beans of type Collection / Map
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) { 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)) { if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
} }
@ -1399,10 +1409,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
String autowiredBeanName; String autowiredBeanName;
Object instanceCandidate; Object instanceCandidate;
// Step 4: determine single candidate
if (matchingBeans.size() > 1) { if (matchingBeans.size() > 1) {
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) { 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); return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
} }
else { else {
@ -1421,6 +1433,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
instanceCandidate = entry.getValue(); instanceCandidate = entry.getValue();
} }
// Step 5: validate single result
if (autowiredBeanNames != null) { if (autowiredBeanNames != null) {
autowiredBeanNames.add(autowiredBeanName); autowiredBeanNames.add(autowiredBeanName);
} }
@ -1430,6 +1443,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
Object result = instanceCandidate; Object result = instanceCandidate;
if (result instanceof NullBean) { if (result instanceof NullBean) {
if (isRequired(descriptor)) { if (isRequired(descriptor)) {
// Raise exception if null encountered for required injection point
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
} }
result = null; result = null;
@ -1491,63 +1505,92 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
} }
return result; return result;
} }
else if (Collection.class.isAssignableFrom(type) && type.isInterface()) { else if (Collection.class == type || Set.class == type || List.class == type) {
Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric(); return resolveMultipleBeanCollection(descriptor, beanName, autowiredBeanNames, typeConverter);
if (elementType == null) {
return null;
}
Map<String, Object> 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<Object> comparator = adaptDependencyComparator(matchingBeans);
if (comparator != null) {
list.sort(comparator);
}
}
return result;
} }
else if (Map.class == type) { else if (Map.class == type) {
ResolvableType mapType = descriptor.getResolvableType().asMap(); return resolveMultipleBeanMap(descriptor, beanName, autowiredBeanNames, typeConverter);
Class<?> keyType = mapType.resolveGeneric(0);
if (String.class != keyType) {
return null;
}
Class<?> valueType = mapType.resolveGeneric(1);
if (valueType == null) {
return null;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,
new MultiElementDescriptor(descriptor));
if (matchingBeans.isEmpty()) {
return null;
}
if (autowiredBeanNames != null) {
autowiredBeanNames.addAll(matchingBeans.keySet());
}
return matchingBeans;
} }
else { return null;
}
@Nullable
private Object resolveMultipleBeansFallback(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> 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<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {
Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
if (elementType == null) {
return null; return null;
} }
Map<String, Object> 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<Object> comparator = adaptDependencyComparator(matchingBeans);
if (comparator != null) {
list.sort(comparator);
}
}
return result;
}
@Nullable
private Object resolveMultipleBeanMap(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> 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<String, Object> 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 indicatesArrayCollectionOrMap(Class<?> type) {
return (type.isArray() || (type.isInterface() &&
(Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type))));
} }
private boolean isRequired(DependencyDescriptor descriptor) { private boolean isRequired(DependencyDescriptor descriptor) {
return getAutowireCandidateResolver().isRequired(descriptor); return getAutowireCandidateResolver().isRequired(descriptor);
} }
private boolean indicatesMultipleBeans(Class<?> type) {
return (type.isArray() || (type.isInterface() &&
(Collection.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type))));
}
@Nullable @Nullable
private Comparator<Object> adaptDependencyComparator(Map<String, ?> matchingBeans) { private Comparator<Object> adaptDependencyComparator(Map<String, ?> matchingBeans) {
Comparator<Object> comparator = getDependencyComparator(); Comparator<Object> comparator = getDependencyComparator();
@ -1609,7 +1652,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
} }
} }
if (result.isEmpty()) { if (result.isEmpty()) {
boolean multiple = indicatesMultipleBeans(requiredType); boolean multiple = indicatesArrayCollectionOrMap(requiredType);
// Consider fallback matches if the first pass failed to find anything... // Consider fallback matches if the first pass failed to find anything...
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch(); DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
for (String candidate : candidateNames) { for (String candidate : candidateNames) {
@ -1675,7 +1718,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
if (priorityCandidate != null) { if (priorityCandidate != null) {
return priorityCandidate; return priorityCandidate;
} }
// Fallback // Fallback: pick directly registered dependency or qualified bean name match
for (Map.Entry<String, Object> entry : candidates.entrySet()) { for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateName = entry.getKey(); String candidateName = entry.getKey();
Object beanInstance = entry.getValue(); Object beanInstance = entry.getValue();
@ -2094,7 +2137,6 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
public boolean isRequired() { public boolean isRequired() {
return false; return false;
} }
@Override @Override
@Nullable @Nullable
public Object resolveNotUnique(ResolvableType type, Map<String, Object> matchingBeans) { public Object resolveNotUnique(ResolvableType type, Map<String, Object> matchingBeans) {

View File

@ -34,6 +34,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@ -1403,6 +1405,20 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBeanMap()).isSameAs(bf.getBean("myTestBeanMap")); 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 @Test
void constructorInjectionWithTypedSetAsBean() { void constructorInjectionWithTypedSetAsBean() {
RootBeanDefinition bd = new RootBeanDefinition(SetConstructorInjectionBean.class); RootBeanDefinition bd = new RootBeanDefinition(SetConstructorInjectionBean.class);
@ -1444,6 +1460,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
RootBeanDefinition tbs = new RootBeanDefinition(CustomCollectionFactoryMethods.class); RootBeanDefinition tbs = new RootBeanDefinition(CustomCollectionFactoryMethods.class);
tbs.setUniqueFactoryMethodName("testBeanSet"); tbs.setUniqueFactoryMethodName("testBeanSet");
bf.registerBeanDefinition("myTestBeanSet", tbs); bf.registerBeanDefinition("myTestBeanSet", tbs);
bf.registerSingleton("testBean1", new TestBean());
bf.registerSingleton("testBean2", new TestBean());
CustomSetConstructorInjectionBean bean = bf.getBean("annotatedBean", CustomSetConstructorInjectionBean.class); CustomSetConstructorInjectionBean bean = bf.getBean("annotatedBean", CustomSetConstructorInjectionBean.class);
assertThat(bean.getTestBeanSet()).isSameAs(bf.getBean("myTestBeanSet")); assertThat(bean.getTestBeanSet()).isSameAs(bf.getBean("myTestBeanSet"));
@ -1451,6 +1469,19 @@ public class AutowiredAnnotationBeanPostProcessorTests {
assertThat(bean.getTestBeanSet()).isSameAs(bf.getBean("myTestBeanSet")); 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 @Test
void selfReference() { void selfReference() {
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectionBean.class)); bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(SelfInjectionBean.class));
@ -3116,6 +3147,21 @@ public class AutowiredAnnotationBeanPostProcessorTests {
} }
public static class SortedMapConstructorInjectionBean {
private SortedMap<String, TestBean> testBeanMap;
@Autowired
public SortedMapConstructorInjectionBean(SortedMap<String, TestBean> testBeanMap) {
this.testBeanMap = testBeanMap;
}
public SortedMap<String, TestBean> getTestBeanMap() {
return this.testBeanMap;
}
}
public static class QualifiedMapConstructorInjectionBean { public static class QualifiedMapConstructorInjectionBean {
private Map<String, TestBean> testBeanMap; private Map<String, TestBean> testBeanMap;
@ -3146,6 +3192,21 @@ public class AutowiredAnnotationBeanPostProcessorTests {
} }
public static class SortedSetConstructorInjectionBean {
private SortedSet<TestBean> testBeanSet;
@Autowired
public SortedSetConstructorInjectionBean(SortedSet<TestBean> testBeanSet) {
this.testBeanSet = testBeanSet;
}
public SortedSet<TestBean> getTestBeanSet() {
return this.testBeanSet;
}
}
public static class SelfInjectionBean { public static class SelfInjectionBean {
@Autowired @Autowired