タツノオトシゴのブログ

主にJavaに関するものです。

SpringBeanをインジェクションするFXMLLoaderを自作する

ここまで紹介したように、FXMLLoaderによるパターンは2つあることが分かったと思います。
毎回、FXMLLoaderでFXMLファイルとControllerを指定して作成するのは面倒なので、パターンに合わせてFXMLLoaderを自作します。

  • FXMLLoader自体、finalクラスで継承付加なので、処理は委譲します。
  • FXMLとControllerは対になっているので、ファイル名を合わせておき、Controllerクラスを指定すると、FXMLファイルの名称も決まるようにします。
    • さらに、プロパティファイル(ResourceBundle)も同様にします。ただし、ResourceBundleの場合は必須ではないので、ファイルが存在すれば設定します。
    • 例えば、を使用した自身がコンポーネントの場合は、SamplePane.javaとSamplePane.fxmlとSamplePane.properties。
    • fx:controller=""を利用したControllerの場合は、SamplePaneController.javaとSamplePane.fxmlとSamplePaneController.propertiesとします。

(1)ControllerとNodeを返すメソッド

  • 戻り値が2つあるので、専用の保持クラス「NodeControllerContainer」を作成します。
import javafx.scene.Node;

/* Node(Scene)とそれに対するControllerを保持するクラス。
 *
 * @param <N> JavaFXのノード
 * @param <C> Controller
 */
public class NodeControllerContainer<N extends Node, C> {
    
    /** JavaFXノード */
    private final N node;
    
    /** Controller */
    private final C controller;
    
    public NodeControllerContainer(final N node, final C controller) {
        this.node = node;
        this.controller = controller;
    }
    
    public N getNode() {
        return node;
    }
    
    public C getController() {
        return controller;
    }
}
public class CustomFXMLLoader {

   /** Stringのアプリケーションコンテキスト */
   private static ApplicationContext applicationContext;
   
   
   /**
     *  Controllerに対応するリソースファイルを取得す
     */
    public static ResourceBundle getResource(final Class<?> controllerClass, final Locale locale) {
        
        try {
            if(locale == null) {
                return ResourceBundle.getBundle(controllerClass.getCanonicalName());
            } else {
                return ResourceBundle.getBundle(controllerClass.getCanonicalName(), locale);
            }
        } catch(MissingResourceException e) {
            // プロパティファイルがない場合はnullを返す
            return null;
        }
    }
    
    /**
     * コントローラを伴うFXMLを読み込む。
     */
    @SuppressWarnings("unchecked")
    public static <N extends Node, C> NodeControllerContainer<N, C> loadNodeAndController(
            final Class<N> nodeClass, final Class<C> controllerClass, final Locale locale) {
        
        final FXMLLoader loader = new FXMLLoader();
        
        // リソースの設定
        ResourceBundle resources = getResource(controllerClass, locale);
        if(resources != null) {
            loader.setResources(resources);
        }
        
        // FXMLの取得
        final String fxmlPath = controllerClass.getCanonicalName().replaceFirst("Controller", "").concat(".fxml");
        final Node node;
        try {
            node = (Node) loader.load(CustomFXMLLoader.class.getResourceAsStream(fxmlPath));
            
        } catch (IOException e) {
            throw new RuntimeException(
                    String.format("fail load fxml : %s", controllerClass.getClass().getCanonicalName()),
                    e);
        }
        
        // Controllerの取得
        final C controller = loader.getController();
        
        return new NodeControllerContainer(node, controller);
        
    }
    
    /**
     * コントローラを伴うFXMLを読み込む。
     * <p>ControllerはSpringコンテナから取得する
     */
    @SuppressWarnings("unchecked")
    public static <N extends Node, C> NodeControllerContainer<N, C> loadNodeAndControllerWithSpringInjection(
            final Class<N> nodeClass, final Class<C> controllerClass, final Locale locale) {
        
        final FXMLLoader loader = new FXMLLoader();
        
        // リソースの設定
        ResourceBundle resources = getResource(controllerClass, locale);
        if(resources != null) {
            loader.setResources(resources);
        }
        
        // Spring用のControllerのCallbackの設定
        loader.setControllerFactory(new Callback<Class<?>, Object>() {
            @Override
            public Object call(Class<?> param) {
                // ControllerをSprign Beanコンテナから取得する
                return applicationContext.getBean(controllerClass);
            }
        });
        
        // FXMLの取得
        final String fxmlPath = controllerClass.getCanonicalName().replaceFirst("Controller", "").concat(".fxml");
        final Node node;
        try {
            node = (Node) loader.load(CustomFXMLLoader.class.getResourceAsStream(fxmlPath));
            
        } catch (IOException e) {
            throw new RuntimeException(
                    String.format("fail load fxml : %s", controllerClass.getClass().getCanonicalName()),
                    e);
        }
        
        // Controllerの取得
        final C controller = loader.getController();
        
        return new NodeControllerContainer(node, controller);
        
    }

}

(2)Componentを返すメソッド

public class CustomFXMLLoader {

   /** Stringのアプリケーションコンテキスト */
   private static ApplicationContext applicationContext;
   
   
   /**
     *  Controllerに対応するリソースファイルを取得す
     */
    public static ResourceBundle getResource(final Class<?> controllerClass, final Locale locale) {
        
        try {
            if(locale == null) {
                return ResourceBundle.getBundle(controllerClass.getCanonicalName());
            } else {
                return ResourceBundle.getBundle(controllerClass.getCanonicalName(), locale);
            }
        } catch(MissingResourceException e) {
            // プロパティファイルがない場合はnullを返す
            return null;
        }
    }
    
    /**
     * コンポーネントを読み込む
     * fxmlが「<fx:root>」となっている場合
     */
    public static void loadComponent(final Object root, final Object controller, final Locale locale) {
        
        final FXMLLoader loader = new FXMLLoader(getControllerFxmlUrl(controller.getClass()));
        
        // リソースの設定
        ResourceBundle resources = getResource(controller.getClass(), locale);
        if(resources != null) {
            loader.setResources(resources);
        }
        
        // FXMLのロード
        loader.setRoot(root);
        loader.setController(controller);
        try {
            loader.load();
        } catch (IOException e) {
            throw new RuntimeException(String.format("fail load fxml : %s", controller.getClass().getCanonicalName()),
                    e);
        }
        
        // CSSの設定
        if(controller instanceof Parent) {
            final URL stylesheet = getControllerCssUrl(controller.getClass());
            if(stylesheet != null) {
                ((Parent) controller).getStylesheets().add(stylesheet.toExternalForm());
            }
        }
    }
    
    /**
     * コンポーネントを読み込む
     * fxmlが「<fx:root>」となっている場合
     * <p>SpringBeanをインジェクションする
     */
    public static void loadloadComponentWithSpringInjection(final Object root, final Object controller, final Locale locale) {
        
        load(root, controller, locale);
        
        // spring beanのインジェクション
        SpringInjector springInjector = applicationContext.getBean(SpringInjector.class);
        springInjector.inject(controller);
        
    }
}
// 使い方
public class SamplePane extends VBox {

    // Springによるインジェクション対象
    @Resource
    private IUserDao userDao;
    
    // Springによるインジェクション対象
    @Autowired
    private ISampleService sampleService;
    
    @FXML
    private TextField textField;

    public SamplePane() {
        // FXMLのロード
        CustomFXMLLoader.loadloadComponentWithSpringInjection(this, this, Locale.getDefault());
    }
   ・・・(省略)・・・
}

ApplictionでのSpringコンテナの初期化と終了方法

  • Application#init()にて、Springのコンテナの初期化を行います。
  • Application#stop()にて、Springのコンテナの終了を行います。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import sample.CustomFXMLLoader;
import sample.NodeControllerContainer;

/**
 * 
 */
public class SourceGenerateApplication extends Application {
    
    /** Sprignのコンテキスト */
    private ApplicationContext applicationContext;
    
    private SamplePaneController samplePaneController;
    
    /**
     * アプリケーションの初期化処理
     */
    @Override
    public void init() throws Exception {
        // ApplicationContextの読み込みと、そのインスタンスを自作のCustomFXMLLoaderに設定する
        applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
        CustomFXMLLoader.setApplicationContext(applicationContext);
    }
    
    @Override
    public void start(final Stage primaryStage) throws IOException {
        
        
        // Controllerを伴うPaneの読み込み
        NodeControllerContainer<AnchorPane, SamplePaneController> samplePaneContainer =
                CustomFXMLLoader.loadWithSpring(AnchorPane.class, SamplePaneController.class, null);
        
        camplePaneController = samplePaneContainer.getController();
        AnchorPane samplePane = container.getNode();

        // 自身がNodeの場合の読み込み
        samplePage.setBottomAnchor(new CustomPane(), 100.0);

        primaryStage.setScene(new Scene(samplePane));
        primaryStage.show();
    }
    
    /**
     * アプリケーションの終了処理
     * <p>Platform.exit()を読んだ際にも実行される
     */
    @Override
    public void stop() throws Exception {
        
        // Springのコンテナの終了
        if(applicationContext instanceof AbstractApplicationContext) {
            ((AbstractApplicationContext) applicationContext).close();
            System.out.println("close");
        }
        
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}