Merge branch '6.2.x'

# Conflicts:
#	spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java
#	spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java
This commit is contained in:
Juergen Hoeller
2025-04-10 18:37:07 +02:00
7 changed files with 180 additions and 36 deletions

View File

@@ -133,6 +133,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
* System property that instructs Spring to enforce strict 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.
* <p>By default, the factory infers strict locking from the encountered thread names:
* If additional threads have names that match the thread prefix of the main bootstrap thread,
* they are considered external (multiple external bootstrap threads calling into the factory)
* and therefore have strict locking applied to them. This inference can be turned off through
* explicitly setting this flag to "false" rather than leaving it unspecified.
* @since 6.2.6
* @see #preInstantiateSingletons()
*/
@@ -156,8 +161,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
private static final Map<String, Reference<DefaultListableBeanFactory>> serializableFactories =
new ConcurrentHashMap<>(8);
/** Whether lenient locking is allowed in this factory. */
private final boolean lenientLockingAllowed = !SpringProperties.getFlag(STRICT_LOCKING_PROPERTY_NAME);
/** Whether strict locking is enforced or relaxed in this factory. */
private @Nullable final Boolean strictLocking = SpringProperties.checkFlag(STRICT_LOCKING_PROPERTY_NAME);
/** Optional id for this factory, for serialization purposes. */
private @Nullable String serializationId;
@@ -208,6 +213,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
private volatile boolean preInstantiationPhase;
private @Nullable volatile String mainThreadPrefix;
private final NamedThreadLocal<PreInstantiation> preInstantiationThread =
new NamedThreadLocal<>("Pre-instantiation thread marker");
@@ -1030,7 +1037,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
}
else {
// Bean intended to be initialized in main bootstrap thread
// Bean intended to be initialized in main bootstrap thread.
if (this.preInstantiationThread.get() == PreInstantiation.BACKGROUND) {
throw new BeanCurrentlyInCreationException(beanName, "Bean marked for mainline initialization " +
"but requested in background thread - enforce early instantiation in mainline thread " +
@@ -1041,8 +1048,28 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
@Override
protected @Nullable Boolean isCurrentThreadAllowedToHoldSingletonLock() {
return (this.lenientLockingAllowed && this.preInstantiationPhase ?
this.preInstantiationThread.get() != PreInstantiation.BACKGROUND : null);
if (this.preInstantiationPhase) {
// We only differentiate in the preInstantiateSingletons phase.
PreInstantiation preInstantiation = this.preInstantiationThread.get();
if (preInstantiation != null) {
// A Spring-managed thread:
// MAIN is allowed to lock (true) or even forced to lock (null),
// BACKGROUND is never allowed to lock (false).
return switch (preInstantiation) {
case MAIN -> (Boolean.TRUE.equals(this.strictLocking) ? null : true);
case BACKGROUND -> false;
};
}
if (Boolean.FALSE.equals(this.strictLocking) ||
(this.strictLocking == null && !getThreadNamePrefix().equals(this.mainThreadPrefix))) {
// An unmanaged thread (assumed to be application-internal) with lenient locking,
// and not part of the same thread pool that provided the main bootstrap thread
// (excluding scenarios where we are hit by multiple external bootstrap threads).
return true;
}
}
// Traditional behavior: forced to always hold a full lock.
return null;
}
@Override
@@ -1060,6 +1087,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
this.preInstantiationPhase = true;
this.preInstantiationThread.set(PreInstantiation.MAIN);
this.mainThreadPrefix = getThreadNamePrefix();
try {
for (String beanName : beanNames) {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
@@ -1072,6 +1100,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
}
finally {
this.mainThreadPrefix = null;
this.preInstantiationThread.remove();
this.preInstantiationPhase = false;
}
@@ -1166,6 +1195,12 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
}
}
private static String getThreadNamePrefix() {
String name = Thread.currentThread().getName();
int numberSeparator = name.lastIndexOf('-');
return (numberSeparator >= 0 ? name.substring(0, numberSeparator) : name);
}
//---------------------------------------------------------------------
// Implementation of BeanDefinitionRegistry interface

View File

@@ -270,7 +270,7 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
// Thread-safe exposure is still guaranteed, there is just a risk of collisions
// when triggering creation of other beans as dependencies of the current bean.
if (logger.isInfoEnabled()) {
logger.info("Creating singleton bean '" + beanName + "' in thread \"" +
logger.info("Obtaining singleton bean '" + beanName + "' in thread \"" +
Thread.currentThread().getName() + "\" while other thread holds " +
"singleton lock for other beans " + this.singletonsCurrentlyInCreation);
}
@@ -441,12 +441,16 @@ public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements
/**
* Determine whether the current thread is allowed to hold the singleton lock.
* <p>By default, any thread may acquire and hold the singleton lock, except
* background threads from {@link DefaultListableBeanFactory#setBootstrapExecutor}.
* @return {@code false} if the current thread is explicitly not allowed to hold
* the lock, {@code true} if it is explicitly allowed to hold the lock but also
* accepts lenient fallback behavior, or {@code null} if there is no specific
* indication (traditional behavior: always holding a full lock)
* <p>By default, all threads are forced to hold a full lock through {@code null}.
* {@link DefaultListableBeanFactory} overrides this to specifically handle its
* threads during the pre-instantiation phase: {@code true} for the main thread,
* {@code false} for managed background threads, and configuration-dependent
* behavior for unmanaged threads.
* @return {@code true} if the current thread is explicitly allowed to hold the
* lock but also accepts lenient fallback behavior, {@code false} if it is
* explicitly not allowed to hold the lock and therefore forced to use lenient
* fallback behavior, or {@code null} if there is no specific indication
* (traditional behavior: forced to always hold a full lock)
* @since 6.2
*/
protected @Nullable Boolean isCurrentThreadAllowedToHoldSingletonLock() {

View File

@@ -77,7 +77,6 @@ import org.springframework.beans.testfixture.beans.NestedTestBean;
import org.springframework.beans.testfixture.beans.SideEffectBean;
import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.beans.testfixture.beans.factory.DummyFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
@@ -1419,7 +1418,6 @@ class DefaultListableBeanFactoryTests {
lbf.registerBeanDefinition("rod", bd);
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
lbf.registerBeanDefinition("rod2", bd2);
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> lbf.autowire(ConstructorDependency.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false))
@@ -1490,7 +1488,6 @@ class DefaultListableBeanFactoryTests {
RootBeanDefinition bd = new RootBeanDefinition(ConstructorDependenciesBean.class);
bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
lbf.registerBeanDefinition("bean", bd);
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
ConstructorDependenciesBean bean = lbf.getBean(ConstructorDependenciesBean.class);
Object spouse1 = lbf.getBean("spouse1");
@@ -1508,7 +1505,6 @@ class DefaultListableBeanFactoryTests {
bd.setAttribute(GenericBeanDefinition.PREFERRED_CONSTRUCTORS_ATTRIBUTE,
ConstructorDependenciesBean.class.getConstructors());
lbf.registerBeanDefinition("bean", bd);
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
ConstructorDependenciesBean bean = lbf.getBean(ConstructorDependenciesBean.class);
Object spouse1 = lbf.getBean("spouse1");
@@ -1526,7 +1522,6 @@ class DefaultListableBeanFactoryTests {
bd.setAttribute(GenericBeanDefinition.PREFERRED_CONSTRUCTORS_ATTRIBUTE,
ConstructorDependenciesBean.class.getConstructor(TestBean.class));
lbf.registerBeanDefinition("bean", bd);
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
ConstructorDependenciesBean bean = lbf.getBean(ConstructorDependenciesBean.class);
Object spouse = lbf.getBean("spouse1");