Skip to content

Commit ae3408f

Browse files
committed
Detect JDK25-style main method in SpringBootTest
This commit adapts SpringBootTest when it has to use a main method to detect package private main method as well as main methods that do not have any arguments. Closes gh-48271
1 parent 709e19f commit ae3408f

File tree

2 files changed

+94
-14
lines changed

2 files changed

+94
-14
lines changed

spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootContextLoader.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.test.context;
1818

1919
import java.lang.reflect.Method;
20+
import java.lang.reflect.Modifier;
2021
import java.util.ArrayList;
2122
import java.util.Arrays;
2223
import java.util.Collections;
@@ -136,7 +137,14 @@ private ApplicationContext loadContext(MergedContextConfiguration mergedConfig,
136137
}
137138
ContextLoaderHook hook = new ContextLoaderHook(mode, initializer,
138139
(application) -> configure(mergedConfig, application));
139-
return hook.runMain(() -> ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { args }));
140+
return hook.runMain(() -> {
141+
if (mainMethod.getParameterCount() == 0) {
142+
ReflectionUtils.invokeMethod(mainMethod, null);
143+
}
144+
else {
145+
ReflectionUtils.invokeMethod(mainMethod, null, new Object[] { args });
146+
}
147+
});
140148
}
141149
SpringApplication application = getSpringApplication();
142150
configure(mergedConfig, application);
@@ -172,7 +180,7 @@ private Method getMainMethod(MergedContextConfiguration mergedConfig, UseMainMet
172180
}
173181

174182
private static Method findMainMethod(Class<?> type) {
175-
Method mainMethod = (type != null) ? ReflectionUtils.findMethod(type, "main", String[].class) : null;
183+
Method mainMethod = (type != null) ? findMainJavaMethod(type) : null;
176184
if (mainMethod == null && KotlinDetector.isKotlinPresent()) {
177185
try {
178186
Class<?> kotlinClass = ClassUtils.forName(type.getName() + "Kt", type.getClassLoader());
@@ -185,6 +193,30 @@ private static Method findMainMethod(Class<?> type) {
185193
return mainMethod;
186194
}
187195

196+
private static Method findMainJavaMethod(Class<?> type) {
197+
try {
198+
Method method = getMainMethod(type);
199+
if (Modifier.isStatic(method.getModifiers())) {
200+
method.setAccessible(true);
201+
return method;
202+
}
203+
}
204+
catch (Exception ex) {
205+
// Ignore
206+
}
207+
return null;
208+
}
209+
210+
private static Method getMainMethod(Class<?> type) throws NoSuchMethodException {
211+
try {
212+
return type.getDeclaredMethod("main", String[].class);
213+
}
214+
catch (NoSuchMethodException ex) {
215+
return type.getDeclaredMethod("main");
216+
}
217+
218+
}
219+
188220
private boolean isSpringBootConfiguration(Class<?> candidate) {
189221
return MergedAnnotations.from(candidate, SearchStrategy.TYPE_HIERARCHY)
190222
.isPresent(SpringBootConfiguration.class);

spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/SpringBootContextLoaderTests.java

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.junit.jupiter.api.BeforeEach;
2525
import org.junit.jupiter.api.Disabled;
2626
import org.junit.jupiter.api.Test;
27+
import org.junit.jupiter.params.ParameterizedTest;
28+
import org.junit.jupiter.params.provider.ValueSource;
2729

2830
import org.springframework.aot.hint.RuntimeHints;
2931
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
@@ -199,10 +201,13 @@ void whenUseMainMethodWhenAvailableAndNoMainMethod() {
199201
assertThat(applicationContext.getEnvironment().getActiveProfiles()).isEmpty();
200202
}
201203

202-
@Test
203-
void whenUseMainMethodWhenAvailableAndMainMethod() {
204-
TestContext testContext = new ExposedTestContextManager(UseMainMethodWhenAvailableAndMainMethod.class)
205-
.getExposedTestContext();
204+
@ParameterizedTest
205+
@ValueSource(classes = { UsePublicMainMethodWhenAvailableAndMainMethod.class,
206+
UsePublicParameterlessMainMethodWhenAvailableAndMainMethod.class,
207+
UsePackagePrivateMainMethodWhenAvailableAndMainMethod.class,
208+
UsePackagePrivateParameterlessMainMethodWhenAvailableAndMainMethod.class })
209+
void whenUseMainMethodWhenAvailableAndMainMethod(Class<?> testClass) {
210+
TestContext testContext = new ExposedTestContextManager(testClass).getExposedTestContext();
206211
ApplicationContext applicationContext = testContext.getApplicationContext();
207212
assertThat(applicationContext.getEnvironment().getActiveProfiles()).contains("frommain");
208213
}
@@ -264,11 +269,11 @@ void whenMainMethodNotAvailableReturnsNoAotContribution() throws Exception {
264269
void whenMainMethodPresentRegisterReflectionHints() throws Exception {
265270
SpringBootContextLoader contextLoader = new SpringBootContextLoader();
266271
MergedContextConfiguration contextConfiguration = BootstrapUtils
267-
.resolveTestContextBootstrapper(UseMainMethodWhenAvailableAndMainMethod.class)
272+
.resolveTestContextBootstrapper(UsePublicMainMethodWhenAvailableAndMainMethod.class)
268273
.buildMergedContextConfiguration();
269274
RuntimeHints runtimeHints = new RuntimeHints();
270275
contextLoader.loadContextForAotProcessing(contextConfiguration, runtimeHints);
271-
assertThat(RuntimeHintsPredicates.reflection().onMethod(ConfigWithMain.class, "main").invoke())
276+
assertThat(RuntimeHintsPredicates.reflection().onMethod(ConfigWithPublicMain.class, "main").invoke())
272277
.accepts(runtimeHints);
273278
}
274279

@@ -370,12 +375,28 @@ static class UseMainMethodWhenAvailableAndNoMainMethod {
370375

371376
}
372377

373-
@SpringBootTest(classes = ConfigWithMain.class, useMainMethod = UseMainMethod.WHEN_AVAILABLE)
374-
static class UseMainMethodWhenAvailableAndMainMethod {
378+
@SpringBootTest(classes = ConfigWithPublicMain.class, useMainMethod = UseMainMethod.WHEN_AVAILABLE)
379+
static class UsePublicMainMethodWhenAvailableAndMainMethod {
380+
381+
}
382+
383+
@SpringBootTest(classes = ConfigWithPublicParameterlessMain.class, useMainMethod = UseMainMethod.WHEN_AVAILABLE)
384+
static class UsePublicParameterlessMainMethodWhenAvailableAndMainMethod {
375385

376386
}
377387

378-
@SpringBootTest(classes = ConfigWithMain.class, useMainMethod = UseMainMethod.NEVER)
388+
@SpringBootTest(classes = ConfigWithPackagePrivateMain.class, useMainMethod = UseMainMethod.WHEN_AVAILABLE)
389+
static class UsePackagePrivateMainMethodWhenAvailableAndMainMethod {
390+
391+
}
392+
393+
@SpringBootTest(classes = ConfigWithPackagePrivateParameterlessMain.class,
394+
useMainMethod = UseMainMethod.WHEN_AVAILABLE)
395+
static class UsePackagePrivateParameterlessMainMethodWhenAvailableAndMainMethod {
396+
397+
}
398+
399+
@SpringBootTest(classes = ConfigWithPublicMain.class, useMainMethod = UseMainMethod.NEVER)
379400
static class UseMainMethodNever {
380401

381402
}
@@ -391,7 +412,7 @@ static class NoMainMethodWithBeanThrowingException {
391412
}
392413

393414
@SpringBootTest(useMainMethod = UseMainMethod.ALWAYS)
394-
@ContextHierarchy({ @ContextConfiguration(classes = ConfigWithMain.class),
415+
@ContextHierarchy({ @ContextConfiguration(classes = ConfigWithPublicMain.class),
395416
@ContextConfiguration(classes = AnotherConfigWithMain.class) })
396417
static class UseMainMethodWithContextHierarchy {
397418

@@ -422,10 +443,37 @@ static class Config {
422443
}
423444

424445
@SpringBootConfiguration(proxyBeanMethods = false)
425-
public static class ConfigWithMain {
446+
public static class ConfigWithPublicMain {
426447

427448
public static void main(String[] args) {
428-
new SpringApplication(ConfigWithMain.class).run("--spring.profiles.active=frommain");
449+
new SpringApplication(ConfigWithPublicMain.class).run("--spring.profiles.active=frommain");
450+
}
451+
452+
}
453+
454+
@SpringBootConfiguration(proxyBeanMethods = false)
455+
public static class ConfigWithPublicParameterlessMain {
456+
457+
public static void main() {
458+
new SpringApplication(ConfigWithPublicMain.class).run("--spring.profiles.active=frommain");
459+
}
460+
461+
}
462+
463+
@SpringBootConfiguration(proxyBeanMethods = false)
464+
public static class ConfigWithPackagePrivateMain {
465+
466+
static void main(String[] args) {
467+
new SpringApplication(ConfigWithPublicMain.class).run("--spring.profiles.active=frommain");
468+
}
469+
470+
}
471+
472+
@SpringBootConfiguration(proxyBeanMethods = false)
473+
public static class ConfigWithPackagePrivateParameterlessMain {
474+
475+
static void main() {
476+
new SpringApplication(ConfigWithPublicMain.class).run("--spring.profiles.active=frommain");
429477
}
430478

431479
}

0 commit comments

Comments
 (0)