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.
1919import org .junit .jupiter .api .Test ;
2020import org .junit .jupiter .api .Timeout ;
2121
22+ import org .springframework .beans .factory .BeanCurrentlyInCreationException ;
23+ import org .springframework .beans .factory .ObjectProvider ;
2224import org .springframework .beans .testfixture .beans .TestBean ;
2325import org .springframework .context .ConfigurableApplicationContext ;
2426import org .springframework .core .testfixture .EnabledForTestGroups ;
2527import org .springframework .scheduling .concurrent .ThreadPoolTaskExecutor ;
2628
29+ import static org .assertj .core .api .Assertions .assertThatExceptionOfType ;
2730import static org .springframework .context .annotation .Bean .Bootstrap .BACKGROUND ;
2831import static org .springframework .core .testfixture .TestGroup .LONG_RUNNING ;
2932
3336 */
3437class BackgroundBootstrapTests {
3538
39+ @ Test
40+ @ Timeout (5 )
41+ @ EnabledForTestGroups (LONG_RUNNING )
42+ void bootstrapWithUnmanagedThread () {
43+ ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext (UnmanagedThreadBeanConfig .class );
44+ ctx .getBean ("testBean1" , TestBean .class );
45+ assertThatExceptionOfType (BeanCurrentlyInCreationException .class ).isThrownBy ( // late - not during refresh
46+ () -> ctx .getBean ("testBean2" , TestBean .class ));
47+ ctx .close ();
48+ }
49+
3650 @ Test
3751 @ Timeout (5 )
3852 @ EnabledForTestGroups (LONG_RUNNING )
@@ -45,7 +59,35 @@ void bootstrapWithCustomExecutor() {
4559 }
4660
4761
48- @ Configuration
62+ @ Configuration (proxyBeanMethods = false )
63+ static class UnmanagedThreadBeanConfig {
64+
65+ @ Bean
66+ public TestBean testBean1 (ObjectProvider <TestBean > testBean2 ) {
67+ new Thread (testBean2 ::getObject ).start ();
68+ try {
69+ Thread .sleep (1000 );
70+ }
71+ catch (InterruptedException ex ) {
72+ throw new RuntimeException (ex );
73+ }
74+ return new TestBean ();
75+ }
76+
77+ @ Bean
78+ public TestBean testBean2 () {
79+ try {
80+ Thread .sleep (2000 );
81+ }
82+ catch (InterruptedException ex ) {
83+ throw new RuntimeException (ex );
84+ }
85+ return new TestBean ();
86+ }
87+ }
88+
89+
90+ @ Configuration (proxyBeanMethods = false )
4991 static class CustomExecutorBeanConfig {
5092
5193 @ Bean
@@ -58,7 +100,7 @@ public ThreadPoolTaskExecutor bootstrapExecutor() {
58100 }
59101
60102 @ Bean (bootstrap = BACKGROUND ) @ DependsOn ("testBean3" )
61- public TestBean testBean1 (TestBean testBean3 ) throws InterruptedException {
103+ public TestBean testBean1 (TestBean testBean3 ) throws InterruptedException {
62104 Thread .sleep (3000 );
63105 return new TestBean ();
64106 }
0 commit comments