このエントリーは JavaFX Advent Calendar 2014 の最終日です。
昨日は @orekyuu さんの 24 日目なのに、タイトルは 25 日目になっている JavaFX Advent Calendar25日目 ~ JavaFXで夢のCanvasライフ でした。
いつも、アニメーションなどの描画ネタが多いのですが、今日は趣を変えて JavaFX のアプリケーションでプラグインを作ろうと思います。
意外に知られていないと思いますが、Java SE にはプラグインを作るのに便利なクラスがあります。
そのクラスは java.util.ServiceLoader クラスです。
ServiceLoader クラスについては、ずいぶん前に ITpro の Java 技術最前線に記事を書いたので、そちらをぜひご参照ください。
「Java SE 6完全攻略」第11回 コンポーネントのロードを行うServiceLoader
ようするに ServiceLoader クラスはプラグインを検索して、ロードしてくれるクラスです。
たとえば、プラグインを表すインタフェースを foo.Bar インタフェースとしましょう。
そして、プラグインごとに Jar ファイルを作成しますが、その時に META-INF/services/foo.Bar というファイルを作成します。その foo.Bar ファイルには foo.Bar インタフェースを実装したクラス名を記述します。
これだけでその Jar ファイルをクラスパスに含めておけば、プラグインとしてロードすることができます。
ここでは、プラグインの仕組みだけを示すために、簡単なアプリケーションを作ることにします。アプリケーションには複数のタブがあり、そのタブの中身をプラグインで表示させるということにします。
こんな感じ。
プラグインは、ファクトリとプラグインの本体という構造にしたいと思います。
まずファクトリは PluginFactory インタフェースとします。
package net.javainthebox.fxplugin.plugin;
import java.util.Optional;
public interface PluginFactory {
String getName();
Optional<Plugin> createPlugin();
}getName メソッドはタブの名前を返すためのメソッド、プラグインの本体は createPlugin メソッドで生成します。
プラグインの JAR ファイルは、このインタフェース名と同じファイルを作成します。つまり、META-INF/services/net.javainthebox.fxplugin.plugin.PluginFactory ファイルです。
そして、プラグインの本体は Plugin インタフェースです。
public interface Plugin {
Node getContent();
}そして、このプラグインをロードするためのアプリケーションはすごい簡単なものにしました。
public class Main extends Application {
@Override
public void start(Stage stage) {
StackPane root = new StackPane();
TabPane tabs = new TabPane();
root.getChildren().add(tabs);
loadPlugins(tabs);
Scene scene = new Scene(root, 300, 250);
stage.setTitle("FXPlugin");
stage.setScene(scene);
stage.show();
}
private void loadPlugins(TabPane tabs) {
ServiceLoader<PluginFactory> loader
= ServiceLoader.load(PluginFactory.class);
loader.forEach(factory -> {
Tab tab = new Tab(factory.getName());
factory.createPlugin().ifPresent(plugin -> tab.setContent(plugin.getContent()));
tabs.getTabs().add(tab);
});
}
public static void main(String... args) {
launch(args);
}
}ここで、プラグインをロードしているのは loadPlugins メソッドです。
ServiceLoader オブジェクトは load メソッドで生成できます。load メソッドの引数はロードするプラグインのインタフェースです。
ServiceLoader クラスは Iterable インタフェースを実装しているので、forEach メソッドを使用できます。
まず、ファクトリの getName メソッドを使用して、Tab オブジェクトを作成します。
そして、プラグインの本体は createPlugin メソッドで生成します。この返り値は Optional クラスなので、値があるときだけ、Tab オブジェクトのコンテンツをセットするようにしました。
これでプラグインをロードする部分はできました。
プラグインの作成
では次にプラグインを作成してみましょう。
ここではシンプルなプラグインということで、ボタンが 1 つだけあるプラグインを作成します。
まずはファクトリクラスです。
public class ButtonPluginFactory implements PluginFactory {
@Override
public String getName() {
return "Button";
}
@Override
public Optional<Plugin> createPlugin() {
try {
return Optional.of(new ButtonPlugin());
} catch (IOException ex) {
return Optional.empty();
}
}
}getName メソッドは Button を返すだけです。
createPlugin メソッドは IOException 例外が発生したら、空の Optional オブジェクトを返します。それ以外は ButtonPlugin オブジェクトを使用します。
では ButtonPlugin クラスを見てみましょう。
public class ButtonPlugin implements Plugin {
private AnchorPane content;
private ButtonPluginViewController controller;
public ButtonPlugin() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("ButtonPluginView.fxml"));
content =loader.load();
controller = loader.getController();
}
@Override
public Node getContent() {
return content;
}
}ここでは、FXML をロードしています。
FXMLLoader オブジェクトを生成しているのは、コントローラクラスを取得するためです。ここではコントローラクラスを直接アクセスしていませんが、大規模なアプリケーションの場合はコントロールクラスからモデルへアクセスするなど、コントローラクラスの取得が必要な場合が多くあります。
そういうときのためにも、FXMLLoader オブジェクトを生成しておく方がいいと思います。
いちおう FXML ファイルも示しておきましょう。
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane prefHeight="200.0" prefWidth="300.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="netjavainthebox.fxplugin.buttonplugin.ButtonPluginViewController">
<children>
<Button layoutX="117.0" layoutY="75.0" mnemonicParsing="false" onAction="#action" style="-fx-font-size: 24;" text="OK" AnchorPane.bottomAnchor="75.0" AnchorPane.leftAnchor="117.0" AnchorPane.rightAnchor="116.0" AnchorPane.topAnchor="75.0" />
</children>
</AnchorPane>コントローラクラスは、ボタンがクリックされたらダイアログを表示するだけです。
ダイアログを使用しているので、このサンプルをビルド、実行するには、JDK 8u40 が必要です。
public class ButtonPluginViewController implements Initializable {
@FXML
private void action(ActionEvent event) {
Alert alert = new Alert(AlertType.INFORMATION);
alert.setTitle("Button Plugin");
alert.getDialogPane().setHeaderText("Button Plugin");
alert.getDialogPane().setContentText("Button Plugin");
alert.show();
}
@Override
public void initialize(URL url, ResourceBundle rb) {
}
}そして、忘れてはいけないのが META-INF/services/net.javainthebox.fxplugin.plugin.PluginFactory ファイルです。
ファイルの中身は PluginFactory インタフェースの実装クラス名です。
netjavainthebox.fxplugin.buttonplugin.ButtonPluginFactory
もう 1 つ、同じように ListPlugin というのも作成しました。
そして、アプリケーションの実行時には、2 つのプラグインの JAR ファイルをクラスパスに付け加えます。Windows だったら、こんな感じで実行します。
java -cp PluginContainer.jar;ButtonPlugin.jar;ListPlugin.jar net.javainthebox.fxplugin.container.Main
実行すると、タブが 2 つ表示されます。
ちゃんとプラグインがロードできました。
ここでは、簡単なアプリケーションですが、これを応用すれば、複雑なアプリケーションもできるはずです!
サンプルのコードは GitHub で公開しています。


