Skip to content

Commit b61e38b

Browse files
committed
Support launching with a parameterless main method
Fixes gh-47311
1 parent 9a1d9f6 commit b61e38b

File tree

2 files changed

+146
-23
lines changed
  • spring-boot-project/spring-boot-tools/spring-boot-loader/src

2 files changed

+146
-23
lines changed

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/launch/Launcher.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,23 @@ private ClassLoader createClassLoader(URL[] urls) {
9797
protected void launch(ClassLoader classLoader, String mainClassName, String[] args) throws Exception {
9898
Thread.currentThread().setContextClassLoader(classLoader);
9999
Class<?> mainClass = Class.forName(mainClassName, false, classLoader);
100-
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
100+
Method mainMethod = getMainMethod(mainClass);
101101
mainMethod.setAccessible(true);
102-
mainMethod.invoke(null, new Object[] { args });
102+
if (mainMethod.getParameterCount() == 0) {
103+
mainMethod.invoke(null);
104+
}
105+
else {
106+
mainMethod.invoke(null, new Object[] { args });
107+
}
108+
}
109+
110+
private Method getMainMethod(Class<?> mainClass) throws Exception {
111+
try {
112+
return mainClass.getDeclaredMethod("main", String[].class);
113+
}
114+
catch (NoSuchMethodException ex) {
115+
return mainClass.getDeclaredMethod("main");
116+
}
103117
}
104118

105119
/**

spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/launch/LauncherTests.java

Lines changed: 130 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,115 @@
4141
@AssertFileChannelDataBlocksClosed
4242
class LauncherTests {
4343

44+
/**
45+
* Main method tests.
46+
*
47+
*/
48+
@Nested
49+
@ExtendWith(OutputCaptureExtension.class)
50+
class MainMethod {
51+
52+
@Test
53+
void publicMainMethod(CapturedOutput output) throws Exception {
54+
new MainMethodTestLauncher(PublicMainMethod.class).launch(new String[0]);
55+
assertThat(output).contains("Launched public static void main(String[] args)");
56+
}
57+
58+
@Test
59+
void packagePrivateMainMethod(CapturedOutput output) throws Exception {
60+
new MainMethodTestLauncher(PackagePrivateMainMethod.class).launch(new String[0]);
61+
assertThat(output).contains("Launched static void main(String[] args)");
62+
}
63+
64+
@Test
65+
void publicParameterlessMainMethod(CapturedOutput output) throws Exception {
66+
new MainMethodTestLauncher(PublicParameterlessMainMethod.class).launch(new String[0]);
67+
assertThat(output).contains("Launched public static void main()");
68+
}
69+
70+
@Test
71+
void packagePrivateParameterlessMainMethod(CapturedOutput output) throws Exception {
72+
new MainMethodTestLauncher(PackagePrivateParameterlessMainMethod.class).launch(new String[0]);
73+
assertThat(output).contains("Launched static void main()");
74+
}
75+
76+
@Test
77+
void prefersSingleParameterMainMethod(CapturedOutput output) throws Exception {
78+
new MainMethodTestLauncher(MultipleMainMethods.class).launch(new String[0]);
79+
assertThat(output).contains("Launched static void main(String[] args)");
80+
}
81+
82+
static class MainMethodTestLauncher extends Launcher {
83+
84+
private final Class<?> mainClass;
85+
86+
MainMethodTestLauncher(Class<?> mainClass) {
87+
this.mainClass = mainClass;
88+
}
89+
90+
@Override
91+
protected Archive getArchive() {
92+
return null;
93+
}
94+
95+
@Override
96+
protected String getMainClass() throws Exception {
97+
return this.mainClass.getName();
98+
}
99+
100+
@Override
101+
protected Set<URL> getClassPathUrls() throws Exception {
102+
return Collections.emptySet();
103+
}
104+
105+
}
106+
107+
public static class PublicMainMethod {
108+
109+
public static void main(String[] args) {
110+
System.out.println("Launched public static void main(String[] args)");
111+
}
112+
113+
}
114+
115+
public static class PackagePrivateMainMethod {
116+
117+
public static void main(String[] args) {
118+
System.out.println("Launched static void main(String[] args)");
119+
}
120+
121+
}
122+
123+
public static class PublicParameterlessMainMethod {
124+
125+
public static void main() {
126+
System.out.println("Launched public static void main()");
127+
}
128+
129+
}
130+
131+
public static class PackagePrivateParameterlessMainMethod {
132+
133+
static void main() {
134+
System.out.println("Launched static void main()");
135+
}
136+
137+
}
138+
139+
public static class MultipleMainMethods {
140+
141+
static void main(String[] args) {
142+
System.out.println("Launched static void main(String[] args)");
143+
}
144+
145+
static void main() {
146+
System.out.println("Launched static void main()");
147+
}
148+
149+
}
150+
151+
}
152+
44153
/**
45154
* Jar Mode tests.
46155
*/
@@ -61,23 +170,23 @@ void cleanup() {
61170
@Test
62171
void launchWhenJarModePropertyIsSetLaunchesJarMode(CapturedOutput out) throws Exception {
63172
System.setProperty("jarmode", "test");
64-
new TestLauncher().launch(new String[] { "boot" });
173+
new JarModeTestLauncher().launch(new String[] { "boot" });
65174
assertThat(out).contains("running in test jar mode [boot]");
66175
assertThat(System.getProperty(JarModeRunner.SUPPRESSED_SYSTEM_EXIT_CODE)).isEqualTo("0");
67176
}
68177

69178
@Test
70179
void launchWhenJarModePropertyIsNotAcceptedThrowsException(CapturedOutput out) throws Exception {
71180
System.setProperty("jarmode", "idontexist");
72-
new TestLauncher().launch(new String[] { "boot" });
181+
new JarModeTestLauncher().launch(new String[] { "boot" });
73182
assertThat(out).contains("Unsupported jarmode 'idontexist'");
74183
assertThat(System.getProperty(JarModeRunner.SUPPRESSED_SYSTEM_EXIT_CODE)).isEqualTo("1");
75184
}
76185

77186
@Test
78187
void launchWhenJarModeRunFailsWithErrorExceptionPrintsSimpleMessage(CapturedOutput out) throws Exception {
79188
System.setProperty("jarmode", "test");
80-
new TestLauncher().launch(new String[] { "error" });
189+
new JarModeTestLauncher().launch(new String[] { "error" });
81190
assertThat(out).contains("running in test jar mode [error]");
82191
assertThat(out).contains("Error: error message");
83192
assertThat(System.getProperty(JarModeRunner.SUPPRESSED_SYSTEM_EXIT_CODE)).isEqualTo("1");
@@ -86,34 +195,34 @@ void launchWhenJarModeRunFailsWithErrorExceptionPrintsSimpleMessage(CapturedOutp
86195
@Test
87196
void launchWhenJarModeRunFailsWithErrorExceptionPrintsStackTrace(CapturedOutput out) throws Exception {
88197
System.setProperty("jarmode", "test");
89-
new TestLauncher().launch(new String[] { "fail" });
198+
new JarModeTestLauncher().launch(new String[] { "fail" });
90199
assertThat(out).contains("running in test jar mode [fail]");
91200
assertThat(out).contains("java.lang.IllegalStateException: bad");
92201
assertThat(System.getProperty(JarModeRunner.SUPPRESSED_SYSTEM_EXIT_CODE)).isEqualTo("1");
93202
}
94203

95-
}
204+
private static final class JarModeTestLauncher extends Launcher {
96205

97-
private static final class TestLauncher extends Launcher {
206+
@Override
207+
protected String getMainClass() throws Exception {
208+
throw new IllegalStateException("Should not be called");
209+
}
98210

99-
@Override
100-
protected String getMainClass() throws Exception {
101-
throw new IllegalStateException("Should not be called");
102-
}
211+
@Override
212+
protected Archive getArchive() {
213+
return null;
214+
}
103215

104-
@Override
105-
protected Archive getArchive() {
106-
return null;
107-
}
216+
@Override
217+
protected Set<URL> getClassPathUrls() throws Exception {
218+
return Collections.emptySet();
219+
}
108220

109-
@Override
110-
protected Set<URL> getClassPathUrls() throws Exception {
111-
return Collections.emptySet();
112-
}
221+
@Override
222+
protected void launch(String[] args) throws Exception {
223+
super.launch(args);
224+
}
113225

114-
@Override
115-
protected void launch(String[] args) throws Exception {
116-
super.launch(args);
117226
}
118227

119228
}

0 commit comments

Comments
 (0)