Edited at

JavaFXでCDI(Weld)を使用する方法メモ

More than 3 years have passed since last update.

JavaFXの実装でCDI(Weld)を利用する方法のメモ。


環境


Java

1.7.0_25


Weld

1.1.10


実装


Mainクラス


Main.java

package fx;

import javafx.application.Application;
import javafx.stage.Stage;

import javax.enterprise.util.AnnotationLiteral;

import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;

public class Main extends Application {

public static void main(String[] args) {
Application.launch(args);
}

private Weld weld;

@SuppressWarnings("serial")
@Override
public void start(Stage stage) throws Exception {
this.weld = new Weld();
WeldContainer container = this.weld.initialize();

container.event()
.select(Stage.class, new AnnotationLiteral<MyQualifier>() {})
.fire(stage);
}

@Override
public void stop() {
this.weld.shutdown();
}
}



イベント通知を絞り込むためのカスタム限定子


MyQualifier.java

package fx;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.inject.Qualifier;

@Qualifier
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyQualifier {
}



JavaFXの画面表示を開始するクラス


ApplicationStarter.java

package fx;

import java.io.IOException;
import java.io.InputStream;

import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import javax.enterprise.event.Observes;
import javax.inject.Inject;

public class ApplicationStarter {

@Inject private FXMLLoader loader;

public void start(@Observes @MyQualifier Stage stage) throws IOException {
try (InputStream fxml = MyController.getFxmlStream()) {
Parent root = (Parent)this.loader.load(fxml);
Scene scene = new Scene(root);

stage.setScene(scene);
stage.show();
}
}
}



FXMLLoader の Producer クラス


FXMLLoaderProducer.java


package fx;

import javafx.fxml.FXMLLoader;
import javafx.util.Callback;

import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;

public class FXMLLoaderProducer {

@Inject Instance<Object> instance;

@Produces
public FXMLLoader createLoader() {
FXMLLoader loader = new FXMLLoader();

loader.setControllerFactory(new Callback<Class<?>, Object>() {
@Override
public Object call(Class<?> param) {
return instance.select(param).get();
}
});

return loader;
}
}



FXML


cdi.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.text.*?>

<AnchorPane fx:id="base" minHeight="158.0" prefHeight="158.0" prefWidth="266.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2" fx:controller="fx.MyController">
<!-- TODO Add Nodes -->
<children>
<Button fx:id="button" layoutX="75.0" layoutY="25.0" mnemonicParsing="false" onAction="#onButtonClick" prefHeight="37.0" prefWidth="118.0" text="Button">
<font>
<Font size="20.0" fx:id="x1" />
</font>
</Button>
<Label id="text" fx:id="label" alignment="CENTER" contentDisplay="CENTER" font="$x1" layoutX="15.0" layoutY="79.0" minHeight="12.0" prefHeight="65.0" prefWidth="237.0" text="Label" textAlignment="CENTER" />
</children>
</AnchorPane>


Scene Builder で見た様子。

cdi.fxml


コントローラクラス


MyController.java

package fx;

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

import javax.inject.Inject;

public class MyController {

public static InputStream getFxmlStream() {
return MyController.class.getResourceAsStream("/cdi.fxml");
}

@FXML private Label label;
@Inject private MyService service;

@FXML
public void onButtonClick() {
label.setText("cnt : " + this.service.nextCount());
}
}



CDIでインジェクションするクラス


MyService.java

package fx;

import javax.inject.Singleton;

@Singleton
public class MyService {

private int cnt;

public int nextCount() {
return ++cnt;
}
}



実行結果

実行結果

クリックする度にカウントが加算される。


Eclipse プロジェクトの様子

Eclipse プロジェクトの様子


説明


起動


Main.java

public class Main extends Application {

public static void main(String[] args) {
Application.launch(args);
}

@SuppressWarnings("serial")
@Override
public void start(Stage stage) throws Exception {
// ....
container.event()
.select(Stage.class, new AnnotationLiteral<MyQualifier>() {})
.fire(stage);
// ....
}
}


まず、 CDI のイベント通知機能を利用して、JavaFX 画面起動用のメソッド(ApplicationStarter#start(Stage))を呼び出している。


CDI 用に改造した FXMLLoader をインジェクションする


ApplicationStarter.java

public class ApplicationStarter {

@Inject private FXMLLoader loader;

public void start(@Observes @MyQualifier Stage stage) throws IOException {
// ....
Parent root = (Parent)this.loader.load(fxml);
// ....
}
}


JavaFX 画面起動用クラスには、 FXMLLoader をインジェクションしている。この FXMLLoader は、 FXMLLoaderProducer というインスタンス提供用クラスを作成して、そこから取得している。


FXMLLoaderProducer.java

public class FXMLLoaderProducer {

@Inject Instance<Object> instance;

@Produces
public FXMLLoader createLoader() {
FXMLLoader loader = new FXMLLoader();

loader.setControllerFactory(new Callback<Class<?>, Object>() {
@Override
public Object call(Class<?> param) {
return instance.select(param).get();
}
});

return loader;
}
}


FXMLLoaderProducer では、 FXMLLoadercontrollerFactory を CDI のコンテナからインスタンスを取得する実装に差し替えたものを提供するようにしている。

こうすることで、 JavaFX が FXML とコントローラクラスを結びつけるときに、コントローラクラスのインスタンスを CDI コンテナから取得するようになる。

結果、コントローラクラスでも CDI の機能が使用できるようになる。


参考


書いた後に気づいたこと

参考に挙げているサイトの実装をそのまま利用しているので、 ApplicationStarter#start(Stage) の実行にはイベントを使用しているけど、よく考えたら普通に ApplicationStarter のインスタンスをコンテナから取得して実行しても問題ない気がする。


Main.java

package fx;

import javafx.application.Application;
import javafx.stage.Stage;

import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;

public class Main extends Application {

public static void main(String[] args) {
Application.launch(args);
}

@Override
public void start(Stage stage) throws Exception {
Weld weld = new Weld();
WeldContainer container = weld.initialize();

// コンテナからインスタンスを取得して実行
ApplicationStarter starter = container.instance().select(ApplicationStarter.class).get();
starter.start(stage);

weld.shutdown();
}
}


一応動いたけど、何が違うのだろう?