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:
Juergen Hoeller
2018-09-13 18:23:19 +02:00
parent 068565172e
commit 65c8fa400f
12 changed files with 304 additions and 137 deletions

View File

@@ -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);

View File

@@ -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());
}
}

View File

@@ -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

View File

@@ -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}.

View File

@@ -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}.

View File

@@ -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());
}
}

View File

@@ -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...