问题
The Hello World-Tutorial of JavaFX says:
The main() method is not required for JavaFX applications when the JAR file for the application is created with the JavaFX Packager tool, which embeds the JavaFX Launcher in the JAR file. However, it is useful to include the main() method so you can run JAR files that were created without the JavaFX Launcher, such as when using an IDE in which the JavaFX tools are not fully integrated. Also, Swing applications that embed JavaFX code require the main() method.
I tried this and its true, I can start my application without a main
method.
However when I declare a main
method an call launch
from the Application
class, the program still works.
The documentation of Application says, that the JavaFX runtime is creating an instance of the Application
class and calls the init
method.
But how does the JavaFX runtime start? I mean there has to be a main
method somewhere, for everything to start. So Iam wondering if I declare a main
method by myself, arent there two of them?
回答1:
I've actually always been interested in how Java launches JavaFX applications, so I decided to debug the process. Some things before the rest of the answer:
- I did the debugging with JDK-10 for a standalone desktop application. Some quick glances at the JDK-11 source code suggests the process has not changed between versions.
- When I use
Application
I'm referring to thejavafx.application.Application
class. - When I use "
main
method" I'm referring to thepublic static void main(String[] args)
method. Similarly, "main class" refers to the class containing themain
method. - All links for source code point to the OpenJDK Mercurial repository.
- Virtually all of this is an implementation detail and is subject to change without notice.
Summary
When launching a JavaFX application, if the main class is a subclass of Application
then the Java launcher uses it's own, internal main class. This internal class is responsible for initializing the JavaFX toolkit. Once the toolkit is initialized, one of two things can happen.
- The
Application
subclass has amain
method.- In this case, some internal JavaFX code invokes the
main
method. It is now the developers responsibility to finish launching the application viaApplication.launch
.
- In this case, some internal JavaFX code invokes the
- The
Application
subclass does not have amain
method.- In this case, the same internal JavaFX code launches the application itself. The first case eventually ends up in the same place this case does.
Basically, any main
method declared in Application
subclasses are not "normal" main
methods. Think of this behavior like this:
- The internal
main
method acts as the entry point for the Java application—just like all "normal"main
methods - The
Application
subclass'main
method serves as an optional entry point for the JavaFX application, where the JavaFX toolkit is already initialized.
Detailed Answer
First, it is not the case that you "override" the main
method of the Application
class; the Application
class has no main
method. What actually happens is that Java uses it's own main class whenever the application's declared main class is a subclass of Application
. For posterity, a main class can be declared using one of the following:
- Specifying it on the command line (file):
java -cp <classpath> foo.Main
- Specifying it on the command line (module):
java -p <modulepath> -m foo/foo.Main
- Specifying it in the JAR manifest:
Main-Class: foo.Main
- (Another way I'm forgetting?)
The Steps
These steps assume a JavaFX application. Most of this doesn't happen if launching a "regular" Java application.
Step 1: Load the Main Class
An internal class, LauncherHelper
, checks and loads the main class via a method named checkAndLoadMain
. This method is responsible for resolving the main class based on how the main class was declared (described above). Once found, this method checks whether or not the main class is a subclass of Application
. If it is, then the main class is changed to a static inner class: LauncherHelper$FXHelper
. Then some validation is performed and the Class
is returned to, I assume, native code.
Relevant code:
- java.base/sun.launcher.LauncherHelper
- java.base/sun.launcher.LauncherHelper.checkAndLoadMain
- java.base/sun.launcher.LauncherHelper$FXHelper
Step 2: Invoke the main
Method
After the main class has been found, loaded, and validated it is called from (I assume) native code. Since we are talking about a JavaFX application the main class is now LauncherHelper$FXHelper
. The main
method of this class does one simple thing: Invoke internal JavaFX code via reflection.
Relevant code:
- java.base/sun.launcher.LauncherHelper$FXHelper.main
Step 3: JavaFX Pre-Startup
The code invoked by step 2 is inside a class named LauncherImpl
; specifically, the launchApplication(String,String,String[])
method. This method appears to do similar things as LauncherHelper.checkAndLoadMain
except more specific to JavaFX.
I believe this method is similar to checkAndLoadMain
because the checkAndLoadMain
method validated the FXHelper
class, which is obviously valid. However, launchApplication
needs to validate the Application
subclass.
Relevant code:
- javafx.graphics/com.sun.javafx.application.LauncherImpl
- javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication
Step 4: JavaFX Startup
The next method called is launchApplicationWithArgs(ModuleAccess,String,String,String[])
. This method is responsible for starting the JavaFX toolkit. After this, it loads the Application
subclass and, if present, Preloader
subclass as actual Class
instances. It does this on the JavaFX Application Thread but then returns to the main thread.
The code then takes one of two paths depending on the presence of a main
method in the Application
subclass:
- A
main
method exists: Proceed to step 5. - A
main
method does not exist: Proceed to step 6 (launch the application directly)
Relevant code:
- javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs
Step 5: Invoke main
Method of Application
Subclass (Optional)
If there is a main
method in the Application
subclass it is invoked via reflection. It is now the responsibility of the developer to continue the launching procedure via a call to Application.launch
. There are two overloads of the launch
method:
Application.launch(String...)
Application.launch(Class,String)
The only difference being the first option uses the calling Class
as the JavaFX Application
subclass. Both end up calling LauncherImpl.launchApplication(Class,String[])
. This latter method simply loads the Class
of the Preloader
, if needed, and then continues on to the next step.
Relevant code:
- javafx.graphics/javafx.application.Application.launch(String...)
- javafx.graphics/javafx.application.Application.launch(Class,String...)
- javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication
- Note: Different method than the one linked in step 3.
Step 6: Finish Launching JavaFX Application
Now we're in the LauncherImpl.launchApplication(Class,Class,String[])
method. This method does two simple things:
- Create and start the JavaFX-Launcher thread which calls another method
LauncherImpl.launchApplication1(Class,Class,String[])
- Park the main thread (or whatever thread called
Application.launch
) in aCountDownLatch
until the JavaFX toolkit exits.
The launchApplication1
method will start the JavaFX toolkit if it hasn't already been started. Then it continues on to implement the standard JavaFX life-cycle. This involves creating the Application
and, if present, Preloader
classes then calling the init()
and start(Stage)
methods at the appropriate times on the appropriate threads. This life-cycle is the publicly defined behavior; virtually everything else mentioned here are implementation details.
Relevant code:
- javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication
- Note: Again, different method from the ones linked in steps 3 and 5.
- javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1
Non-Application
Main Class
There is another way to launch JavaFX applications where the main class is not a subclass of Application
, like so:
// 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
}
}
Since Main
is not a subclass of Application
the change to FXHelper
, in step 1, doesn't take place. This also means that steps 2-5 don't happen automatically. Instead, the call to Application.launch
in Main
starts this process at the final step: 6. That's why the the launchApplication1
method also tries to start the JavaFX toolkit since it wouldn't have been started in this scenario.
回答2:
One possible answer would probably be, that there is a main
method declared in Application
. And if another class, which extends from Application
, has its own main
method, the code would still be valid and the main
method of Application
will be ignored::
class Main extends OtherMain {
public Main(String text) {
System.out.println(text);
}
/* If this method is removed, the main-method is called from OtherMain */
public static void main(String[] args) {
new Main("Called from Main-Class");
}
}
class OtherMain {
public static void main(String[] args) {
new Main("Called from Other-Main-Class");
}
}
Does this make sense?
来源:https://stackoverflow.com/questions/52434404/javafx-runtime-main-method