単独のJavaプログラムを起動するには、staticなmainメソッドを持つクラスをjavaコマンドに指定します。
public class Hello { public static void main(String... args) { System.out.println("Hello, world"); } }
~$ java Hello Hello, world ~$
ところが、JDK 8でJavaFXアプリケーションを作った場合、mainメソッドがないのに実行できてしまいます。
import javafx.application.Application; import javafx.stage.Stage; public class HelloFx extends Application { @Override public void start(Stage stage) { stage.show(); } }
~$ java HelloFx
コンパイルされたHelloFx.classを調べても、mainメソッドはありません。
~$ javap HelloFx Compiled from "HelloFx.java" public class HelloFx extends javafx.application.Application { public HelloFx(); public void start(javafx.stage.Stage) throws java.lang.Exception; } ~$
javaコマンドに何か細工が施されて、JavaFXだけmainメソッドなしでも実行できるようになっているのでしょうか?
JDK 8のソースコードを取得して中を調べてみました。
~$ hg clone http://hg.openjdk.java.net/jdk8u/jdk8u; cd jdk8u; sh get_source.sh destination directory: jdk8u requesting all changes adding changesets adding manifests adding file changes added 1312 changesets with 1709 changes to 141 files updating to branch default 85 files updated, 0 files merged, 0 files removed, 0 files unresolved # Repositories: corba jaxp jaxws langtools jdk hotspot nashorn :(後略) jdk8u$
javaコマンドのソースコードは、jdk/src/share/bin にあります。
- jdk/src/share/bin/main.c
- main()
- コマンドラインを解析後、JLI_Launchに処理を渡す
- main()
- jdk/src/share/bin/java.c
- JLI_Launch()
- JVMをロード後、JVMInitに処理を渡す
- JLI_Launch()
- jdk/src/windows/bin/java_md.c
- JVMInit()
- ShowSplashScreenを呼び、次にContinueInNewThreadに処理を渡す
- JVMInit()
- jdk/src/share/bin/java.c
- ContinueInNewThread()
- ContinueInNewThread0に処理を渡す
- ContinueInNewThread()
- jdk/src/windows/bin/java_md.c
- ContinueInNewThread0()
- スレッドを生成し、スレッドでJavaMainを実行
- ContinueInNewThread0()
- jdk/src/share/bin/java.c
- JavaMain()
- InitializationJVMを呼び、
- LoadMainClassを呼び、[*1]
- GetApplicationClassを呼び、
- PostJVMInitを呼び
- GetStaticMethodIDで[*1]で取得したmainClassからメソッド名"main"、シグニチャが"([Ljava/lang/String;)V"のメソッドを取得[*2]
- CreateApplicationArgsを呼び
- CallStaticVoidMethodで[*1]のmainClassをターゲットに[*2]のメソッドを実行
- JavaMain()
という流れです。
ここまでで、やはりmainメソッドがないとプログラム起動ができないことが分かります。メソッド名mainがハードコーディングされているので、ここが揺らぐことはないでしょう。
すると、次に怪しむ箇所はmainメソッドを呼び出すターゲットのmainClassです。これが差し替えられている疑いがあります。
そこで、jdk/src/share/bin/java.cにあるLoadMainClass関数を追いかけてみます。
- jdk/src/share/bin/java.c
- LoadMainClass()
- GetLauncherHelperClassを呼び(これはsun.launcher.LauncherHelperクラスを返却)
- このクラスのメソッドcheckAndLoadMainを実行
- LoadMainClass()
ここからJavaのコードに替わります。
このメソッドのコメントを見ると、次の記述があります。
* 4. if no main method and if the class extends FX Application, then call
* on FXHelper to determine the main class to launch
訳すと、「もしmainメソッドが定義されておらず、かつ FXのApplicationを継承したクラスであれば、そのときは起動するメインクラスを決定するためFXHelperを呼び出す」ということです。
checkAndLoadMainメソッドの中から、関係しそうな処理を抜き出して見ます。
if (mainClass.equals(FXHelper.class) || FXHelper.doesExtendFXApplication(mainClass)) { // Will abort() if there are problems with the FX runtime FXHelper.setFXLaunchParameters(what, mode); return FXHelper.class; }
FXHelperは、LauncherHelperのネストクラスです。doesExtendFXApplicationメソッドは、引数に指定されたクラスが、javafx.application.Applicationクラスをスーパークラスに持つときにtrueを返します(次のコード)。
private static boolean doesExtendFXApplication(Class<?> mainClass) { for (Class<?> sc = mainClass.getSuperclass(); sc != null; sc = sc.getSuperclass()) { if (sc.getName().equals(JAVAFX_APPLICATION_CLASS_NAME)) { return true; } } return false; }
そして、FXHelperクラスをリターンしています。FXHelperはmainメソッドを定義しています。
FXHelperのmainメソッドでは、com.sun.javafx.application.LauncherImplクラスのlaunchApplication(String, String, String[])メソッドを呼び出しています。
public static void main(String... args) throws Exception { if (fxLauncherMethod == null || fxLaunchMode == null || fxLaunchName == null) { throw new RuntimeException("Invalid JavaFX launch parameters"); } // launch appClass via fxLauncherMethod fxLauncherMethod.invoke(null, new Object[] {fxLaunchName, fxLaunchMode, args}); }
このあとは、LauncherImplクラスのlaunchApplicationWithArgsメソッドが呼び出され、そこでJavaFXの初期化、もともとjavaコマンドで指定したアプリケーションクラスのロードとそのmainメソッド実行が行われます。mainメソッドがないときは、アプリケーションクラスをインスタンス化し、init()を呼び、Stageをインスタンス化し、startメソッドの引数に指定して呼び出しています。