2019-09-02  Java プログラミング

IntelliJ IDEAでGradleプロジェクトの実行がエラーになる!?

Eclipse で開発していた JavaFX アプリケーション (Gradle プロジェクト形式) がありました。この Gradle プロジェクトを IntelliJ IDEA にインポートして実行すると、 IllegalStateException: Location is not set. というエラーが発生してアプリケーションの起動に失敗してしまいました。Eclipse では実行できていたのになぜ?

その原因は Eclipse と IntelliJ IDEA のリソースの扱いの違いにありました。Gradle プロジェクトでリソースをどこに配置するのがよいか紹介したいと思います。

IntelliJ IDEA で JavaFX アプリケーションを実行したときに発生した例外は以下の内容でした。

Exception in Application start method
java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:464)
	at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:363)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1051)
Caused by: java.lang.RuntimeException: Exception in Application start method
	at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:900)
	at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(LauncherImpl.java:195)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: java.lang.IllegalStateException: Location is not set.
	at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2459)
	at javafx.fxml/javafx.fxml.FXMLLoader.load(FXMLLoader.java:2435)
	at com.example.myapp.Main.start(Main.java:21)
	at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$(LauncherImpl.java:846)
	at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:455)
	at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:428)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:427)
	at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
	at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
	at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:174)
	... 1 more

どうやら FXML のロードに失敗しているようです。

Gradleプロジェクトの構成

私は JavaFX アプリケーションを以下のような構成にしています。

  • myapp
    • build.gradle
    • settings.gradle
    • src
      • main
        • java
          • com
            • example
              • myapp
                • Main.java
                • Main.fxml
                • Main.css
        • resources
          • img
            • icon.png

FXML と CSS をソースコード (.java) と同じ場所に配置しています。こうすることで、 以下のように Main.java から Main.fxml をロードすることができます。

FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml"));
Parent root = (Parent)loader.load();

Eclipse(Buildship)の場合

Eclipse からアプリケーションを実行すると、 bin ディレクトリーにビルドしたファイル一式が出力されます。

  • myapp
    • bin
      • main
        • com
          • example
            • myapp
              • Main.class
              • Main.fxml
              • Main.css
        • img
          • icon.png

Eclipse の場合はコンパイルされた .class だけでなく、 ソースディレクトリーに含まれている Main.fxml と Main.css も出力ディレクトリーにコピーされています。これなら Main.class から Main.fxml がリソースとして参照できます。

IntelliJ IDEAの場合

IntelliJ IDEA からアプリケーションを実行すると、 out/production ディレクトリーにビルドしたファイル一式が出力されます。

  • myapp
    • out
      • production
        • classes
          • com
            • example
              • myapp
                • Main.class
        • resources
          • img
            • icon.png

おや? IntelliJ IDEA では Main.fxml と Main.css が出力ディレクトリーにコピーされていません。これでは、 FXML のロードに失敗するのも当然ですよね…。

FXMLをどこに置けばいいの?

Gradle としては IntelliJ IDEA の動作が正しいようです。Eclipse ではソースディレクトリーに含まれるリソースも出力ディレクトリーにコピーされるため、 運よく動作していたということでした。

.fxml も .css もリソースディレクトリーに配置するのが Gradle の正しい作法です。

私だって、 リソースディレクトリー (resources) の存在を知らなかったわけではありません。画像などのリソースは resources に配置していました。ただ、 Main.java と対になる Main.fxml は同じ場所に置いておいたほうが管理しやすいと思ったんです。

以下のように Main.fxml と Main.css を resources の配下に配置すれば、 IntelliJ IDEA でもアプリケーションの起動に成功しました。

  • myapp
    • build.gradle
    • settings.gradle
    • src
      • main
        • java
          • com
            • example
              • myapp
                • Main.java
        • resources
          • com
            • example
              • myapp
                • Main.fxml
                • Main.css
          • img
            • icon.png

うーん、 でもやっぱり、 このツリー構造は嫌です。

開発時には .java、 .fxml、 .css を相互に行ったり来たりしてコーディングをおこなうわけで、 これらのファイルが異なるディレクトリーに配置されているというのは扱いづらいです。

リソースディレクトリーを追加しよう!

この問題はリソースディレクトリーを追加することで解決することができます。

Gradle では既定で以下の構成になっています。

ソースディレクトリー
src/main/java
リソースディレクトリー
src/main/resources

これはあくまでも既定値であって build.gradle を書き換えればディレクトリーを変更したり追加したりすることもできます。そこで、 src/main/java をリソースディレクトリーに追加してみましょう。

ソースディレクトリー
src/main/java
リソースディレクトリー
src/main/resources
src/main/java (←追加)

このような構成にするわけです。src/main/java はソースディレクトリーでもあり、 リソースディレクトリーでもあるということです。これなら、 src/main/java 配下のファイルも出力ディレクトリーにコピーされるはず。

具体的には build.gradle に以下の内容を追加します。

sourceSets.main {
    resources {
        srcDirs = ['src/main/resources', 'src/main/java']
    }
}

これで、 当初のツリー構造を維持したまま、 無事に IntelliJ IDEA でもアプリケーションを実行できるようになりました。

  • myapp
    • build.gradle
    • settings.gradle
    • src
      • main
        • java ← ソースディレクトリー兼リソースディレクトリー
          • com
            • example
              • myapp
                • Main.java
                • Main.fxml
                • Main.css
        • resources
          • img
            • icon.png

リソースディレクトリーに src/main/java を追加したのにあわせて、 .java が出力ディレクトリーにコピーされないように exclude 指定も必要かなと思ったのですが、 .java は出力ディレクトリーにコピーされていませんでした。特別扱いされているのかもしれません。

サンプルコード一式

最後に、 動作確認に使用したコード一式です。

Main.java
package com.example.myapp; import java.io.IOException; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) throws IOException { FXMLLoader loader = new FXMLLoader(getClass().getResource("Main.fxml")); loader.setController(this); Parent root = (Parent)loader.load(); Scene scene = new Scene(root); primaryStage.setScene(scene); primaryStage.show(); } }
Main.fxml
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <StackPane stylesheets="@Main.css" xmlns:fx="http://javafx.com/fxml" prefWidth="240" prefHeight="160"> <Button text="Hello!"/> </StackPane>
Main.css
.root { -fx-font-family: "Meiryo"; -fx-font-size: 12px; } Button { -fx-text-fill: blue; }
build.gradle
apply plugin: 'java' def defaultEncoding = 'UTF-8' tasks.withType(AbstractCompile).each { it.options.encoding = defaultEncoding } tasks.withType(GroovyCompile).each { it.groovyOptions.encoding = defaultEncoding } sourceSets.main { resources { srcDirs = ['src/main/resources', 'src/main/java'] } } repositories { jcenter() } dependencies { }

サンプルコードは以下のリンクからダウンロードできます。

最終更新日 2024-12-13
この記事を共有しませんか?
ブックマーク ポスト