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 355e73d5abb..0e444f567bc 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 @@ -76,6 +76,7 @@ import org.springframework.core.NamedThreadLocal; import org.springframework.core.OrderComparator; import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; +import org.springframework.core.SpringProperties; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations.SearchStrategy; @@ -128,6 +129,17 @@ import org.springframework.util.StringUtils; public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { + /** + * System property that instructs Spring to enforce string locking during bean creation, + * rather than the mix of strict and lenient locking that 6.2 applies by default. Setting + * this flag to "true" restores 6.1.x style locking in the entire pre-instantiation phase. + * @since 6.2.6 + * @see #preInstantiateSingletons() + */ + public static final String STRICT_LOCKING_PROPERTY_NAME = "spring.locking.strict"; + + private static final boolean lenientLockingAllowed = !SpringProperties.getFlag(STRICT_LOCKING_PROPERTY_NAME); + @Nullable private static Class jakartaInjectProviderClass; @@ -1031,7 +1043,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto @Override @Nullable protected Boolean isCurrentThreadAllowedToHoldSingletonLock() { - return (this.preInstantiationPhase ? this.preInstantiationThread.get() != PreInstantiation.BACKGROUND : null); + return (lenientLockingAllowed && this.preInstantiationPhase ? + this.preInstantiationThread.get() != PreInstantiation.BACKGROUND : null); } @Override 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 dda782ce897..913cc863d04 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 @@ -20,11 +20,15 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.springframework.beans.factory.ObjectProvider; +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.core.SpringProperties; import org.springframework.core.testfixture.EnabledForTestGroups; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.context.annotation.Bean.Bootstrap.BACKGROUND; import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING; @@ -56,6 +60,21 @@ class BackgroundBootstrapTests { ctx.close(); } + @Test + @Timeout(5) + @EnabledForTestGroups(LONG_RUNNING) + void bootstrapWithStrictLockingThread() { + SpringProperties.setFlag(DefaultListableBeanFactory.STRICT_LOCKING_PROPERTY_NAME); + try { + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(StrictLockingBeanConfig.class); + assertThat(ctx.getBean("testBean2", TestBean.class).getSpouse()).isSameAs(ctx.getBean("testBean1")); + ctx.close(); + } + finally { + SpringProperties.setProperty(DefaultListableBeanFactory.STRICT_LOCKING_PROPERTY_NAME, null); + } + } + @Test @Timeout(5) @EnabledForTestGroups(LONG_RUNNING) @@ -148,6 +167,28 @@ class BackgroundBootstrapTests { } + @Configuration(proxyBeanMethods = false) + static class StrictLockingBeanConfig { + + @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(ConfigurableListableBeanFactory beanFactory) { + return new TestBean((TestBean) beanFactory.getSingleton("testBean1")); + } + } + + @Configuration(proxyBeanMethods = false) static class CircularReferenceBeanConfig { diff --git a/spring-core/src/main/java/org/springframework/core/SpringProperties.java b/spring-core/src/main/java/org/springframework/core/SpringProperties.java index 299ec0d987b..fbb94deba25 100644 --- a/spring-core/src/main/java/org/springframework/core/SpringProperties.java +++ b/spring-core/src/main/java/org/springframework/core/SpringProperties.java @@ -39,6 +39,7 @@ import org.springframework.lang.Nullable; * @author Juergen Hoeller * @since 3.2.7 * @see org.springframework.beans.StandardBeanInfoFactory#IGNORE_BEANINFO_PROPERTY_NAME + * @see org.springframework.beans.factory.support.DefaultListableBeanFactory#STRICT_LOCKING_PROPERTY_NAME * @see org.springframework.core.env.AbstractEnvironment#IGNORE_GETENV_PROPERTY_NAME * @see org.springframework.expression.spel.SpelParserConfiguration#SPRING_EXPRESSION_COMPILER_MODE_PROPERTY_NAME * @see org.springframework.jdbc.core.StatementCreatorUtils#IGNORE_GETPARAMETERTYPE_PROPERTY_NAME