Simplify exception handling and reporting in the launcher
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
This commit is contained in:
parent
491ab3dd31
commit
313b6f6451
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue