Skip to content

Commit ace2f0a

Browse files
committed
Expose resolveAutowireCandidates method with basic candidate filtering
DependencyObjectProvider is aligned to check basic autowire-candidate status even in case of custom filtering. Closes gh-34203
1 parent 1a573d6 commit ace2f0a

File tree

6 files changed

+93
-65
lines changed

6 files changed

+93
-65
lines changed

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

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 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.
@@ -39,10 +39,14 @@
3939
* (which the methods defined on the ListableBeanFactory interface don't,
4040
* in contrast to the methods defined on the BeanFactory interface).
4141
*
42+
* <p><b>NOTE:</b> It is generally preferable to use {@link ObjectProvider#stream()}
43+
* via {@link BeanFactory#getBeanProvider} instead of this utility class.
44+
*
4245
* @author Rod Johnson
4346
* @author Juergen Hoeller
4447
* @author Chris Beams
4548
* @since 04.07.2003
49+
* @see BeanFactory#getBeanProvider
4650
*/
4751
public abstract class BeanFactoryUtils {
4852

@@ -308,7 +312,7 @@ public static String[] beanNamesForAnnotationIncludingAncestors(
308312
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
309313
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
310314
* @param lbf the bean factory
311-
* @param type type of bean to match
315+
* @param type the type of bean to match
312316
* @return the Map of matching bean instances, or an empty Map if none
313317
* @throws BeansException if a bean could not be created
314318
* @see ListableBeanFactory#getBeansOfType(Class)
@@ -347,7 +351,7 @@ public static <T> Map<String, T> beansOfTypeIncludingAncestors(ListableBeanFacto
347351
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
348352
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
349353
* @param lbf the bean factory
350-
* @param type type of bean to match
354+
* @param type the type of bean to match
351355
* @param includeNonSingletons whether to include prototype or scoped beans too
352356
* or just singletons (also applies to FactoryBeans)
353357
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
@@ -395,7 +399,7 @@ public static <T> Map<String, T> beansOfTypeIncludingAncestors(
395399
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
396400
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
397401
* @param lbf the bean factory
398-
* @param type type of bean to match
402+
* @param type the type of bean to match
399403
* @return the matching bean instance
400404
* @throws NoSuchBeanDefinitionException if no bean of the given type was found
401405
* @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
@@ -425,7 +429,7 @@ public static <T> T beanOfTypeIncludingAncestors(ListableBeanFactory lbf, Class<
425429
* 'replacing' beans by explicitly choosing the same bean name in a child factory;
426430
* the bean in the ancestor factory won't be visible then, not even for by-type lookups.
427431
* @param lbf the bean factory
428-
* @param type type of bean to match
432+
* @param type the type of bean to match
429433
* @param includeNonSingletons whether to include prototype or scoped beans too
430434
* or just singletons (also applies to FactoryBeans)
431435
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
@@ -457,7 +461,7 @@ public static <T> T beanOfTypeIncludingAncestors(
457461
* <p>This version of {@code beanOfType} automatically includes
458462
* prototypes and FactoryBeans.
459463
* @param lbf the bean factory
460-
* @param type type of bean to match
464+
* @param type the type of bean to match
461465
* @return the matching bean instance
462466
* @throws NoSuchBeanDefinitionException if no bean of the given type was found
463467
* @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
@@ -481,7 +485,7 @@ public static <T> T beanOfType(ListableBeanFactory lbf, Class<T> type) throws Be
481485
* only raw FactoryBeans will be checked (which doesn't require initialization
482486
* of each FactoryBean).
483487
* @param lbf the bean factory
484-
* @param type type of bean to match
488+
* @param type the type of bean to match
485489
* @param includeNonSingletons whether to include prototype or scoped beans too
486490
* or just singletons (also applies to FactoryBeans)
487491
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
@@ -529,7 +533,7 @@ private static String[] mergeNamesWithParent(String[] result, String[] parentRes
529533

530534
/**
531535
* Extract a unique bean for the given type from the given Map of matching beans.
532-
* @param type type of bean to match
536+
* @param type the type of bean to match
533537
* @param matchingBeans all matching beans found
534538
* @return the unique bean instance
535539
* @throws NoSuchBeanDefinitionException if no bean of the given type was found

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,13 @@
5555
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
5656

5757
/**
58-
* A predicate for unfiltered type matches.
58+
* A predicate for unfiltered type matches, including non-default candidates
59+
* but still excluding non-autowire candidates when used on injection points.
5960
* @since 6.2.3
6061
* @see #stream(Predicate)
6162
* @see #orderedStream(Predicate)
63+
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
64+
* @see org.springframework.beans.factory.support.AbstractBeanDefinition#isDefaultCandidate()
6265
*/
6366
Predicate<Class<?>> UNFILTERED = (clazz -> true);
6467

@@ -210,7 +213,7 @@ default Iterator<T> iterator() {
210213
* without specific ordering guarantees (but typically in registration order).
211214
* <p>Note: The result may be filtered by default according to qualifiers on the
212215
* injection point versus target beans and the general autowire candidate status
213-
* of matching beans. For custom filtering against the raw type matches, use
216+
* of matching beans. For custom filtering against type-matching candidates, use
214217
* {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}).
215218
* @since 5.1
216219
* @see #iterator()
@@ -235,7 +238,7 @@ default Stream<T> stream() {
235238
* if necessary.
236239
* <p>Note: The result may be filtered by default according to qualifiers on the
237240
* injection point versus target beans and the general autowire candidate status
238-
* of matching beans. For custom filtering against the raw type matches, use
241+
* of matching beans. For custom filtering against type-matching candidates, use
239242
* {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}).
240243
* @since 5.1
241244
* @see #stream()

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

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 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.
@@ -33,7 +33,9 @@
3333
import java.util.Set;
3434

3535
import org.springframework.beans.BeanMetadataElement;
36+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3637
import org.springframework.beans.factory.ObjectFactory;
38+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
3739
import org.springframework.beans.factory.config.TypedStringValue;
3840
import org.springframework.lang.Nullable;
3941
import org.springframework.util.Assert;
@@ -259,6 +261,24 @@ else if (arg instanceof TypedStringValue typedValue) {
259261
return method.getReturnType();
260262
}
261263

264+
/**
265+
* Check the autowire-candidate status for the specified bean.
266+
* @param beanFactory the bean factory
267+
* @param beanName the name of the bean to check
268+
* @return whether the specified bean qualifies as an autowire candidate
269+
* @since 6.2.3
270+
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
271+
*/
272+
public static boolean isAutowireCandidate(ConfigurableBeanFactory beanFactory, String beanName) {
273+
try {
274+
return beanFactory.getMergedBeanDefinition(beanName).isAutowireCandidate();
275+
}
276+
catch (NoSuchBeanDefinitionException ex) {
277+
// A manually registered singleton instance not backed by a BeanDefinition.
278+
return true;
279+
}
280+
}
281+
262282

263283
/**
264284
* Reflective {@link InvocationHandler} for lazy access to the current target object.

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -2516,6 +2516,7 @@ private Stream<Object> resolveStream(boolean ordered) {
25162516
@Override
25172517
public Stream<Object> stream(Predicate<Class<?>> customFilter) {
25182518
return Arrays.stream(getBeanNamesForTypedStream(this.descriptor.getResolvableType(), true))
2519+
.filter(name -> AutowireUtils.isAutowireCandidate(DefaultListableBeanFactory.this, name))
25192520
.filter(name -> customFilter.test(getType(name)))
25202521
.map(name -> getBean(name))
25212522
.filter(bean -> !(bean instanceof NullBean));
@@ -2529,7 +2530,8 @@ public Stream<Object> orderedStream(Predicate<Class<?>> customFilter) {
25292530
}
25302531
Map<String, Object> matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length);
25312532
for (String beanName : beanNames) {
2532-
if (customFilter.test(getType(beanName))) {
2533+
if (AutowireUtils.isAutowireCandidate(DefaultListableBeanFactory.this, beanName) &&
2534+
customFilter.test(getType(beanName))) {
25332535
Object beanInstance = getBean(beanName);
25342536
if (!(beanInstance instanceof NullBean)) {
25352537
matchingBeans.put(beanName, beanInstance);
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.
@@ -16,9 +16,13 @@
1616

1717
package org.springframework.beans.factory.support;
1818

19-
import org.springframework.beans.factory.config.BeanDefinitionHolder;
20-
import org.springframework.beans.factory.config.DependencyDescriptor;
21-
import org.springframework.lang.Nullable;
19+
import java.util.LinkedHashMap;
20+
import java.util.Map;
21+
22+
import org.springframework.beans.BeansException;
23+
import org.springframework.beans.factory.BeanFactoryUtils;
24+
import org.springframework.beans.factory.ListableBeanFactory;
25+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2226

2327
/**
2428
* {@link AutowireCandidateResolver} implementation to use when no annotation
@@ -36,46 +40,6 @@ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolve
3640
*/
3741
public static final SimpleAutowireCandidateResolver INSTANCE = new SimpleAutowireCandidateResolver();
3842

39-
40-
@Override
41-
public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
42-
return bdHolder.getBeanDefinition().isAutowireCandidate();
43-
}
44-
45-
@Override
46-
public boolean isRequired(DependencyDescriptor descriptor) {
47-
return descriptor.isRequired();
48-
}
49-
50-
@Override
51-
public boolean hasQualifier(DependencyDescriptor descriptor) {
52-
return false;
53-
}
54-
55-
@Override
56-
@Nullable
57-
public String getSuggestedName(DependencyDescriptor descriptor) {
58-
return null;
59-
}
60-
61-
@Override
62-
@Nullable
63-
public Object getSuggestedValue(DependencyDescriptor descriptor) {
64-
return null;
65-
}
66-
67-
@Override
68-
@Nullable
69-
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
70-
return null;
71-
}
72-
73-
@Override
74-
@Nullable
75-
public Class<?> getLazyResolutionProxyClass(DependencyDescriptor descriptor, @Nullable String beanName) {
76-
return null;
77-
}
78-
7943
/**
8044
* This implementation returns {@code this} as-is.
8145
* @see #INSTANCE
@@ -85,4 +49,31 @@ public AutowireCandidateResolver cloneIfNecessary() {
8549
return this;
8650
}
8751

52+
53+
/**
54+
* Resolve a map of all beans of the given type, also picking up beans defined in
55+
* ancestor bean factories, with the specific condition that each bean actually
56+
* has autowire candidate status. This matches simple injection point resolution
57+
* as implemented by this {@link AutowireCandidateResolver} strategy, including
58+
* beans which are not marked as default candidates but excluding beans which
59+
* are not even marked as autowire candidates.
60+
* @param lbf the bean factory
61+
* @param type the type of bean to match
62+
* @return the Map of matching bean instances, or an empty Map if none
63+
* @throws BeansException if a bean could not be created
64+
* @since 6.2.3
65+
* @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class)
66+
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
67+
* @see AbstractBeanDefinition#isDefaultCandidate()
68+
*/
69+
public static <T> Map<String, T> resolveAutowireCandidates(ConfigurableListableBeanFactory lbf, Class<T> type) {
70+
Map<String, T> candidates = new LinkedHashMap<>();
71+
for (String beanName : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(lbf, type)) {
72+
if (AutowireUtils.isAutowireCandidate(lbf, beanName)) {
73+
candidates.put(beanName, lbf.getBean(beanName, type));
74+
}
75+
}
76+
return candidates;
77+
}
78+
8879
}

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

+16-8
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848

4949
import org.springframework.beans.factory.BeanCreationException;
5050
import org.springframework.beans.factory.BeanFactory;
51+
import org.springframework.beans.factory.BeanFactoryUtils;
5152
import org.springframework.beans.factory.BeanNameAware;
5253
import org.springframework.beans.factory.DisposableBean;
5354
import org.springframework.beans.factory.FactoryBean;
@@ -64,6 +65,7 @@
6465
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
6566
import org.springframework.beans.factory.support.GenericBeanDefinition;
6667
import org.springframework.beans.factory.support.RootBeanDefinition;
68+
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
6769
import org.springframework.beans.testfixture.beans.DerivedTestBean;
6870
import org.springframework.beans.testfixture.beans.ITestBean;
6971
import org.springframework.beans.testfixture.beans.IndexedTestBean;
@@ -1756,14 +1758,17 @@ void objectProviderInjectionWithNonCandidatesInStream() {
17561758
RootBeanDefinition tb2 = new RootBeanDefinition(TestBeanFactory.class);
17571759
tb2.setFactoryMethodName("newTestBean2");
17581760
bf.registerBeanDefinition("testBean2", tb2);
1761+
1762+
DefaultListableBeanFactory parent = new DefaultListableBeanFactory();
17591763
RootBeanDefinition tb3 = new RootBeanDefinition(TestBean.class);
17601764
tb3.setAutowireCandidate(false);
17611765
tb3.setLazyInit(true);
1762-
bf.registerBeanDefinition("testBean3", tb3);
1766+
parent.registerBeanDefinition("testBean3", tb3);
17631767
RootBeanDefinition tb4 = new RootBeanDefinition(DerivedTestBean.class);
17641768
tb4.setDefaultCandidate(false);
17651769
tb4.setLazyInit(true);
1766-
bf.registerBeanDefinition("testBean4", tb4);
1770+
parent.registerBeanDefinition("testBean4", tb4);
1771+
bf.setParentBeanFactory(parent);
17671772

17681773
ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class);
17691774
assertThat(bean.streamTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
@@ -1772,16 +1777,19 @@ void objectProviderInjectionWithNonCandidatesInStream() {
17721777
bf.getBean("testBean1", TestBean.class));
17731778
assertThat(bf.containsSingleton("testBean3")).isFalse();
17741779
assertThat(bean.plainTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
1775-
bf.getBean("testBean2", TestBean.class), bf.getBean("testBean3", TestBean.class));
1780+
bf.getBean("testBean2", TestBean.class));
17761781
assertThat(bean.plainTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
1777-
bf.getBean("testBean1", TestBean.class), bf.getBean("testBean3", TestBean.class));
1782+
bf.getBean("testBean1", TestBean.class));
17781783
assertThat(bf.containsSingleton("testBean4")).isFalse();
17791784
assertThat(bean.allTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
1780-
bf.getBean("testBean2", TestBean.class), bf.getBean("testBean3", TestBean.class),
1781-
bf.getBean("testBean4", TestBean.class));
1785+
bf.getBean("testBean2", TestBean.class), bf.getBean("testBean4", TestBean.class));
17821786
assertThat(bean.allTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
1783-
bf.getBean("testBean1", TestBean.class), bf.getBean("testBean3", TestBean.class),
1784-
bf.getBean("testBean4", TestBean.class));
1787+
bf.getBean("testBean1", TestBean.class), bf.getBean("testBean4", TestBean.class));
1788+
1789+
Map<String, TestBean> typeMatches = BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, TestBean.class);
1790+
assertThat(typeMatches.remove("testBean3")).isNotNull();
1791+
Map<String, TestBean> candidates = SimpleAutowireCandidateResolver.resolveAutowireCandidates(bf, TestBean.class);
1792+
assertThat(candidates).containsExactlyEntriesOf(candidates);
17851793
}
17861794

17871795
@Test

0 commit comments

Comments
 (0)