From 80e7ee321ef22a80b68d4e214fb475ab6a1fe7e5 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 6 Oct 2025 14:08:03 +0200 Subject: [PATCH] Mark bootstrap thread for entire finishBeanFactoryInitialization phase Closes gh-35398 --- .../ConfigurableListableBeanFactory.java | 13 +++++ .../support/DefaultListableBeanFactory.java | 9 ++- .../support/AbstractApplicationContext.java | 3 + .../annotation/BackgroundBootstrapTests.java | 55 +++++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableListableBeanFactory.java index 88be1a60c5..a696d6d5e0 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConfigurableListableBeanFactory.java @@ -152,6 +152,18 @@ public interface ConfigurableListableBeanFactory */ boolean isConfigurationFrozen(); + /** + * Mark current thread as main bootstrap thread for singleton instantiation, + * with lenient bootstrap locking applying for background threads. + *

Any such marker is to be removed at the end of the managed bootstrap in + * {@link #preInstantiateSingletons()}. + * @since 6.2.12 + * @see #setBootstrapExecutor + * @see #preInstantiateSingletons() + */ + default void prepareSingletonBootstrap() { + } + /** * Ensure that all non-lazy-init singletons are instantiated, also considering * {@link org.springframework.beans.factory.FactoryBean FactoryBeans}. @@ -159,6 +171,7 @@ public interface ConfigurableListableBeanFactory * @throws BeansException if one of the singleton beans could not be created. * Note: This may have left the factory with some beans already initialized! * Call {@link #destroySingletons()} for full cleanup in this case. + * @see #prepareSingletonBootstrap() * @see #destroySingletons() */ void preInstantiateSingletons() throws BeansException; 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 80e24655bf..32a66f3fac 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 @@ -1102,6 +1102,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto return null; } + @Override + public void prepareSingletonBootstrap() { + this.mainThreadPrefix = getThreadNamePrefix(); + } + @Override public void preInstantiateSingletons() throws BeansException { if (logger.isTraceEnabled()) { @@ -1114,7 +1119,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto // Trigger initialization of all non-lazy singleton beans... this.preInstantiationThread.set(PreInstantiation.MAIN); - this.mainThreadPrefix = getThreadNamePrefix(); + if (this.mainThreadPrefix == null) { + this.mainThreadPrefix = getThreadNamePrefix(); + } try { List> futures = new ArrayList<>(); for (String beanName : beanNames) { diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index ee8fd7ddda..8c4ff89759 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -936,6 +936,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader */ @SuppressWarnings("unchecked") protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { + // Mark current thread for singleton instantiation with applied bootstrap locking. + beanFactory.prepareSingletonBootstrap(); + // Initialize bootstrap executor for this context. if (beanFactory.containsBean(BOOTSTRAP_EXECUTOR_BEAN_NAME) && beanFactory.isTypeMatch(BOOTSTRAP_EXECUTOR_BEAN_NAME, Executor.class)) { diff --git a/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java b/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java index de620578c1..91a9e726fd 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java @@ -31,6 +31,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.weaving.LoadTimeWeaverAware; import org.springframework.core.SpringProperties; import org.springframework.core.testfixture.EnabledForTestGroups; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @@ -68,6 +69,16 @@ class BackgroundBootstrapTests { ctx.close(); } + @Test + @Timeout(10) + @EnabledForTestGroups(LONG_RUNNING) + void bootstrapWithLoadTimeWeaverAware() { + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(LoadTimeWeaverAwareBeanConfig.class); + ctx.getBean("testBean1", TestBean.class); + ctx.getBean("testBean2", TestBean.class); + ctx.close(); + } + @Test @Timeout(10) @EnabledForTestGroups(LONG_RUNNING) @@ -266,6 +277,50 @@ class BackgroundBootstrapTests { } + @Configuration(proxyBeanMethods = false) + static class LoadTimeWeaverAwareBeanConfig { + + @Bean + LoadTimeWeaverAware loadTimeWeaverAware(ObjectProvider testBean1) { + Thread thread = new Thread(testBean1::getObject); + thread.start(); + try { + thread.join(); + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + return (loadTimeWeaver -> {}); + } + + @Bean + public TestBean testBean1(TestBean testBean2) { + return new TestBean(testBean2); + } + + @Bean @Lazy + public FactoryBean testBean2() { + try { + Thread.sleep(2000); + } + catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + TestBean testBean = new TestBean(); + return new FactoryBean<>() { + @Override + public TestBean getObject() { + return testBean; + } + @Override + public Class getObjectType() { + return testBean.getClass(); + } + }; + } + } + + @Configuration(proxyBeanMethods = false) static class StrictLockingBeanConfig {