Skip to content

Commit fa168ca

Browse files
committed
Revise FactoryBean locking behavior for strict/lenient consistency
After the bootstrap phase (and with spring.locking.strict=true during the bootstrap phase), getSingletonFactoryBeanForTypeCheck always locks. In a background bootstrap thread, it never locks. Otherwise, it tries locking and explicitly resolves the bean class for subsequent type-based resolution (even for a component-scanned class) when it fails to acquire the lock. Furthermore, getObjectFromFactoryBean follows the same locking algorithm for post-processing. Closes gh-34902
1 parent 3c228a5 commit fa168ca

File tree

4 files changed

+53
-18
lines changed

4 files changed

+53
-18
lines changed

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

+11-3
Original file line numberDiff line numberDiff line change
@@ -997,9 +997,17 @@ protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd,
997997
*/
998998
@Nullable
999999
private FactoryBean<?> getSingletonFactoryBeanForTypeCheck(String beanName, RootBeanDefinition mbd) {
1000-
boolean locked = this.singletonLock.tryLock();
1001-
if (!locked) {
1002-
return null;
1000+
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
1001+
if (lockFlag == null) {
1002+
this.singletonLock.lock();
1003+
}
1004+
else {
1005+
boolean locked = (lockFlag && this.singletonLock.tryLock());
1006+
if (!locked) {
1007+
// Avoid shortcut FactoryBean instance but allow for subsequent type-based resolution.
1008+
resolveBeanClass(mbd, beanName);
1009+
return null;
1010+
}
10031011
}
10041012

10051013
try {

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -271,13 +271,15 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
271271
// Fallback as of 6.2: process given singleton bean outside of singleton lock.
272272
// Thread-safe exposure is still guaranteed, there is just a risk of collisions
273273
// when triggering creation of other beans as dependencies of the current bean.
274-
if (logger.isInfoEnabled()) {
275-
logger.info("Obtaining singleton bean '" + beanName + "' in thread \"" +
276-
Thread.currentThread().getName() + "\" while other thread holds " +
277-
"singleton lock for other beans " + this.singletonsCurrentlyInCreation);
278-
}
279274
this.lenientCreationLock.lock();
280275
try {
276+
if (logger.isInfoEnabled()) {
277+
Set<String> lockedBeans = new HashSet<>(this.singletonsCurrentlyInCreation);
278+
lockedBeans.removeAll(this.singletonsInLenientCreation);
279+
logger.info("Obtaining singleton bean '" + beanName + "' in thread \"" +
280+
currentThread.getName() + "\" while other thread holds singleton " +
281+
"lock for other beans " + lockedBeans);
282+
}
281283
this.singletonsInLenientCreation.add(beanName);
282284
}
283285
finally {

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

+22-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -118,7 +118,15 @@ protected Object getCachedObjectForFactoryBean(String beanName) {
118118
*/
119119
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
120120
if (factory.isSingleton() && containsSingleton(beanName)) {
121-
this.singletonLock.lock();
121+
Boolean lockFlag = isCurrentThreadAllowedToHoldSingletonLock();
122+
boolean locked;
123+
if (lockFlag == null) {
124+
this.singletonLock.lock();
125+
locked = true;
126+
}
127+
else {
128+
locked = (lockFlag && this.singletonLock.tryLock());
129+
}
122130
try {
123131
Object object = this.factoryBeanObjectCache.get(beanName);
124132
if (object == null) {
@@ -131,11 +139,13 @@ protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanNam
131139
}
132140
else {
133141
if (shouldPostProcess) {
134-
if (isSingletonCurrentlyInCreation(beanName)) {
135-
// Temporarily return non-post-processed object, not storing it yet
136-
return object;
142+
if (locked) {
143+
if (isSingletonCurrentlyInCreation(beanName)) {
144+
// Temporarily return non-post-processed object, not storing it yet
145+
return object;
146+
}
147+
beforeSingletonCreation(beanName);
137148
}
138-
beforeSingletonCreation(beanName);
139149
try {
140150
object = postProcessObjectFromFactoryBean(object, beanName);
141151
}
@@ -144,7 +154,9 @@ protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanNam
144154
"Post-processing of FactoryBean's singleton object failed", ex);
145155
}
146156
finally {
147-
afterSingletonCreation(beanName);
157+
if (locked) {
158+
afterSingletonCreation(beanName);
159+
}
148160
}
149161
}
150162
if (containsSingleton(beanName)) {
@@ -155,7 +167,9 @@ protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanNam
155167
return object;
156168
}
157169
finally {
158-
this.singletonLock.unlock();
170+
if (locked) {
171+
this.singletonLock.unlock();
172+
}
159173
}
160174
}
161175
else {

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

+13-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import org.springframework.beans.factory.BeanCreationException;
2626
import org.springframework.beans.factory.BeanCurrentlyInCreationException;
27+
import org.springframework.beans.factory.FactoryBean;
2728
import org.springframework.beans.factory.ObjectProvider;
2829
import org.springframework.beans.factory.UnsatisfiedDependencyException;
2930
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -243,14 +244,24 @@ public TestBean testBean3(TestBean testBean4) {
243244
}
244245

245246
@Bean
246-
public TestBean testBean4() {
247+
public FactoryBean<TestBean> testBean4() {
247248
try {
248249
Thread.sleep(2000);
249250
}
250251
catch (InterruptedException ex) {
251252
Thread.currentThread().interrupt();
252253
}
253-
return new TestBean();
254+
TestBean testBean = new TestBean();
255+
return new FactoryBean<>() {
256+
@Override
257+
public TestBean getObject() {
258+
return testBean;
259+
}
260+
@Override
261+
public Class<?> getObjectType() {
262+
return testBean.getClass();
263+
}
264+
};
254265
}
255266
}
256267

0 commit comments

Comments
 (0)