Skip to content

Commit e5a01c5

Browse files
committed
JarLauncherDetector->SpringBootFatJarMain
Closes gh-86
1 parent 9db0032 commit e5a01c5

File tree

6 files changed

+104
-96
lines changed

6 files changed

+104
-96
lines changed

config/checkstyle/checkstyle-suppressions.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
<!-- Ignore third-party code -->
77
<suppress files="LoggingMavenRepositoryListener\.java|LoggingMavenTransferListener\.java" checks=".*"/>
88
<suppress files="SpringBootApplicationMain\.java" checks=".*"/>
9-
<suppress files="JarLauncherDetector\.java" checks=".*"/>
9+
<suppress files="SpringBootFatJarMain\.java" checks=".*"/>
1010
</suppressions>

spring-boot-testjars/src/main/java/org/springframework/experimental/boot/server/exec/CommonsExecWebServerFactoryBean.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public class CommonsExecWebServerFactoryBean
5656

5757
private Map<String, String> systemProperties = new HashMap<>();
5858

59-
private String mainClass = "org.springframework.experimental.boot.server.exec.detector.JarLauncherDetector";
59+
private String mainClass = "org.springframework.experimental.boot.server.exec.detector.SpringBootFatJarMain";
6060

6161
private File applicationPortFile = createApplicationPortFile();
6262

spring-boot-testjars/src/main/java/org/springframework/experimental/boot/server/exec/detector/JarLauncherDetector.java

Lines changed: 0 additions & 74 deletions
This file was deleted.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.experimental.boot.server.exec.detector;
18+
19+
import java.lang.reflect.InvocationTargetException;
20+
21+
import org.apache.commons.logging.Log;
22+
import org.apache.commons.logging.LogFactory;
23+
24+
import org.springframework.core.log.LogMessage;
25+
import org.springframework.experimental.boot.server.exec.imports.GenericSpringBootApplicationMain;
26+
import org.springframework.util.ClassUtils;
27+
28+
/**
29+
* Detect which JarLauncher main class to use, and call its {@code main(String[] args)}
30+
* methods.
31+
* <p>
32+
* The location depends on the Boot version. Prior to 3.2, it was in the
33+
* {@code org.springframework.boot.loader} package. In 3.2, it was moved to the
34+
* {@code org.springframework.boot.loader.launch} package.
35+
*
36+
* @author Daniel Garnier-Moiroux
37+
*/
38+
public class SpringBootFatJarMain {
39+
40+
private static Log log = LogFactory.getLog(SpringBootFatJarMain.class);
41+
42+
static final String SPRING_BOOT_32_PLUS_LAUNCHER_CLASSNAME = "org.springframework.boot.loader.launch.JarLauncher";
43+
44+
static final String SPRING_BOOT_PRE_32_LAUNCHER_CLASSNAME = "org.springframework.boot.loader.JarLauncher";
45+
46+
public static void main(String[] args) {
47+
if (runMain(SPRING_BOOT_32_PLUS_LAUNCHER_CLASSNAME, args, "Spring Boot >= 3.2 fat jar")) {
48+
return;
49+
}
50+
if (runMain(SPRING_BOOT_PRE_32_LAUNCHER_CLASSNAME, args, "Spring Boot < 3.2 fat jar")) {
51+
return;
52+
}
53+
String classPath = System.getProperty("java.class.path");
54+
throw new IllegalStateException(
55+
"The application could not be launched as a Spring Boot fat jar using the classpath " + classPath + "\n"
56+
+ " - If you expect the application to run as a fat jar, ensure that the classpath includes the Spring Boot fat jar\n"
57+
+ " - If you are not using a fat jar, ensure to specify the main class using CommonsExecWebServerFactoryBean.mainClass(String)\n"
58+
+ " - If your application is an adhoc application that does not contain a main class, you can use CommonsExecWebServerFactoryBean.useGenericSpringBootMain()");
59+
}
60+
61+
private static boolean runMain(String className, String[] args, String description) {
62+
log.debug(LogMessage.format("Trying to run as %s using %s.main(String[])", description, className));
63+
try {
64+
Class<?> jarLauncher = ClassUtils.forName(className,
65+
GenericSpringBootApplicationMain.class.getClassLoader());
66+
var mainMethod = jarLauncher.getMethod("main", String[].class);
67+
mainMethod.invoke(null, (Object) args);
68+
log.debug(LogMessage.format("Successfully ran as %s", description));
69+
return true;
70+
}
71+
catch (ClassNotFoundException ex) {
72+
log.debug(LogMessage.format("Failed to run as %s", description), ex);
73+
}
74+
catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
75+
throw new RuntimeException(ex);
76+
}
77+
return false;
78+
}
79+
80+
}

spring-boot-testjars/src/test/java/org/springframework/experimental/boot/server/exec/CommonsExecWebServerFactoryBeanTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ void usesJarLauncherwhenNoMainClassDefined() throws Exception {
8585
CommonsExecWebServer webServer = CommonsExecWebServerFactoryBean.builder().getObject();
8686
String[] args = webServer.getCommandLine().getArguments();
8787
assertThat(args[args.length - 1])
88-
.isEqualTo("org.springframework.experimental.boot.server.exec.detector.JarLauncherDetector");
88+
.isEqualTo("org.springframework.experimental.boot.server.exec.detector.SpringBootFatJarMain");
8989
}
9090

9191
@Test
Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
import org.junit.jupiter.api.Test;
2121
import org.mockito.Mockito;
2222

23-
import org.springframework.experimental.boot.server.exec.imports.SpringBootApplicationMain;
23+
import org.springframework.util.ClassUtils;
2424

2525
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2627
import static org.mockito.ArgumentMatchers.any;
28+
import static org.mockito.ArgumentMatchers.eq;
2729

28-
class JarLauncherDetectorTests {
30+
class SpringBootFatJarMainTests {
2931

3032
@BeforeEach
3133
void setUp() {
@@ -34,14 +36,15 @@ void setUp() {
3436

3537
@Test
3638
void whenJarLauncherInLoaderLaunchPackage() {
37-
try (var mocked = Mockito.mockStatic(JarLauncherDetector.class)) {
38-
mocked.when(() -> JarLauncherDetector.main(any())).thenCallRealMethod();
39-
mocked.when(() -> JarLauncherDetector.loadClass(any())).thenThrow(new ClassNotFoundException());
40-
mocked.when(() -> JarLauncherDetector.loadClass("org.springframework.boot.loader.launch.JarLauncher"))
39+
try (var mocked = Mockito.mockStatic(ClassUtils.class)) {
40+
mocked.when(
41+
() -> ClassUtils.forName(eq(SpringBootFatJarMain.SPRING_BOOT_32_PLUS_LAUNCHER_CLASSNAME), any()))
42+
.thenThrow(new ClassNotFoundException());
43+
mocked.when(() -> ClassUtils.forName(eq(SpringBootFatJarMain.SPRING_BOOT_PRE_32_LAUNCHER_CLASSNAME), any()))
4144
.thenReturn(MockJarLauncher.class);
4245

4346
var args = new String[] { "one", "two" };
44-
JarLauncherDetector.main(args);
47+
SpringBootFatJarMain.main(args);
4548

4649
assertThat(MockJarLauncher.callCount).isEqualTo(1);
4750
assertThat(MockJarLauncher.callArgs).isSameAs(args);
@@ -50,14 +53,15 @@ void whenJarLauncherInLoaderLaunchPackage() {
5053

5154
@Test
5255
void whenJarLauncherInLoaderPackage() {
53-
try (var mocked = Mockito.mockStatic(JarLauncherDetector.class)) {
54-
mocked.when(() -> JarLauncherDetector.main(any())).thenCallRealMethod();
55-
mocked.when(() -> JarLauncherDetector.loadClass(any())).thenThrow(new ClassNotFoundException());
56-
mocked.when(() -> JarLauncherDetector.loadClass("org.springframework.boot.loader.JarLauncher"))
56+
try (var mocked = Mockito.mockStatic(ClassUtils.class)) {
57+
mocked.when(
58+
() -> ClassUtils.forName(eq(SpringBootFatJarMain.SPRING_BOOT_32_PLUS_LAUNCHER_CLASSNAME), any()))
59+
.thenThrow(new ClassNotFoundException());
60+
mocked.when(() -> ClassUtils.forName(eq(SpringBootFatJarMain.SPRING_BOOT_PRE_32_LAUNCHER_CLASSNAME), any()))
5761
.thenReturn(MockJarLauncher.class);
5862

5963
var args = new String[] { "one", "two" };
60-
JarLauncherDetector.main(args);
64+
SpringBootFatJarMain.main(args);
6165

6266
assertThat(MockJarLauncher.callCount).isEqualTo(1);
6367
assertThat(MockJarLauncher.callArgs).isSameAs(args);
@@ -66,15 +70,13 @@ void whenJarLauncherInLoaderPackage() {
6670

6771
@Test
6872
void whenJarLauncherMissing() {
69-
try (var mocked = Mockito.mockStatic(JarLauncherDetector.class);
70-
var mockedSpringBootMain = Mockito.mockStatic(SpringBootApplicationMain.class)) {
71-
mocked.when(() -> JarLauncherDetector.main(any())).thenCallRealMethod();
72-
mocked.when(() -> JarLauncherDetector.loadClass(any())).thenThrow(new ClassNotFoundException());
73+
try (var mocked = Mockito.mockStatic(ClassUtils.class)) {
74+
mocked.when(() -> ClassUtils.forName(any(), any())).thenThrow(new ClassNotFoundException());
7375

7476
final var callArgs = new String[] { "one", "two" };
75-
JarLauncherDetector.main(callArgs);
76-
77-
mockedSpringBootMain.verify(() -> SpringBootApplicationMain.main(callArgs));
77+
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> SpringBootFatJarMain.main(callArgs))
78+
.withMessageContaining(
79+
"The application could not be launched as a Spring Boot fat jar using the classpath ");
7880
}
7981
}
8082

0 commit comments

Comments
 (0)