Skip to content

Commit 7bb7456

Browse files
committed
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
2 parents e35db33 + 6ea9f66 commit 7bb7456

File tree

7 files changed

+180
-36
lines changed

7 files changed

+180
-36
lines changed

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

+40-5
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
133133
* System property that instructs Spring to enforce strict locking during bean creation,
134134
* rather than the mix of strict and lenient locking that 6.2 applies by default. Setting
135135
* this flag to "true" restores 6.1.x style locking in the entire pre-instantiation phase.
136+
* <p>By default, the factory infers strict locking from the encountered thread names:
137+
* If additional threads have names that match the thread prefix of the main bootstrap thread,
138+
* they are considered external (multiple external bootstrap threads calling into the factory)
139+
* and therefore have strict locking applied to them. This inference can be turned off through
140+
* explicitly setting this flag to "false" rather than leaving it unspecified.
136141
* @since 6.2.6
137142
* @see #preInstantiateSingletons()
138143
*/
@@ -156,8 +161,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
156161
private static final Map<String, Reference<DefaultListableBeanFactory>> serializableFactories =
157162
new ConcurrentHashMap<>(8);
158163

159-
/** Whether lenient locking is allowed in this factory. */
160-
private final boolean lenientLockingAllowed = !SpringProperties.getFlag(STRICT_LOCKING_PROPERTY_NAME);
164+
/** Whether strict locking is enforced or relaxed in this factory. */
165+
private @Nullable final Boolean strictLocking = SpringProperties.checkFlag(STRICT_LOCKING_PROPERTY_NAME);
161166

162167
/** Optional id for this factory, for serialization purposes. */
163168
private @Nullable String serializationId;
@@ -208,6 +213,8 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
208213

209214
private volatile boolean preInstantiationPhase;
210215

216+
private @Nullable volatile String mainThreadPrefix;
217+
211218
private final NamedThreadLocal<PreInstantiation> preInstantiationThread =
212219
new NamedThreadLocal<>("Pre-instantiation thread marker");
213220

@@ -1030,7 +1037,7 @@ protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName
10301037
}
10311038
}
10321039
else {
1033-
// Bean intended to be initialized in main bootstrap thread
1040+
// Bean intended to be initialized in main bootstrap thread.
10341041
if (this.preInstantiationThread.get() == PreInstantiation.BACKGROUND) {
10351042
throw new BeanCurrentlyInCreationException(beanName, "Bean marked for mainline initialization " +
10361043
"but requested in background thread - enforce early instantiation in mainline thread " +
@@ -1041,8 +1048,28 @@ protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName
10411048

10421049
@Override
10431050
protected @Nullable Boolean isCurrentThreadAllowedToHoldSingletonLock() {
1044-
return (this.lenientLockingAllowed && this.preInstantiationPhase ?
1045-
this.preInstantiationThread.get() != PreInstantiation.BACKGROUND : null);
1051+
if (this.preInstantiationPhase) {
1052+
// We only differentiate in the preInstantiateSingletons phase.
1053+
PreInstantiation preInstantiation = this.preInstantiationThread.get();
1054+
if (preInstantiation != null) {
1055+
// A Spring-managed thread:
1056+
// MAIN is allowed to lock (true) or even forced to lock (null),
1057+
// BACKGROUND is never allowed to lock (false).
1058+
return switch (preInstantiation) {
1059+
case MAIN -> (Boolean.TRUE.equals(this.strictLocking) ? null : true);
1060+
case BACKGROUND -> false;
1061+
};
1062+
}
1063+
if (Boolean.FALSE.equals(this.strictLocking) ||
1064+
(this.strictLocking == null && !getThreadNamePrefix().equals(this.mainThreadPrefix))) {
1065+
// An unmanaged thread (assumed to be application-internal) with lenient locking,
1066+
// and not part of the same thread pool that provided the main bootstrap thread
1067+
// (excluding scenarios where we are hit by multiple external bootstrap threads).
1068+
return true;
1069+
}
1070+
}
1071+
// Traditional behavior: forced to always hold a full lock.
1072+
return null;
10461073
}
10471074

10481075
@Override
@@ -1060,6 +1087,7 @@ public void preInstantiateSingletons() throws BeansException {
10601087

10611088
this.preInstantiationPhase = true;
10621089
this.preInstantiationThread.set(PreInstantiation.MAIN);
1090+
this.mainThreadPrefix = getThreadNamePrefix();
10631091
try {
10641092
for (String beanName : beanNames) {
10651093
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
@@ -1072,6 +1100,7 @@ public void preInstantiateSingletons() throws BeansException {
10721100
}
10731101
}
10741102
finally {
1103+
this.mainThreadPrefix = null;
10751104
this.preInstantiationThread.remove();
10761105
this.preInstantiationPhase = false;
10771106
}
@@ -1166,6 +1195,12 @@ private void instantiateSingleton(String beanName) {
11661195
}
11671196
}
11681197

1198+
private static String getThreadNamePrefix() {
1199+
String name = Thread.currentThread().getName();
1200+
int numberSeparator = name.lastIndexOf('-');
1201+
return (numberSeparator >= 0 ? name.substring(0, numberSeparator) : name);
1202+
}
1203+
11691204

11701205
//---------------------------------------------------------------------
11711206
// Implementation of BeanDefinitionRegistry interface

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

+11-7
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
270270
// Thread-safe exposure is still guaranteed, there is just a risk of collisions
271271
// when triggering creation of other beans as dependencies of the current bean.
272272
if (logger.isInfoEnabled()) {
273-
logger.info("Creating singleton bean '" + beanName + "' in thread \"" +
273+
logger.info("Obtaining singleton bean '" + beanName + "' in thread \"" +
274274
Thread.currentThread().getName() + "\" while other thread holds " +
275275
"singleton lock for other beans " + this.singletonsCurrentlyInCreation);
276276
}
@@ -441,12 +441,16 @@ private boolean checkDependentWaitingThreads(Thread waitingThread, Thread candid
441441

442442
/**
443443
* Determine whether the current thread is allowed to hold the singleton lock.
444-
* <p>By default, any thread may acquire and hold the singleton lock, except
445-
* background threads from {@link DefaultListableBeanFactory#setBootstrapExecutor}.
446-
* @return {@code false} if the current thread is explicitly not allowed to hold
447-
* the lock, {@code true} if it is explicitly allowed to hold the lock but also
448-
* accepts lenient fallback behavior, or {@code null} if there is no specific
449-
* indication (traditional behavior: always holding a full lock)
444+
* <p>By default, all threads are forced to hold a full lock through {@code null}.
445+
* {@link DefaultListableBeanFactory} overrides this to specifically handle its
446+
* threads during the pre-instantiation phase: {@code true} for the main thread,
447+
* {@code false} for managed background threads, and configuration-dependent
448+
* behavior for unmanaged threads.
449+
* @return {@code true} if the current thread is explicitly allowed to hold the
450+
* lock but also accepts lenient fallback behavior, {@code false} if it is
451+
* explicitly not allowed to hold the lock and therefore forced to use lenient
452+
* fallback behavior, or {@code null} if there is no specific indication
453+
* (traditional behavior: forced to always hold a full lock)
450454
* @since 6.2
451455
*/
452456
protected @Nullable Boolean isCurrentThreadAllowedToHoldSingletonLock() {

Diff for: spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java

-5
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@
7777
import org.springframework.beans.testfixture.beans.SideEffectBean;
7878
import org.springframework.beans.testfixture.beans.TestBean;
7979
import org.springframework.beans.testfixture.beans.factory.DummyFactory;
80-
import org.springframework.core.DefaultParameterNameDiscoverer;
8180
import org.springframework.core.MethodParameter;
8281
import org.springframework.core.Ordered;
8382
import org.springframework.core.ResolvableType;
@@ -1419,7 +1418,6 @@ void autowireWithTwoMatchesForConstructorDependency() {
14191418
lbf.registerBeanDefinition("rod", bd);
14201419
RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class);
14211420
lbf.registerBeanDefinition("rod2", bd2);
1422-
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
14231421

14241422
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
14251423
.isThrownBy(() -> lbf.autowire(ConstructorDependency.class, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false))
@@ -1490,7 +1488,6 @@ void autowirePreferredConstructors() {
14901488
RootBeanDefinition bd = new RootBeanDefinition(ConstructorDependenciesBean.class);
14911489
bd.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
14921490
lbf.registerBeanDefinition("bean", bd);
1493-
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
14941491

14951492
ConstructorDependenciesBean bean = lbf.getBean(ConstructorDependenciesBean.class);
14961493
Object spouse1 = lbf.getBean("spouse1");
@@ -1508,7 +1505,6 @@ void autowirePreferredConstructorsFromAttribute() {
15081505
bd.setAttribute(GenericBeanDefinition.PREFERRED_CONSTRUCTORS_ATTRIBUTE,
15091506
ConstructorDependenciesBean.class.getConstructors());
15101507
lbf.registerBeanDefinition("bean", bd);
1511-
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
15121508

15131509
ConstructorDependenciesBean bean = lbf.getBean(ConstructorDependenciesBean.class);
15141510
Object spouse1 = lbf.getBean("spouse1");
@@ -1526,7 +1522,6 @@ void autowirePreferredConstructorFromAttribute() throws Exception {
15261522
bd.setAttribute(GenericBeanDefinition.PREFERRED_CONSTRUCTORS_ATTRIBUTE,
15271523
ConstructorDependenciesBean.class.getConstructor(TestBean.class));
15281524
lbf.registerBeanDefinition("bean", bd);
1529-
lbf.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
15301525

15311526
ConstructorDependenciesBean bean = lbf.getBean(ConstructorDependenciesBean.class);
15321527
Object spouse = lbf.getBean("spouse1");

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

+81-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.context.annotation;
1818

19+
import java.util.concurrent.ExecutorService;
20+
import java.util.concurrent.Executors;
21+
1922
import org.junit.jupiter.api.Test;
2023
import org.junit.jupiter.api.Timeout;
2124

@@ -67,7 +70,7 @@ void bootstrapWithUnmanagedThreads() {
6770
@Test
6871
@Timeout(10)
6972
@EnabledForTestGroups(LONG_RUNNING)
70-
void bootstrapWithStrictLockingThread() {
73+
void bootstrapWithStrictLockingFlag() {
7174
SpringProperties.setFlag(DefaultListableBeanFactory.STRICT_LOCKING_PROPERTY_NAME);
7275
try {
7376
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(StrictLockingBeanConfig.class);
@@ -79,6 +82,42 @@ void bootstrapWithStrictLockingThread() {
7982
}
8083
}
8184

85+
@Test
86+
@Timeout(10)
87+
@EnabledForTestGroups(LONG_RUNNING)
88+
void bootstrapWithStrictLockingInferred() throws InterruptedException {
89+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
90+
ctx.register(InferredLockingBeanConfig.class);
91+
ExecutorService threadPool = Executors.newFixedThreadPool(2);
92+
threadPool.submit(() -> ctx.refresh());
93+
Thread.sleep(500);
94+
threadPool.submit(() -> ctx.getBean("testBean2"));
95+
Thread.sleep(1000);
96+
assertThat(ctx.getBean("testBean2", TestBean.class).getSpouse()).isSameAs(ctx.getBean("testBean1"));
97+
ctx.close();
98+
}
99+
100+
@Test
101+
@Timeout(10)
102+
@EnabledForTestGroups(LONG_RUNNING)
103+
void bootstrapWithStrictLockingTurnedOff() throws InterruptedException {
104+
SpringProperties.setFlag(DefaultListableBeanFactory.STRICT_LOCKING_PROPERTY_NAME, false);
105+
try {
106+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
107+
ctx.register(InferredLockingBeanConfig.class);
108+
ExecutorService threadPool = Executors.newFixedThreadPool(2);
109+
threadPool.submit(() -> ctx.refresh());
110+
Thread.sleep(500);
111+
threadPool.submit(() -> ctx.getBean("testBean2"));
112+
Thread.sleep(1000);
113+
assertThat(ctx.getBean("testBean2", TestBean.class).getSpouse()).isNull();
114+
ctx.close();
115+
}
116+
finally {
117+
SpringProperties.setProperty(DefaultListableBeanFactory.STRICT_LOCKING_PROPERTY_NAME, null);
118+
}
119+
}
120+
82121
@Test
83122
@Timeout(10)
84123
@EnabledForTestGroups(LONG_RUNNING)
@@ -128,6 +167,24 @@ void bootstrapWithCustomExecutor() {
128167
ctx.close();
129168
}
130169

170+
@Test
171+
@Timeout(10)
172+
@EnabledForTestGroups(LONG_RUNNING)
173+
void bootstrapWithCustomExecutorAndStrictLocking() {
174+
SpringProperties.setFlag(DefaultListableBeanFactory.STRICT_LOCKING_PROPERTY_NAME);
175+
try {
176+
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CustomExecutorBeanConfig.class);
177+
ctx.getBean("testBean1", TestBean.class);
178+
ctx.getBean("testBean2", TestBean.class);
179+
ctx.getBean("testBean3", TestBean.class);
180+
ctx.getBean("testBean4", TestBean.class);
181+
ctx.close();
182+
}
183+
finally {
184+
SpringProperties.setProperty(DefaultListableBeanFactory.STRICT_LOCKING_PROPERTY_NAME, null);
185+
}
186+
}
187+
131188

132189
@Configuration(proxyBeanMethods = false)
133190
static class UnmanagedThreadBeanConfig {
@@ -220,6 +277,27 @@ public TestBean testBean2(ConfigurableListableBeanFactory beanFactory) {
220277
}
221278

222279

280+
@Configuration(proxyBeanMethods = false)
281+
static class InferredLockingBeanConfig {
282+
283+
@Bean
284+
public TestBean testBean1() {
285+
try {
286+
Thread.sleep(1000);
287+
}
288+
catch (InterruptedException ex) {
289+
Thread.currentThread().interrupt();
290+
}
291+
return new TestBean("testBean1");
292+
}
293+
294+
@Bean
295+
public TestBean testBean2(ConfigurableListableBeanFactory beanFactory) {
296+
return new TestBean((TestBean) beanFactory.getSingleton("testBean1"));
297+
}
298+
}
299+
300+
223301
@Configuration(proxyBeanMethods = false)
224302
static class CircularReferenceAgainstMainThreadBeanConfig {
225303

@@ -377,13 +455,13 @@ public ThreadPoolTaskExecutor bootstrapExecutor() {
377455

378456
@Bean(bootstrap = BACKGROUND) @DependsOn("testBean3")
379457
public TestBean testBean1(TestBean testBean3) throws InterruptedException {
380-
Thread.sleep(3000);
458+
Thread.sleep(6000);
381459
return new TestBean();
382460
}
383461

384462
@Bean(bootstrap = BACKGROUND) @Lazy
385463
public TestBean testBean2() throws InterruptedException {
386-
Thread.sleep(3000);
464+
Thread.sleep(6000);
387465
return new TestBean();
388466
}
389467

Diff for: spring-core/src/main/java/org/springframework/cglib/core/ReflectUtils.java

+19-8
Original file line numberDiff line numberDiff line change
@@ -527,15 +527,26 @@ public static Class defineClass(String className, byte[] b, ClassLoader loader,
527527
c = lookup.defineClass(b);
528528
}
529529
catch (LinkageError | IllegalAccessException ex) {
530-
throw new CodeGenerationException(ex) {
531-
@Override
532-
public String getMessage() {
533-
return "ClassLoader mismatch for [" + contextClass.getName() +
534-
"]: JVM should be started with --add-opens=java.base/java.lang=ALL-UNNAMED " +
535-
"for ClassLoader.defineClass to be accessible on " + loader.getClass().getName() +
536-
"; consider co-locating the affected class in that target ClassLoader instead.";
530+
if (ex instanceof LinkageError) {
531+
// Could be a ClassLoader mismatch with the class pre-existing in a
532+
// parent ClassLoader -> try loadClass before giving up completely.
533+
try {
534+
c = contextClass.getClassLoader().loadClass(className);
537535
}
538-
};
536+
catch (ClassNotFoundException cnfe) {
537+
}
538+
}
539+
if (c == null) {
540+
throw new CodeGenerationException(ex) {
541+
@Override
542+
public String getMessage() {
543+
return "ClassLoader mismatch for [" + contextClass.getName() +
544+
"]: JVM should be started with --add-opens=java.base/java.lang=ALL-UNNAMED " +
545+
"for ClassLoader.defineClass to be accessible on " + loader.getClass().getName() +
546+
"; consider co-locating the affected class in that target ClassLoader instead.";
547+
}
548+
};
549+
}
539550
}
540551
catch (Throwable ex) {
541552
throw new CodeGenerationException(ex);

Diff for: spring-core/src/main/java/org/springframework/core/SpringProperties.java

+27-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,18 @@ public static void setProperty(String key, @Nullable String value) {
117117
* @param key the property key
118118
*/
119119
public static void setFlag(String key) {
120-
localProperties.put(key, Boolean.TRUE.toString());
120+
localProperties.setProperty(key, Boolean.TRUE.toString());
121+
}
122+
123+
/**
124+
* Programmatically set a local flag to the given value, overriding
125+
* an entry in the {@code spring.properties} file (if any).
126+
* @param key the property key
127+
* @param value the associated boolean value
128+
* @since 6.2.6
129+
*/
130+
public static void setFlag(String key, boolean value) {
131+
localProperties.setProperty(key, Boolean.toString(value));
121132
}
122133

123134
/**
@@ -130,4 +141,19 @@ public static boolean getFlag(String key) {
130141
return Boolean.parseBoolean(getProperty(key));
131142
}
132143

144+
/**
145+
* Retrieve the flag for the given property key, returning {@code null}
146+
* instead of {@code false} in case of no actual flag set.
147+
* @param key the property key
148+
* @return {@code true} if the property is set to the string "true"
149+
* (ignoring case), {@code} false if it is set to any other value,
150+
* {@code null} if it is not set at all
151+
* @since 6.2.6
152+
*/
153+
@Nullable
154+
public static Boolean checkFlag(String key) {
155+
String flag = getProperty(key);
156+
return (flag != null ? Boolean.valueOf(flag) : null);
157+
}
158+
133159
}

0 commit comments

Comments
 (0)