rich client / community javafx in spring - oracle.com ·...
TRANSCRIPT
ORACLE.COM/JAVAMAGAZINE NOVEMBER/DECEMBER 2012
JAVA TECH
35
COMMUNITY
JAVA IN
ACTION
ABOUT US
blog
//rich client /
Javaでサーバー・サイドの開発を行ったことがある開発者で
あれば、JavaエンタープライズWebアプリケーションの構築や強化のためにSpring Frameworkの何らかの要素を使用した経験があるのではないでしょうか。Spring Frameworkには、強力な依存性注入(DI)システム、Web版のモデル・ビュー・コントローラ(MVC)フレームワーク、さらにはデータベース・アクセスからセキュリティ認証制御までのあらゆる機能に対応するヘルパー・ライブラリが含まれています。一方、JavaFXでは2.xリリース
でPure Java APIが提供され、SpringのようなJavaライブラリの統合が非常に容易になりました。Spring Frameworkをクライアント上で利用した場合、次のようなメリットがあります。
■ UIの制御の反転̶制御の反転(IoC)は、タスクの実行をそのタスクの実装から切り離すために用いられるオブジェクト指向の技法です。UIの場合、IoCはスタンドアロン・モジュールの作成に非常に便利です。スタンドアロン・モジュールを作成
して、Springなどの依存性管理フレームワークでそれらのモジュールの関係をインジェクションできます。
■ 1行で記述できるWebサービス̶クライアントとサーバーで互換性のあるRESTフレームワークを利用することで、同じモデル・オブジェクトを再利用し、データの取得、作成、削除、更新を行うWebサービスの呼び出しを1行の文で実装できます。
■ 認証と認可̶クライアント上での認証/認可機能を一から開発するのではなく、サーバー要求のセキュリティ保護に使用しているものと同じ認証メカニズムを利用できます。また、詳細な認可テストを行って、権限に応じてUIの外観や操作性を変更することもできます。非常に単純なアプリケーション
の場合、これらの機能によって得られるメリットは、新しいライブラリの学習や統合にかかるコストを大きく上回るほどではないかもしれません。一方で、複数の画面で構成され、バックエンド・サーバーへの接続やユーザーの認証を伴う大規模なアプリケーションを構築する場合に
は、アプリケーションが大規模で複雑であればあるほど、得られるメリットも飛躍的に増します。本記事では、実際のアプリ
ケーションの中でこうしたメリットを実証するために、Spr i ng FrameworkとJavaFXを組み合わせたアプリケーションとして、フロントエンド・コンポーネントとバックエンド・コンポーネントの両方を含む顧客データアプリケーションを一から構築します。この顧客データアプリケーションには、ログ
イン・ユーザーの認証用モジュール、SpringのRestTemplateを使用したバックエンドのWebサービス統合機能、ロール別の適切な権限に基づく作成/削除操作が含まれます。アプリケーションの最終的な外観は図1のようになります。全2回のシリーズを通してこの
サンプルを完成させていきますが、GitHubではすでにサンプルの完全なソースコードを閲覧できます。また、ダウンロードして実行することも可能です。
クライアントでSpringフレームワークを使用する理由
STEPHEN CHIN
BIO
パート1
JavaFX in Spring
図1
Stephen Chin (@steveon java) オラクルの Java Technology Ambassador。 JavaFXの代表的技術文献であるPro JavaFX 2 (Apress、2012年)を共同執筆。Devoxx、CodeMash、OSCON、J-Fall、GeeCON、JAZOON、JavaOneなど多くのJavaカンファレンスで取り上げられ、Rock Star アワードを2度受賞。
ORACLE.COM/JAVAMAGAZINE NOVEMBER/DECEMBER 2012
JAVA TECH
36
COMMUNITY
JAVA IN
ACTION
ABOUT US
blog
//rich client / アプリケーション・テンプレートSpringを基盤としたサーバー・サイドの開発に関わったことがある方は、Springを使用する新規アプリケーションの設定が簡単であることをご存じでしょう。JARファイル(またはMavenの依存関係)を追加し、web.xmlファイルにコードを数行追加するだけです。一方、クライアントでは、メイン・クラス
から手動でSpringランタイムを起動する必要があります。本項で作成するアプリケーション・テンプレートには、スレッドセーフの状態でJavaFXランタイムとSpringランタイムを起動する方法が示されています。その前にまずは、スレッドセーフにする
理由を理解するために、JavaFXのスレッドに関する背景情報を少し確認しましょう。JavaFXには、目的の異なる2種類の
スレッドがあります。 ■ アプリケーション・スレッド̶JavaFXのシーン・グラフに対するすべての操作と、トップレベル・ウィンドウを使用するオブジェクトの作成を行うスレッド。Swingのイベント・ディスパッチ・スレッドに相当します(ただし、同じスレッドというわけではないため、Swingとの相互運用のためのコードでは注意してください)。
■ 描画スレッド̶表示するシーンを準備するために、グラフィックが変化するたびに呼び出されるスレッド。グラフィックの変化としては 、アニメーション/CSS/レイアウトの処理、シーン・グラフと描画ツリーの同期、マウス・ホバーの状態の更新などがあります。ほとんどの場合、描画スレッドはシー
ンのバックグラウンドで実行され、開発
者が考慮する必要はありません。一方、アプリケーション・スレッドに対しては少し注意が必要です。JavaFXでアプリケーション・コードを
呼び出す場合は、startやstopといったアプリケーションのメソッド内であろうとイベント・ハンドラ内であろうと、その呼び出しは必ずアプリケーション・スレッド上で実行されます。そのため、アプリケーション側で新しいJavaFXオブジェクトを安全に作成して、シーン・グラフを自由に操作できます。ただし、アプリケーションをメイン・スレッドで実行する場合や、独自のスレッドを生成する場合は、描画ツリーに影響を及ぼすJavaFXのメソッドを呼び出さないように注意してください。呼び出した場合は、例外やデッドロックが発生することや、さらに悪い結果につながることもあります。リスト1(CustomerModel.java)
は、CustomerAppデモ・プロジェクトのメイン・アプリケーション・クラスです。JavaFXアプリケーション・スレッドの初期化や、JavaクラスからのSpring設定のロードを行っています。startメソッドの手前までは、定型的な
JavaFXアプリケーションに非常に近いコードです。startメソッドでは、SpringのApplicationContextを初期化しています。その際、Java設定を使用する場合は新しいAnnotationConfigApplication Contextを作成し、XMLからロードする場合はClassPathXmlApplication Contextを作成します。ここでは、Spring Framework 3.0
で新たに導入されたアノテーションベースのJava設定サポートを利用することを強くお勧めします。コード内で設定を
定義できる利便性があるだけでなく、SpringのXMLベース設定で実現される強力な機能や容易なデバッグ処理もそのまま活用できるためです。本記事のサンプルではアノテーションベースとXMLベースの設定、両方のスタイルを組み合わせて宣言しています。アプリケーションの目的に応じて、こういった組み合わせた方法も便利です。。アプリケーションのメイン・メソッド内
でSpringのコンテキストを初期化したくなるかもしれませんが、これは非常に危険です。Springのコンテキストをロードした場合に、Springを起動したスレッドにJavaFXのオブジェクトがロードされる可能性があるからです。Stage、Popup、Menuなどのトップレベル・ウィ
ンドウを使用するJavaFXオブジェクトを作成した場合は、この問題を示す例外が発生します。一方、リスト1のstartメソッドはJavaFXアプリケーション・スレッド上で呼び出されます。そのため、任意の型のJavaFXオブジェクトを安全に作成し、それらのオブジェクトをSpring設定内からでもシーン・グラフに追加できます。リスト1のサンプルでは、Screens
Configurationというサブ設定クラスをロードし、その設定オブジェクトにstageを設定して、ログイン・ダイアログ・ボックスを表示するメソッドを呼び出しています。
Download all listings in this issue as text
public class CustomerApp extends Application { public static void main(String[] args) { launch(args); }
@Override public void start(Stage stage) throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext CustomerAppConfiguration.class); ScreensConfiguration screens = context.getBean(ScreensConfiguration.class); screens.setPrimaryStage(stage); screens.loginDialog().show(); }}
LISTING 1
ORACLE.COM/JAVAMAGAZINE NOVEMBER/DECEMBER 2012
JAVA TECH
37
COMMUNITY
JAVA IN
ACTION
ABOUT US
blog
//rich client / DIによるUIのモジュール化以上でSpringをロードできるようになりました。次にSpringの一部の機能を利用して、JavaFXアプリケーションをモジュール化します。この顧客データアプリケーションは最終的に次の画面で構成されます。
■ ログイン画面 ■ データ表示画面 ■ エラー画面 ■ 「顧客の追加」画面これらの画面は、それぞれ別個の
JavaFXクラスまたはFXMLファイルとして実装します。また、他の画面の参照(ナビゲーションを行う場合)とモデル(データ・アクセスやデータの更新が発生する場合)も必要になります。さらに、最初の利用時に画面が作成され、かつ、画面ごとに一度に1インスタンスしか保持しないようにします。リスト 2 aと 2 b( S c r e e n s
Configuration.java)は、各画面をそれぞれ別個のBeanとして定義するSpring設定クラスです。ScreensConfigurationファイルで
は、@Beanアノテーションを使用して、各画面および関連コントローラを別個のBeanとして定義しています。また、一部のBean(本記事のサンプルでは、ダイアログ・ボックスを表示するBeanのすべて)を@Scope("prototype")アノテーションを使用して定義しています。これにより、インスタンスを取得しようとするたびに新しいインスタンスが作成されます。一方、メインのデータ表示画面のスコープはデフォルトの"singleton"です。そのため、メソッドを何回呼び出したとしても、メイン画面はアプリケーション全
体で1つのみ存在します。また、設定クラス全体に対して@Lazy
アノテーションを指定しています。そのため、それぞれのBeanは初回のアクセス時まで作成されません。ただし、@Lazyアノテーションを指定しただけでは、アプリケーションの起動時にBeanが作成されないことを保証するためには不十分です。複数の画面の間に直接リンクがある場合は、最初の画面がロードされた際に、他の参照先の画面もすべて連鎖的にロードされます。全画面が一度にロードされないよ
うにするには、リンクする画面の参照をインジェクションするのではなく、ScreensConfigurationクラスの参照を渡します。この方法によって、別のScreensConfigurationインスタンスを設定して実装を入れ替えるという柔軟性を確保しながら、特定の画面をロードするメソッドが呼び出されるまでオブジェクトの作成を遅らせることができます。モ デ ル お よ び W e b サ ー ビ ス
に 関 す る そ の 他 の 設 定 は 、別のクラス・ファイルに定義します(CustomerAppConfiguration.j a v a、リスト3参照)。このクラスでは、アノテーションを使用してScreensConfigurationをロードしています。
FXMLへのコントローラのインジェクション前項では複数の画面を、Javaクラス・ファイルまたはFXMLとして定義しました。Springによって初期化されたコントローラをJavaオブジェクトに割り当てることは非常に簡単ですが、同様の
Download all listings in this issue as text
@Configuration@Lazypublic class ScreensConfiguration { private Stage primaryStage;
public void setPrimaryStage(Stage primaryStage) { this.primaryStage = primaryStage; }
public void showScreen(Parent screen) { primaryStage.setScene(new Scene(screen, 777, 500)); primaryStage.show(); }
@Bean CustomerDataScreen customerDataScreen() { return new CustomerDataScreen(customerDataScreenController()); }
@Bean CustomerDataScreenController customerDataScreenCon-troller() { return new CustomerDataScreenController(this); }
@Bean @Scope("prototype") FXMLDialog errorDialog() { return new FXMLDialog(errorController(),getClass().getResource("Error.fxml"), primaryStage, StageStyle.UNDECORATED); }
LISTING 2a LISTING 2b
ORACLE.COM/JAVAMAGAZINE NOVEMBER/DECEMBER 2012
JAVA TECH
38
COMMUNITY
JAVA IN
ACTION
ABOUT US
blog
//rich client /
コントローラをFXMLに割り当てることは、JavaFX 2.0リリースでは不可能でした。しかし、JavaFX 2.1リリースでは、この問題を解決して各種IoCフレームワークに対応するためのAPIが追加されました。ここでの各種IoCフレームワークには、コンテキストと依存性の注入(CDI)、Guiceなどのほか、言うまでもなくSpringも含まれます。例として、ErrorDialogの定義を詳し
く見てみます。ビューとコントローラは両方ともScreensConfigurationクラス内で作成されます。クラスを作成するコードをリスト4に示します。errorControllerが、FXMLDialogク
ラスに依存関係としてインジェクションされていることにお気づきでしょうか。FXMLDialogは、FXMLファイルのロード・ロジックとコントローラ・オブジェクトのインジェクション・ロジックをまとめてカプセル化したカスタム・クラスです。FXMLDialogのソース・コード全体をリスト5(FXMLDialog.java)に示します。FXMLDialogクラスは、FXMLファ
イルを新しいダイアログ(Stage)としてロードするために通常必要となるコードの一部を一般化したものです。特に、コントローラのインジェクションにとって重要な部分は、loader.setControl-lerFactory()の呼び出しです。この呼び出しにより、インナー・クラスを作成
し、その中で、パラメータとして渡されたコントローラを返しています。このようにコントローラを作
成してインジェクションするテクニックによって、コントロー
ラがSpringで管理される他のあらゆるオブジェクトと同様に扱われます。その結果、DIや「Autowiring」(自動関連付け)などのフレームワークの機能を利用できます。ErrorControllerのソース・コード全体をリスト6(ErrorController.j ava)に示します。また、基底インタフェースのDialogControllerはリスト7(DialogController.java)のとおりです。
本記事のFXML UIはSceneBuilderを 使 用 し て 作 成 し ま し た 。SceneBuilderは、JavaFXの各種コンポーネントについて学習し、UIのモックアップを簡単に作成できる優れたビジュアル・ツールです。作成した最終的なエラー・ダイアログ・ボックスは図2のようになります。
まとめ本記事では、SpringとJavaFXを組み合わせて使用するアプリケーションのテンプレートを紹介しました。このテンプレートは各自のプロジェクトで再利用できます。Springの設定とDIを利用するだけで、Pure Javaでの開発を基盤としたアプリケーションの設計上のメリットが得られます。パート2ではRestTemplateを使用
して、再利用可能なモデル・オブジェクトを含む軽量のバックエンドや、クライアント側での1行で記述できるWebサービ
スなど、サンプル・アプリケーションの残りの部分を構築します。 </article>
図2
@Configuration@Import(ScreensConfiguration.class)@ImportResource("classpath:applicationContext-security.xml")public class CustomerAppConfiguration { @Bean CustomerModel customerModel() throws IOException { CustomerModel customerModel = new CustomerMod-el(); customerModel.setRestTemplate(restTemplate()); customerModel.loadData(); return customerModel; }
@Bean RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); restTemplate.setMessageConverters Collections.<HttpMessageConverter<?>>singletonList( new MappingJacksonHttpMessageConverter())); return restTemplate; }}
LISTING 3 LISTING 4 LISTING 5 LISTING 6 LISTING 7
Download all listings in this issue as text
LEARN MORE• Stephen Chinのブログ