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:
parent
068565172e
commit
65c8fa400f
|
|
@ -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...
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ import org.springframework.tests.sample.beans.NestedTestBean;
|
|||
import org.springframework.tests.sample.beans.TestBean;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.SerializationTestUtils;
|
||||
import org.springframework.util.comparator.Comparators;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
|
@ -978,6 +979,19 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
assertSame(bf.getBean("myTestBeanMap"), bean.getTestBeanMap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorInjectionWithPlainHashMapAsBean() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(QualifiedMapConstructorInjectionBean.class);
|
||||
bd.setScope(RootBeanDefinition.SCOPE_PROTOTYPE);
|
||||
bf.registerBeanDefinition("annotatedBean", bd);
|
||||
bf.registerBeanDefinition("myTestBeanMap", new RootBeanDefinition(HashMap.class));
|
||||
|
||||
QualifiedMapConstructorInjectionBean bean = (QualifiedMapConstructorInjectionBean) bf.getBean("annotatedBean");
|
||||
assertSame(bf.getBean("myTestBeanMap"), bean.getTestBeanMap());
|
||||
bean = (QualifiedMapConstructorInjectionBean) bf.getBean("annotatedBean");
|
||||
assertSame(bf.getBean("myTestBeanMap"), bean.getTestBeanMap());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructorInjectionWithTypedSetAsBean() {
|
||||
RootBeanDefinition bd = new RootBeanDefinition(SetConstructorInjectionBean.class);
|
||||
|
|
@ -1162,6 +1176,9 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
testBeans = bean.streamTestBeans();
|
||||
assertEquals(1, testBeans.size());
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean")));
|
||||
testBeans = bean.sortedTestBeans();
|
||||
assertEquals(1, testBeans.size());
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -1187,6 +1204,9 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
testBeans = bean.streamTestBeans();
|
||||
assertEquals(1, testBeans.size());
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean")));
|
||||
testBeans = bean.sortedTestBeans();
|
||||
assertEquals(1, testBeans.size());
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -1214,6 +1234,8 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
assertTrue(testBeans.isEmpty());
|
||||
testBeans = bean.streamTestBeans();
|
||||
assertTrue(testBeans.isEmpty());
|
||||
testBeans = bean.sortedTestBeans();
|
||||
assertTrue(testBeans.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -1249,25 +1271,33 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
|
||||
List<?> testBeans = bean.iterateTestBeans();
|
||||
assertEquals(2, testBeans.size());
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean1")));
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean2")));
|
||||
assertSame(bf.getBean("testBean1"), testBeans.get(0));
|
||||
assertSame(bf.getBean("testBean2"), testBeans.get(1));
|
||||
testBeans = bean.forEachTestBeans();
|
||||
assertEquals(2, testBeans.size());
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean1")));
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean2")));
|
||||
assertSame(bf.getBean("testBean1"), testBeans.get(0));
|
||||
assertSame(bf.getBean("testBean2"), testBeans.get(1));
|
||||
testBeans = bean.streamTestBeans();
|
||||
assertEquals(2, testBeans.size());
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean1")));
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean2")));
|
||||
assertSame(bf.getBean("testBean1"), testBeans.get(0));
|
||||
assertSame(bf.getBean("testBean2"), testBeans.get(1));
|
||||
testBeans = bean.sortedTestBeans();
|
||||
assertEquals(2, testBeans.size());
|
||||
assertSame(bf.getBean("testBean1"), testBeans.get(0));
|
||||
assertSame(bf.getBean("testBean2"), testBeans.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObjectProviderInjectionWithTargetPrimary() {
|
||||
bf.setDependencyComparator(Comparators.comparable());
|
||||
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class));
|
||||
RootBeanDefinition tb1 = new RootBeanDefinition(TestBean.class);
|
||||
tb1.getPropertyValues().add("name", "yours");
|
||||
tb1.setPrimary(true);
|
||||
bf.registerBeanDefinition("testBean1", tb1);
|
||||
RootBeanDefinition tb2 = new RootBeanDefinition(TestBean.class);
|
||||
tb2.getPropertyValues().add("name", "mine");
|
||||
tb2.setLazyInit(true);
|
||||
bf.registerBeanDefinition("testBean2", tb2);
|
||||
|
||||
|
|
@ -1281,16 +1311,20 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
|
||||
List<?> testBeans = bean.iterateTestBeans();
|
||||
assertEquals(2, testBeans.size());
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean1")));
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean2")));
|
||||
assertSame(bf.getBean("testBean1"), testBeans.get(0));
|
||||
assertSame(bf.getBean("testBean2"), testBeans.get(1));
|
||||
testBeans = bean.forEachTestBeans();
|
||||
assertEquals(2, testBeans.size());
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean1")));
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean2")));
|
||||
assertSame(bf.getBean("testBean1"), testBeans.get(0));
|
||||
assertSame(bf.getBean("testBean2"), testBeans.get(1));
|
||||
testBeans = bean.streamTestBeans();
|
||||
assertEquals(2, testBeans.size());
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean1")));
|
||||
assertTrue(testBeans.contains(bf.getBean("testBean2")));
|
||||
assertSame(bf.getBean("testBean1"), testBeans.get(0));
|
||||
assertSame(bf.getBean("testBean2"), testBeans.get(1));
|
||||
testBeans = bean.sortedTestBeans();
|
||||
assertEquals(2, testBeans.size());
|
||||
assertSame(bf.getBean("testBean1"), testBeans.get(1));
|
||||
assertSame(bf.getBean("testBean2"), testBeans.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -2668,6 +2702,21 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
}
|
||||
|
||||
|
||||
public static class QualifiedMapConstructorInjectionBean {
|
||||
|
||||
private Map<String, TestBean> testBeanMap;
|
||||
|
||||
@Autowired
|
||||
public QualifiedMapConstructorInjectionBean(@Qualifier("myTestBeanMap") Map<String, TestBean> testBeanMap) {
|
||||
this.testBeanMap = testBeanMap;
|
||||
}
|
||||
|
||||
public Map<String, TestBean> getTestBeanMap() {
|
||||
return this.testBeanMap;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class SetConstructorInjectionBean {
|
||||
|
||||
private Set<TestBean> testBeanSet;
|
||||
|
|
@ -2834,6 +2883,10 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
public List<TestBean> streamTestBeans() {
|
||||
return this.testBeanProvider.stream().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<TestBean> sortedTestBeans() {
|
||||
return this.testBeanProvider.toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import org.springframework.tests.sample.beans.GenericBean;
|
|||
import org.springframework.tests.sample.beans.GenericIntegerBean;
|
||||
import org.springframework.tests.sample.beans.GenericSetOfIntegerBean;
|
||||
import org.springframework.tests.sample.beans.TestBean;
|
||||
import org.springframework.util.comparator.Comparators;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
|
@ -134,7 +135,7 @@ public class BeanFactoryGenericsTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testGenericListPropertyWithOptionalAutowiring() throws MalformedURLException {
|
||||
public void testGenericListPropertyWithOptionalAutowiring() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
|
||||
RootBeanDefinition rbd = new RootBeanDefinition(GenericBean.class);
|
||||
|
|
@ -846,6 +847,7 @@ public class BeanFactoryGenericsTests {
|
|||
public void testGenericMatchingWithFullTypeDifferentiation() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver());
|
||||
bf.setDependencyComparator(Comparators.comparable());
|
||||
|
||||
bf.registerBeanDefinition("store1", new RootBeanDefinition(DoubleStore.class));
|
||||
bf.registerBeanDefinition("store2", new RootBeanDefinition(FloatStore.class));
|
||||
|
|
@ -892,38 +894,51 @@ public class BeanFactoryGenericsTests {
|
|||
assertSame(bf.getBean("store2"), floatStoreProvider.getIfAvailable());
|
||||
assertSame(bf.getBean("store2"), floatStoreProvider.getIfUnique());
|
||||
|
||||
Set<Object> resolved = new HashSet<>();
|
||||
List<NumberStore<?>> resolved = new ArrayList<>();
|
||||
for (NumberStore<?> instance : numberStoreProvider) {
|
||||
resolved.add(instance);
|
||||
}
|
||||
assertEquals(2, resolved.size());
|
||||
assertTrue(resolved.contains(bf.getBean("store1")));
|
||||
assertTrue(resolved.contains(bf.getBean("store2")));
|
||||
assertSame(bf.getBean("store1"), resolved.get(0));
|
||||
assertSame(bf.getBean("store2"), resolved.get(1));
|
||||
|
||||
resolved = numberStoreProvider.stream().collect(Collectors.toSet());
|
||||
resolved = numberStoreProvider.stream().collect(Collectors.toList());
|
||||
assertEquals(2, resolved.size());
|
||||
assertTrue(resolved.contains(bf.getBean("store1")));
|
||||
assertTrue(resolved.contains(bf.getBean("store2")));
|
||||
assertSame(bf.getBean("store1"), resolved.get(0));
|
||||
assertSame(bf.getBean("store2"), resolved.get(1));
|
||||
|
||||
resolved = new HashSet<>();
|
||||
resolved = numberStoreProvider.toList();
|
||||
assertEquals(2, resolved.size());
|
||||
assertSame(bf.getBean("store2"), resolved.get(0));
|
||||
assertSame(bf.getBean("store1"), resolved.get(1));
|
||||
|
||||
resolved = new ArrayList<>();
|
||||
for (NumberStore<Double> instance : doubleStoreProvider) {
|
||||
resolved.add(instance);
|
||||
}
|
||||
assertEquals(1, resolved.size());
|
||||
assertTrue(resolved.contains(bf.getBean("store1")));
|
||||
|
||||
resolved = doubleStoreProvider.stream().collect(Collectors.toSet());
|
||||
resolved = doubleStoreProvider.stream().collect(Collectors.toList());
|
||||
assertEquals(1, resolved.size());
|
||||
assertTrue(resolved.contains(bf.getBean("store1")));
|
||||
|
||||
resolved = new HashSet<>();
|
||||
resolved = (List) doubleStoreProvider.toList();
|
||||
assertEquals(1, resolved.size());
|
||||
assertTrue(resolved.contains(bf.getBean("store1")));
|
||||
|
||||
resolved = new ArrayList<>();
|
||||
for (NumberStore<Float> instance : floatStoreProvider) {
|
||||
resolved.add(instance);
|
||||
}
|
||||
assertEquals(1, resolved.size());
|
||||
assertTrue(resolved.contains(bf.getBean("store2")));
|
||||
|
||||
resolved = floatStoreProvider.stream().collect(Collectors.toSet());
|
||||
resolved = floatStoreProvider.stream().collect(Collectors.toList());
|
||||
assertEquals(1, resolved.size());
|
||||
assertTrue(resolved.contains(bf.getBean("store2")));
|
||||
|
||||
resolved = (List) floatStoreProvider.toList();
|
||||
assertEquals(1, resolved.size());
|
||||
assertTrue(resolved.contains(bf.getBean("store2")));
|
||||
}
|
||||
|
|
@ -991,7 +1006,12 @@ public class BeanFactoryGenericsTests {
|
|||
}
|
||||
|
||||
|
||||
public static class NumberStore<T extends Number> {
|
||||
public static class NumberStore<T extends Number> implements Comparable<NumberStore> {
|
||||
|
||||
@Override
|
||||
public int compareTo(NumberStore other) {
|
||||
return getClass().getName().compareTo(other.getClass().getName()) * -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ import org.springframework.context.ApplicationContext;
|
|||
import org.springframework.context.annotation.componentscan.simple.SimpleComponent;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.core.io.DescriptiveResource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
|
@ -1650,11 +1651,18 @@ public class ConfigurationClassPostProcessorTests {
|
|||
@Configuration
|
||||
public static class MapArgumentConfiguration {
|
||||
|
||||
@Autowired
|
||||
ConfigurableEnvironment env;
|
||||
|
||||
Map<String, Runnable> testBeans;
|
||||
|
||||
@Bean(autowireCandidate = false)
|
||||
Runnable testBean(Map<String, Runnable> testBeans) {
|
||||
Runnable testBean(Map<String, Runnable> testBeans,
|
||||
@Qualifier("systemProperties") Map<String, String> sysprops,
|
||||
@Qualifier("systemEnvironment") Map<String, String> sysenv) {
|
||||
this.testBeans = testBeans;
|
||||
assertSame(env.getSystemProperties(), sysprops);
|
||||
assertSame(env.getSystemEnvironment(), sysenv);
|
||||
return () -> {};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -382,6 +382,32 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
|||
return this.propertySources;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public Map<String, Object> getSystemProperties() {
|
||||
try {
|
||||
return (Map) System.getProperties();
|
||||
}
|
||||
catch (AccessControlException ex) {
|
||||
return (Map) new ReadOnlySystemAttributesMap() {
|
||||
@Override
|
||||
@Nullable
|
||||
protected String getSystemAttribute(String attributeName) {
|
||||
try {
|
||||
return System.getProperty(attributeName);
|
||||
}
|
||||
catch (AccessControlException ex) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Caught AccessControlException when accessing system property '" +
|
||||
attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public Map<String, Object> getSystemEnvironment() {
|
||||
|
|
@ -426,32 +452,6 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment {
|
|||
return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public Map<String, Object> getSystemProperties() {
|
||||
try {
|
||||
return (Map) System.getProperties();
|
||||
}
|
||||
catch (AccessControlException ex) {
|
||||
return (Map) new ReadOnlySystemAttributesMap() {
|
||||
@Override
|
||||
@Nullable
|
||||
protected String getSystemAttribute(String attributeName) {
|
||||
try {
|
||||
return System.getProperty(attributeName);
|
||||
}
|
||||
catch (AccessControlException ex) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Caught AccessControlException when accessing system property '" +
|
||||
attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(ConfigurableEnvironment parent) {
|
||||
for (PropertySource<?> ps : parent.getPropertySources()) {
|
||||
|
|
|
|||
|
|
@ -118,21 +118,6 @@ public interface ConfigurableEnvironment extends Environment, ConfigurableProper
|
|||
*/
|
||||
MutablePropertySources getPropertySources();
|
||||
|
||||
/**
|
||||
* Return the value of {@link System#getenv()} if allowed by the current
|
||||
* {@link SecurityManager}, otherwise return a map implementation that will attempt
|
||||
* to access individual keys using calls to {@link System#getenv(String)}.
|
||||
* <p>Note that most {@link Environment} implementations will include this system
|
||||
* environment map as a default {@link PropertySource} to be searched. Therefore, it
|
||||
* is recommended that this method not be used directly unless bypassing other
|
||||
* property sources is expressly intended.
|
||||
* <p>Calls to {@link Map#get(Object)} on the Map returned will never throw
|
||||
* {@link IllegalAccessException}; in cases where the SecurityManager forbids access
|
||||
* to a property, {@code null} will be returned and an INFO-level log message will be
|
||||
* issued noting the exception.
|
||||
*/
|
||||
Map<String, Object> getSystemEnvironment();
|
||||
|
||||
/**
|
||||
* Return the value of {@link System#getProperties()} if allowed by the current
|
||||
* {@link SecurityManager}, otherwise return a map implementation that will attempt
|
||||
|
|
@ -148,6 +133,21 @@ public interface ConfigurableEnvironment extends Environment, ConfigurableProper
|
|||
*/
|
||||
Map<String, Object> getSystemProperties();
|
||||
|
||||
/**
|
||||
* Return the value of {@link System#getenv()} if allowed by the current
|
||||
* {@link SecurityManager}, otherwise return a map implementation that will attempt
|
||||
* to access individual keys using calls to {@link System#getenv(String)}.
|
||||
* <p>Note that most {@link Environment} implementations will include this system
|
||||
* environment map as a default {@link PropertySource} to be searched. Therefore, it
|
||||
* is recommended that this method not be used directly unless bypassing other
|
||||
* property sources is expressly intended.
|
||||
* <p>Calls to {@link Map#get(Object)} on the Map returned will never throw
|
||||
* {@link IllegalAccessException}; in cases where the SecurityManager forbids access
|
||||
* to a property, {@code null} will be returned and an INFO-level log message will be
|
||||
* issued noting the exception.
|
||||
*/
|
||||
Map<String, Object> getSystemEnvironment();
|
||||
|
||||
/**
|
||||
* Append the given parent environment's active profiles, default profiles and
|
||||
* property sources to this (child) environment's respective collections of each.
|
||||
|
|
|
|||
Loading…
Reference in New Issue