实际上我一直对 Java 如何启动 JavaFX 应用程序很感兴趣,所以我决定调试这个过程。在其余答案之前的一些事情:
- 我使用 JDK-10 对独立桌面应用程序进行了调试。快速浏览一下 JDK-11 源代码表明该过程在版本之间没有改变。
- 当我使用
Application 时,我指的是 javafx.application.Application 类。
- 当我使用“
main 方法”时,我指的是public static void main(String[] args) 方法。同样,“主类”是指包含main 方法的类。
- 源代码的所有链接都指向 OpenJDK Mercurial 存储库。
- 实际上所有这些都是实施细节,如有更改,恕不另行通知。
总结
启动 JavaFX 应用程序时,如果主类是 Application 的子类,则 Java 启动器使用 它自己的内部主类。这个内部类负责初始化 JavaFX 工具包。初始化工具包后,可能会发生以下两种情况之一。
-
Application 子类有一个main 方法。
- 在这种情况下,一些内部 JavaFX 代码会调用
main 方法。现在由开发人员负责通过Application.launch 完成启动应用程序。
-
Application 子类没有 main 方法。
- 在这种情况下,相同的内部 JavaFX 代码会启动应用程序本身。第一个案例最终会在同一个地方结束。
基本上,在Application 子类中声明的任何main 方法都不是“正常的”main 方法。想想这样的行为:
- 内部
main 方法充当 Java 应用程序的入口点,就像所有“普通”main 方法一样
-
Application 子类的main 方法用作 JavaFX 应用程序的可选入口点,其中 JavaFX 工具包已经初始化。
详细解答
首先,您不能“覆盖”Application 类的main 方法; Application 类没有 main 方法。实际发生的是,只要应用程序声明的主类是Application 的子类,Java 就会使用它自己的主类。对于后代,可以使用以下方法之一声明主类:
- 在命令行(文件)上指定它:
java -cp <classpath> foo.Main
- 在命令行(模块)上指定它:
java -p <modulepath> -m foo/foo.Main
- 在 JAR 清单中指定它:
Main-Class: foo.Main
- (我忘记的另一种方式?)
步骤
这些步骤假设一个 JavaFX 应用程序。如果启动“常规”Java 应用程序,大多数情况都不会发生。
第一步:加载主类
内部类LauncherHelper 通过名为checkAndLoadMain 的方法检查并加载主类。此方法负责根据主类的声明方式(如上所述)解析主类。一旦找到,该方法检查主类是否是Application 的子类。如果是,则主类更改为静态内部类:LauncherHelper$FXHelper。然后执行一些验证并将Class 返回到本机代码。
相关代码:
第 2 步:调用main 方法
在找到、加载和验证主类之后,它会从(我假设)本机代码中调用。由于我们讨论的是 JavaFX 应用程序,因此主类现在是 LauncherHelper$FXHelper。该类的main 方法做了一件简单的事情:通过反射调用内部JavaFX 代码。
相关代码:
第 3 步:JavaFX 预启动
第 2 步调用的代码在一个名为 LauncherImpl 的类中;具体来说,launchApplication(String,String,String[]) 方法。此方法似乎与 LauncherHelper.checkAndLoadMain 执行类似的操作,但更具体到 JavaFX。
我相信这个方法类似于checkAndLoadMain,因为checkAndLoadMain 方法验证了FXHelper 类,这显然是有效的。但是,launchApplication 需要验证 Application 子类。
相关代码:
第 4 步:JavaFX 启动
下一个调用的方法是launchApplicationWithArgs(ModuleAccess,String,String,String[])。此方法负责启动 JavaFX 工具包。在此之后,它加载 Application 子类,如果存在,则将 Preloader 子类加载为实际的 Class 实例。它在 JavaFX 应用程序线程 上执行此操作,然后返回到主线程。
然后根据main 子类中main 方法的存在,代码采用两条路径之一:
-
存在
main 方法:继续执行步骤 5。
-
main 方法不存在: 继续第 6 步(直接启动应用程序)
相关代码:
第5步:调用Application子类的main方法(可选)
如果Application 子类中有main 方法,则通过反射调用它。现在开发人员有责任通过调用Application.launch 继续启动过程。 launch 方法有两个重载:
Application.launch(String...)
Application.launch(Class,String)
唯一的区别是第一个选项使用调用 Class 作为 JavaFX Application 子类。两人最终都打电话给LauncherImpl.launchApplication(Class,String[])。如果需要,后一种方法只需加载Preloader 的Class,然后继续下一步。
相关代码:
第 6 步:完成启动 JavaFX 应用程序
现在我们使用LauncherImpl.launchApplication(Class,Class,String[]) 方法。这个方法做了两个简单的事情:
- 创建并启动调用另一个方法的 JavaFX-Launcher 线程
LauncherImpl.launchApplication1(Class,Class,String[])
- 将主线程(或任何称为
Application.launch 的线程)驻留在 CountDownLatch 中,直到 JavaFX 工具包退出。
launchApplication1 方法将启动 JavaFX 工具包(如果尚未启动)。然后它继续实现标准的 JavaFX 生命周期。这涉及创建Application 和Preloader 类(如果存在),然后在适当的时间在适当的线程上调用init() 和start(Stage) 方法。这个生命周期是公开定义的行为;几乎这里提到的所有其他内容都是实现细节。
相关代码:
非Application主类
还有另一种启动 JavaFX 应用程序的方法,其中主类不是Application 的子类,如下所示:
// main class
public class Main {
public static void main(String[] args) {
Application.launch(App.class, args);
}
}
// JavaFX Application class
public class App extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
// setup and show primaryStage
}
}
由于Main 不是Application 的子类,因此不会在步骤1 中更改为FXHelper。这也意味着步骤 2-5 不会自动发生。相反,在Main 中对Application.launch 的调用在最后一步开始此过程: 6. 这就是launchApplication1 方法也尝试启动JavaFX 工具包的原因,因为它不会在这种情况下启动。