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 48bbeec50f07..94e3855130c4 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 @@ -1089,8 +1089,15 @@ else if (logger.isInfoEnabled()) { "without bootstrap executor configured - falling back to mainline initialization"); } } + if (!mbd.isLazyInit()) { - instantiateSingleton(beanName); + try { + instantiateSingleton(beanName); + } + catch (BeanCurrentlyInCreationException ex) { + logger.info("Bean '" + beanName + "' marked for pre-instantiation (not lazy-init) " + + "but currently initialized by other thread - skipping it in mainline thread"); + } } return null; } 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 8ea9ba0db194..3a0dc34c28b3 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 @@ -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. @@ -19,11 +19,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.springframework.beans.factory.BeanCurrentlyInCreationException; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.testfixture.beans.TestBean; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.testfixture.EnabledForTestGroups; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.springframework.context.annotation.Bean.Bootstrap.BACKGROUND; import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING; @@ -33,6 +36,17 @@ */ class BackgroundBootstrapTests { + @Test + @Timeout(5) + @EnabledForTestGroups(LONG_RUNNING) + void bootstrapWithUnmanagedThread() { + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(UnmanagedThreadBeanConfig.class); + ctx.getBean("testBean1", TestBean.class); + assertThatExceptionOfType(BeanCurrentlyInCreationException.class).isThrownBy( // late - not during refresh + () -> ctx.getBean("testBean2", TestBean.class)); + ctx.close(); + } + @Test @Timeout(5) @EnabledForTestGroups(LONG_RUNNING) @@ -45,7 +59,35 @@ void bootstrapWithCustomExecutor() { } - @Configuration + @Configuration(proxyBeanMethods = false) + static class UnmanagedThreadBeanConfig { + + @Bean + public TestBean testBean1(ObjectProvider testBean2) { + new Thread(testBean2::getObject).start(); + try { + Thread.sleep(1000); + } + catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + return new TestBean(); + } + + @Bean + public TestBean testBean2() { + try { + Thread.sleep(2000); + } + catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + return new TestBean(); + } + } + + + @Configuration(proxyBeanMethods = false) static class CustomExecutorBeanConfig { @Bean @@ -58,7 +100,7 @@ public ThreadPoolTaskExecutor bootstrapExecutor() { } @Bean(bootstrap = BACKGROUND) @DependsOn("testBean3") - public TestBean testBean1(TestBean testBean3) throws InterruptedException{ + public TestBean testBean1(TestBean testBean3) throws InterruptedException { Thread.sleep(3000); return new TestBean(); }