JavaFX × Spring Boot

この記事は [JavaFX Advent Calendar 2015 - Qiita](http://qiita.com/advent-calendar/2015/javafx) の 21 日目の記事です。
昨日は [@fukai_yas](https://twitter.com/fukai_yas) さんの [ScalaFXでのListViewの扱い](http://blog.livedoor.jp/fukai_yas/archives/47274295.html) でした。
明日は [@orekyuu](https://twitter.com/orekyuu) さんです。

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

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

[JavaFXのControllerにおけるSpringBeanのインジェクションについて - タツノオトシゴの日記](http://d.hatena.ne.jp/tatsu-no-toshigo/20130503/1367810193)

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

```groovy: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` を用意して。。。

```java: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` は、以下のような感じです。

```java: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` を設定しているところです。

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

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

```java: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` で他のビーンをインジェクションしています。

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

```java: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](https://qiita-image-store.s3.amazonaws.com/0/28302/08cd6ba1-ce13-7a51-f4b4-e2d2e9fefabe.gif)

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

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

# おまけ
これで終わるのはアレなので、 [Java EE のアドベントカレンダー](http://qiita.com/opengl-8080/items/a9dddb09d4a3e8f74439) で作ったライフゲームを JavaFX に移植してみました。

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

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

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

![javafx-springboot_2.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/5bb61905-52d2-8a55-01d7-295dc0be409e.jpeg)

↓

![javafx_springboot-compressor.gif](https://qiita-image-store.s3.amazonaws.com/0/28302/e79eaadc-6069-91a7-e65e-8a15e3007282.gif)

(；ﾟдﾟ)デューーーーーーーーーーーーーーーーーーーーーーク！！！！


# 参考
- [JavaFXのControllerにおけるSpringBeanのインジェクションについて - タツノオトシゴの日記](http://d.hatena.ne.jp/tatsu-no-toshigo/20130503/1367810193)
- [Java技術最前線 - JavaFX 2ではじめる、GUI開発　第17回 キャンバス：ITpro](http://itpro.nikkeibp.co.jp/article/COLUMN/20131022/512742/?rt=nocnt)
- [JavaFX 2.2 独自コンポーネントをFXMLで使う - denko_iの日記](http://d.hatena.ne.jp/denko_i/20121116/1353051352)
- [Spring Boot Reference Guide](http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-application-arguments)