Help us understand the problem. What is going on with this article?

JavaFX × Spring Boot

More than 3 years have passed since last update.

この記事は JavaFX Advent Calendar 2015 - Qiita の 21 日目の記事です。
昨日は @fukai_yas さんの ScalaFXでのListViewの扱い でした。
明日は @orekyuu さんです。

JavaFX を Spring Boot から起動してみます。

といっても、素の Spring と連携させる方法はすでに多く情報が公開されています。

JavaFXのControllerにおけるSpringBeanのインジェクションについて - タツノオトシゴの日記

なので、 Spring Boot から起動するのもほぼ同じ方法でいけます。

build.gradle
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.3.1.RELEASE'
    }
}

apply plugin: 'java'
apply plugin: 'spring-boot'

sourceCompatibility = '1.8'
targetCompatibility = '1.8'
compileJava.options.encoding = 'UTF-8'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
}

まずは普通に Spring Boot 用の build.gradle を用意して。。。

Main.java
package gl8080.javafx;

import java.io.IOException;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

@SpringBootApplication
public class Main extends Application { // ★Application を継承

    private static ConfigurableApplicationContext context;

    public static void main(String[] args) throws IOException {
        // ★ApplicationContext は後で使うので static 変数に保存しておく
        context = SpringApplication.run(Main.class, args); // ★Spring Boot を起動
        launch(args); // ★JavaFX を起動
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        // ★自作の FXMLLoader を Spring のコンテナから取得
        MySpringFXMLLoader loader = context.getBean(MySpringFXMLLoader.class);

        // ★fxml をロード
        Parent root = loader.load("sample.fxml");

        // ★あとは普通の JavaFX と同じ
        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    @Override
    public void stop() throws Exception {
        context.close(); // ★アプリ終了時に stop() メソッドがコールバックされるので、 Spring コンテナを終了させる
    }
}

起動用のクラスを作ります。

クラス自体は JavaFX の Application を継承して、 JavaFX と同じ感じで作ります。
そして、 main() メソッドの先頭で Spring Boot を起動してから JavaFX を起動(launch())させます。

start() メソッドが呼びだされたら、 main() メソッドで保存しておいた ConfigurableApplicationContext から自作の FXMLLoader を取得します。
自作の FXMLLoader から fxml をロードしたら、あとは普通の JavaFX と同じです。

Spring Boot の終了処理は、 Application クラスの stop() メソッドがアプリケーション終了時にコールバックされることを利用して、そこで実行するようにしています。

自作の FXMLLoader は、以下のような感じです。

MySpringFXMLLoader.java
package gl8080.javafx;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;

@Component // ★コンポーネントとして登録
public class MySpringFXMLLoader {

    @Autowired
    private ApplicationContext context;

    public Parent load(String path) throws IOException {
        FXMLLoader loader = new FXMLLoader(); // ★オリジナルの FXMLLoader を生成

        loader.setControllerFactory(this.context::getBean); // ★ControllerFactory に ApplicationContext を利用する

        return loader.load(MySpringFXMLLoader.class.getClassLoader().getResourceAsStream(path));
    }
}

処理自体は、ほとんどオリジナルの FXMLLoader に丸投げです。
唯一違うのが、 Controller のインスタンスを生成するための ControllerFactory を設定しているところです。

FXMLLoadersetControllerFacrorey() には javafx.util.Callback を実装したオブジェクトを渡します。
このインターフェースは関数型インターフェースになっていて、 ControllerClass<?> を受け取り、 Controller のインスタンスを返す必要があります。
それは、まさしく ApplicationContext#getBean() と一致するので、メソッド参照を使ってセットしています。

こうすることで、 Controller のインスタンスが Spring のコンテナから取得されるようになります。

MyController.java
package gl8080.javafx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javafx.fxml.FXML;
import javafx.scene.control.Label;

@Component
public class MyController {

    @FXML
    private Label label;

    @Autowired
    private MyBean bean;

    @FXML
    public void onClickButton() {
        String text = this.bean.getText();
        this.label.setText(text);
    }
}

コントローラクラスです。
Spring の @Autowired で他のビーンをインジェクションしています。

ボタンを押したら、ラベルの文字列をビーンが返した値で書き換えるだけの単純な実装です。

MyBean.java
package gl8080.javafx;

import org.springframework.stereotype.Component;

@Component
public class MyBean {

    public String getText() {
        return "Hello Spring Boot!!";
    }
}

MyBean の実装はこんな感じ。

実際に動かしてみます。

javafx_springboot.gif

問題なく連携できました!

コードは GitHub にあげています ( https://github.com/opengl-8080/javafx-and-spring-boot ) 。

おまけ

これで終わるのはアレなので、 Java EE のアドベントカレンダー で作ったライフゲームを JavaFX に移植してみました。

といっても、編集機能とかまで移植している余裕は、時間的にも体力的にもなかったので、とりあえずサンプルのゲーム定義だけを動かせるようにしました。

コードはこちら( https://github.com/opengl-8080/lifegame-javafx )です。

では、今回も彼に活躍していただきましょう!

javafx-springboot_2.JPG

javafx_springboot-compressor.gif

(;゚д゚)デューーーーーーーーーーーーーーーーーーーーーーク!!!!

参考

opengl-8080
ただのSE。Java好き。
tis
創業40年超のSIerです。
https://www.tis.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away