From 8c2b44bd6c862dca64b657b4c4511aa0c14dba2a Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 3 Feb 2025 15:04:27 +0100 Subject: [PATCH 1/2] Support filtered/unfiltered stream access on ObjectProvider Closes gh-34318 Closes gh-34203 --- .../beans/factory/ObjectProvider.java | 48 +++++++++- .../support/DefaultListableBeanFactory.java | 58 +++++++++++- .../DefaultListableBeanFactoryTests.java | 33 ++++++- ...wiredAnnotationBeanPostProcessorTests.java | 90 +++++++++++++++++-- 4 files changed, 213 insertions(+), 16 deletions(-) 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 c008e6537c6..6127fd79566 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 @@ -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. @@ -18,6 +18,7 @@ package org.springframework.beans.factory; import java.util.Iterator; import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Stream; @@ -53,6 +54,15 @@ import org.springframework.lang.Nullable; */ public interface ObjectProvider extends ObjectFactory, Iterable { + /** + * A predicate for unfiltered type matches. + * @since 6.2.3 + * @see #stream(Predicate) + * @see #orderedStream(Predicate) + */ + Predicate> UNFILTERED = (clazz -> true); + + @Override default T getObject() throws BeansException { Iterator it = iterator(); @@ -198,6 +208,10 @@ public interface ObjectProvider extends ObjectFactory, Iterable { /** * Return a sequential {@link Stream} over all matching object instances, * 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 + * {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}). * @since 5.1 * @see #iterator() * @see #orderedStream() @@ -219,6 +233,10 @@ public interface ObjectProvider extends ObjectFactory, Iterable { * {@link #stream()} method. You may override this to apply an * {@link org.springframework.core.annotation.AnnotationAwareOrderComparator} * 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 + * {@link #stream(Predicate)} instead (potentially with {@link #UNFILTERED}). * @since 5.1 * @see #stream() * @see org.springframework.core.OrderComparator @@ -227,4 +245,32 @@ public interface ObjectProvider extends ObjectFactory, Iterable { return stream().sorted(OrderComparator.INSTANCE); } + /** + * Return a custom-filtered {@link Stream} over all matching object instances, + * without specific ordering guarantees (but typically in registration order). + * @param customFilter a custom type filter for selecting beans among the raw + * bean type matches (or {@link #UNFILTERED} for all raw type matches without + * any default filtering) + * @since 6.2.3 + * @see #stream() + * @see #orderedStream(Predicate) + */ + default Stream stream(Predicate> customFilter) { + return stream().filter(obj -> customFilter.test(obj.getClass())); + } + + /** + * Return a custom-filtered {@link Stream} over all matching object instances, + * pre-ordered according to the factory's common order comparator. + * @param customFilter a custom type filter for selecting beans among the raw + * bean type matches (or {@link #UNFILTERED} for all raw type matches without + * any default filtering) + * @since 6.2.3 + * @see #orderedStream() + * @see #stream(Predicate) + */ + default Stream orderedStream(Predicate> customFilter) { + return orderedStream().filter(obj -> customFilter.test(obj.getClass())); + } + } 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 94e3855130c..26ec8b9496b 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 @@ -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. @@ -508,6 +508,32 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto Stream stream = matchingBeans.values().stream(); return stream.sorted(adaptOrderComparator(matchingBeans)); } + @SuppressWarnings("unchecked") + @Override + public Stream stream(Predicate> customFilter) { + return Arrays.stream(getBeanNamesForTypedStream(requiredType, allowEagerInit)) + .filter(name -> customFilter.test(getType(name))) + .map(name -> (T) getBean(name)) + .filter(bean -> !(bean instanceof NullBean)); + } + @SuppressWarnings("unchecked") + @Override + public Stream orderedStream(Predicate> customFilter) { + String[] beanNames = getBeanNamesForTypedStream(requiredType, allowEagerInit); + if (beanNames.length == 0) { + return Stream.empty(); + } + Map matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length); + for (String beanName : beanNames) { + if (customFilter.test(getType(beanName))) { + Object beanInstance = getBean(beanName); + if (!(beanInstance instanceof NullBean)) { + matchingBeans.put(beanName, (T) beanInstance); + } + } + } + return matchingBeans.values().stream().sorted(adaptOrderComparator(matchingBeans)); + } }; } @@ -1892,8 +1918,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto candidates.put(candidateName, beanInstance); } } - else if (containsSingleton(candidateName) || (descriptor instanceof StreamDependencyDescriptor streamDescriptor && - streamDescriptor.isOrdered())) { + else if (containsSingleton(candidateName) || + (descriptor instanceof StreamDependencyDescriptor streamDescriptor && streamDescriptor.isOrdered())) { Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this); candidates.put(candidateName, (beanInstance instanceof NullBean ? null : beanInstance)); } @@ -2486,6 +2512,32 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto Object result = doResolveDependency(descriptorToUse, this.beanName, null, null); return (result instanceof Stream stream ? stream : Stream.of(result)); } + + @Override + public Stream stream(Predicate> customFilter) { + return Arrays.stream(getBeanNamesForTypedStream(this.descriptor.getResolvableType(), true)) + .filter(name -> customFilter.test(getType(name))) + .map(name -> getBean(name)) + .filter(bean -> !(bean instanceof NullBean)); + } + + @Override + public Stream orderedStream(Predicate> customFilter) { + String[] beanNames = getBeanNamesForTypedStream(this.descriptor.getResolvableType(), true); + if (beanNames.length == 0) { + return Stream.empty(); + } + Map matchingBeans = CollectionUtils.newLinkedHashMap(beanNames.length); + for (String beanName : beanNames) { + if (customFilter.test(getType(beanName))) { + Object beanInstance = getBean(beanName); + if (!(beanInstance instanceof NullBean)) { + matchingBeans.put(beanName, beanInstance); + } + } + } + return matchingBeans.values().stream().sorted(adaptOrderComparator(matchingBeans)); + } } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index 0621279dc81..a758c5c48c3 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.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. @@ -1515,12 +1515,16 @@ class DefaultListableBeanFactoryTests { bd1.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.LOWEST_PRECEDENCE); lbf.registerBeanDefinition("bean1", bd1); GenericBeanDefinition bd2 = new GenericBeanDefinition(); - bd2.setBeanClass(TestBean.class); + bd2.setBeanClass(DerivedTestBean.class); bd2.setPropertyValues(new MutablePropertyValues(List.of(new PropertyValue("name", "highest")))); bd2.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.HIGHEST_PRECEDENCE); lbf.registerBeanDefinition("bean2", bd2); assertThat(lbf.getBeanProvider(TestBean.class).orderedStream().map(TestBean::getName)) .containsExactly("highest", "lowest"); + assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(ObjectProvider.UNFILTERED).map(TestBean::getName)) + .containsExactly("highest", "lowest"); + assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(clazz -> !DerivedTestBean.class.isAssignableFrom(clazz)) + .map(TestBean::getName)).containsExactly("lowest"); } @Test @@ -1540,6 +1544,8 @@ class DefaultListableBeanFactoryTests { lbf.registerBeanDefinition("bean2", bd2); assertThat(lbf.getBeanProvider(TestBean.class).orderedStream().map(TestBean::getName)) .containsExactly("fromLowestPrecedenceTestBeanFactoryBean", "fromHighestPrecedenceTestBeanFactoryBean"); + assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(ObjectProvider.UNFILTERED).map(TestBean::getName)) + .containsExactly("fromLowestPrecedenceTestBeanFactoryBean", "fromHighestPrecedenceTestBeanFactoryBean"); } @Test @@ -1934,6 +1940,11 @@ class DefaultListableBeanFactoryTests { assertThat(resolved).hasSize(2); assertThat(resolved).contains(lbf.getBean("bd1")); assertThat(resolved).contains(lbf.getBean("bd2")); + + resolved = provider.stream(ObjectProvider.UNFILTERED).collect(Collectors.toSet()); + assertThat(resolved).hasSize(2); + assertThat(resolved).contains(lbf.getBean("bd1")); + assertThat(resolved).contains(lbf.getBean("bd2")); } @Test @@ -1983,6 +1994,11 @@ class DefaultListableBeanFactoryTests { assertThat(resolved).hasSize(2); assertThat(resolved).contains(lbf.getBean("bd1")); assertThat(resolved).contains(lbf.getBean("bd2")); + + resolved = provider.stream(ObjectProvider.UNFILTERED).collect(Collectors.toSet()); + assertThat(resolved).hasSize(2); + assertThat(resolved).contains(lbf.getBean("bd1")); + assertThat(resolved).contains(lbf.getBean("bd2")); } @Test @@ -2378,11 +2394,20 @@ class DefaultListableBeanFactoryTests { parentBf.registerBeanDefinition("highPriorityTestBean", bd2); ObjectProvider testBeanProvider = lbf.getBeanProvider(ResolvableType.forClass(TestBean.class)); - List resolved = testBeanProvider.orderedStream().toList(); - assertThat(resolved).containsExactly( + assertThat(testBeanProvider.orderedStream()).containsExactly( lbf.getBean("highPriorityTestBean", TestBean.class), lbf.getBean("lowPriorityTestBean", TestBean.class), lbf.getBean("plainTestBean", TestBean.class)); + assertThat(testBeanProvider.orderedStream(clazz -> clazz != TestBean.class).toList()).containsExactly( + lbf.getBean("highPriorityTestBean", TestBean.class), + lbf.getBean("lowPriorityTestBean", TestBean.class)); + assertThat(testBeanProvider.stream()).containsExactly( + lbf.getBean("plainTestBean", TestBean.class), + lbf.getBean("lowPriorityTestBean", TestBean.class), + lbf.getBean("highPriorityTestBean", TestBean.class)); + assertThat(testBeanProvider.orderedStream(clazz -> clazz != TestBean.class).toList()).containsExactly( + lbf.getBean("lowPriorityTestBean", TestBean.class), + lbf.getBean("highPriorityTestBean", TestBean.class)); } @Test 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 91642841999..669c9d5f995 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 @@ -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. @@ -64,6 +64,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.testfixture.beans.DerivedTestBean; import org.springframework.beans.testfixture.beans.ITestBean; import org.springframework.beans.testfixture.beans.IndexedTestBean; import org.springframework.beans.testfixture.beans.NestedTestBean; @@ -1605,7 +1606,11 @@ class AutowiredAnnotationBeanPostProcessorTests { assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class)); testBeans = bean.streamTestBeans(); assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class)); - testBeans = bean.sortedTestBeans(); + testBeans = bean.streamTestBeansInOrder(); + assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class)); + testBeans = bean.allTestBeans(); + assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class)); + testBeans = bean.allTestBeansInOrder(); assertThat(testBeans).containsExactly(bf.getBean("testBean1", TestBean.class), bf.getBean("testBean2", TestBean.class)); } @@ -1632,7 +1637,13 @@ class AutowiredAnnotationBeanPostProcessorTests { testBeans = bean.streamTestBeans(); assertThat(testBeans).hasSize(1); assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class)); - testBeans = bean.sortedTestBeans(); + testBeans = bean.streamTestBeansInOrder(); + assertThat(testBeans).hasSize(1); + assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class)); + testBeans = bean.allTestBeans(); + assertThat(testBeans).hasSize(1); + assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class)); + testBeans = bean.allTestBeansInOrder(); assertThat(testBeans).hasSize(1); assertThat(testBeans).contains(bf.getBean("testBean", TestBean.class)); } @@ -1656,7 +1667,11 @@ class AutowiredAnnotationBeanPostProcessorTests { assertThat(testBeans).isEmpty(); testBeans = bean.streamTestBeans(); assertThat(testBeans).isEmpty(); - testBeans = bean.sortedTestBeans(); + testBeans = bean.streamTestBeansInOrder(); + assertThat(testBeans).isEmpty(); + testBeans = bean.allTestBeans(); + assertThat(testBeans).isEmpty(); + testBeans = bean.allTestBeansInOrder(); assertThat(testBeans).isEmpty(); } @@ -1678,7 +1693,9 @@ class AutowiredAnnotationBeanPostProcessorTests { assertThat(bean.iterateTestBeans()).containsExactly(testBean1, testBean2); assertThat(bean.forEachTestBeans()).containsExactly(testBean1, testBean2); assertThat(bean.streamTestBeans()).containsExactly(testBean1, testBean2); - assertThat(bean.sortedTestBeans()).containsExactly(testBean1, testBean2); + assertThat(bean.streamTestBeansInOrder()).containsExactly(testBean1, testBean2); + assertThat(bean.allTestBeans()).containsExactly(testBean1, testBean2); + assertThat(bean.allTestBeansInOrder()).containsExactly(testBean1, testBean2); } @Test @@ -1706,7 +1723,9 @@ class AutowiredAnnotationBeanPostProcessorTests { assertThat(bean.iterateTestBeans()).containsExactly(testBean1, testBean2); assertThat(bean.forEachTestBeans()).containsExactly(testBean1, testBean2); assertThat(bean.streamTestBeans()).containsExactly(testBean1, testBean2); - assertThat(bean.sortedTestBeans()).containsExactly(testBean2, testBean1); + assertThat(bean.streamTestBeansInOrder()).containsExactly(testBean2, testBean1); + assertThat(bean.allTestBeans()).containsExactly(testBean1, testBean2); + assertThat(bean.allTestBeansInOrder()).containsExactly(testBean2, testBean1); } @Test @@ -1722,8 +1741,47 @@ class AutowiredAnnotationBeanPostProcessorTests { bf.registerBeanDefinition("testBean2", tb2); ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class); - assertThat(bean.sortedTestBeans()).containsExactly(bf.getBean("testBean2", TestBean.class), + assertThat(bean.streamTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class), + bf.getBean("testBean1", TestBean.class)); + assertThat(bean.allTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class), + bf.getBean("testBean1", TestBean.class)); + } + + @Test + void objectProviderInjectionWithNonCandidatesInStream() { + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(ObjectProviderInjectionBean.class)); + RootBeanDefinition tb1 = new RootBeanDefinition(TestBeanFactory.class); + tb1.setFactoryMethodName("newTestBean1"); + bf.registerBeanDefinition("testBean1", tb1); + RootBeanDefinition tb2 = new RootBeanDefinition(TestBeanFactory.class); + tb2.setFactoryMethodName("newTestBean2"); + bf.registerBeanDefinition("testBean2", tb2); + RootBeanDefinition tb3 = new RootBeanDefinition(TestBean.class); + tb3.setAutowireCandidate(false); + tb3.setLazyInit(true); + bf.registerBeanDefinition("testBean3", tb3); + RootBeanDefinition tb4 = new RootBeanDefinition(DerivedTestBean.class); + tb4.setDefaultCandidate(false); + tb4.setLazyInit(true); + bf.registerBeanDefinition("testBean4", tb4); + + ObjectProviderInjectionBean bean = bf.getBean("annotatedBean", ObjectProviderInjectionBean.class); + assertThat(bean.streamTestBeans()).containsExactly(bf.getBean("testBean1", TestBean.class), + bf.getBean("testBean2", TestBean.class)); + assertThat(bean.streamTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class), 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)); + assertThat(bean.plainTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class), + bf.getBean("testBean1", TestBean.class), bf.getBean("testBean3", 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)); + assertThat(bean.allTestBeansInOrder()).containsExactly(bf.getBean("testBean2", TestBean.class), + bf.getBean("testBean1", TestBean.class), bf.getBean("testBean3", TestBean.class), + bf.getBean("testBean4", TestBean.class)); } @Test @@ -3304,9 +3362,25 @@ class AutowiredAnnotationBeanPostProcessorTests { return this.testBean.stream().toList(); } - public List sortedTestBeans() { + public List streamTestBeansInOrder() { return this.testBean.orderedStream().toList(); } + + public List plainTestBeans() { + return this.testBean.stream(clazz -> !DerivedTestBean.class.isAssignableFrom(clazz)).toList(); + } + + public List plainTestBeansInOrder() { + return this.testBean.orderedStream(clazz -> !DerivedTestBean.class.isAssignableFrom(clazz)).toList(); + } + + public List allTestBeans() { + return this.testBean.stream(ObjectProvider.UNFILTERED).toList(); + } + + public List allTestBeansInOrder() { + return this.testBean.orderedStream(ObjectProvider.UNFILTERED).toList(); + } } From 1b18928bf057967509c03937c3832be3a9d462c7 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 3 Feb 2025 15:23:51 +0100 Subject: [PATCH 2/2] Explicitly set custom ClassLoader on CGLIB Enhancer Closes gh-34274 --- .../context/annotation/ConfigurationClassEnhancer.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index 113e0d3007d..25631c7e08b 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.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. @@ -129,6 +129,9 @@ class ConfigurationClassEnhancer { */ private Enhancer newEnhancer(Class configSuperClass, @Nullable ClassLoader classLoader) { Enhancer enhancer = new Enhancer(); + if (classLoader != null) { + enhancer.setClassLoader(classLoader); + } enhancer.setSuperclass(configSuperClass); enhancer.setInterfaces(new Class[] {EnhancedConfiguration.class}); enhancer.setUseFactory(false);