Skip to content

Commit 41d4cb5

Browse files
committed
Ordered stream access on ObjectProvider with strong order guarantees
Issue: SPR-17272
1 parent 12240c7 commit 41d4cb5

File tree

6 files changed

+90
-69
lines changed

6 files changed

+90
-69
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@
1717
package org.springframework.beans.factory;
1818

1919
import java.util.Iterator;
20-
import java.util.List;
2120
import java.util.function.Consumer;
2221
import java.util.function.Supplier;
23-
import java.util.stream.Collectors;
2422
import java.util.stream.Stream;
2523

2624
import org.springframework.beans.BeansException;
@@ -141,38 +139,41 @@ default void ifUnique(Consumer<T> dependencyConsumer) throws BeansException {
141139
}
142140

143141
/**
144-
* Return a sequential {@link Stream} over lazily resolved object instances,
142+
* Return an {@link Iterator} over all matching object instances,
145143
* without specific ordering guarantees (but typically in registration order).
146144
* @since 5.1
147-
* @see #iterator()
145+
* @see #stream()
148146
*/
149-
default Stream<T> stream() {
150-
throw new UnsupportedOperationException("Multi-element access not supported");
147+
@Override
148+
default Iterator<T> iterator() {
149+
return stream().iterator();
151150
}
152151

153152
/**
154-
* Return an {@link Iterator} over lazily resolved object instances,
153+
* Return a sequential {@link Stream} over all matching object instances,
155154
* without specific ordering guarantees (but typically in registration order).
156155
* @since 5.1
157-
* @see #stream()
156+
* @see #iterator()
157+
* @see #orderedStream()
158158
*/
159-
@Override
160-
default Iterator<T> iterator() {
161-
return stream().iterator();
159+
default Stream<T> stream() {
160+
throw new UnsupportedOperationException("Multi-element access not supported");
162161
}
163162

164163
/**
165-
* Return a {@link List} with fully resolved object instances,
166-
* potentially pre-ordered according to a common comparator.
167-
* <p>In a common Spring application context, this will be ordered
168-
* according to {@link org.springframework.core.Ordered} /
169-
* {@link org.springframework.core.annotation.Order} conventions,
164+
* Return a sequential {@link Stream} over all matching object instances,
165+
* pre-ordered according to the factory's common order comparator.
166+
* <p>In a standard Spring application context, this will be ordered
167+
* according to {@link org.springframework.core.Ordered} conventions,
168+
* and in case of annotation-based configuration also considering the
169+
* {@link org.springframework.core.annotation.Order} annotation,
170170
* analogous to multi-element injection points of list/array type.
171171
* @since 5.1
172172
* @see #stream()
173+
* @see org.springframework.core.OrderComparator
173174
*/
174-
default List<T> toList() {
175-
return stream().collect(Collectors.toList());
175+
default Stream<T> orderedStream() {
176+
throw new UnsupportedOperationException("Multi-element access not supported");
176177
}
177178

178179
}

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import java.util.Optional;
4141
import java.util.Set;
4242
import java.util.concurrent.ConcurrentHashMap;
43-
import java.util.stream.Collectors;
4443
import java.util.stream.Stream;
4544
import javax.inject.Provider;
4645

@@ -388,7 +387,7 @@ public Stream<T> stream() {
388387
.filter(bean -> !(bean instanceof NullBean));
389388
}
390389
@Override
391-
public List<T> toList() {
390+
public Stream<T> orderedStream() {
392391
String[] beanNames = getBeanNamesForType(requiredType);
393392
Map<String, T> matchingBeans = new LinkedHashMap<>(beanNames.length);
394393
for (String beanName : beanNames) {
@@ -397,12 +396,8 @@ public List<T> toList() {
397396
matchingBeans.put(beanName, (T) beanInstance);
398397
}
399398
}
400-
List<T> result = new ArrayList<>(matchingBeans.values());
401-
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
402-
if (comparator != null) {
403-
result.sort(comparator);
404-
}
405-
return result;
399+
Stream<T> stream = matchingBeans.values().stream();
400+
return stream.sorted(adaptOrderComparator(matchingBeans));
406401
}
407402
};
408403
}
@@ -1267,16 +1262,13 @@ private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable S
12671262
if (autowiredBeanNames != null) {
12681263
autowiredBeanNames.addAll(matchingBeans.keySet());
12691264
}
1270-
Stream<Object> result = matchingBeans.keySet().stream()
1265+
Stream<Object> stream = matchingBeans.keySet().stream()
12711266
.map(name -> descriptor.resolveCandidate(name, type, this))
12721267
.filter(bean -> !(bean instanceof NullBean));
1273-
if (((StreamDependencyDescriptor) descriptor).isSorted()) {
1274-
Comparator<Object> comparator = adaptDependencyComparator(matchingBeans);
1275-
if (comparator != null) {
1276-
result = result.sorted(comparator);
1277-
}
1268+
if (((StreamDependencyDescriptor) descriptor).isOrdered()) {
1269+
stream = stream.sorted(adaptOrderComparator(matchingBeans));
12781270
}
1279-
return result;
1271+
return stream;
12801272
}
12811273
else if (type.isArray()) {
12821274
Class<?> componentType = type.getComponentType();
@@ -1375,6 +1367,13 @@ private Comparator<Object> adaptDependencyComparator(Map<String, ?> matchingBean
13751367
}
13761368
}
13771369

1370+
private Comparator<Object> adaptOrderComparator(Map<String, ?> matchingBeans) {
1371+
Comparator<Object> dependencyComparator = getDependencyComparator();
1372+
OrderComparator comparator = (dependencyComparator instanceof OrderComparator ?
1373+
(OrderComparator) dependencyComparator : OrderComparator.INSTANCE);
1374+
return comparator.withSourceProvider(createFactoryAwareOrderSourceProvider(matchingBeans));
1375+
}
1376+
13781377
private OrderComparator.OrderSourceProvider createFactoryAwareOrderSourceProvider(Map<String, ?> beans) {
13791378
IdentityHashMap<Object, String> instancesToBeanNames = new IdentityHashMap<>();
13801379
beans.forEach((beanName, instance) -> instancesToBeanNames.put(instance, beanName));
@@ -1779,15 +1778,15 @@ public MultiElementDescriptor(DependencyDescriptor original) {
17791778
*/
17801779
private static class StreamDependencyDescriptor extends DependencyDescriptor {
17811780

1782-
private final boolean sorted;
1781+
private final boolean ordered;
17831782

1784-
public StreamDependencyDescriptor(DependencyDescriptor original, boolean sorted) {
1783+
public StreamDependencyDescriptor(DependencyDescriptor original, boolean ordered) {
17851784
super(original);
1786-
this.sorted = sorted;
1785+
this.ordered = ordered;
17871786
}
17881787

1789-
public boolean isSorted() {
1790-
return this.sorted;
1788+
public boolean isOrdered() {
1789+
return this.ordered;
17911790
}
17921791
}
17931792

@@ -1897,22 +1896,22 @@ protected Object getValue() throws BeansException {
18971896
}
18981897
}
18991898

1900-
@SuppressWarnings("unchecked")
19011899
@Override
19021900
public Stream<Object> stream() {
1903-
DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, false);
1904-
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null);
1905-
Assert.state(result instanceof Stream, "Stream expected");
1906-
return (Stream<Object>) result;
1901+
return resolveStream(false);
19071902
}
19081903

1909-
@SuppressWarnings("unchecked")
19101904
@Override
1911-
public List<Object> toList() {
1912-
DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, true);
1905+
public Stream<Object> orderedStream() {
1906+
return resolveStream(true);
1907+
}
1908+
1909+
@SuppressWarnings("unchecked")
1910+
private Stream<Object> resolveStream(boolean ordered) {
1911+
DependencyDescriptor descriptorToUse = new StreamDependencyDescriptor(this.descriptor, ordered);
19131912
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null);
19141913
Assert.state(result instanceof Stream, "Stream expected");
1915-
return ((Stream<Object>) result).collect(Collectors.toList());
1914+
return (Stream<Object>) result;
19161915
}
19171916
}
19181917

spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
3636
import org.springframework.beans.factory.ObjectProvider;
3737
import org.springframework.beans.factory.SmartFactoryBean;
38+
import org.springframework.core.OrderComparator;
3839
import org.springframework.core.ResolvableType;
3940
import org.springframework.core.annotation.AnnotationUtils;
4041
import org.springframework.lang.Nullable;
@@ -245,6 +246,10 @@ public T getIfUnique() throws BeansException {
245246
public Stream<T> stream() {
246247
return Arrays.stream(getBeanNamesForType(requiredType)).map(name -> (T) getBean(name));
247248
}
249+
@Override
250+
public Stream<T> orderedStream() {
251+
return stream().sorted(OrderComparator.INSTANCE);
252+
}
248253
};
249254
}
250255

spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,15 +1289,13 @@ public void testObjectProviderInjectionWithTargetNotUnique() {
12891289

12901290
@Test
12911291
public void testObjectProviderInjectionWithTargetPrimary() {
1292-
bf.setDependencyComparator(Comparators.comparable());
1293-
12941292
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class));
1295-
RootBeanDefinition tb1 = new RootBeanDefinition(TestBean.class);
1296-
tb1.getPropertyValues().add("name", "yours");
1293+
RootBeanDefinition tb1 = new RootBeanDefinition(OrderedTestBean.class);
1294+
tb1.getPropertyValues().add("order", 1);
12971295
tb1.setPrimary(true);
12981296
bf.registerBeanDefinition("testBean1", tb1);
1299-
RootBeanDefinition tb2 = new RootBeanDefinition(TestBean.class);
1300-
tb2.getPropertyValues().add("name", "mine");
1297+
RootBeanDefinition tb2 = new RootBeanDefinition(OrderedTestBean.class);
1298+
tb2.getPropertyValues().add("order", 0);
13011299
tb2.setLazyInit(true);
13021300
bf.registerBeanDefinition("testBean2", tb2);
13031301

@@ -2885,7 +2883,7 @@ public List<TestBean> streamTestBeans() {
28852883
}
28862884

28872885
public List<TestBean> sortedTestBeans() {
2888-
return this.testBeanProvider.toList();
2886+
return this.testBeanProvider.orderedStream().collect(Collectors.toList());
28892887
}
28902888
}
28912889

@@ -2983,6 +2981,21 @@ public boolean isSingleton() {
29832981
}
29842982

29852983

2984+
public static class OrderedTestBean extends TestBean implements Ordered {
2985+
2986+
private int order;
2987+
2988+
public void setOrder(int order) {
2989+
this.order = order;
2990+
}
2991+
2992+
@Override
2993+
public int getOrder() {
2994+
return this.order;
2995+
}
2996+
}
2997+
2998+
29862999
public static class OrderedNestedTestBean extends NestedTestBean implements Ordered {
29873000

29883001
private int order;

spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.springframework.beans.factory.config.TypedStringValue;
4444
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
4545
import org.springframework.beans.propertyeditors.CustomNumberEditor;
46+
import org.springframework.core.Ordered;
4647
import org.springframework.core.OverridingClassLoader;
4748
import org.springframework.core.ResolvableType;
4849
import org.springframework.core.io.ClassPathResource;
@@ -53,7 +54,6 @@
5354
import org.springframework.tests.sample.beans.GenericIntegerBean;
5455
import org.springframework.tests.sample.beans.GenericSetOfIntegerBean;
5556
import org.springframework.tests.sample.beans.TestBean;
56-
import org.springframework.util.comparator.Comparators;
5757

5858
import static org.junit.Assert.*;
5959

@@ -847,7 +847,6 @@ public void testGenericMatchingWithBeanNameDifferentiation() {
847847
public void testGenericMatchingWithFullTypeDifferentiation() {
848848
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
849849
bf.setAutowireCandidateResolver(new GenericTypeAwareAutowireCandidateResolver());
850-
bf.setDependencyComparator(Comparators.comparable());
851850

852851
bf.registerBeanDefinition("store1", new RootBeanDefinition(DoubleStore.class));
853852
bf.registerBeanDefinition("store2", new RootBeanDefinition(FloatStore.class));
@@ -907,7 +906,7 @@ public void testGenericMatchingWithFullTypeDifferentiation() {
907906
assertSame(bf.getBean("store1"), resolved.get(0));
908907
assertSame(bf.getBean("store2"), resolved.get(1));
909908

910-
resolved = numberStoreProvider.toList();
909+
resolved = numberStoreProvider.orderedStream().collect(Collectors.toList());
911910
assertEquals(2, resolved.size());
912911
assertSame(bf.getBean("store2"), resolved.get(0));
913912
assertSame(bf.getBean("store1"), resolved.get(1));
@@ -923,7 +922,7 @@ public void testGenericMatchingWithFullTypeDifferentiation() {
923922
assertEquals(1, resolved.size());
924923
assertTrue(resolved.contains(bf.getBean("store1")));
925924

926-
resolved = (List) doubleStoreProvider.toList();
925+
resolved = (List) doubleStoreProvider.orderedStream().collect(Collectors.toList());
927926
assertEquals(1, resolved.size());
928927
assertTrue(resolved.contains(bf.getBean("store1")));
929928

@@ -938,7 +937,7 @@ public void testGenericMatchingWithFullTypeDifferentiation() {
938937
assertEquals(1, resolved.size());
939938
assertTrue(resolved.contains(bf.getBean("store2")));
940939

941-
resolved = (List) floatStoreProvider.toList();
940+
resolved = (List) floatStoreProvider.orderedStream().collect(Collectors.toList());
942941
assertEquals(1, resolved.size());
943942
assertTrue(resolved.contains(bf.getBean("store2")));
944943
}
@@ -1006,20 +1005,25 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
10061005
}
10071006

10081007

1009-
public static class NumberStore<T extends Number> implements Comparable<NumberStore> {
1008+
public static class NumberStore<T extends Number> {
1009+
}
1010+
1011+
1012+
public static class DoubleStore extends NumberStore<Double> implements Ordered {
10101013

10111014
@Override
1012-
public int compareTo(NumberStore other) {
1013-
return getClass().getName().compareTo(other.getClass().getName()) * -1;
1015+
public int getOrder() {
1016+
return 1;
10141017
}
10151018
}
10161019

10171020

1018-
public static class DoubleStore extends NumberStore<Double> {
1019-
}
1021+
public static class FloatStore extends NumberStore<Float> implements Ordered {
10201022

1021-
1022-
public static class FloatStore extends NumberStore<Float> {
1023+
@Override
1024+
public int getOrder() {
1025+
return 0;
1026+
}
10231027
}
10241028

10251029

spring-core/src/main/java/org/springframework/core/OrderComparator.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public class OrderComparator implements Comparator<Object> {
5959
* @return the adapted comparator
6060
* @since 4.1
6161
*/
62-
public Comparator<Object> withSourceProvider(final OrderSourceProvider sourceProvider) {
62+
public Comparator<Object> withSourceProvider(OrderSourceProvider sourceProvider) {
6363
return (o1, o2) -> doCompare(o1, o2, sourceProvider);
6464
}
6565

@@ -78,10 +78,9 @@ else if (p2 && !p1) {
7878
return 1;
7979
}
8080

81-
// Direct evaluation instead of Integer.compareTo to avoid unnecessary object creation.
8281
int i1 = getOrder(o1, sourceProvider);
8382
int i2 = getOrder(o2, sourceProvider);
84-
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
83+
return Integer.compare(i1, i2);
8584
}
8685

8786
/**

0 commit comments

Comments
 (0)