Eclipse で開発していた JavaIllegal
というエラーが発生してアプリケーションの起動に失敗してしまいました。Eclipse では実行できていたのになぜ?
その原因は Eclipse と Intelli
Intelli
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 プロジェクトの構成
私は Java
- myapp
- build
.gradle - settings
.gradle - src
- main
- java
- com
- example
- myapp
- Main
.java - Main
.fxml - Main
.css
- Main
- myapp
- example
- com
- resources
- img
- icon
.png
- icon
- img
- java
- main
- build
FXML と CSS をソースコード (.java
) と同じ場所に配置しています。こうすることで、 以下のように Main
から Main
をロードすることができます。
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
- Main
- myapp
- example
- img
- icon
.png
- icon
- com
- main
- bin
Eclipse の場合はコンパイルされた .class
だけでなく、 ソースディレクトリーに含まれている Main
と Main
も出力ディレクトリーにコピーされています。これなら Main
から Main
がリソースとして参照できます。
IntelliJ IDEA の場合
Intelliout
ディレクトリーにビルドしたファイル一式が出力されます。
- myapp
- out
- production
- classes
- com
- example
- myapp
- Main
.class
- Main
- myapp
- example
- com
- resources
- img
- icon
.png
- icon
- img
- classes
- production
- out
おや? IntelliMain
と Main
が出力ディレクトリーにコピーされていません。これでは、 FXML のロードに失敗するのも当然ですよね…。
FXML をどこに置けばいいの?
Gradle としては Intelli
.fxml
も .css
もリソースディレクトリーに配置するのが Gradle の正しい作法です。
私だって、 リソースディレクトリー (resources
) の存在を知らなかったわけではありません。画像などのリソースは resources
に配置していました。ただ、 Main
と対になる Main
は同じ場所に置いておいたほうが管理しやすいと思ったんです。
以下のように Main
と Main
を resources
の配下に配置すれば、 Intelli
- myapp
- build
.gradle - settings
.gradle - src
- main
- java
- com
- example
- myapp
- Main
.java
- Main
- myapp
- example
- com
- resources
- com
- example
- myapp
- Main
.fxml - Main
.css
- Main
- myapp
- example
- img
- icon
.png
- icon
- com
- java
- main
- build
うーん、 でもやっぱり、 このツリー構造は嫌です。
開発時には .java
、 .fxml
、 .css
を相互に行ったり来たりしてコーディングをおこなうわけで、 これらのファイルが異なるディレクトリーに配置されているというのは扱いづらいです。
リソースディレクトリーを追加しよう!
この問題はリソースディレクトリーを追加することで解決することができます。
Gradle では既定で以下の構成になっています。
- ソースディレクトリー
src
/main /java - リソースディレクトリー
src
/main /resources
これはあくまでも既定値であって build
を書き換えればディレクトリーを変更したり追加したりすることもできます。そこで、 src
をリソースディレクトリーに追加してみましょう。
- ソースディレクトリー
src
/main /java - リソースディレクトリー
src
/main /resources src
(←追加)/main /java
このような構成にするわけです。src
はソースディレクトリーでもあり、 リソースディレクトリーでもあるということです。これなら、 src
配下のファイルも出力ディレクトリーにコピーされるはず。
具体的には build
に以下の内容を追加します。
sourceSets.main {
resources {
srcDirs = ['src/main/resources', 'src/main/java']
}
}
これで、 当初のツリー構造を維持したまま、 無事に Intelli
- myapp
- build
.gradle - settings
.gradle - src
- main
- java ← ソースディレクトリー兼リソースディレクトリー
- com
- example
- myapp
- Main
.java - Main
.fxml - Main
.css
- Main
- myapp
- example
- com
- resources
- img
- icon
.png
- icon
- img
- java ← ソースディレクトリー兼リソースディレクトリー
- main
- build
リソースディレクトリーに src
を追加したのにあわせて、 .java
が出力ディレクトリーにコピーされないように exclude
指定も必要かなと思ったのですが、 .java
は出力ディレクトリーにコピーされていませんでした。特別扱いされているのかもしれません。
サンプルコード一式
最後に、 動作確認に使用したコード一式です。
Main.javapackage 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.gradleapply 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 {
}
サンプルコードは以下のリンクからダウンロードできます。