Consistent ordered list access and lazy streaming for ObjectProvider
Includes fallback match for collection/map dependency if qualified. Issue: SPR-17272 Issue: SPR-17197
This commit is contained in:
@@ -214,15 +214,23 @@ public interface BeanFactory {
|
||||
* @param requiredType type the bean must match; can be an interface or superclass
|
||||
* @return a corresponding provider handle
|
||||
* @since 5.1
|
||||
* @see #getBeanProvider(ResolvableType)
|
||||
*/
|
||||
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
|
||||
|
||||
/**
|
||||
* Return an provider for the specified bean, allowing for lazy on-demand retrieval
|
||||
* of instances, including availability and uniqueness options.
|
||||
* @param requiredType type the bean must match; can be a generic type declaration
|
||||
* @param requiredType type the bean must match; can be a generic type declaration.
|
||||
* Note that collection types are not supported here, in contrast to reflective
|
||||
* injection points. For programmatically retrieving a list of beans matching a
|
||||
* specific type, specify the actual bean type as an argument here and subsequently
|
||||
* use {@link ObjectProvider#toList()} or its lazy streaming/iteration options.
|
||||
* @return a corresponding provider handle
|
||||
* @since 5.1
|
||||
* @see ObjectProvider#stream()
|
||||
* @see ObjectProvider#iterator()
|
||||
* @see ObjectProvider#toList()
|
||||
*/
|
||||
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
|
||||
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
package org.springframework.beans.factory;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
@@ -35,6 +37,8 @@ import org.springframework.lang.Nullable;
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.3
|
||||
* @param <T> the object type
|
||||
* @see BeanFactory#getBeanProvider
|
||||
* @see org.springframework.beans.factory.annotation.Autowired
|
||||
*/
|
||||
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
||||
|
||||
@@ -137,8 +141,18 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an {@link Iterator} over resolved object instances.
|
||||
* <p>The default implementation delegates to {@link #stream()}.
|
||||
* Return a sequential {@link Stream} over lazily resolved object instances,
|
||||
* without specific ordering guarantees (but typically in registration order).
|
||||
* @since 5.1
|
||||
* @see #iterator()
|
||||
*/
|
||||
default Stream<T> stream() {
|
||||
throw new UnsupportedOperationException("Multi-element access not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an {@link Iterator} over lazily resolved object instances,
|
||||
* without specific ordering guarantees (but typically in registration order).
|
||||
* @since 5.1
|
||||
* @see #stream()
|
||||
*/
|
||||
@@ -148,15 +162,17 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a sequential {@link Stream} over resolved object instances.
|
||||
* <p>The default implementation returns a stream of one element or an
|
||||
* empty stream if not available, resolved via {@link #getIfAvailable()}.
|
||||
* Return a {@link List} with fully resolved object instances,
|
||||
* potentially pre-ordered according to a common comparator.
|
||||
* <p>In a common Spring application context, this will be ordered
|
||||
* according to {@link org.springframework.core.Ordered} /
|
||||
* {@link org.springframework.core.annotation.Order} conventions,
|
||||
* analogous to multi-element injection points of list/array type.
|
||||
* @since 5.1
|
||||
* @see #iterator()
|
||||
* @see #stream()
|
||||
*/
|
||||
default Stream<T> stream() {
|
||||
T instance = getIfAvailable();
|
||||
return (instance != null ? Stream.of(instance) : Stream.empty());
|
||||
default List<T> toList() {
|
||||
return stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -325,6 +325,21 @@ public class QualifierAnnotationAutowireCandidateResolver extends GenericTypeAwa
|
||||
return (autowired == null || autowired.required());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given dependency declares a qualifier annotation.
|
||||
* @see #isQualifier(Class)
|
||||
* @see Qualifier
|
||||
*/
|
||||
@Override
|
||||
public boolean hasQualifier(DependencyDescriptor descriptor) {
|
||||
for (Annotation ann : descriptor.getAnnotations()) {
|
||||
if (isQualifier(ann.annotationType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given dependency declares a value annotation.
|
||||
* @see Value
|
||||
|
||||
@@ -199,23 +199,6 @@ public class DependencyDescriptor extends InjectionPoint implements Serializable
|
||||
return this.eager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this descriptor allows for stream-style access to
|
||||
* result instances.
|
||||
* <p>By default, dependencies are strictly resolved to the declaration of
|
||||
* the injection point and therefore only resolve multiple entries if the
|
||||
* injection point is declared as an array, collection or map. This is
|
||||
* indicated by returning {@code false} here.
|
||||
* <p>Overriding this method to return {@code true} indicates that the
|
||||
* injection point declares the bean type but the resolution is meant to
|
||||
* end up in a {@link java.util.stream.Stream} for the declared bean type,
|
||||
* with the caller handling the multi-instance case for the injection point.
|
||||
* @since 5.1
|
||||
*/
|
||||
public boolean isStreamAccess() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the specified not-unique scenario: by default,
|
||||
* throwing a {@link NoUniqueBeanDefinitionException}.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -57,6 +57,20 @@ public interface AutowireCandidateResolver {
|
||||
return descriptor.isRequired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given descriptor declares a qualifier beyond the type
|
||||
* (typically - but not necessarily - a specific kind of annotation).
|
||||
* <p>The default implementation returns {@code false}.
|
||||
* @param descriptor the descriptor for the target method parameter or field
|
||||
* @return whether the descriptor declares a qualifier, narrowing the candidate
|
||||
* status beyond the type match
|
||||
* @since 5.1
|
||||
* @see org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#hasQualifier
|
||||
*/
|
||||
default boolean hasQualifier(DependencyDescriptor descriptor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a default value is suggested for the given dependency.
|
||||
* <p>The default implementation simply returns {@code null}.
|
||||
|
||||
@@ -40,6 +40,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Provider;
|
||||
|
||||
@@ -386,6 +387,23 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
.map(name -> (T) getBean(name))
|
||||
.filter(bean -> !(bean instanceof NullBean));
|
||||
}
|
||||
@Override
|
||||
public List<T> toList() {
|
||||
String[] beanNames = getBeanNamesForType(requiredType);
|
||||
Map<String, T> matchingBeans = new LinkedHashMap<>(beanNames.length);
|
||||
for (String beanName : beanNames) {
|
||||
Object beanInstance = getBean(beanName);
|
||||
if (!(beanInstance instanceof NullBean)) {
|
||||
matchingBeans.put(beanName, (T) beanInstance);
|
||||
}
|
||||
}
|
||||
List<T> result = new ArrayList<>(matchingBeans.values());
|
||||
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
|
||||
if (comparator != null) {
|
||||
result.sort(comparator);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1242,29 +1260,36 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
|
||||
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {
|
||||
|
||||
Class<?> type = descriptor.getDependencyType();
|
||||
final Class<?> type = descriptor.getDependencyType();
|
||||
|
||||
if (descriptor.isStreamAccess()) {
|
||||
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type,
|
||||
new MultiElementDescriptor(descriptor, false));
|
||||
if (descriptor instanceof StreamDependencyDescriptor) {
|
||||
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
|
||||
if (autowiredBeanNames != null) {
|
||||
autowiredBeanNames.addAll(matchingBeans.keySet());
|
||||
}
|
||||
return matchingBeans.values().stream();
|
||||
Stream<Object> result = matchingBeans.keySet().stream()
|
||||
.map(name -> descriptor.resolveCandidate(name, type, this))
|
||||
.filter(bean -> !(bean instanceof NullBean));
|
||||
if (((StreamDependencyDescriptor) descriptor).isSorted()) {
|
||||
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
|
||||
if (comparator != null) {
|
||||
result = result.sorted(comparator);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else if (type.isArray()) {
|
||||
Class<?> componentType = type.getComponentType();
|
||||
ResolvableType resolvableType = descriptor.getResolvableType();
|
||||
Class<?> resolvedArrayType = resolvableType.resolve();
|
||||
if (resolvedArrayType != null && resolvedArrayType != type) {
|
||||
type = resolvedArrayType;
|
||||
Class<?> resolvedArrayType = resolvableType.resolve(type);
|
||||
if (resolvedArrayType != type) {
|
||||
componentType = resolvableType.getComponentType().resolve();
|
||||
}
|
||||
if (componentType == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
|
||||
new MultiElementDescriptor(descriptor, true));
|
||||
new MultiElementDescriptor(descriptor));
|
||||
if (matchingBeans.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
@@ -1272,9 +1297,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
autowiredBeanNames.addAll(matchingBeans.keySet());
|
||||
}
|
||||
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
|
||||
Object result = converter.convertIfNecessary(matchingBeans.values(), type);
|
||||
if (getDependencyComparator() != null && result instanceof Object[]) {
|
||||
Arrays.sort((Object[]) result, adaptDependencyComparator(matchingBeans));
|
||||
Object result = converter.convertIfNecessary(matchingBeans.values(), resolvedArrayType);
|
||||
if (result instanceof Object[]) {
|
||||
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
|
||||
if (comparator != null) {
|
||||
Arrays.sort((Object[]) result, comparator);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1284,7 +1312,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
|
||||
new MultiElementDescriptor(descriptor, true));
|
||||
new MultiElementDescriptor(descriptor));
|
||||
if (matchingBeans.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
@@ -1293,8 +1321,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
|
||||
Object result = converter.convertIfNecessary(matchingBeans.values(), type);
|
||||
if (getDependencyComparator() != null && result instanceof List) {
|
||||
((List<?>) result).sort(adaptDependencyComparator(matchingBeans));
|
||||
if (result instanceof List) {
|
||||
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
|
||||
if (comparator != null) {
|
||||
((List<?>) result).sort(comparator);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1309,7 +1340,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType,
|
||||
new MultiElementDescriptor(descriptor, true));
|
||||
new MultiElementDescriptor(descriptor));
|
||||
if (matchingBeans.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
@@ -1333,7 +1364,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Comparator<Object> adaptDependencyComparator(Map<String, Object> matchingBeans) {
|
||||
private Comparator<Object> adaptDependencyComparator(Map<String, ?> matchingBeans) {
|
||||
Comparator<Object> comparator = getDependencyComparator();
|
||||
if (comparator instanceof OrderComparator) {
|
||||
return ((OrderComparator) comparator).withSourceProvider(
|
||||
@@ -1344,7 +1375,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
}
|
||||
}
|
||||
|
||||
private OrderComparator.OrderSourceProvider createFactoryAwareOrderSourceProvider(Map<String, Object> beans) {
|
||||
private OrderComparator.OrderSourceProvider createFactoryAwareOrderSourceProvider(Map<String, ?> beans) {
|
||||
IdentityHashMap<Object, String> instancesToBeanNames = new IdentityHashMap<>();
|
||||
beans.forEach((beanName, instance) -> instancesToBeanNames.put(instance, beanName));
|
||||
return new FactoryAwareOrderSourceProvider(instancesToBeanNames);
|
||||
@@ -1385,15 +1416,17 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
addCandidateEntry(result, candidate, descriptor, requiredType);
|
||||
}
|
||||
}
|
||||
if (result.isEmpty() && !indicatesMultipleBeans(requiredType)) {
|
||||
if (result.isEmpty()) {
|
||||
boolean multiple = indicatesMultipleBeans(requiredType);
|
||||
// Consider fallback matches if the first pass failed to find anything...
|
||||
DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
|
||||
for (String candidate : candidateNames) {
|
||||
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor)) {
|
||||
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
|
||||
(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
|
||||
addCandidateEntry(result, candidate, descriptor, requiredType);
|
||||
}
|
||||
}
|
||||
if (result.isEmpty()) {
|
||||
if (result.isEmpty() && !multiple) {
|
||||
// Consider self references as a final pass...
|
||||
// but in the case of a dependency collection, not the very same bean itself.
|
||||
for (String candidate : candidateNames) {
|
||||
@@ -1731,15 +1764,30 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
|
||||
|
||||
/**
|
||||
* A dependency descriptor marker for multiple elements.
|
||||
* A dependency descriptor for a multi-element declaration with nested elements.
|
||||
*/
|
||||
private static class MultiElementDescriptor extends DependencyDescriptor {
|
||||
private static class MultiElementDescriptor extends NestedDependencyDescriptor {
|
||||
|
||||
public MultiElementDescriptor(DependencyDescriptor original, boolean nested) {
|
||||
public MultiElementDescriptor(DependencyDescriptor original) {
|
||||
super(original);
|
||||
if (nested) {
|
||||
increaseNestingLevel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A dependency descriptor marker for stream access to multiple elements.
|
||||
*/
|
||||
private static class StreamDependencyDescriptor extends DependencyDescriptor {
|
||||
|
||||
private final boolean sorted;
|
||||
|
||||
public StreamDependencyDescriptor(DependencyDescriptor original, boolean sorted) {
|
||||
super(original);
|
||||
this.sorted = sorted;
|
||||
}
|
||||
|
||||
public boolean isSorted() {
|
||||
return this.sorted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1852,22 +1900,19 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Stream<Object> stream() {
|
||||
DependencyDescriptor descriptorToUse = new DependencyDescriptor(this.descriptor) {
|
||||
@Override
|
||||
public boolean isStreamAccess() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, false);
|
||||
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null);
|
||||
if (result instanceof Stream) {
|
||||
return (Stream<Object>) result;
|
||||
}
|
||||
else if (result instanceof Collection) {
|
||||
return ((Collection<Object>) result).stream();
|
||||
}
|
||||
else {
|
||||
return (result != null ? Stream.of(result) : Stream.empty());
|
||||
}
|
||||
Assert.state(result instanceof Stream, "Stream expected");
|
||||
return (Stream<Object>) result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public List<Object> toList() {
|
||||
DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, true);
|
||||
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null);
|
||||
Assert.state(result instanceof Stream, "Stream expected");
|
||||
return ((Stream<Object>) result).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.beans.factory.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
@@ -127,7 +128,11 @@ public class GenericTypeAwareAutowireCandidateResolver extends SimpleAutowireCan
|
||||
if (cacheType) {
|
||||
rbd.targetType = targetType;
|
||||
}
|
||||
if (descriptor.fallbackMatchAllowed() && targetType.hasUnresolvableGenerics()) {
|
||||
if (descriptor.fallbackMatchAllowed() &&
|
||||
(targetType.hasUnresolvableGenerics() || targetType.resolve() == Properties.class)) {
|
||||
// Fallback matches allow unresolvable generics, e.g. plain HashMap to Map<String,String>;
|
||||
// and pragmatically also java.util.Properties to any Map (since despite formally being a
|
||||
// Map<Object,Object>, java.util.Properties is usually perceived as a Map<String,String>).
|
||||
return true;
|
||||
}
|
||||
// Full check for complex generic type match...
|
||||
|
||||
Reference in New Issue
Block a user