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が効かず、個別に終了処理を実行しないといけない。
- そのため、自身でSpring用のFXMLLoaderを自作して、@PreDestroyを呼び出すようにします(SpringBeanをインジェクションするFXMLLoaderを自作する - タツノオトシゴの日記)。
@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つにしておきます。
- 実際には、システムでFXMLのロードを共通化しておき、その中のクラスでアプリケーション実行時に、初期化し、ApplicationContexのインスタンスを設定して利用します。
- http://d.hatena.ne.jp/tatsu-no-toshigo/20130503/1367810194
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); } ・・・(省略)・・・ }