Revise multiple beans resolution for custom collection types
Closes gh-30022
This commit is contained in:
parent
c65b0a199e
commit
9ede1d07a0
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue