この記事は「JavaEE Advent Calendar 2013」の1日目の記事となります。
明日はManabu Matsuzaki(@matsumana)さんです。宜しくお願いします!
今年は6月にJava EE 7がリリースされました。
Java EE 6からのアップデートは大小様々ありますが、今日はJava EE 7の中からJSF(JavaServer Faces)に着目して、JSF2.2で新しく追加されたFaces Flowsについて書いてみたいと思います。Faces Flowsは、Big Ticketと言われる目玉機能の1つですが、探してみたら意外と日本語情報がなかったので(^^;
本題の前に、そもそもJSF(JavaServer Faces)って何?ということについて少しだけ。
JSFは現在のJava EEにおける画面構築の標準仕様で、コンポーネントベースにWeb画面を構築するものです。.NETのWebFormと感覚的には近いものです。
JSFは大きな風呂敷でWeb内部を隠したような抽象化がされており、逆にそれが敬遠されることも多いのが現状です。
オープンなWebシステム構築にはあまり向いていませんが、業務系システム等では有効に使えるケースもあるのではないかな?と私は実際に使って思っています。
また、海外では様々なJSF用のリッチコンポーネントライブラリが開発されていたり、情報も細かいもの含めて豊富にあるので、それなりに利用されているように思います。
JSF2.2のアップデート内容
ここを細かく書くと今日の主題に到達しない…のと、日本語情報も既に多くあるため、後日まとめようと思います。
概要をざっと知りたい方は@den2snさんが以前にOracleさんのセミナーでお話された以下が役に立つと思います。
Faces Flows
Faces Flowsとは何か?ですが、すごくざっくり書くと、あるページからあるページまでの一連の流れを1つのフローとして定義する仕組みです。
絵でイメージかくとこんな感じ。ちょっと雑すぎましたが…。
ノードが1つのWebページで、ノードをつないでいるエッジが画面遷移です。
上段のフローと下段のフローは別物ですが、お互いのフローを結びつけることも可能です。
だいぶざっくりな説明ですが、JSR-344では「有向グラフ(directed graph)」という言葉を使ってノード(node)がビュー、エッジ(edge)を画面遷移と表現していました。詳しく知りたい方は以下を参照して頂くのが良いと思います。
※海外サイトみると「Faces Flow」とFlowが単数形のものが多いですが、JSRではFlowsと複数形になっているので、ここではすべて複数形にして書いています。
フローの定義方法
フローを定義する方法は大きく分けて2種類あります。
FlowBuilderクラスを利用してJavaプログラムでフローを定義します。
XMLタグを利用してフローを定義します。
言葉だけだとわかりにくいので、今回Advent用に作ったサンプルコードから定義部分を表示します。
プログラムによる定義
FlowBuilderが持つメソッドでフロー定義をしていく形です。今回のサンプルでは
- フローが呼び出された際に処理を呼ぶ(initializer())
- ノードとページのマッピング(viewNode())
- メソッドを呼び出すノード(methodCallNode())
- 条件によるフローのスイッチング(switchNode(), switchCase())
- 別に定義されたフローへの遷移(flowCallNode())
などのメソッドを使った例にしています。
先のクラスで定義したのと同じレベルが記述できると思いますが、今回のサンプルでは以下のように一部だけとなっています。すみません。
サンプルアプリケーション
作ったサンプルは以下のような動きをするものです。
上図のノードはページを表していますが、裏でデータを保持するBacking Bean(管理対象Bean)は、会員登録フロー、特典選択フローともにそれぞれ1つだけあります。@FlowScopedアノテーションをつけることで、フローの間だけ生きているBacking Beanとなります。
また、特典選択フローはWebページとBacking Beanが1つのJarファイルになっており、会員登録フローのWebアプリがそのJarを参照する形で構成されています。
実際のソースコードは以下GitHubに置きました。
実際に画面を動かした結果は以下です。
フロー図のTopノードにあたるindex.xhtmlです。これが最初に表示されます。
会員登録するリンクを押すと、画面遷移し、会員登録フローが呼び出されます。
会員登録フローの最初のページです。
会員登録フローが呼び出されたので、initalizerメソッドが走ってコンソールに以下表示されています。
名前と年齢を入れます。
メッセージ入力ボタンを押します。
メッセージ入れて登録確認へ
で、今まで入力した情報が表示され「名前の修正」を押せば、名前入れたページへ、「終了」を押せばindexに遷移します。
なんてことない普通の画面遷移なんですが…(^^;
ちなみに、会員登録のBacking Beanのフィールドは以下のような定義です。
年齢を保持する変数を定義していません。
代わりにFacesFlowsで追加されたEL式オブジェクトである#{flowScope}を使っています。
ビュー定義は以下だけなので、お手軽です。中身はjava.util.Mapのようで、キーが自分で定義した属性(ここではage)となっていて、値に入力された値が格納されていました。
<h:inputText value="#{flowScope.age}" />
ちなみにこの値をプログラム内で扱いたい場合には
Map flowObject = FacesContext.getCurrentInstance().getApplication().getFlowHandler().getCurrentFlowScope();
System.out.println(flowObject.get("age"));
という感じで取れます。下手に使うとキー定義の嵐になりそうですが(^^;
サンプルでは「テストボタン」を押したときに、この処理を読んでいるので試してみて下さい。
別フローを呼び出す
では、先ほどの状態から「名前の修正」を押して以下画面に戻ってみます。
選択された特典コードというラベルの横は空白となっていることを確認して「特典を選ぶ」を押します。
ここで、Jarにパッケージ化された中に定義してある特典選択フローへ移ります。
特典選択フローにもinitializer定義(こちらはXMLなのでタグですが)しており、処理が呼び出されて以下表示されます。
画面はこんな感じに。
会員登録フローで入力していた情報をinbound-parameterで受け取って表示しています。
同一のフロー内ではBacking Beanを通じて簡単にデータ共有できますが、別定義のフローと値をやりとりする場合には、こうした定義が必要となるようです。
次に、特典は適当にBを選んで、戻るボタンを押します。
すると、選ばれた特典のコードが表示されています。
ここも先ほどと同じですが、今度はoutbound-parameterで値を呼び出し側に渡しています。
メリットは?
このサンプル、しょぼいわりには意外と手間がかかったのですが、これって何が嬉しいんだろう…(^^?とちょっと思ったり思わなかったり。
メリットをあげてみると大体以下のような感じかと思います。
海外のサイトでも概ねこの辺りの記述なので。。。
- RequestScopeやViewScopeより長く、SessionScopeより短いスコープ定義
SessionScopeだと長すぎるけど、ViewScopeだと1画面だけで短い…もやもや、って感じたことがあるので、FlowScopeは嬉しい気もします。
でもそれってConversationScopeも同じようなことができるよね?というのがありそうですが、ConversationScopeであれこれ画面遷移がある場合、ちゃんと設計して作らないと
「あれ、会話が終わらね…」
「うわ、この操作したときダメじゃん」
とか、なり…ませんかね(^^;?
自分は前ちょっと触ったときそんな感じでした。センスがないだけかもですが;
- フローをパッケージング(jar化)することができるため、フローの再利用ができる
今回のサンプルもそうですが、これが大きいメリットなのだと思います。
JSFは「コンポーネント」という概念が強く、再利用するには便利です。
複数の画面部品をくっつけた「複合コンポーネント」も、その1つでした。
今回FacesFlowsが入ることで、ページと遷移のまとまった単位を再利用できるようになりました。うまく設計すれば、プロジェクトが違っても再利用したフローが作れるかもしれません。
実際はなかなか難しそうですが(^^;
- ブラウザの複数ウィンドウ(タブ)でも各々でフローが管理される
FacesFlowsでは、ブラウザのタブごとにフローを制御することができます。
サンプルでやると、以下のような感じです。
1つのタブで入力して
もう1つタブを起動して、入力します
同一セッションですが、各々で制御されており、情報が混ざることはありません。
これもConvrsationScopeは同じなんですが(^^;うーん。