JavaFX Application source 通过解析堆栈跟踪来确定和检查调用者(例如,确保它只从扩展 Application 的类中调用)来做一些时髦的事情。
/**
* Launch a standalone application. This method is typically called
* from the main method(). It must not be called more than once or an
* exception will be thrown.
* This is equivalent to launch(TheClass.class, args) where TheClass is the
* immediately enclosing class of the method that called launch. It must
* be a subclass of Application or a RuntimeException will be thrown.
*
* <p>
* The launch method does not return until the application has exited,
* either via a call to Platform.exit or all of the application windows
* have been closed.
*
* <p>
* Typical usage is:
* <ul>
* <pre>
* public static void main(String[] args) {
* Application.launch(args);
* }
* </pre>
* </ul>
*
* @param args the command line arguments passed to the application.
* An application may get these parameters using the
* {@link #getParameters()} method.
*
* @throws IllegalStateException if this method is called more than once.
*/
public static void launch(String... args) {
// Figure out the right class to call
StackTraceElement[] cause = Thread.currentThread().getStackTrace();
boolean foundThisMethod = false;
String callingClassName = null;
for (StackTraceElement se : cause) {
// Skip entries until we get to the entry for this class
String className = se.getClassName();
String methodName = se.getMethodName();
if (foundThisMethod) {
callingClassName = className;
break;
} else if (Application.class.getName().equals(className)
&& "launch".equals(methodName)) {
foundThisMethod = true;
}
}
if (callingClassName == null) {
throw new RuntimeException("Error: unable to determine Application class");
}
try {
Class theClass = Class.forName(callingClassName, true,
Thread.currentThread().getContextClassLoader());
if (Application.class.isAssignableFrom(theClass)) {
Class<? extends Application> appClass = theClass;
LauncherImpl.launchApplication(appClass, args);
} else {
throw new RuntimeException("Error: " + theClass
+ " is not a subclass of javafx.application.Application");
}
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
LauncherImpl 代码利用私有静态 AtomicBoolean 上的 getAndSet 操作来确保应用程序不会多次启动。
// Ensure that launchApplication method is only called once
private static AtomicBoolean launchCalled = new AtomicBoolean(false);
/**
* This method is called by the standalone launcher.
* It must not be called more than once or an exception will be thrown.
*
* Note that it is always called on a thread other than the FX application
* thread, since that thread is only created at startup.
*
* @param appClass application class
* @param preloaderClass preloader class, may be null
* @param args command line arguments
*/
public static void launchApplication(final Class<? extends Application> appClass,
final Class<? extends Preloader> preloaderClass,
final String[] args) {
if (launchCalled.getAndSet(true)) {
throw new IllegalStateException("Application launch must not be called more than once");
}
if (! Application.class.isAssignableFrom(appClass)) {
throw new IllegalArgumentException("Error: " + appClass.getName()
+ " is not a subclass of javafx.application.Application");
}
if (preloaderClass != null && ! Preloader.class.isAssignableFrom(preloaderClass)) {
throw new IllegalArgumentException("Error: " + preloaderClass.getName()
+ " is not a subclass of javafx.application.Preloader");
}
// Create a new Launcher thread and then wait for that thread to finish
final CountDownLatch launchLatch = new CountDownLatch(1);
Thread launcherThread = new Thread(new Runnable() {
@Override public void run() {
try {
launchApplication1(appClass, preloaderClass, args);
} catch (RuntimeException rte) {
launchException = rte;
} catch (Exception ex) {
launchException =
new RuntimeException("Application launch exception", ex);
} catch (Error err) {
launchException =
new RuntimeException("Application launch error", err);
} finally {
launchLatch.countDown();
}
}
});
launcherThread.setName("JavaFX-Launcher");
launcherThread.start();
// Wait for FX launcher thread to finish before returning to user
try {
launchLatch.await();
} catch (InterruptedException ex) {
throw new RuntimeException("Unexpected exception: ", ex);
}
if (launchException != null) {
throw launchException;
}
}
所以这有点复杂和奇怪,但是如果您想要一个适用于 JavaFX 代码库的经过验证的解决方案,您可以尝试解析它以了解正在发生的事情并使其适应您的情况。
我想说,只有在对您的应用程序至关重要的情况下才将这种额外的复杂性引入您的应用程序。
Orodous 提出了一些很好的观点,说明这种逻辑对单元测试的阻碍程度。例如,看看这个关于JavaFX application 的单元测试部分的建议。由于启动器中的奇怪检查,为了独立测试其应用程序的功能,开发人员需要通过奇怪的箍绕过调用任何启动器代码(例如,使用基于 Swing 的 JFXPanel 而不是应用程序初始化 JavaFX,因为应用程序只能启动一次)。