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 {