Refine SpringBootTest.useMainMethod support
Refine `SpringBootContextLoader` so that calls to the main method do not exit early and the hook is only used when necessary. See gh-22405
This commit is contained in:
@@ -17,10 +17,11 @@
|
||||
package org.springframework.boot.test.context;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.boot.AotApplicationContextInitializer;
|
||||
@@ -95,6 +96,9 @@ import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||
*/
|
||||
public class SpringBootContextLoader extends AbstractContextLoader implements AotContextLoader {
|
||||
|
||||
private static final Consumer<SpringApplication> ALREADY_CONFIGURED = (springApplication) -> {
|
||||
};
|
||||
|
||||
@Override
|
||||
public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
|
||||
return loadContext(mergedConfig, Mode.STANDARD, null);
|
||||
@@ -117,15 +121,19 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
|
||||
SpringBootTestAnnotation annotation = SpringBootTestAnnotation.get(mergedConfig);
|
||||
String[] args = annotation.getArgs();
|
||||
UseMainMethod useMainMethod = annotation.getUseMainMethod();
|
||||
ContextLoaderHook hook = new ContextLoaderHook(mergedConfig, mode, initializer);
|
||||
if (useMainMethod != UseMainMethod.NEVER) {
|
||||
Method mainMethod = getMainMethod(mergedConfig, useMainMethod);
|
||||
if (mainMethod != null) {
|
||||
return hook.run(() -> ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { args }));
|
||||
}
|
||||
Method mainMethod = getMainMethod(mergedConfig, useMainMethod);
|
||||
if (mainMethod != null) {
|
||||
ContextLoaderHook hook = new ContextLoaderHook(mode, initializer,
|
||||
(application) -> configure(mergedConfig, application));
|
||||
return hook.run(() -> ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { args }));
|
||||
}
|
||||
SpringApplication application = getSpringApplication();
|
||||
return hook.run(() -> application.run(args));
|
||||
configure(mergedConfig, application);
|
||||
if (mode == Mode.AOT_PROCESSING || mode == Mode.AOT_RUNTIME) {
|
||||
ContextLoaderHook hook = new ContextLoaderHook(mode, initializer, ALREADY_CONFIGURED);
|
||||
return hook.run(() -> application.run(args));
|
||||
}
|
||||
return application.run(args);
|
||||
}
|
||||
|
||||
private void assertHasClassesOrLocations(MergedContextConfiguration mergedConfig) {
|
||||
@@ -138,6 +146,9 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
|
||||
}
|
||||
|
||||
private Method getMainMethod(MergedContextConfiguration mergedConfig, UseMainMethod useMainMethod) {
|
||||
if (useMainMethod == UseMainMethod.NEVER) {
|
||||
return null;
|
||||
}
|
||||
Class<?> springBootConfiguration = Arrays.stream(mergedConfig.getClasses())
|
||||
.filter(this::isSpringBootConfiguration).findFirst().orElse(null);
|
||||
Assert.state(springBootConfiguration != null || useMainMethod == UseMainMethod.WHEN_AVAILABLE,
|
||||
@@ -459,17 +470,19 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
|
||||
*/
|
||||
private class ContextLoaderHook implements SpringApplicationHook {
|
||||
|
||||
private final MergedContextConfiguration mergedConfig;
|
||||
|
||||
private final Mode mode;
|
||||
|
||||
private final ApplicationContextInitializer<ConfigurableApplicationContext> initializer;
|
||||
|
||||
ContextLoaderHook(MergedContextConfiguration mergedConfig, Mode mode,
|
||||
ApplicationContextInitializer<ConfigurableApplicationContext> initializer) {
|
||||
this.mergedConfig = mergedConfig;
|
||||
private final Consumer<SpringApplication> configurer;
|
||||
|
||||
private final List<ApplicationContext> contexts = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
ContextLoaderHook(Mode mode, ApplicationContextInitializer<ConfigurableApplicationContext> initializer,
|
||||
Consumer<SpringApplication> configurer) {
|
||||
this.mode = mode;
|
||||
this.initializer = initializer;
|
||||
this.configurer = configurer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -478,8 +491,8 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
|
||||
|
||||
@Override
|
||||
public void starting(ConfigurableBootstrapContext bootstrapContext) {
|
||||
SpringBootContextLoader.this.configure(ContextLoaderHook.this.mergedConfig, application);
|
||||
if (ContextLoaderHook.this.initializer != null) {
|
||||
ContextLoaderHook.this.configurer.accept(application);
|
||||
if (ContextLoaderHook.this.mode == Mode.AOT_RUNTIME) {
|
||||
application.addInitializers(
|
||||
AotApplicationContextInitializer.of(ContextLoaderHook.this.initializer));
|
||||
}
|
||||
@@ -487,27 +500,26 @@ public class SpringBootContextLoader extends AbstractContextLoader implements Ao
|
||||
|
||||
@Override
|
||||
public void contextLoaded(ConfigurableApplicationContext context) {
|
||||
ContextLoaderHook.this.contexts.add(context);
|
||||
if (ContextLoaderHook.this.mode == Mode.AOT_PROCESSING) {
|
||||
throw new AbandonedRunException(context);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
|
||||
throw new AbandonedRunException(context);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private <T> ApplicationContext run(ThrowingSupplier<T> action) {
|
||||
try {
|
||||
SpringApplication.withHook(this, action);
|
||||
throw new IllegalStateException("ApplicationContext not loaded");
|
||||
}
|
||||
catch (AbandonedRunException ex) {
|
||||
return ex.getApplicationContext();
|
||||
}
|
||||
List<ApplicationContext> rootContexts = this.contexts.stream()
|
||||
.filter((context) -> context.getParent() == null).toList();
|
||||
Assert.state(!rootContexts.isEmpty(), "No root application context located");
|
||||
Assert.state(rootContexts.size() == 1, "No unique root application context located");
|
||||
return rootContexts.get(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user