Mark bootstrap thread for entire finishBeanFactoryInitialization phase
Backport Bot / build (push) Has been cancelled Details
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Has been cancelled Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:ubuntu-latest name:Linux]) (push) Has been cancelled Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:ubuntu-latest name:Linux]) (push) Has been cancelled Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:25], map[id:ubuntu-latest name:Linux]) (push) Has been cancelled Details
Deploy Docs / Dispatch docs deployment (push) Has been cancelled Details
Build and Deploy Snapshot / Verify (push) Has been cancelled Details

Closes gh-35398
This commit is contained in:
Juergen Hoeller 2025-10-06 14:08:03 +02:00
parent ecd3dd8883
commit 80e7ee321e
4 changed files with 79 additions and 1 deletions

View File

@ -152,6 +152,18 @@ public interface ConfigurableListableBeanFactory
*/ */
boolean isConfigurationFrozen(); boolean isConfigurationFrozen();
/**
* Mark current thread as main bootstrap thread for singleton instantiation,
* with lenient bootstrap locking applying for background threads.
* <p>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 * Ensure that all non-lazy-init singletons are instantiated, also considering
* {@link org.springframework.beans.factory.FactoryBean FactoryBeans}. * {@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. * @throws BeansException if one of the singleton beans could not be created.
* Note: This may have left the factory with some beans already initialized! * Note: This may have left the factory with some beans already initialized!
* Call {@link #destroySingletons()} for full cleanup in this case. * Call {@link #destroySingletons()} for full cleanup in this case.
* @see #prepareSingletonBootstrap()
* @see #destroySingletons() * @see #destroySingletons()
*/ */
void preInstantiateSingletons() throws BeansException; void preInstantiateSingletons() throws BeansException;

View File

@ -1102,6 +1102,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
return null; return null;
} }
@Override
public void prepareSingletonBootstrap() {
this.mainThreadPrefix = getThreadNamePrefix();
}
@Override @Override
public void preInstantiateSingletons() throws BeansException { public void preInstantiateSingletons() throws BeansException {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
@ -1114,7 +1119,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
// Trigger initialization of all non-lazy singleton beans... // Trigger initialization of all non-lazy singleton beans...
this.preInstantiationThread.set(PreInstantiation.MAIN); this.preInstantiationThread.set(PreInstantiation.MAIN);
if (this.mainThreadPrefix == null) {
this.mainThreadPrefix = getThreadNamePrefix(); this.mainThreadPrefix = getThreadNamePrefix();
}
try { try {
List<CompletableFuture<?>> futures = new ArrayList<>(); List<CompletableFuture<?>> futures = new ArrayList<>();
for (String beanName : beanNames) { for (String beanName : beanNames) {

View File

@ -936,6 +936,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) { protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Mark current thread for singleton instantiation with applied bootstrap locking.
beanFactory.prepareSingletonBootstrap();
// Initialize bootstrap executor for this context. // Initialize bootstrap executor for this context.
if (beanFactory.containsBean(BOOTSTRAP_EXECUTOR_BEAN_NAME) && if (beanFactory.containsBean(BOOTSTRAP_EXECUTOR_BEAN_NAME) &&
beanFactory.isTypeMatch(BOOTSTRAP_EXECUTOR_BEAN_NAME, Executor.class)) { beanFactory.isTypeMatch(BOOTSTRAP_EXECUTOR_BEAN_NAME, Executor.class)) {

View File

@ -31,6 +31,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.weaving.LoadTimeWeaverAware;
import org.springframework.core.SpringProperties; import org.springframework.core.SpringProperties;
import org.springframework.core.testfixture.EnabledForTestGroups; import org.springframework.core.testfixture.EnabledForTestGroups;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@ -68,6 +69,16 @@ class BackgroundBootstrapTests {
ctx.close(); 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 @Test
@Timeout(10) @Timeout(10)
@EnabledForTestGroups(LONG_RUNNING) @EnabledForTestGroups(LONG_RUNNING)
@ -266,6 +277,50 @@ class BackgroundBootstrapTests {
} }
@Configuration(proxyBeanMethods = false)
static class LoadTimeWeaverAwareBeanConfig {
@Bean
LoadTimeWeaverAware loadTimeWeaverAware(ObjectProvider<TestBean> 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<TestBean> 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) @Configuration(proxyBeanMethods = false)
static class StrictLockingBeanConfig { static class StrictLockingBeanConfig {