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
This commit is contained in:
Juergen Hoeller
2025-02-07 19:00:25 +01:00
parent 1a573d6e3c
commit ace2f0a3e5
6 changed files with 93 additions and 65 deletions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2022 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -39,10 +39,14 @@ import org.springframework.util.StringUtils;
* (which the methods defined on the ListableBeanFactory interface don't, * (which the methods defined on the ListableBeanFactory interface don't,
* in contrast to the methods defined on the BeanFactory interface). * in contrast to the methods defined on the BeanFactory interface).
* *
* <p><b>NOTE:</b> It is generally preferable to use {@link ObjectProvider#stream()}
* via {@link BeanFactory#getBeanProvider} instead of this utility class.
*
* @author Rod Johnson * @author Rod Johnson
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Chris Beams * @author Chris Beams
* @since 04.07.2003 * @since 04.07.2003
* @see BeanFactory#getBeanProvider
*/ */
public abstract class BeanFactoryUtils { public abstract class BeanFactoryUtils {
@@ -308,7 +312,7 @@ public abstract class BeanFactoryUtils {
* 'replacing' beans by explicitly choosing the same bean name in a child factory; * 'replacing' beans by explicitly choosing the same bean name in a child factory;
* the bean in the ancestor factory won't be visible then, not even for by-type lookups. * the bean in the ancestor factory won't be visible then, not even for by-type lookups.
* @param lbf the bean factory * @param lbf the bean factory
* @param type type of bean to match * @param type the type of bean to match
* @return the Map of matching bean instances, or an empty Map if none * @return the Map of matching bean instances, or an empty Map if none
* @throws BeansException if a bean could not be created * @throws BeansException if a bean could not be created
* @see ListableBeanFactory#getBeansOfType(Class) * @see ListableBeanFactory#getBeansOfType(Class)
@@ -347,7 +351,7 @@ public abstract class BeanFactoryUtils {
* 'replacing' beans by explicitly choosing the same bean name in a child factory; * 'replacing' beans by explicitly choosing the same bean name in a child factory;
* the bean in the ancestor factory won't be visible then, not even for by-type lookups. * the bean in the ancestor factory won't be visible then, not even for by-type lookups.
* @param lbf the bean factory * @param lbf the bean factory
* @param type type of bean to match * @param type the type of bean to match
* @param includeNonSingletons whether to include prototype or scoped beans too * @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans) * or just singletons (also applies to FactoryBeans)
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and * @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
@@ -395,7 +399,7 @@ public abstract class BeanFactoryUtils {
* 'replacing' beans by explicitly choosing the same bean name in a child factory; * 'replacing' beans by explicitly choosing the same bean name in a child factory;
* the bean in the ancestor factory won't be visible then, not even for by-type lookups. * the bean in the ancestor factory won't be visible then, not even for by-type lookups.
* @param lbf the bean factory * @param lbf the bean factory
* @param type type of bean to match * @param type the type of bean to match
* @return the matching bean instance * @return the matching bean instance
* @throws NoSuchBeanDefinitionException if no bean of the given type was found * @throws NoSuchBeanDefinitionException if no bean of the given type was found
* @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
@@ -425,7 +429,7 @@ public abstract class BeanFactoryUtils {
* 'replacing' beans by explicitly choosing the same bean name in a child factory; * 'replacing' beans by explicitly choosing the same bean name in a child factory;
* the bean in the ancestor factory won't be visible then, not even for by-type lookups. * the bean in the ancestor factory won't be visible then, not even for by-type lookups.
* @param lbf the bean factory * @param lbf the bean factory
* @param type type of bean to match * @param type the type of bean to match
* @param includeNonSingletons whether to include prototype or scoped beans too * @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans) * or just singletons (also applies to FactoryBeans)
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and * @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
@@ -457,7 +461,7 @@ public abstract class BeanFactoryUtils {
* <p>This version of {@code beanOfType} automatically includes * <p>This version of {@code beanOfType} automatically includes
* prototypes and FactoryBeans. * prototypes and FactoryBeans.
* @param lbf the bean factory * @param lbf the bean factory
* @param type type of bean to match * @param type the type of bean to match
* @return the matching bean instance * @return the matching bean instance
* @throws NoSuchBeanDefinitionException if no bean of the given type was found * @throws NoSuchBeanDefinitionException if no bean of the given type was found
* @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
@@ -481,7 +485,7 @@ public abstract class BeanFactoryUtils {
* only raw FactoryBeans will be checked (which doesn't require initialization * only raw FactoryBeans will be checked (which doesn't require initialization
* of each FactoryBean). * of each FactoryBean).
* @param lbf the bean factory * @param lbf the bean factory
* @param type type of bean to match * @param type the type of bean to match
* @param includeNonSingletons whether to include prototype or scoped beans too * @param includeNonSingletons whether to include prototype or scoped beans too
* or just singletons (also applies to FactoryBeans) * or just singletons (also applies to FactoryBeans)
* @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and * @param allowEagerInit whether to initialize <i>lazy-init singletons</i> and
@@ -529,7 +533,7 @@ public abstract class BeanFactoryUtils {
/** /**
* Extract a unique bean for the given type from the given Map of matching beans. * Extract a unique bean for the given type from the given Map of matching beans.
* @param type type of bean to match * @param type the type of bean to match
* @param matchingBeans all matching beans found * @param matchingBeans all matching beans found
* @return the unique bean instance * @return the unique bean instance
* @throws NoSuchBeanDefinitionException if no bean of the given type was found * @throws NoSuchBeanDefinitionException if no bean of the given type was found

View File

@@ -55,10 +55,13 @@ import org.springframework.lang.Nullable;
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> { public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
/** /**
* A predicate for unfiltered type matches. * A predicate for unfiltered type matches, including non-default candidates
* but still excluding non-autowire candidates when used on injection points.
* @since 6.2.3 * @since 6.2.3
* @see #stream(Predicate) * @see #stream(Predicate)
* @see #orderedStream(Predicate) * @see #orderedStream(Predicate)
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
* @see org.springframework.beans.factory.support.AbstractBeanDefinition#isDefaultCandidate()
*/ */
Predicate<Class<?>> UNFILTERED = (clazz -> true); Predicate<Class<?>> UNFILTERED = (clazz -> true);
@@ -210,7 +213,7 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
* without specific ordering guarantees (but typically in registration order). * without specific ordering guarantees (but typically in registration order).
* <p>Note: The result may be filtered by default according to qualifiers on the * <p>Note: The result may be filtered by default according to qualifiers on the
* injection point versus target beans and the general autowire candidate status * injection point versus target beans and the general autowire candidate status
* of matching beans. For custom filtering against the raw type matches, use * of matching beans. For custom filtering against type-matching candidates, use
* {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}). * {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}).
* @since 5.1 * @since 5.1
* @see #iterator() * @see #iterator()
@@ -235,7 +238,7 @@ public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
* if necessary. * if necessary.
* <p>Note: The result may be filtered by default according to qualifiers on the * <p>Note: The result may be filtered by default according to qualifiers on the
* injection point versus target beans and the general autowire candidate status * injection point versus target beans and the general autowire candidate status
* of matching beans. For custom filtering against the raw type matches, use * of matching beans. For custom filtering against type-matching candidates, use
* {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}). * {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}).
* @since 5.1 * @since 5.1
* @see #stream() * @see #stream()

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2023 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -33,7 +33,9 @@ import java.util.Comparator;
import java.util.Set; import java.util.Set;
import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.beans.factory.config.TypedStringValue;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@@ -259,6 +261,24 @@ abstract class AutowireUtils {
return method.getReturnType(); return method.getReturnType();
} }
/**
* Check the autowire-candidate status for the specified bean.
* @param beanFactory the bean factory
* @param beanName the name of the bean to check
* @return whether the specified bean qualifies as an autowire candidate
* @since 6.2.3
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
*/
public static boolean isAutowireCandidate(ConfigurableBeanFactory beanFactory, String beanName) {
try {
return beanFactory.getMergedBeanDefinition(beanName).isAutowireCandidate();
}
catch (NoSuchBeanDefinitionException ex) {
// A manually registered singleton instance not backed by a BeanDefinition.
return true;
}
}
/** /**
* Reflective {@link InvocationHandler} for lazy access to the current target object. * Reflective {@link InvocationHandler} for lazy access to the current target object.

View File

@@ -2516,6 +2516,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
@Override @Override
public Stream<Object> stream(Predicate<Class<?>> customFilter) { public Stream<Object> stream(Predicate<Class<?>> customFilter) {
return Arrays.stream(getBeanNamesForTypedStream(this.descriptor.getResolvableType(), true)) return Arrays.stream(getBeanNamesForTypedStream(this.descriptor.getResolvableType(), true))
.filter(name -> AutowireUtils.isAutowireCandidate(DefaultListableBeanFactory.this, name))
.filter(name -> customFilter.test(getType(name))) .filter(name -> customFilter.test(getType(name)))
.map(name -> getBean(name)) .map(name -> getBean(name))
.filter(bean -> !(bean instanceof NullBean)); .filter(bean -> !(bean instanceof NullBean));
@@ -2529,7 +2530,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
} }
Map<String, Object> matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length); Map<String, Object> matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length);
for (String beanName : beanNames) { for (String beanName : beanNames) {
if (customFilter.test(getType(beanName))) { if (AutowireUtils.isAutowireCandidate(DefaultListableBeanFactory.this, beanName) &&
customFilter.test(getType(beanName))) {
Object beanInstance = getBean(beanName); Object beanInstance = getBean(beanName);
if (!(beanInstance instanceof NullBean)) { if (!(beanInstance instanceof NullBean)) {
matchingBeans.put(beanName, beanInstance); matchingBeans.put(beanName, beanInstance);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2024 the original author or authors. * Copyright 2002-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,9 +16,13 @@
package org.springframework.beans.factory.support; package org.springframework.beans.factory.support;
import org.springframework.beans.factory.config.BeanDefinitionHolder; import java.util.LinkedHashMap;
import org.springframework.beans.factory.config.DependencyDescriptor; import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/** /**
* {@link AutowireCandidateResolver} implementation to use when no annotation * {@link AutowireCandidateResolver} implementation to use when no annotation
@@ -36,46 +40,6 @@ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolve
*/ */
public static final SimpleAutowireCandidateResolver INSTANCE = new SimpleAutowireCandidateResolver(); public static final SimpleAutowireCandidateResolver INSTANCE = new SimpleAutowireCandidateResolver();
@Override
public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
return bdHolder.getBeanDefinition().isAutowireCandidate();
}
@Override
public boolean isRequired(DependencyDescriptor descriptor) {
return descriptor.isRequired();
}
@Override
public boolean hasQualifier(DependencyDescriptor descriptor) {
return false;
}
@Override
@Nullable
public String getSuggestedName(DependencyDescriptor descriptor) {
return null;
}
@Override
@Nullable
public Object getSuggestedValue(DependencyDescriptor descriptor) {
return null;
}
@Override
@Nullable
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
return null;
}
@Override
@Nullable
public Class<?> getLazyResolutionProxyClass(DependencyDescriptor descriptor, @Nullable String beanName) {
return null;
}
/** /**
* This implementation returns {@code this} as-is. * This implementation returns {@code this} as-is.
* @see #INSTANCE * @see #INSTANCE
@@ -85,4 +49,31 @@ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolve
return this; return this;
} }
/**
* Resolve a map of all beans of the given type, also picking up beans defined in
* ancestor bean factories, with the specific condition that each bean actually
* has autowire candidate status. This matches simple injection point resolution
* as implemented by this {@link AutowireCandidateResolver} strategy, including
* beans which are not marked as default candidates but excluding beans which
* are not even marked as autowire candidates.
* @param lbf the bean factory
* @param type the type of bean to match
* @return the Map of matching bean instances, or an empty Map if none
* @throws BeansException if a bean could not be created
* @since 6.2.3
* @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class)
* @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate()
* @see AbstractBeanDefinition#isDefaultCandidate()
*/
public static <T> Map<String, T> resolveAutowireCandidates(ConfigurableListableBeanFactory lbf, Class<T> type) {
Map<String, T> candidates = new LinkedHashMap<>();
for (String beanName : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(lbf, type)) {
if (AutowireUtils.isAutowireCandidate(lbf, beanName)) {
candidates.put(beanName, lbf.getBean(beanName, type));
}
}
return candidates;
}
} }

View File

@@ -48,6 +48,7 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
@@ -64,6 +65,7 @@ import org.springframework.beans.factory.support.AutowireCandidateQualifier;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
import org.springframework.beans.testfixture.beans.DerivedTestBean; import org.springframework.beans.testfixture.beans.DerivedTestBean;
import org.springframework.beans.testfixture.beans.ITestBean; import org.springframework.beans.testfixture.beans.ITestBean;
import org.springframework.beans.testfixture.beans.IndexedTestBean; import org.springframework.beans.testfixture.beans.IndexedTestBean;
@@ -1756,14 +1758,17 @@ class AutowiredAnnotationBeanPostProcessorTests {
RootBeanDefinition tb2 = new RootBeanDefinition(TestBeanFactory.class); RootBeanDefinition tb2 = new RootBeanDefinition(TestBeanFactory.class);
tb2.setFactoryMethodName("newTestBean2"); tb2.setFactoryMethodName("newTestBean2");
bf.registerBeanDefinition("testBean2", tb2); bf.registerBeanDefinition("testBean2", tb2);
DefaultListableBeanFactory parent = new DefaultListableBeanFactory();
RootBeanDefinition tb3 = new RootBeanDefinition(TestBean.class); RootBeanDefinition tb3 = new RootBeanDefinition(TestBean.class);
tb3.setAutowireCandidate(false); tb3.setAutowireCandidate(false);
tb3.setLazyInit(true); tb3.setLazyInit(true);
bf.registerBeanDefinition("testBean3", tb3); parent.registerBeanDefinition("testBean3", tb3);
RootBeanDefinition tb4 = new RootBeanDefinition(DerivedTestBean.class); RootBeanDefinition tb4 = new RootBeanDefinition(DerivedTestBean.class);
tb4.setDefaultCandidate(false); tb4.setDefaultCandidate(false);
tb4.setLazyInit(true); tb4.setLazyInit(true);
bf.registerBeanDefinition("testBean4", tb4); parent.registerBeanDefinition("testBean4", tb4);
bf.setParentBeanFactory(parent);
ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class); ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class);
assertThat(bean.streamTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class), assertThat(bean.streamTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
@@ -1772,16 +1777,19 @@ class AutowiredAnnotationBeanPostProcessorTests {
bf.getBean("testBean1", TestBean.class)); bf.getBean("testBean1", TestBean.class));
assertThat(bf.containsSingleton("testBean3")).isFalse(); assertThat(bf.containsSingleton("testBean3")).isFalse();
assertThat(bean.plainTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class), assertThat(bean.plainTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
bf.getBean("testBean2", TestBean.class), bf.getBean("testBean3", TestBean.class)); bf.getBean("testBean2", TestBean.class));
assertThat(bean.plainTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class), assertThat(bean.plainTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
bf.getBean("testBean1", TestBean.class), bf.getBean("testBean3", TestBean.class)); bf.getBean("testBean1", TestBean.class));
assertThat(bf.containsSingleton("testBean4")).isFalse(); assertThat(bf.containsSingleton("testBean4")).isFalse();
assertThat(bean.allTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class), assertThat(bean.allTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class),
bf.getBean("testBean2", TestBean.class), bf.getBean("testBean3", TestBean.class), bf.getBean("testBean2", TestBean.class), bf.getBean("testBean4", TestBean.class));
bf.getBean("testBean4", TestBean.class));
assertThat(bean.allTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class), assertThat(bean.allTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class),
bf.getBean("testBean1", TestBean.class), bf.getBean("testBean3", TestBean.class), bf.getBean("testBean1", TestBean.class), bf.getBean("testBean4", TestBean.class));
bf.getBean("testBean4", TestBean.class));
Map<String, TestBean> typeMatches = BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, TestBean.class);
assertThat(typeMatches.remove("testBean3")).isNotNull();
Map<String, TestBean> candidates = SimpleAutowireCandidateResolver.resolveAutowireCandidates(bf, TestBean.class);
assertThat(candidates).containsExactlyEntriesOf(candidates);
} }
@Test @Test