Skip to content

Commit 8c2b44b

Browse files
committed
Support filtered/unfiltered stream access on ObjectProvider
Closes gh-34318 Closes gh-34203
1 parent 2df8ea9 commit 8c2b44b

File tree

4 files changed

+213
-16
lines changed

4 files changed

+213
-16
lines changed

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

+47-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import java.util.Iterator;
2020
import java.util.function.Consumer;
21+
import java.util.function.Predicate;
2122
import java.util.function.Supplier;
2223
import java.util.stream.Stream;
2324

@@ -53,6 +54,15 @@
5354
*/
5455
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
5556

57+
/**
58+
* A predicate for unfiltered type matches.
59+
* @since 6.2.3
60+
* @see #stream(Predicate)
61+
* @see #orderedStream(Predicate)
62+
*/
63+
Predicate<Class<?>> UNFILTERED = (clazz -> true);
64+
65+
5666
@Override
5767
default T getObject() throws BeansException {
5868
Iterator<T> it = iterator();
@@ -198,6 +208,10 @@ default Iterator<T> iterator() {
198208
/**
199209
* Return a sequential {@link Stream} over all matching object instances,
200210
* without specific ordering guarantees (but typically in registration order).
211+
* <p>Note: The result may be filtered by default according to qualifiers on the
212+
* injection point versus target beans and the general autowire candidate status
213+
* of matching beans. For custom filtering against the raw type matches, use
214+
* {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}).
201215
* @since 5.1
202216
* @see #iterator()
203217
* @see #orderedStream()
@@ -219,6 +233,10 @@ default Stream<T> stream() {
219233
* {@link #stream()} method. You may override this to apply an
220234
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator}
221235
* if necessary.
236+
* <p>Note: The result may be filtered by default according to qualifiers on the
237+
* injection point versus target beans and the general autowire candidate status
238+
* of matching beans. For custom filtering against the raw type matches, use
239+
* {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}).
222240
* @since 5.1
223241
* @see #stream()
224242
* @see org.springframework.core.OrderComparator
@@ -227,4 +245,32 @@ default Stream<T> orderedStream() {
227245
return stream().sorted(OrderComparator.INSTANCE);
228246
}
229247

248+
/**
249+
* Return a custom-filtered {@link Stream} over all matching object instances,
250+
* without specific ordering guarantees (but typically in registration order).
251+
* @param customFilter a custom type filter for selecting beans among the raw
252+
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
253+
* any default filtering)
254+
* @since 6.2.3
255+
* @see #stream()
256+
* @see #orderedStream(Predicate)
257+
*/
258+
default Stream<T> stream(Predicate<Class<?>> customFilter) {
259+
return stream().filter(obj -> customFilter.test(obj.getClass()));
260+
}
261+
262+
/**
263+
* Return a custom-filtered {@link Stream} over all matching object instances,
264+
* pre-ordered according to the factory's common order comparator.
265+
* @param customFilter a custom type filter for selecting beans among the raw
266+
* bean type matches (or {@link #UNFILTERED} for all raw type matches without
267+
* any default filtering)
268+
* @since 6.2.3
269+
* @see #orderedStream()
270+
* @see #stream(Predicate)
271+
*/
272+
default Stream<T> orderedStream(Predicate<Class<?>> customFilter) {
273+
return orderedStream().filter(obj -> customFilter.test(obj.getClass()));
274+
}
275+
230276
}

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

+55-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -508,6 +508,32 @@ public Stream<T> orderedStream() {
508508
Stream<T> stream = matchingBeans.values().stream();
509509
return stream.sorted(adaptOrderComparator(matchingBeans));
510510
}
511+
@SuppressWarnings("unchecked")
512+
@Override
513+
public Stream<T> stream(Predicate<Class<?>> customFilter) {
514+
return Arrays.stream(getBeanNamesForTypedStream(requiredType, allowEagerInit))
515+
.filter(name -> customFilter.test(getType(name)))
516+
.map(name -> (T) getBean(name))
517+
.filter(bean -> !(bean instanceof NullBean));
518+
}
519+
@SuppressWarnings("unchecked")
520+
@Override
521+
public Stream<T> orderedStream(Predicate<Class<?>> customFilter) {
522+
String[] beanNames = getBeanNamesForTypedStream(requiredType, allowEagerInit);
523+
if (beanNames.length == 0) {
524+
return Stream.empty();
525+
}
526+
Map<String, T> matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length);
527+
for (String beanName : beanNames) {
528+
if (customFilter.test(getType(beanName))) {
529+
Object beanInstance = getBean(beanName);
530+
if (!(beanInstance instanceof NullBean)) {
531+
matchingBeans.put(beanName, (T) beanInstance);
532+
}
533+
}
534+
}
535+
return matchingBeans.values().stream().sorted(adaptOrderComparator(matchingBeans));
536+
}
511537
};
512538
}
513539

@@ -1892,8 +1918,8 @@ private void addCandidateEntry(Map<String, Object> candidates, String candidateN
18921918
candidates.put(candidateName, beanInstance);
18931919
}
18941920
}
1895-
else if (containsSingleton(candidateName) || (descriptor instanceof StreamDependencyDescriptor streamDescriptor &&
1896-
streamDescriptor.isOrdered())) {
1921+
else if (containsSingleton(candidateName) ||
1922+
(descriptor instanceof StreamDependencyDescriptor streamDescriptor && streamDescriptor.isOrdered())) {
18971923
Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this);
18981924
candidates.put(candidateName, (beanInstance instanceof NullBean ? null : beanInstance));
18991925
}
@@ -2486,6 +2512,32 @@ private Stream<Object> resolveStream(boolean ordered) {
24862512
Object result = doResolveDependency(descriptorToUse, this.beanName, null, null);
24872513
return (result instanceof Stream stream ? stream : Stream.of(result));
24882514
}
2515+
2516+
@Override
2517+
public Stream<Object> stream(Predicate<Class<?>> customFilter) {
2518+
return Arrays.stream(getBeanNamesForTypedStream(this.descriptor.getResolvableType(), true))
2519+
.filter(name -> customFilter.test(getType(name)))
2520+
.map(name -> getBean(name))
2521+
.filter(bean -> !(bean instanceof NullBean));
2522+
}
2523+
2524+
@Override
2525+
public Stream<Object> orderedStream(Predicate<Class<?>> customFilter) {
2526+
String[] beanNames = getBeanNamesForTypedStream(this.descriptor.getResolvableType(), true);
2527+
if (beanNames.length == 0) {
2528+
return Stream.empty();
2529+
}
2530+
Map<String, Object> matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length);
2531+
for (String beanName : beanNames) {
2532+
if (customFilter.test(getType(beanName))) {
2533+
Object beanInstance = getBean(beanName);
2534+
if (!(beanInstance instanceof NullBean)) {
2535+
matchingBeans.put(beanName, beanInstance);
2536+
}
2537+
}
2538+
}
2539+
return matchingBeans.values().stream().sorted(adaptOrderComparator(matchingBeans));
2540+
}
24892541
}
24902542

24912543

spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java

+29-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -1515,12 +1515,16 @@ void orderFromAttribute() {
15151515
bd1.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.LOWEST_PRECEDENCE);
15161516
lbf.registerBeanDefinition("bean1", bd1);
15171517
GenericBeanDefinition bd2 = new GenericBeanDefinition();
1518-
bd2.setBeanClass(TestBean.class);
1518+
bd2.setBeanClass(DerivedTestBean.class);
15191519
bd2.setPropertyValues(new MutablePropertyValues(List.of(new PropertyValue("name", "highest"))));
15201520
bd2.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.HIGHEST_PRECEDENCE);
15211521
lbf.registerBeanDefinition("bean2", bd2);
15221522
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream().map(TestBean::getName))
15231523
.containsExactly("highest", "lowest");
1524+
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(ObjectProvider.UNFILTERED).map(TestBean::getName))
1525+
.containsExactly("highest", "lowest");
1526+
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(clazz -> !DerivedTestBean.class.isAssignableFrom(clazz))
1527+
.map(TestBean::getName)).containsExactly("lowest");
15241528
}
15251529

15261530
@Test
@@ -1540,6 +1544,8 @@ void orderFromAttributeOverrideAnnotation() {
15401544
lbf.registerBeanDefinition("bean2", bd2);
15411545
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream().map(TestBean::getName))
15421546
.containsExactly("fromLowestPrecedenceTestBeanFactoryBean", "fromHighestPrecedenceTestBeanFactoryBean");
1547+
assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(ObjectProvider.UNFILTERED).map(TestBean::getName))
1548+
.containsExactly("fromLowestPrecedenceTestBeanFactoryBean", "fromHighestPrecedenceTestBeanFactoryBean");
15431549
}
15441550

15451551
@Test
@@ -1934,6 +1940,11 @@ void getBeanByTypeInstanceWithAmbiguity() {
19341940
assertThat(resolved).hasSize(2);
19351941
assertThat(resolved).contains(lbf.getBean("bd1"));
19361942
assertThat(resolved).contains(lbf.getBean("bd2"));
1943+
1944+
resolved = provider.stream(ObjectProvider.UNFILTERED).collect(Collectors.toSet());
1945+
assertThat(resolved).hasSize(2);
1946+
assertThat(resolved).contains(lbf.getBean("bd1"));
1947+
assertThat(resolved).contains(lbf.getBean("bd2"));
19371948
}
19381949

19391950
@Test
@@ -1983,6 +1994,11 @@ void getBeanByTypeInstanceWithPrimary() {
19831994
assertThat(resolved).hasSize(2);
19841995
assertThat(resolved).contains(lbf.getBean("bd1"));
19851996
assertThat(resolved).contains(lbf.getBean("bd2"));
1997+
1998+
resolved = provider.stream(ObjectProvider.UNFILTERED).collect(Collectors.toSet());
1999+
assertThat(resolved).hasSize(2);
2000+
assertThat(resolved).contains(lbf.getBean("bd1"));
2001+
assertThat(resolved).contains(lbf.getBean("bd2"));
19862002
}
19872003

19882004
@Test
@@ -2378,11 +2394,20 @@ void beanProviderWithParentBeanFactoryAndMixedOrder() {
23782394
parentBf.registerBeanDefinition("highPriorityTestBean", bd2);
23792395

23802396
ObjectProvider<TestBean> testBeanProvider = lbf.getBeanProvider(ResolvableType.forClass(TestBean.class));
2381-
List<TestBean> resolved = testBeanProvider.orderedStream().toList();
2382-
assertThat(resolved).containsExactly(
2397+
assertThat(testBeanProvider.orderedStream()).containsExactly(
23832398
lbf.getBean("highPriorityTestBean", TestBean.class),
23842399
lbf.getBean("lowPriorityTestBean", TestBean.class),
23852400
lbf.getBean("plainTestBean", TestBean.class));
2401+
assertThat(testBeanProvider.orderedStream(clazz -> clazz != TestBean.class).toList()).containsExactly(
2402+
lbf.getBean("highPriorityTestBean", TestBean.class),
2403+
lbf.getBean("lowPriorityTestBean", TestBean.class));
2404+
assertThat(testBeanProvider.stream()).containsExactly(
2405+
lbf.getBean("plainTestBean", TestBean.class),
2406+
lbf.getBean("lowPriorityTestBean", TestBean.class),
2407+
lbf.getBean("highPriorityTestBean", TestBean.class));
2408+
assertThat(testBeanProvider.orderedStream(clazz -> clazz != TestBean.class).toList()).containsExactly(
2409+
lbf.getBean("lowPriorityTestBean", TestBean.class),
2410+
lbf.getBean("highPriorityTestBean", TestBean.class));
23862411
}
23872412

23882413
@Test

0 commit comments

Comments
 (0)