SpringBeanをインジェクションするFXMLLoaderを自作する
ここまで紹介したように、FXMLLoaderによるパターンは2つあることが分かったと思います。
毎回、FXMLLoaderでFXMLファイルとControllerを指定して作成するのは面倒なので、パターンに合わせてFXMLLoaderを自作します。
- FXMLLoader自体、finalクラスで継承付加なので、処理は委譲します。
- FXMLとControllerは対になっているので、ファイル名を合わせておき、Controllerクラスを指定すると、FXMLファイルの名称も決まるようにします。
(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); } }