タツノオトシゴのブログ

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

JavaFXのControllerにおけるSpringBeanのインジェクションについて

JavaFXでSpringのBeanをControllerにインジェクションする方法は2つあります。これは、自身がPaneなどを継承して作られているかどうかで変わります。

方法(1)「ControllerをSpring Beanとして扱う」

Scene(Node)とControllerのインスタンスを別々に作成する場合がこれに該当します。

ApplicationContext.xmlの設定

ApplicationContext.xmlにて、アノテーションが有効にできるようにします。

  • Controllerクラスをcomponent-scan対象にします。
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
    ">
    
    <!-- アノテーションの有効化 -->
    <context:annotation-config />
    
    <!-- コンポーネントスキャンの対象設定 -->
    <context:component-scan base-package="sample.gui" />
</beans>
Controllerクラスの作成
  • クラスに「@Component」アノテーションを付与する。
  • スコープはprototypeにする。アノテーション@Scopeを付与する。
    • 確実に1箇所でしか使用しないならば、singletonでもよいが、確証がなければprototypeにする。
    • webのように1つのURLに対して1つのControllerではなく、例えばTabのように1種類のPaneを複数作成する場合があるため、prototypeにするのが無難。
  • ただし、スコープがprototypeの場合、@PreDestroyが効かず、個別に終了処理を実行しないといけない。
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@Component
public class SampleController {
    
    // Springによるインジェクション対象
    @Resource
    private IUserDao userDao;
    
    @FXML
    private TextField textField;
}
FXMLLoderでのControllerFactoryの実装

FXMLLoader#setControllerFactory()にて、Controllerのインスタンスを作成するメソッドの実装にて、Springのコンテナから取得するようにします。

// Springのコンテナ
ApplicationContext applicationContext = ...;

FXMLLoader loader = new FXMLLoader();

// staticメソッドは異なり、InputStream形式なので、Class#getResourceAsStreamから値を取得する
AnchorPane sample = (AnchorPane) loader.load(getClass().getResourceAsStream("SamplePane.fxml"));


// ControllerのCallbackの設定
loader.setControllerFactory(new Callback<Class<?>, Object>() {
    @Override
    public Object call(Class<?> param) {
        // ControllerをSprign Beanコンテナから取得する
        return applicationContext.getBean(SamplePaneController.class);
        
        // Spring2.5の場合は、bean名を指定する
        //return applicationContext.getBean("samplePaneController");
    }
});

// Controllerのインスタンスの取得
SamplePaneController controller = loader.getController();

方法(2)「Springコンテナの管理外のクラスにSpringBeanをインジェクションする」

PaneやSceneを継承しているクラスに対して作成している場合にこれが該当します。

  • JavaFXコンポーネントとしてどこでも自由にインスタンスを作成できないといけないので、SpringBeanとして管理するのは得策ではありません。
  • Spring BeanをインジェクションするためのInjectorクラスを作成します。
SpringInjectorの作成
  • これはValidation用OValのSpringInjectorやWicketのInjectionHolderなどを参考にしています。
  • アノテーション「@Resouce」でインジェクションするためには、「CommonAnnotationBeanPostProcessor」を利用します。
  • アノテーション「@Autowire」でインジェクションするにためには、「AutowiredAnnotationBeanPostProcessor」を利用します。
  • SpringInjector自体、SpringBeanとして登録します。
import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope(BeanDefinition.SCOPE_SINGLETON)
@Component
public class SpringInjector {
    
    @Autowired
    private AutowiredAnnotationBeanPostProcessor autowiredProcessor;
    
    @Resource
    private CommonAnnotationBeanPostProcessor commonProcessor;
    
    public void inject(final Object unmanagedBean) {
        
        // javax.* 関連のアノテーションのインジェクション(@Resoure)
        commonProcessor.postProcessPropertyValues(null, null, unmanagedBean, unmanagedBean.getClass().getSimpleName());
        
        // @Autowire、@Valueのアノテーションのインジェクション
        autowiredProcessor.processInjection(unmanagedBean);
    }
}
ApplicationContext.xmlの設定

ApplicationContext.xmlにて、アノテーションが有効にできるようにします。

  • SpringInjectorをSpringBeanとして登録します。
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
    ">
    
    <!-- アノテーションの有効化 -->
    <context:annotation-config />
    
    <!-- SpringInjectorの登録 -->
    <bean id="springInjector" class="sample.SpringInjector"/>
</beans>
SpringBeanのインジェクション方法
  • 作成した「SpringInjector#inject(インジェクション対象のクラスのインスタンス)」でインジェクションします。
  • SpringのApplicationContextはが必要なので、staticにしておき、アプリケーション内で1つにしておきます。
public class SamplePane extends VBox {

    // SpringのアプリケーションContext(static)
    static ApplicationContext applicationContext;

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

    public SamplePane() {
        // FXMLのロード
        try {
            final FXMLLoader fxmlLoader = new FXMLLoader(
                    getClass().getClassLoader().getResource("/sample/SampePane.fxml"));
            fxmlLoader.setRoot(this);
            fxmlLoader.setController(this);
            fxmlLoader.load();
        } catch(IOException e) {
            throw new RuntimeException(e);
        }
        
        // SpringBeanのインジェクション
        // 自身のインスタンスに対してインジェクションする
        SpringInjector springInjector = applicationContext.getBean(SpringInjector.class);
        springInjector.inject(this);
        
    }
   ・・・(省略)・・・
}