diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java index 81b083cadf5..2e9580b6c6f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java @@ -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"); * you may not use this file except in compliance with the License. @@ -40,10 +40,14 @@ import org.springframework.util.StringUtils; * (which the methods defined on the ListableBeanFactory interface don't, * in contrast to the methods defined on the BeanFactory interface). * + *

NOTE: It is generally preferable to use {@link ObjectProvider#stream()} + * via {@link BeanFactory#getBeanProvider} instead of this utility class. + * * @author Rod Johnson * @author Juergen Hoeller * @author Chris Beams * @since 04.07.2003 + * @see BeanFactory#getBeanProvider */ public abstract class BeanFactoryUtils { @@ -309,7 +313,7 @@ public abstract class BeanFactoryUtils { * '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. * @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 * @throws BeansException if a bean could not be created * @see ListableBeanFactory#getBeansOfType(Class) @@ -348,7 +352,7 @@ public abstract class BeanFactoryUtils { * '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. * @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 * or just singletons (also applies to FactoryBeans) * @param allowEagerInit whether to initialize lazy-init singletons and @@ -396,7 +400,7 @@ public abstract class BeanFactoryUtils { * '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. * @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 * @throws NoSuchBeanDefinitionException if no bean of the given type was found * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found @@ -426,7 +430,7 @@ public abstract class BeanFactoryUtils { * '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. * @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 * or just singletons (also applies to FactoryBeans) * @param allowEagerInit whether to initialize lazy-init singletons and @@ -458,7 +462,7 @@ public abstract class BeanFactoryUtils { *

This version of {@code beanOfType} automatically includes * prototypes and FactoryBeans. * @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 * @throws NoSuchBeanDefinitionException if no bean of the given type was found * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found @@ -482,7 +486,7 @@ public abstract class BeanFactoryUtils { * only raw FactoryBeans will be checked (which doesn't require initialization * of each FactoryBean). * @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 * or just singletons (also applies to FactoryBeans) * @param allowEagerInit whether to initialize lazy-init singletons and @@ -530,7 +534,7 @@ public abstract class BeanFactoryUtils { /** * 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 * @return the unique bean instance * @throws NoSuchBeanDefinitionException if no bean of the given type was found diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java index a2cdfb4ffa8..2768d066c3b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/ObjectProvider.java @@ -56,10 +56,13 @@ import org.springframework.core.OrderComparator; public interface ObjectProvider extends ObjectFactory, Iterable { /** - * 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 * @see #stream(Predicate) * @see #orderedStream(Predicate) + * @see org.springframework.beans.factory.config.BeanDefinition#isAutowireCandidate() + * @see org.springframework.beans.factory.support.AbstractBeanDefinition#isDefaultCandidate() */ Predicate> UNFILTERED = (clazz -> true); @@ -209,7 +212,7 @@ public interface ObjectProvider extends ObjectFactory, Iterable { * without specific ordering guarantees (but typically in registration order). *

Note: The result may be filtered by default according to qualifiers on the * 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}). * @since 5.1 * @see #iterator() @@ -234,7 +237,7 @@ public interface ObjectProvider extends ObjectFactory, Iterable { * if necessary. *

Note: The result may be filtered by default according to qualifiers on the * 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}). * @since 5.1 * @see #stream() diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java index fc038766219..5a9faf928e8 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java @@ -35,7 +35,9 @@ import java.util.Set; import org.jspecify.annotations.Nullable; import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.TypedStringValue; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -260,6 +262,24 @@ abstract class AutowireUtils { 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. diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 659b565aa9e..10ac3533fa3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -2480,6 +2480,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override public Stream stream(Predicate> customFilter) { return Arrays.stream(getBeanNamesForTypedStream(this.descriptor.getResolvableType(), true)) + .filter(name -> AutowireUtils.isAutowireCandidate(DefaultListableBeanFactory.this, name)) .filter(name -> customFilter.test(getType(name))) .map(name -> getBean(name)) .filter(bean -> !(bean instanceof NullBean)); @@ -2493,7 +2494,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto } Map matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length); for (String beanName : beanNames) { - if (customFilter.test(getType(beanName))) { + if (AutowireUtils.isAutowireCandidate(DefaultListableBeanFactory.this, beanName) && + customFilter.test(getType(beanName))) { Object beanInstance = getBean(beanName); if (!(beanInstance instanceof NullBean)) { matchingBeans.put(beanName, beanInstance); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java index 6e2a25d81f8..b67fdb6e548 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/SimpleAutowireCandidateResolver.java @@ -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"); * you may not use this file except in compliance with the License. @@ -16,10 +16,13 @@ package org.springframework.beans.factory.support; -import org.jspecify.annotations.Nullable; +import java.util.LinkedHashMap; +import java.util.Map; -import org.springframework.beans.factory.config.BeanDefinitionHolder; -import org.springframework.beans.factory.config.DependencyDescriptor; +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 @@ -37,42 +40,6 @@ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolve */ 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 - public @Nullable String getSuggestedName(DependencyDescriptor descriptor) { - return null; - } - - @Override - public @Nullable Object getSuggestedValue(DependencyDescriptor descriptor) { - return null; - } - - @Override - public @Nullable Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) { - return null; - } - - @Override - public @Nullable Class getLazyResolutionProxyClass(DependencyDescriptor descriptor, @Nullable String beanName) { - return null; - } - /** * This implementation returns {@code this} as-is. * @see #INSTANCE @@ -82,4 +49,31 @@ public class SimpleAutowireCandidateResolver implements AutowireCandidateResolve 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 Map resolveAutowireCandidates(ConfigurableListableBeanFactory lbf, Class type) { + Map candidates = new LinkedHashMap<>(); + for (String beanName : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(lbf, type)) { + if (AutowireUtils.isAutowireCandidate(lbf, beanName)) { + candidates.put(beanName, lbf.getBean(beanName, type)); + } + } + return candidates; + } + } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index 85e9721d0da..aed293304a7 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -49,6 +49,7 @@ import org.mockito.Mockito; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; @@ -65,6 +66,7 @@ import org.springframework.beans.factory.support.AutowireCandidateQualifier; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.GenericBeanDefinition; 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.ITestBean; import org.springframework.beans.testfixture.beans.IndexedTestBean; @@ -1756,14 +1758,17 @@ class AutowiredAnnotationBeanPostProcessorTests { RootBeanDefinition tb2 = new RootBeanDefinition(TestBeanFactory.class); tb2.setFactoryMethodName("newTestBean2"); bf.registerBeanDefinition("testBean2", tb2); + + DefaultListableBeanFactory parent = new DefaultListableBeanFactory(); RootBeanDefinition tb3 = new RootBeanDefinition(TestBean.class); tb3.setAutowireCandidate(false); tb3.setLazyInit(true); - bf.registerBeanDefinition("testBean3", tb3); + parent.registerBeanDefinition("testBean3", tb3); RootBeanDefinition tb4 = new RootBeanDefinition(DerivedTestBean.class); tb4.setDefaultCandidate(false); tb4.setLazyInit(true); - bf.registerBeanDefinition("testBean4", tb4); + parent.registerBeanDefinition("testBean4", tb4); + bf.setParentBeanFactory(parent); ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class); assertThat(bean.streamTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class), @@ -1772,16 +1777,19 @@ class AutowiredAnnotationBeanPostProcessorTests { bf.getBean("testBean1", TestBean.class)); assertThat(bf.containsSingleton("testBean3")).isFalse(); 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), - bf.getBean("testBean1", TestBean.class), bf.getBean("testBean3", TestBean.class)); + bf.getBean("testBean1", TestBean.class)); assertThat(bf.containsSingleton("testBean4")).isFalse(); assertThat(bean.allTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class), - bf.getBean("testBean2", TestBean.class), bf.getBean("testBean3", TestBean.class), - bf.getBean("testBean4", TestBean.class)); + bf.getBean("testBean2", TestBean.class), bf.getBean("testBean4", TestBean.class)); assertThat(bean.allTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class), - bf.getBean("testBean1", TestBean.class), bf.getBean("testBean3", TestBean.class), - bf.getBean("testBean4", TestBean.class)); + bf.getBean("testBean1", TestBean.class), bf.getBean("testBean4", TestBean.class)); + + Map typeMatches = BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, TestBean.class); + assertThat(typeMatches.remove("testBean3")).isNotNull(); + Map candidates = SimpleAutowireCandidateResolver.resolveAutowireCandidates(bf, TestBean.class); + assertThat(candidates).containsExactlyEntriesOf(candidates); } @Test diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 48aefd8daa8..3efe15ffcf6 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -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"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.function.Supplier; import java.util.stream.Stream; import org.jspecify.annotations.Nullable; @@ -71,7 +72,9 @@ public class TypeDescriptor implements Serializable { private final ResolvableType resolvableType; - private final AnnotatedElementAdapter annotatedElement; + private final AnnotatedElementSupplier annotatedElementSupplier; + + private volatile @Nullable AnnotatedElementAdapter annotatedElement; /** @@ -83,7 +86,7 @@ public class TypeDescriptor implements Serializable { public TypeDescriptor(MethodParameter methodParameter) { this.resolvableType = ResolvableType.forMethodParameter(methodParameter); this.type = this.resolvableType.resolve(methodParameter.getNestedParameterType()); - this.annotatedElement = AnnotatedElementAdapter.from(methodParameter.getParameterIndex() == -1 ? + this.annotatedElementSupplier = () -> AnnotatedElementAdapter.from(methodParameter.getParameterIndex() == -1 ? methodParameter.getMethodAnnotations() : methodParameter.getParameterAnnotations()); } @@ -95,7 +98,7 @@ public class TypeDescriptor implements Serializable { public TypeDescriptor(Field field) { this.resolvableType = ResolvableType.forField(field); this.type = this.resolvableType.resolve(field.getType()); - this.annotatedElement = AnnotatedElementAdapter.from(field.getAnnotations()); + this.annotatedElementSupplier = () -> AnnotatedElementAdapter.from(field.getAnnotations()); } /** @@ -108,7 +111,7 @@ public class TypeDescriptor implements Serializable { Assert.notNull(property, "Property must not be null"); this.resolvableType = ResolvableType.forMethodParameter(property.getMethodParameter()); this.type = this.resolvableType.resolve(property.getType()); - this.annotatedElement = AnnotatedElementAdapter.from(property.getAnnotations()); + this.annotatedElementSupplier = () -> AnnotatedElementAdapter.from(property.getAnnotations()); } /** @@ -124,7 +127,7 @@ public class TypeDescriptor implements Serializable { public TypeDescriptor(ResolvableType resolvableType, @Nullable Class type, Annotation @Nullable [] annotations) { this.resolvableType = resolvableType; this.type = (type != null ? type : resolvableType.toClass()); - this.annotatedElement = AnnotatedElementAdapter.from(annotations); + this.annotatedElementSupplier = () -> AnnotatedElementAdapter.from(annotations); } @@ -249,12 +252,21 @@ public class TypeDescriptor implements Serializable { return getType().isPrimitive(); } + private AnnotatedElementAdapter getAnnotatedElement() { + AnnotatedElementAdapter annotatedElement = this.annotatedElement; + if (annotatedElement == null) { + annotatedElement = this.annotatedElementSupplier.get(); + this.annotatedElement = annotatedElement; + } + return annotatedElement; + } + /** * Return the annotations associated with this type descriptor, if any. * @return the annotations, or an empty array if none */ public Annotation[] getAnnotations() { - return this.annotatedElement.getAnnotations(); + return getAnnotatedElement().getAnnotations(); } /** @@ -265,12 +277,13 @@ public class TypeDescriptor implements Serializable { * @return {@code true} if the annotation is present */ public boolean hasAnnotation(Class annotationType) { - if (this.annotatedElement.isEmpty()) { + AnnotatedElementAdapter annotatedElement = getAnnotatedElement(); + if (annotatedElement.isEmpty()) { // Shortcut: AnnotatedElementUtils would have to expect AnnotatedElement.getAnnotations() // to return a copy of the array, whereas we can do it more efficiently here. return false; } - return AnnotatedElementUtils.isAnnotated(this.annotatedElement, annotationType); + return AnnotatedElementUtils.isAnnotated(annotatedElement, annotationType); } /** @@ -280,12 +293,13 @@ public class TypeDescriptor implements Serializable { * @return the annotation, or {@code null} if no such annotation exists on this type descriptor */ public @Nullable T getAnnotation(Class annotationType) { - if (this.annotatedElement.isEmpty()) { + AnnotatedElementAdapter annotatedElement = getAnnotatedElement(); + if (annotatedElement.isEmpty()) { // Shortcut: AnnotatedElementUtils would have to expect AnnotatedElement.getAnnotations() // to return a copy of the array, whereas we can do it more efficiently here. return null; } - return AnnotatedElementUtils.getMergedAnnotation(this.annotatedElement, annotationType); + return AnnotatedElementUtils.getMergedAnnotation(annotatedElement, annotationType); } /** @@ -792,4 +806,8 @@ public class TypeDescriptor implements Serializable { } } + + private interface AnnotatedElementSupplier extends Supplier, Serializable { + } + }