From df67c1cf2d4d477e78d8711c9a875608acf9d5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sch=C3=A4fer=2C=20H=2EH=2E=20=28Hans=20Hosea=29?= Date: Mon, 13 Oct 2025 22:46:55 +0200 Subject: [PATCH 1/4] 35626: defaultCandidate for scoped proxies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Schäfer, H.H. (Hans Hosea) --- .../aop/scope/ScopedProxyUtils.java | 4 + .../aop/scope/ScopedProxyUtilsTests.java | 73 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java index f6715a15d15..004012b0123 100644 --- a/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java @@ -83,11 +83,15 @@ public abstract class ScopedProxyUtils { proxyDefinition.setPrimary(targetDefinition.isPrimary()); if (targetDefinition instanceof AbstractBeanDefinition abd) { proxyDefinition.copyQualifiersFrom(abd); + proxyDefinition.setDefaultCandidate(abd.isDefaultCandidate()); } // The target bean should be ignored in favor of the scoped proxy. targetDefinition.setAutowireCandidate(false); targetDefinition.setPrimary(false); + if (targetDefinition instanceof AbstractBeanDefinition abd) { + abd.setDefaultCandidate(false); + } // Register the target bean as separate bean in the factory. registry.registerBeanDefinition(targetBeanName, targetDefinition); diff --git a/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyUtilsTests.java b/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyUtilsTests.java index ff3299cd4a5..164e6c85216 100644 --- a/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyUtilsTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyUtilsTests.java @@ -18,9 +18,19 @@ package org.springframework.aop.scope; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.AutowireCandidateQualifier; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + /** * Tests for {@link ScopedProxyUtils}. * @@ -64,4 +74,67 @@ class ScopedProxyUtilsTests { .withMessage("bean name 'myBean' does not refer to the target of a scoped proxy"); } + @Test + void createScopedProxyTargetOvertakesAutowireSettingsToProxyBeanDefinition() { + final AbstractBeanDefinition targetDefinition = new GenericBeanDefinition(); + // Opposite of defaults + targetDefinition.setAutowireCandidate(false); + targetDefinition.setDefaultCandidate(false); + targetDefinition.setPrimary(true); + + final BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); + final BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false); + assertThat(proxyHolder).isNotNull(); + final BeanDefinition proxyBeanDefinition = proxyHolder.getBeanDefinition(); + assertThat(proxyBeanDefinition).isNotNull(); + assertThat(proxyBeanDefinition).isInstanceOf(RootBeanDefinition.class); + + assertThat(proxyBeanDefinition.isAutowireCandidate()).isFalse(); + assertThat(proxyBeanDefinition.isPrimary()).isTrue(); + assertThat(((RootBeanDefinition)proxyBeanDefinition).isDefaultCandidate()).isFalse(); + } + + @Test + void createScopedProxyTargetOvertakesBeanAttributesToProxyBeanDefinition() { + final GenericBeanDefinition targetDefinition = new GenericBeanDefinition(); + // Opposite of defaults + targetDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + targetDefinition.setSource("theSource"); + targetDefinition.addQualifier(new AutowireCandidateQualifier("myQualifier")); + + final BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); + final BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false); + assertThat(proxyHolder).isNotNull(); + final BeanDefinition proxyBeanDefinition = proxyHolder.getBeanDefinition(); + assertThat(proxyBeanDefinition).isNotNull(); + + assertThat(proxyBeanDefinition.getRole()).isEqualTo(BeanDefinition.ROLE_INFRASTRUCTURE); + assertThat(proxyBeanDefinition).isInstanceOf(RootBeanDefinition.class); + assertThat(proxyBeanDefinition.getPropertyValues()).hasSize(2); + assertThat(proxyBeanDefinition.getPropertyValues().get("proxyTargetClass")).isEqualTo(false); + assertThat(proxyBeanDefinition.getPropertyValues().get("targetBeanName")).isEqualTo(ScopedProxyUtils.getTargetBeanName("myBean")); + + final RootBeanDefinition rootBeanDefinition = (RootBeanDefinition) proxyBeanDefinition; + assertThat(rootBeanDefinition.getQualifiers()).hasSize(1); + assertThat(rootBeanDefinition.hasQualifier("myQualifier")).isTrue(); + assertThat(rootBeanDefinition.getSource()).isEqualTo("theSource"); + } + + @Test + void createScopedProxyTargetCleansAutowireSettingsInTargetDefinition() { + final AbstractBeanDefinition targetDefinition = new GenericBeanDefinition(); + targetDefinition.setAutowireCandidate(true); + targetDefinition.setDefaultCandidate(true); + targetDefinition.setPrimary(true); + + final BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); + final BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false); + assertThat(proxyHolder).isNotNull(); + final BeanDefinition proxyBeanDefinition = proxyHolder.getBeanDefinition(); + assertThat(proxyBeanDefinition).isNotNull(); + + assertThat(targetDefinition.isAutowireCandidate()).isFalse(); + assertThat(targetDefinition.isDefaultCandidate()).isFalse(); + assertThat(targetDefinition.isPrimary()).isFalse(); + } } From d733023a2978a4725c6686645ad2dc05e640266f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Oct 2025 19:17:57 +0200 Subject: [PATCH 2/4] Consistently apply fallback attribute to proxy definition as well See gh-35627 --- .../aop/scope/ScopedProxyUtils.java | 4 +- .../aop/scope/ScopedProxyUtilsTests.java | 59 ++++++++++--------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java b/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java index 004012b0123..411a47fa5fb 100644 --- a/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/scope/ScopedProxyUtils.java @@ -81,14 +81,16 @@ public abstract class ScopedProxyUtils { // Copy autowire settings from original bean definition. proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate()); proxyDefinition.setPrimary(targetDefinition.isPrimary()); + proxyDefinition.setFallback(targetDefinition.isFallback()); if (targetDefinition instanceof AbstractBeanDefinition abd) { - proxyDefinition.copyQualifiersFrom(abd); proxyDefinition.setDefaultCandidate(abd.isDefaultCandidate()); + proxyDefinition.copyQualifiersFrom(abd); } // The target bean should be ignored in favor of the scoped proxy. targetDefinition.setAutowireCandidate(false); targetDefinition.setPrimary(false); + targetDefinition.setFallback(false); if (targetDefinition instanceof AbstractBeanDefinition abd) { abd.setDefaultCandidate(false); } diff --git a/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyUtilsTests.java b/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyUtilsTests.java index 164e6c85216..de5e76db2b7 100644 --- a/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyUtilsTests.java +++ b/spring-aop/src/test/java/org/springframework/aop/scope/ScopedProxyUtilsTests.java @@ -30,11 +30,11 @@ import org.springframework.beans.factory.support.SimpleBeanDefinitionRegistry; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - /** * Tests for {@link ScopedProxyUtils}. * * @author Sam Brannen + * @author Juergen Hoeller * @since 5.1.10 */ class ScopedProxyUtilsTests { @@ -63,58 +63,58 @@ class ScopedProxyUtilsTests { @Test void getOriginalBeanNameForNullTargetBean() { assertThatIllegalArgumentException() - .isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName(null)) - .withMessage("bean name 'null' does not refer to the target of a scoped proxy"); + .isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName(null)) + .withMessage("bean name 'null' does not refer to the target of a scoped proxy"); } @Test void getOriginalBeanNameForNonScopedTarget() { assertThatIllegalArgumentException() - .isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName("myBean")) - .withMessage("bean name 'myBean' does not refer to the target of a scoped proxy"); + .isThrownBy(() -> ScopedProxyUtils.getOriginalBeanName("myBean")) + .withMessage("bean name 'myBean' does not refer to the target of a scoped proxy"); } @Test - void createScopedProxyTargetOvertakesAutowireSettingsToProxyBeanDefinition() { - final AbstractBeanDefinition targetDefinition = new GenericBeanDefinition(); + void createScopedProxyTargetAppliesAutowireSettingsToProxyBeanDefinition() { + AbstractBeanDefinition targetDefinition = new GenericBeanDefinition(); // Opposite of defaults targetDefinition.setAutowireCandidate(false); targetDefinition.setDefaultCandidate(false); targetDefinition.setPrimary(true); + targetDefinition.setFallback(true); - final BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); - final BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false); - assertThat(proxyHolder).isNotNull(); - final BeanDefinition proxyBeanDefinition = proxyHolder.getBeanDefinition(); - assertThat(proxyBeanDefinition).isNotNull(); - assertThat(proxyBeanDefinition).isInstanceOf(RootBeanDefinition.class); + BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); + BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy( + new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false); + AbstractBeanDefinition proxyBeanDefinition = (AbstractBeanDefinition) proxyHolder.getBeanDefinition(); assertThat(proxyBeanDefinition.isAutowireCandidate()).isFalse(); + assertThat(proxyBeanDefinition.isDefaultCandidate()).isFalse(); assertThat(proxyBeanDefinition.isPrimary()).isTrue(); - assertThat(((RootBeanDefinition)proxyBeanDefinition).isDefaultCandidate()).isFalse(); + assertThat(proxyBeanDefinition.isFallback()).isTrue(); } @Test - void createScopedProxyTargetOvertakesBeanAttributesToProxyBeanDefinition() { - final GenericBeanDefinition targetDefinition = new GenericBeanDefinition(); + void createScopedProxyTargetAppliesBeanAttributesToProxyBeanDefinition() { + GenericBeanDefinition targetDefinition = new GenericBeanDefinition(); // Opposite of defaults targetDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); targetDefinition.setSource("theSource"); targetDefinition.addQualifier(new AutowireCandidateQualifier("myQualifier")); - final BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); - final BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false); - assertThat(proxyHolder).isNotNull(); - final BeanDefinition proxyBeanDefinition = proxyHolder.getBeanDefinition(); - assertThat(proxyBeanDefinition).isNotNull(); + BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); + BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy( + new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false); + BeanDefinition proxyBeanDefinition = proxyHolder.getBeanDefinition(); assertThat(proxyBeanDefinition.getRole()).isEqualTo(BeanDefinition.ROLE_INFRASTRUCTURE); assertThat(proxyBeanDefinition).isInstanceOf(RootBeanDefinition.class); assertThat(proxyBeanDefinition.getPropertyValues()).hasSize(2); assertThat(proxyBeanDefinition.getPropertyValues().get("proxyTargetClass")).isEqualTo(false); - assertThat(proxyBeanDefinition.getPropertyValues().get("targetBeanName")).isEqualTo(ScopedProxyUtils.getTargetBeanName("myBean")); + assertThat(proxyBeanDefinition.getPropertyValues().get("targetBeanName")).isEqualTo( + ScopedProxyUtils.getTargetBeanName("myBean")); - final RootBeanDefinition rootBeanDefinition = (RootBeanDefinition) proxyBeanDefinition; + RootBeanDefinition rootBeanDefinition = (RootBeanDefinition) proxyBeanDefinition; assertThat(rootBeanDefinition.getQualifiers()).hasSize(1); assertThat(rootBeanDefinition.hasQualifier("myQualifier")).isTrue(); assertThat(rootBeanDefinition.getSource()).isEqualTo("theSource"); @@ -122,19 +122,20 @@ class ScopedProxyUtilsTests { @Test void createScopedProxyTargetCleansAutowireSettingsInTargetDefinition() { - final AbstractBeanDefinition targetDefinition = new GenericBeanDefinition(); + AbstractBeanDefinition targetDefinition = new GenericBeanDefinition(); targetDefinition.setAutowireCandidate(true); targetDefinition.setDefaultCandidate(true); targetDefinition.setPrimary(true); + targetDefinition.setFallback(true); - final BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); - final BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false); - assertThat(proxyHolder).isNotNull(); - final BeanDefinition proxyBeanDefinition = proxyHolder.getBeanDefinition(); - assertThat(proxyBeanDefinition).isNotNull(); + BeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry(); + ScopedProxyUtils.createScopedProxy( + new BeanDefinitionHolder(targetDefinition, "myBean"), registry, false); assertThat(targetDefinition.isAutowireCandidate()).isFalse(); assertThat(targetDefinition.isDefaultCandidate()).isFalse(); assertThat(targetDefinition.isPrimary()).isFalse(); + assertThat(targetDefinition.isFallback()).isFalse(); } + } From 5033b9d3c5ab88dc33c0d35b096ccb58a5cc59d1 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Oct 2025 19:20:57 +0200 Subject: [PATCH 3/4] Reset by-type cache for Object after registering a new singleton Closes gh-35634 --- .../beans/factory/support/DefaultListableBeanFactory.java | 3 +++ .../beans/factory/DefaultListableBeanFactoryTests.java | 5 +++++ 2 files changed, 8 insertions(+) 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 32a66f3fac8..4799953a491 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 @@ -1481,7 +1481,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException { super.registerSingleton(beanName, singletonObject); + updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName)); + this.allBeanNamesByType.remove(Object.class); + this.singletonBeanNamesByType.remove(Object.class); } @Override 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 c85c9493216..a70dca808c4 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 @@ -87,6 +87,7 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.core.testfixture.io.SerializationTestUtils; import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; import org.springframework.util.StringValueResolver; import static org.assertj.core.api.Assertions.assertThat; @@ -3223,6 +3224,10 @@ class DefaultListableBeanFactoryTests { assertThat(lbf.getBeanNamesForType(DerivedTestBean.class)).containsExactly("bd1"); assertThat(lbf.getBeanNamesForType(NestedTestBean.class)).isSameAs(nestedBeanNames); assertThat(lbf.getBeanNamesForType(Object.class)).isSameAs(allBeanNames); + + lbf.registerSingleton("bd3", new Object()); + assertThat(lbf.getBeanNamesForType(NestedTestBean.class)).isSameAs(nestedBeanNames); + assertThat(lbf.getBeanNamesForType(Object.class)).containsExactly(StringUtils.addStringToArray(allBeanNames, "bd3")); } From f4438ce9e3c0f3c5c2d89a5c8ebf251849943355 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 14 Oct 2025 19:21:34 +0200 Subject: [PATCH 4/4] Upgrade to Reactor 2024.0.11 and Micrometer 1.14.12 Includes Netty 4.1.128 Closes gh-35638 Closes gh-35640 --- framework-platform/framework-platform.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework-platform/framework-platform.gradle b/framework-platform/framework-platform.gradle index cf682ee2a16..ade52d54443 100644 --- a/framework-platform/framework-platform.gradle +++ b/framework-platform/framework-platform.gradle @@ -8,10 +8,10 @@ javaPlatform { dependencies { api(platform("com.fasterxml.jackson:jackson-bom:2.18.4.1")) - api(platform("io.micrometer:micrometer-bom:1.14.11")) - api(platform("io.netty:netty-bom:4.1.127.Final")) + api(platform("io.micrometer:micrometer-bom:1.14.12")) + api(platform("io.netty:netty-bom:4.1.128.Final")) api(platform("io.netty:netty5-bom:5.0.0.Alpha5")) - api(platform("io.projectreactor:reactor-bom:2024.0.10")) + api(platform("io.projectreactor:reactor-bom:2024.0.11")) api(platform("io.rsocket:rsocket-bom:1.1.5")) api(platform("org.apache.groovy:groovy-bom:4.0.28")) api(platform("org.apache.logging.log4j:log4j-bom:2.21.1"))