Skip to content

Commit ac7c7ff

Browse files
committed
Merge branch '6.2.x'
# Conflicts: # spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
2 parents 51c084f + 75e5a75 commit ac7c7ff

File tree

3 files changed

+68
-5
lines changed

3 files changed

+68
-5
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,6 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
138138
*/
139139
public static final String STRICT_LOCKING_PROPERTY_NAME = "spring.locking.strict";
140140

141-
private static final boolean lenientLockingAllowed = !SpringProperties.getFlag(STRICT_LOCKING_PROPERTY_NAME);
142-
143141
private static @Nullable Class<?> jakartaInjectProviderClass;
144142

145143
static {
@@ -158,6 +156,9 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
158156
private static final Map<String, Reference<DefaultListableBeanFactory>> serializableFactories =
159157
new ConcurrentHashMap<>(8);
160158

159+
/** Whether lenient locking is allowed in this factory. */
160+
private final boolean lenientLockingAllowed = !SpringProperties.getFlag(STRICT_LOCKING_PROPERTY_NAME);
161+
161162
/** Optional id for this factory, for serialization purposes. */
162163
private @Nullable String serializationId;
163164

@@ -1035,7 +1036,7 @@ protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName
10351036

10361037
@Override
10371038
protected @Nullable Boolean isCurrentThreadAllowedToHoldSingletonLock() {
1038-
return (lenientLockingAllowed && this.preInstantiationPhase ?
1039+
return (this.lenientLockingAllowed && this.preInstantiationPhase ?
10391040
this.preInstantiationThread.get() != PreInstantiation.BACKGROUND : null);
10401041
}
10411042

spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultSingletonBeanRegistry.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
111111
/** Names of beans that are currently in lenient creation. */
112112
private final Set<String> singletonsInLenientCreation = new HashSet<>();
113113

114+
/** Map from bean name to actual creation thread for leniently created beans. */
115+
private final Map<String, Thread> lenientCreationThreads = new ConcurrentHashMap<>();
116+
114117
/** Flag that indicates whether we're currently within destroySingletons. */
115118
private volatile boolean singletonsCurrentlyInDestruction = false;
116119

@@ -305,6 +308,9 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
305308
if (!this.singletonsInLenientCreation.contains(beanName)) {
306309
break;
307310
}
311+
if (this.lenientCreationThreads.get(beanName) == Thread.currentThread()) {
312+
throw ex;
313+
}
308314
try {
309315
this.lenientCreationFinished.await();
310316
}
@@ -342,7 +348,18 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
342348
// Leniently created singleton object could have appeared in the meantime.
343349
singletonObject = this.singletonObjects.get(beanName);
344350
if (singletonObject == null) {
345-
singletonObject = singletonFactory.getObject();
351+
if (locked) {
352+
singletonObject = singletonFactory.getObject();
353+
}
354+
else {
355+
this.lenientCreationThreads.put(beanName, Thread.currentThread());
356+
try {
357+
singletonObject = singletonFactory.getObject();
358+
}
359+
finally {
360+
this.lenientCreationThreads.remove(beanName);
361+
}
362+
}
346363
newSingleton = true;
347364
}
348365
}

spring-context/src/test/java/org/springframework/context/annotation/BackgroundBootstrapTests.java

+46-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import org.junit.jupiter.api.Test;
2020
import org.junit.jupiter.api.Timeout;
2121

22+
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
2223
import org.springframework.beans.factory.ObjectProvider;
24+
import org.springframework.beans.factory.UnsatisfiedDependencyException;
2325
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2426
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
2527
import org.springframework.beans.testfixture.beans.TestBean;
@@ -29,6 +31,7 @@
2931
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
3032

3133
import static org.assertj.core.api.Assertions.assertThat;
34+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
3235
import static org.springframework.context.annotation.Bean.Bootstrap.BACKGROUND;
3336
import static org.springframework.core.testfixture.TestGroup.LONG_RUNNING;
3437

@@ -85,6 +88,15 @@ void bootstrapWithCircularReference() {
8588
ctx.close();
8689
}
8790

91+
@Test
92+
@Timeout(5)
93+
@EnabledForTestGroups(LONG_RUNNING)
94+
void bootstrapWithCircularReferenceInSameThread() {
95+
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
96+
.isThrownBy(() -> new AnnotationConfigApplicationContext(CircularReferenceInSameThreadBeanConfig.class))
97+
.withRootCauseInstanceOf(BeanCurrentlyInCreationException.class);
98+
}
99+
88100
@Test
89101
@Timeout(5)
90102
@EnabledForTestGroups(LONG_RUNNING)
@@ -179,7 +191,7 @@ public TestBean testBean1(ObjectProvider<TestBean> testBean2) {
179191
catch (InterruptedException ex) {
180192
throw new RuntimeException(ex);
181193
}
182-
return new TestBean();
194+
return new TestBean("testBean1");
183195
}
184196

185197
@Bean
@@ -217,6 +229,39 @@ public TestBean testBean2(TestBean testBean1) {
217229
}
218230

219231

232+
@Configuration(proxyBeanMethods = false)
233+
static class CircularReferenceInSameThreadBeanConfig {
234+
235+
@Bean
236+
public TestBean testBean1(ObjectProvider<TestBean> testBean2) {
237+
new Thread(testBean2::getObject).start();
238+
try {
239+
Thread.sleep(1000);
240+
}
241+
catch (InterruptedException ex) {
242+
throw new RuntimeException(ex);
243+
}
244+
return new TestBean();
245+
}
246+
247+
@Bean
248+
public TestBean testBean2(TestBean testBean3) {
249+
try {
250+
Thread.sleep(2000);
251+
}
252+
catch (InterruptedException ex) {
253+
throw new RuntimeException(ex);
254+
}
255+
return new TestBean();
256+
}
257+
258+
@Bean
259+
public TestBean testBean3(TestBean testBean2) {
260+
return new TestBean();
261+
}
262+
}
263+
264+
220265
@Configuration(proxyBeanMethods = false)
221266
static class CustomExecutorBeanConfig {
222267

0 commit comments

Comments
 (0)