16
16
17
17
package org .springframework .boot .test .context ;
18
18
19
+ import java .lang .reflect .Method ;
19
20
import java .util .ArrayList ;
20
21
import java .util .Arrays ;
21
22
import java .util .List ;
22
23
23
24
import org .springframework .beans .BeanUtils ;
24
25
import org .springframework .boot .ApplicationContextFactory ;
26
+ import org .springframework .boot .ConfigurableBootstrapContext ;
25
27
import org .springframework .boot .SpringApplication ;
28
+ import org .springframework .boot .SpringApplicationHook ;
29
+ import org .springframework .boot .SpringApplicationRunListener ;
30
+ import org .springframework .boot .SpringBootConfiguration ;
26
31
import org .springframework .boot .WebApplicationType ;
27
32
import org .springframework .boot .context .event .ApplicationEnvironmentPreparedEvent ;
28
- import org .springframework .boot .test .context .SpringBootTest .WebEnvironment ;
33
+ import org .springframework .boot .test .context .SpringBootTest .UseMainMethod ;
29
34
import org .springframework .boot .test .mock .web .SpringBootMockServletContext ;
30
35
import org .springframework .boot .test .util .TestPropertyValues ;
31
36
import org .springframework .boot .test .util .TestPropertyValues .Type ;
54
59
import org .springframework .test .context .web .WebMergedContextConfiguration ;
55
60
import org .springframework .util .Assert ;
56
61
import org .springframework .util .ObjectUtils ;
62
+ import org .springframework .util .ReflectionUtils ;
57
63
import org .springframework .util .StringUtils ;
64
+ import org .springframework .util .function .ThrowingSupplier ;
58
65
import org .springframework .web .context .ConfigurableWebApplicationContext ;
59
66
import org .springframework .web .context .support .GenericWebApplicationContext ;
60
67
@@ -86,7 +93,47 @@ public class SpringBootContextLoader extends AbstractContextLoader {
86
93
@ Override
87
94
public ApplicationContext loadContext (MergedContextConfiguration mergedConfig ) throws Exception {
88
95
assertHasClassesOrLocations (mergedConfig );
96
+ SpringBootTestAnnotation annotation = SpringBootTestAnnotation .get (mergedConfig );
97
+ String [] args = annotation .getArgs ();
98
+ UseMainMethod useMainMethod = annotation .getUseMainMethod ();
99
+ ContextLoaderHook hook = new ContextLoaderHook (mergedConfig );
100
+ if (useMainMethod != UseMainMethod .NEVER ) {
101
+ Method mainMethod = getMainMethod (mergedConfig , useMainMethod );
102
+ if (mainMethod != null ) {
103
+ return hook .run (() -> ReflectionUtils .invokeMethod (mainMethod , null , new Object [] { args }));
104
+ }
105
+ }
89
106
SpringApplication application = getSpringApplication ();
107
+ return hook .run (() -> application .run (args ));
108
+ }
109
+
110
+ private void assertHasClassesOrLocations (MergedContextConfiguration mergedConfig ) {
111
+ boolean hasClasses = !ObjectUtils .isEmpty (mergedConfig .getClasses ());
112
+ boolean hasLocations = !ObjectUtils .isEmpty (mergedConfig .getLocations ());
113
+ Assert .state (hasClasses || hasLocations ,
114
+ () -> "No configuration classes or locations found in @SpringApplicationConfiguration. "
115
+ + "For default configuration detection to work you need Spring 4.0.3 or better (found "
116
+ + SpringVersion .getVersion () + ")." );
117
+ }
118
+
119
+ private Method getMainMethod (MergedContextConfiguration mergedConfig , UseMainMethod useMainMethod ) {
120
+ Class <?> springBootConfiguration = Arrays .stream (mergedConfig .getClasses ())
121
+ .filter (this ::isSpringBootConfiguration ).findFirst ().orElse (null );
122
+ Assert .state (springBootConfiguration != null || useMainMethod == UseMainMethod .WHEN_AVAILABLE ,
123
+ "Cannot use main method as no @SpringBootConfiguration-annotated class is available" );
124
+ Method mainMethod = (springBootConfiguration != null )
125
+ ? ReflectionUtils .findMethod (springBootConfiguration , "main" , String [].class ) : null ;
126
+ Assert .state (mainMethod != null || useMainMethod == UseMainMethod .WHEN_AVAILABLE ,
127
+ () -> "Main method not found on '%s'" .formatted (springBootConfiguration .getName ()));
128
+ return mainMethod ;
129
+ }
130
+
131
+ private boolean isSpringBootConfiguration (Class <?> candidate ) {
132
+ return MergedAnnotations .from (candidate , SearchStrategy .TYPE_HIERARCHY )
133
+ .isPresent (SpringBootConfiguration .class );
134
+ }
135
+
136
+ private void configure (MergedContextConfiguration mergedConfig , SpringApplication application ) {
90
137
application .setMainApplicationClass (mergedConfig .getTestClass ());
91
138
application .addPrimarySources (Arrays .asList (mergedConfig .getClasses ()));
92
139
application .getSources ().addAll (Arrays .asList (mergedConfig .getLocations ()));
@@ -103,7 +150,8 @@ else if (mergedConfig instanceof ReactiveWebMergedContextConfiguration) {
103
150
else {
104
151
application .setWebApplicationType (WebApplicationType .NONE );
105
152
}
106
- application .setApplicationContextFactory ((type ) -> getApplicationContextFactory (mergedConfig , type ));
153
+ application .setApplicationContextFactory (
154
+ (webApplicationType ) -> getApplicationContextFactory (mergedConfig , webApplicationType ));
107
155
application .setInitializers (initializers );
108
156
ConfigurableEnvironment environment = getEnvironment ();
109
157
if (environment != null ) {
@@ -113,30 +161,19 @@ else if (mergedConfig instanceof ReactiveWebMergedContextConfiguration) {
113
161
else {
114
162
application .addListeners (new PrepareEnvironmentListener (mergedConfig ));
115
163
}
116
- String [] args = SpringBootTestArgs .get (mergedConfig .getContextCustomizers ());
117
- return application .run (args );
118
- }
119
-
120
- private void assertHasClassesOrLocations (MergedContextConfiguration mergedConfig ) {
121
- boolean hasClasses = !ObjectUtils .isEmpty (mergedConfig .getClasses ());
122
- boolean hasLocations = !ObjectUtils .isEmpty (mergedConfig .getLocations ());
123
- Assert .state (hasClasses || hasLocations ,
124
- () -> "No configuration classes or locations found in @SpringApplicationConfiguration. "
125
- + "For default configuration detection to work you need Spring 4.0.3 or better (found "
126
- + SpringVersion .getVersion () + ")." );
127
164
}
128
165
129
166
private ConfigurableApplicationContext getApplicationContextFactory (MergedContextConfiguration mergedConfig ,
130
- WebApplicationType type ) {
131
- if (type != WebApplicationType .NONE && !isEmbeddedWebEnvironment (mergedConfig )) {
132
- if (type == WebApplicationType .REACTIVE ) {
167
+ WebApplicationType webApplicationType ) {
168
+ if (webApplicationType != WebApplicationType .NONE && !isEmbeddedWebEnvironment (mergedConfig )) {
169
+ if (webApplicationType == WebApplicationType .REACTIVE ) {
133
170
return new GenericReactiveWebApplicationContext ();
134
171
}
135
- if (type == WebApplicationType .SERVLET ) {
172
+ if (webApplicationType == WebApplicationType .SERVLET ) {
136
173
return new GenericWebApplicationContext ();
137
174
}
138
175
}
139
- return ApplicationContextFactory .DEFAULT .create (type );
176
+ return ApplicationContextFactory .DEFAULT .create (webApplicationType );
140
177
}
141
178
142
179
private void prepareEnvironment (MergedContextConfiguration mergedConfig , SpringApplication application ,
@@ -165,9 +202,10 @@ private void setActiveProfiles(ConfigurableEnvironment environment, String[] pro
165
202
}
166
203
167
204
/**
168
- * Builds new {@link org.springframework.boot.SpringApplication} instance. You can
169
- * override this method to add custom behavior
170
- * @return {@link org.springframework.boot.SpringApplication} instance
205
+ * Builds new {@link org.springframework.boot.SpringApplication} instance. This method
206
+ * is only called when a {@code main} method isn't being used to create the
207
+ * {@link SpringApplication}.
208
+ * @return a {@link SpringApplication} instance
171
209
*/
172
210
protected SpringApplication getSpringApplication () {
173
211
return new SpringApplication ();
@@ -215,16 +253,14 @@ protected List<ApplicationContextInitializer<?>> getInitializers(MergedContextCo
215
253
initializers .add (BeanUtils .instantiateClass (initializerClass ));
216
254
}
217
255
if (mergedConfig .getParent () != null ) {
218
- initializers
219
- .add (new ParentContextApplicationContextInitializer (mergedConfig . getParentApplicationContext () ));
256
+ ApplicationContext parentApplicationContext = mergedConfig . getParentApplicationContext ();
257
+ initializers .add (new ParentContextApplicationContextInitializer (parentApplicationContext ));
220
258
}
221
259
return initializers ;
222
260
}
223
261
224
262
private boolean isEmbeddedWebEnvironment (MergedContextConfiguration mergedConfig ) {
225
- return MergedAnnotations .from (mergedConfig .getTestClass (), SearchStrategy .TYPE_HIERARCHY )
226
- .get (SpringBootTest .class ).getValue ("webEnvironment" , WebEnvironment .class ).orElse (WebEnvironment .NONE )
227
- .isEmbedded ();
263
+ return SpringBootTestAnnotation .get (mergedConfig ).getWebEnvironment ().isEmbedded ();
228
264
}
229
265
230
266
@ Override
@@ -371,4 +407,45 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
371
407
372
408
}
373
409
410
+ /**
411
+ * {@link SpringApplicationHook} used to capture the {@link ApplicationContext} and to
412
+ * trigger early exit for the {@link Mode#AOT_PROCESSING} mode.
413
+ */
414
+ private class ContextLoaderHook implements SpringApplicationHook {
415
+
416
+ private final MergedContextConfiguration mergedConfig ;
417
+
418
+ private ApplicationContext applicationContext ;
419
+
420
+ ContextLoaderHook (MergedContextConfiguration mergedConfig ) {
421
+ this .mergedConfig = mergedConfig ;
422
+ }
423
+
424
+ @ Override
425
+ public SpringApplicationRunListener getRunListener (SpringApplication application ) {
426
+ return new SpringApplicationRunListener () {
427
+
428
+ @ Override
429
+ public void starting (ConfigurableBootstrapContext bootstrapContext ) {
430
+ SpringBootContextLoader .this .configure (ContextLoaderHook .this .mergedConfig , application );
431
+ }
432
+
433
+ @ Override
434
+ public void contextLoaded (ConfigurableApplicationContext context ) {
435
+ Assert .state (ContextLoaderHook .this .applicationContext == null ,
436
+ "ApplicationContext already loaded" );
437
+ ContextLoaderHook .this .applicationContext = context ;
438
+ }
439
+
440
+ };
441
+ }
442
+
443
+ private <T > ApplicationContext run (ThrowingSupplier <T > action ) {
444
+ SpringApplication .withHook (this , action );
445
+ Assert .state (this .applicationContext != null , "ApplicationContext not loaded" );
446
+ return this .applicationContext ;
447
+ }
448
+
449
+ }
450
+
374
451
}
0 commit comments