From 313b6f6451ea7caa314ea95cd56d37ae677a507f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 30 Mar 2016 16:28:21 +0100 Subject: [PATCH] Simplify exception handling and reporting in the launcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following changes to LaunchedURLClassLoader made in 87fe0b2a, it is no longer necessary for the launcher to load MainMethodRunner via reflection as both the app class loader that the launcher URL class loader share the same MainMethodRunner class. This commit takes advantage of this by updating Launcher to instantiate MainMethodRunner directly rather than via reflection, removing one source of possible exceptions in the launcher. As the MainMethodRunner is now loaded directly and its class is shared between the two class loaders, there’s no longer a need for it to implement Runnable. This allows it to throw Exception from its run method, rather than having to wrap any Exception in a RuntimeException. Lastly, rather than catching any exception thrown from the launch, Launcher and its subclasses have been updated to allow this exception to be thrown from the main method. This allows the Exception to reach the JVM, to be processed by our registered uncaught exception handler, and to trigger the JVM’s standard processing for exiting due to a failure. This removes the need for the Launcher itself to call System.exit(1) and ensures that the exception is only output to the console if it hasn’t been registered as a logged exception. Closes gh-5358 --- .../boot/loader/JarLauncher.java | 2 +- .../springframework/boot/loader/Launcher.java | 37 +++++-------------- .../boot/loader/MainMethodRunner.java | 33 +++++------------ .../boot/loader/WarLauncher.java | 2 +- 4 files changed, 21 insertions(+), 53 deletions(-) diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java index 6f19c70c23..ec7b0bcdc7 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java @@ -54,7 +54,7 @@ public class JarLauncher extends ExecutableArchiveLauncher { archives.add(0, getArchive()); } - public static void main(String[] args) { + public static void main(String[] args) throws Exception { new JarLauncher().launch(args); } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java index 3938031c37..2b0f106e66 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java @@ -17,7 +17,6 @@ package org.springframework.boot.loader; import java.io.File; -import java.lang.reflect.Constructor; import java.net.URI; import java.net.URL; import java.security.CodeSource; @@ -39,27 +38,16 @@ import org.springframework.boot.loader.jar.JarFile; */ public abstract class Launcher { - /** - * The main runner class. This must be loaded by the created ClassLoader so cannot be - * directly referenced. - */ - private static final String RUNNER_CLASS = Launcher.class.getPackage().getName() - + ".MainMethodRunner"; - /** * Launch the application. This method is the initial entry point that should be * called by a subclass {@code public static void main(String[] args)} method. * @param args the incoming arguments + * @throws Exception if the application fails to launch */ - protected void launch(String[] args) { - try { - JarFile.registerUrlProtocolHandler(); - ClassLoader classLoader = createClassLoader(getClassPathArchives()); - launch(args, getMainClass(), classLoader); - } - catch (Exception ex) { - System.exit(1); - } + protected void launch(String[] args) throws Exception { + JarFile.registerUrlProtocolHandler(); + ClassLoader classLoader = createClassLoader(getClassPathArchives()); + launch(args, getMainClass(), classLoader); } /** @@ -95,9 +83,8 @@ public abstract class Launcher { */ protected void launch(String[] args, String mainClass, ClassLoader classLoader) throws Exception { - Runnable runner = createMainMethodRunner(mainClass, args, classLoader); Thread.currentThread().setContextClassLoader(classLoader); - runner.run(); + createMainMethodRunner(mainClass, args, classLoader).run(); } /** @@ -105,15 +92,11 @@ public abstract class Launcher { * @param mainClass the main class * @param args the incoming arguments * @param classLoader the classloader - * @return a runnable used to start the application - * @throws Exception if the main method runner cannot be created + * @return the main method runner */ - protected Runnable createMainMethodRunner(String mainClass, String[] args, - ClassLoader classLoader) throws Exception { - Class runnerClass = classLoader.loadClass(RUNNER_CLASS); - Constructor constructor = runnerClass.getConstructor(String.class, - String[].class); - return (Runnable) constructor.newInstance(mainClass, args); + protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, + ClassLoader classLoader) { + return new MainMethodRunner(mainClass, args); } /** diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/MainMethodRunner.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/MainMethodRunner.java index 91cfbeee8e..bd6ea870f1 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/MainMethodRunner.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/MainMethodRunner.java @@ -16,16 +16,16 @@ package org.springframework.boot.loader; -import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Method; /** - * Utility class that used by {@link Launcher}s to call a main method. This class allows - * methods to be executed within a thread configured with a specific context class loader. + * Utility class that is used by {@link Launcher}s to call a main method. The class + * containing the main method is loaded using the thread context class loader. * * @author Phillip Webb + * @author Andy Wilkinson */ -public class MainMethodRunner implements Runnable { +public class MainMethodRunner { private final String mainClassName; @@ -41,26 +41,11 @@ public class MainMethodRunner implements Runnable { this.args = (args == null ? null : args.clone()); } - @Override - public void run() { - try { - Class mainClass = Thread.currentThread().getContextClassLoader() - .loadClass(this.mainClassName); - Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); - if (mainMethod == null) { - throw new IllegalStateException( - this.mainClassName + " does not have a main method"); - } - mainMethod.invoke(null, new Object[] { this.args }); - } - catch (Exception ex) { - UncaughtExceptionHandler handler = Thread.currentThread() - .getUncaughtExceptionHandler(); - if (handler != null) { - handler.uncaughtException(Thread.currentThread(), ex); - } - throw new RuntimeException(ex); - } + public void run() throws Exception { + Class mainClass = Thread.currentThread().getContextClassLoader() + .loadClass(this.mainClassName); + Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); + mainMethod.invoke(null, new Object[] { this.args }); } } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java index 1d8601bd3d..ca3c902de0 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java @@ -55,7 +55,7 @@ public class WarLauncher extends ExecutableArchiveLauncher { } } - public static void main(String[] args) { + public static void main(String[] args) throws Exception { new WarLauncher().launch(args); }