diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java index 3ccbda3aea3..dc8cdef1a27 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java @@ -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) */ ObjectProvider getBeanProvider(Class 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() */ ObjectProvider getBeanProvider(ResolvableType requiredType); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java index 7f4ef21cb4e..c13195c8537 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java @@ -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 the object type + * @see BeanFactory#getBeanProvider + * @see org.springframework.beans.factory.annotation.Autowired */ public interface ObjectProvider extends ObjectFactory, Iterable { @@ -137,8 +141,18 @@ public interface ObjectProvider extends ObjectFactory, Iterable { } /** - * Return an {@link Iterator} over resolved object instances. - *

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 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 extends ObjectFactory, Iterable { } /** - * Return a sequential {@link Stream} over resolved object instances. - *

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. + *

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 stream() { - T instance = getIfAvailable(); - return (instance != null ? Stream.of(instance) : Stream.empty()); + default List toList() { + return stream().collect(Collectors.toList()); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java index 45b6b8ef37d..0296dc128cf 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/annotation/QualifierAnnotationAutowireCandidateResolver.java @@ -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 diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java index 27514496378..6f8506649d3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/DependencyDescriptor.java @@ -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. - *

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

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}. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java index 60afe0a0f18..6efac36f56e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireCandidateResolver.java @@ -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). + *

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

The default implementation simply returns {@code null}. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 82cc30af73c..531d5c0dafc 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -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 toList() { + String[] beanNames = getBeanNamesForType(requiredType); + Map matchingBeans = new LinkedHashMap<>(beanNames.length); + for (String beanName : beanNames) { + Object beanInstance = getBean(beanName); + if (!(beanInstance instanceof NullBean)) { + matchingBeans.put(beanName, (T) beanInstance); + } + } + List result = new ArrayList<>(matchingBeans.values()); + Comparator 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 autowiredBeanNames, @Nullable TypeConverter typeConverter) { - Class type = descriptor.getDependencyType(); + final Class type = descriptor.getDependencyType(); - if (descriptor.isStreamAccess()) { - Map matchingBeans = findAutowireCandidates(beanName, type, - new MultiElementDescriptor(descriptor, false)); + if (descriptor instanceof StreamDependencyDescriptor) { + Map matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (autowiredBeanNames != null) { autowiredBeanNames.addAll(matchingBeans.keySet()); } - return matchingBeans.values().stream(); + Stream result = matchingBeans.keySet().stream() + .map(name -> descriptor.resolveCandidate(name, type, this)) + .filter(bean -> !(bean instanceof NullBean)); + if (((StreamDependencyDescriptor) descriptor).isSorted()) { + Comparator 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 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 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 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 comparator = adaptDependencyComparator(matchingBeans); + if (comparator != null) { + ((List) result).sort(comparator); + } } return result; } @@ -1309,7 +1340,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return null; } Map 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 adaptDependencyComparator(Map matchingBeans) { + private Comparator adaptDependencyComparator(Map matchingBeans) { Comparator comparator = getDependencyComparator(); if (comparator instanceof OrderComparator) { return ((OrderComparator) comparator).withSourceProvider( @@ -1344,7 +1375,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } } - private OrderComparator.OrderSourceProvider createFactoryAwareOrderSourceProvider(Map beans) { + private OrderComparator.OrderSourceProvider createFactoryAwareOrderSourceProvider(Map beans) { IdentityHashMap 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 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) result; - } - else if (result instanceof Collection) { - return ((Collection) result).stream(); - } - else { - return (result != null ? Stream.of(result) : Stream.empty()); - } + Assert.state(result instanceof Stream, "Stream expected"); + return (Stream) result; + } + + @SuppressWarnings("unchecked") + @Override + public List 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) result).collect(Collectors.toList()); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java index f427a993686..78acb12fd57 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java @@ -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; + // and pragmatically also java.util.Properties to any Map (since despite formally being a + // Map, java.util.Properties is usually perceived as a Map). return true; } // Full check for complex generic type match... diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index ad74600d4be..65d31fa1652 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -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 testBeanMap; + + @Autowired + public QualifiedMapConstructorInjectionBean(@Qualifier("myTestBeanMap") Map testBeanMap) { + this.testBeanMap = testBeanMap; + } + + public Map getTestBeanMap() { + return this.testBeanMap; + } + } + + public static class SetConstructorInjectionBean { private Set testBeanSet; @@ -2834,6 +2883,10 @@ public class AutowiredAnnotationBeanPostProcessorTests { public List streamTestBeans() { return this.testBeanProvider.stream().collect(Collectors.toList()); } + + public List sortedTestBeans() { + return this.testBeanProvider.toList(); + } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java index bdb98541191..3297bf711ff 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java @@ -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 resolved = new HashSet<>(); + List> 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 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 = (List) doubleStoreProvider.toList(); assertEquals(1, resolved.size()); assertTrue(resolved.contains(bf.getBean("store1"))); - resolved = new HashSet<>(); + resolved = new ArrayList<>(); for (NumberStore 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 { + public static class NumberStore implements Comparable { + + @Override + public int compareTo(NumberStore other) { + return getClass().getName().compareTo(other.getClass().getName()) * -1; + } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index b38f436b6b7..3e4a496362e 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -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 testBeans; @Bean(autowireCandidate = false) - Runnable testBean(Map testBeans) { + Runnable testBean(Map testBeans, + @Qualifier("systemProperties") Map sysprops, + @Qualifier("systemEnvironment") Map sysenv) { this.testBeans = testBeans; + assertSame(env.getSystemProperties(), sysprops); + assertSame(env.getSystemEnvironment(), sysenv); return () -> {}; } diff --git a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java index eaa78737073..da9544e6ed7 100644 --- a/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java +++ b/spring-core/src/main/java/org/springframework/core/env/AbstractEnvironment.java @@ -384,12 +384,9 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { @Override @SuppressWarnings({"unchecked", "rawtypes"}) - public Map getSystemEnvironment() { - if (suppressGetenvAccess()) { - return Collections.emptyMap(); - } + public Map getSystemProperties() { try { - return (Map) System.getenv(); + return (Map) System.getProperties(); } catch (AccessControlException ex) { return (Map) new ReadOnlySystemAttributesMap() { @@ -397,11 +394,11 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { @Nullable protected String getSystemAttribute(String attributeName) { try { - return System.getenv(attributeName); + return System.getProperty(attributeName); } catch (AccessControlException ex) { if (logger.isInfoEnabled()) { - logger.info("Caught AccessControlException when accessing system environment variable '" + + logger.info("Caught AccessControlException when accessing system property '" + attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage()); } return null; @@ -411,26 +408,14 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { } } - /** - * Determine whether to suppress {@link System#getenv()}/{@link System#getenv(String)} - * access for the purposes of {@link #getSystemEnvironment()}. - *

If this method returns {@code true}, an empty dummy Map will be used instead - * of the regular system environment Map, never even trying to call {@code getenv} - * and therefore avoiding security manager warnings (if any). - *

The default implementation checks for the "spring.getenv.ignore" system property, - * returning {@code true} if its value equals "true" in any case. - * @see #IGNORE_GETENV_PROPERTY_NAME - * @see SpringProperties#getFlag - */ - protected boolean suppressGetenvAccess() { - return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME); - } - @Override @SuppressWarnings({"unchecked", "rawtypes"}) - public Map getSystemProperties() { + public Map getSystemEnvironment() { + if (suppressGetenvAccess()) { + return Collections.emptyMap(); + } try { - return (Map) System.getProperties(); + return (Map) System.getenv(); } catch (AccessControlException ex) { return (Map) new ReadOnlySystemAttributesMap() { @@ -438,11 +423,11 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { @Nullable protected String getSystemAttribute(String attributeName) { try { - return System.getProperty(attributeName); + return System.getenv(attributeName); } catch (AccessControlException ex) { if (logger.isInfoEnabled()) { - logger.info("Caught AccessControlException when accessing system property '" + + logger.info("Caught AccessControlException when accessing system environment variable '" + attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage()); } return null; @@ -452,6 +437,21 @@ public abstract class AbstractEnvironment implements ConfigurableEnvironment { } } + /** + * Determine whether to suppress {@link System#getenv()}/{@link System#getenv(String)} + * access for the purposes of {@link #getSystemEnvironment()}. + *

If this method returns {@code true}, an empty dummy Map will be used instead + * of the regular system environment Map, never even trying to call {@code getenv} + * and therefore avoiding security manager warnings (if any). + *

The default implementation checks for the "spring.getenv.ignore" system property, + * returning {@code true} if its value equals "true" in any case. + * @see #IGNORE_GETENV_PROPERTY_NAME + * @see SpringProperties#getFlag + */ + protected boolean suppressGetenvAccess() { + return SpringProperties.getFlag(IGNORE_GETENV_PROPERTY_NAME); + } + @Override public void merge(ConfigurableEnvironment parent) { for (PropertySource ps : parent.getPropertySources()) { diff --git a/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java b/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java index d69716a52af..1d875f2cd04 100644 --- a/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java +++ b/spring-core/src/main/java/org/springframework/core/env/ConfigurableEnvironment.java @@ -119,34 +119,34 @@ public interface ConfigurableEnvironment extends Environment, ConfigurableProper MutablePropertySources getPropertySources(); /** - * Return the value of {@link System#getenv()} if allowed by the current + * Return the value of {@link System#getProperties()} 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)}. - *

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. + * to access individual keys using calls to {@link System#getProperty(String)}. + *

Note that most {@code Environment} implementations will include this system + * properties 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. *

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 getSystemEnvironment(); + Map getSystemProperties(); /** - * Return the value of {@link System#getProperties()} if allowed by the current + * 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#getProperty(String)}. - *

Note that most {@code Environment} implementations will include this system - * properties 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. + * to access individual keys using calls to {@link System#getenv(String)}. + *

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

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 getSystemProperties(); + Map getSystemEnvironment(); /** * Append the given parent environment's active profiles, default profiles and