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

View File

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

View File

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

View File

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

View File

@ -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()) {

View File

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