diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java index 88f58ffee8..fd88d2c44c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java @@ -253,7 +253,6 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock(); boolean acquireLock = !Boolean.FALSE.equals(lockFlag); boolean locked = (acquireLock && this.singletonLock.tryLock()); - boolean lenient = false; try { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { @@ -268,7 +267,6 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements Thread.currentThread().getName() + "\" while other thread holds " + "singleton lock for other beans " + this.singletonsCurrentlyInCreation); } - lenient = true; this.lenientCreationLock.lock(); try { this.singletonsInLenientCreation.add(beanName); @@ -329,7 +327,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements // Try late locking for waiting on specific bean to be finished. this.singletonLock.lock(); locked = true; - // Singleton object should have appeared in the meantime. + // Lock-created singleton object should have appeared in the meantime. singletonObject = this.singletonObjects.get(beanName); if (singletonObject != null) { return singletonObject; @@ -343,8 +341,12 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements this.suppressedExceptions = new LinkedHashSet<>(); } try { - singletonObject = singletonFactory.getObject(); - newSingleton = true; + // Leniently created singleton object could have appeared in the meantime. + singletonObject = this.singletonObjects.get(beanName); + if (singletonObject == null) { + singletonObject = singletonFactory.getObject(); + newSingleton = true; + } } catch (IllegalStateException ex) { // Has the singleton object implicitly appeared in the meantime -> @@ -388,15 +390,13 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements if (locked) { this.singletonLock.unlock(); } - if (lenient) { - this.lenientCreationLock.lock(); - try { - this.singletonsInLenientCreation.remove(beanName); - this.lenientCreationFinished.signalAll(); - } - finally { - this.lenientCreationLock.unlock(); - } + this.lenientCreationLock.lock(); + try { + this.singletonsInLenientCreation.remove(beanName); + this.lenientCreationFinished.signalAll(); + } + finally { + this.lenientCreationLock.unlock(); } } } 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 c95f930d92..dda782ce89 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 @@ -56,6 +56,16 @@ class BackgroundBootstrapTests { ctx.close(); } + @Test + @Timeout(5) + @EnabledForTestGroups(LONG_RUNNING) + void bootstrapWithCircularReference() { + ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CircularReferenceBeanConfig.class); + ctx.getBean("testBean1", TestBean.class); + ctx.getBean("testBean2", TestBean.class); + ctx.close(); + } + @Test @Timeout(5) @EnabledForTestGroups(LONG_RUNNING) @@ -138,6 +148,34 @@ class BackgroundBootstrapTests { } + @Configuration(proxyBeanMethods = false) + static class CircularReferenceBeanConfig { + + @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(TestBean testBean1) { + try { + Thread.sleep(2000); + } + catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + return new TestBean(); + } + } + + @Configuration(proxyBeanMethods = false) static class CustomExecutorBeanConfig {