読者です 読者をやめる 読者になる 読者になる

Challenge Java EE !

Java EEを中心に趣味や仕事における開発メモを書いています。

JSF2.2のFaces Flows(FlowScoped)

Java EE 7 JSF AdventCalendar

この記事は「JavaEE Advent Calendar 2013」の1日目の記事となります。
明日はManabu Matsuzaki(@matsumana)さんです。宜しくお願いします!

Java EE 7登場

今年は6月にJava EE 7がリリースされました。

Java EE 6からのアップデートは大小様々ありますが、今日はJava EE 7の中からJSF(JavaServer Faces)に着目して、JSF2.2で新しく追加されたFaces Flowsについて書いてみたいと思います。Faces Flowsは、Big Ticketと言われる目玉機能の1つですが、探してみたら意外と日本語情報がなかったので(^^;

JSFとは?

本題の前に、そもそもJSF(JavaServer Faces)って何?ということについて少しだけ。

JSFは現在のJava EEにおける画面構築の標準仕様で、コンポーネントベースにWeb画面を構築するものです。.NETのWebFormと感覚的には近いものです。

JSFは大きな風呂敷でWeb内部を隠したような抽象化がされており、逆にそれが敬遠されることも多いのが現状です。
オープンなWebシステム構築にはあまり向いていませんが、業務系システム等では有効に使えるケースもあるのではないかな?と私は実際に使って思っています。

また、海外では様々なJSF用のリッチコンポーネントライブラリが開発されていたり、情報も細かいもの含めて豊富にあるので、それなりに利用されているように思います。

JSF2.2のアップデート内容

ここを細かく書くと今日の主題に到達しない…のと、日本語情報も既に多くあるため、後日まとめようと思います。

概要をざっと知りたい方は@den2snさんが以前にOracleさんのセミナーでお話された以下が役に立つと思います。

Faces Flows

Faces Flowsとは何か?ですが、すごくざっくり書くと、あるページからあるページまでの一連の流れを1つのフローとして定義する仕組みです。

絵でイメージかくとこんな感じ。ちょっと雑すぎましたが…。

f:id:kikutaro777:20131130235823j:plain

ノードが1つのWebページで、ノードをつないでいるエッジが画面遷移です。
上段のフローと下段のフローは別物ですが、お互いのフローを結びつけることも可能です。

だいぶざっくりな説明ですが、JSR-344では「有向グラフ(directed graph)」という言葉を使ってノード(node)がビュー、エッジ(edge)を画面遷移と表現していました。詳しく知りたい方は以下を参照して頂くのが良いと思います。

※海外サイトみると「Faces Flow」とFlowが単数形のものが多いですが、JSRではFlowsと複数形になっているので、ここではすべて複数形にして書いています。

フローの定義方法

フローを定義する方法は大きく分けて2種類あります。

  • プログラムによる定義

FlowBuilderクラスを利用してJavaプログラムでフローを定義します。

  • XMLによる定義

XMLタグを利用してフローを定義します。

言葉だけだとわかりにくいので、今回Advent用に作ったサンプルコードから定義部分を表示します。

プログラムによる定義

FlowBuilderが持つメソッドでフロー定義をしていく形です。今回のサンプルでは

  • フローが呼び出された際に処理を呼ぶ(initializer())
  • ノードとページのマッピング(viewNode())
  • メソッドを呼び出すノード(methodCallNode())
  • 条件によるフローのスイッチング(switchNode(), switchCase())
  • 別に定義されたフローへの遷移(flowCallNode())

などのメソッドを使った例にしています。

XMLによる定義

先のクラスで定義したのと同じレベルが記述できると思いますが、今回のサンプルでは以下のように一部だけとなっています。すみません。

サンプルアプリケーション

作ったサンプルは以下のような動きをするものです。

f:id:kikutaro777:20131201000303j:plain

上図のノードはページを表していますが、裏でデータを保持するBacking Bean(管理対象Bean)は、会員登録フロー、特典選択フローともにそれぞれ1つだけあります。@FlowScopedアノテーションをつけることで、フローの間だけ生きているBacking Beanとなります。

また、特典選択フローはWebページとBacking Beanが1つのJarファイルになっており、会員登録フローのWebアプリがそのJarを参照する形で構成されています。

実際のソースコードは以下GitHubに置きました。

実際に画面を動かした結果は以下です。

フロー図のTopノードにあたるindex.xhtmlです。これが最初に表示されます。
会員登録するリンクを押すと、画面遷移し、会員登録フローが呼び出されます。
f:id:kikutaro777:20131201000712j:plain

会員登録フローの最初のページです。
f:id:kikutaro777:20131201000742j:plain

会員登録フローが呼び出されたので、initalizerメソッドが走ってコンソールに以下表示されています。

f:id:kikutaro777:20131201104103j:plain

名前と年齢を入れます。
f:id:kikutaro777:20131201102348j:plain

メッセージ入力ボタンを押します。
f:id:kikutaro777:20131201102406j:plain

メッセージ入れて登録確認へ
f:id:kikutaro777:20131201102441j:plain

で、今まで入力した情報が表示され「名前の修正」を押せば、名前入れたページへ、「終了」を押せばindexに遷移します。
f:id:kikutaro777:20131201102517j:plain

なんてことない普通の画面遷移なんですが…(^^;

ちなみに、会員登録の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"));

という感じで取れます。下手に使うとキー定義の嵐になりそうですが(^^;
サンプルでは「テストボタン」を押したときに、この処理を読んでいるので試してみて下さい。

別フローを呼び出す

では、先ほどの状態から「名前の修正」を押して以下画面に戻ってみます。
f:id:kikutaro777:20131201104934j:plain

選択された特典コードというラベルの横は空白となっていることを確認して「特典を選ぶ」を押します。

ここで、Jarにパッケージ化された中に定義してある特典選択フローへ移ります。
特典選択フローにもinitializer定義(こちらはXMLなのでタグですが)しており、処理が呼び出されて以下表示されます。

f:id:kikutaro777:20131201104451j:plain

画面はこんな感じに。
f:id:kikutaro777:20131201105022j:plain

会員登録フローで入力していた情報をinbound-parameterで受け取って表示しています。
同一のフロー内ではBacking Beanを通じて簡単にデータ共有できますが、別定義のフローと値をやりとりする場合には、こうした定義が必要となるようです。

次に、特典は適当にBを選んで、戻るボタンを押します。
すると、選ばれた特典のコードが表示されています。
f:id:kikutaro777:20131201105148j:plain

ここも先ほどと同じですが、今度はoutbound-parameterで値を呼び出し側に渡しています。

メリットは?

このサンプル、しょぼいわりには意外と手間がかかったのですが、これって何が嬉しいんだろう…(^^?とちょっと思ったり思わなかったり。

メリットをあげてみると大体以下のような感じかと思います。
海外のサイトでも概ねこの辺りの記述なので。。。

  • RequestScopeやViewScopeより長く、SessionScopeより短いスコープ定義

SessionScopeだと長すぎるけど、ViewScopeだと1画面だけで短い…もやもや、って感じたことがあるので、FlowScopeは嬉しい気もします。

でもそれってConversationScopeも同じようなことができるよね?というのがありそうですが、ConversationScopeであれこれ画面遷移がある場合、ちゃんと設計して作らないと

「あれ、会話が終わらね…」
「うわ、この操作したときダメじゃん」

とか、なり…ませんかね(^^;?
自分は前ちょっと触ったときそんな感じでした。センスがないだけかもですが;

  • フローをパッケージング(jar化)することができるため、フローの再利用ができる

今回のサンプルもそうですが、これが大きいメリットなのだと思います。
JSFは「コンポーネント」という概念が強く、再利用するには便利です。
複数の画面部品をくっつけた「複合コンポーネント」も、その1つでした。

今回FacesFlowsが入ることで、ページと遷移のまとまった単位を再利用できるようになりました。うまく設計すれば、プロジェクトが違っても再利用したフローが作れるかもしれません。

実際はなかなか難しそうですが(^^;

  • ブラウザの複数ウィンドウ(タブ)でも各々でフローが管理される

FacesFlowsでは、ブラウザのタブごとにフローを制御することができます。
サンプルでやると、以下のような感じです。

1つのタブで入力して
f:id:kikutaro777:20131201111408j:plain

もう1つタブを起動して、入力します
f:id:kikutaro777:20131201111413j:plain

同一セッションですが、各々で制御されており、情報が混ざることはありません。
f:id:kikutaro777:20131201111522j:plain

f:id:kikutaro777:20131201111526j:plain

これもConvrsationScopeは同じなんですが(^^;うーん。

余談

Faces FlowsはJSF2.2が出たときに少し試そうとしたのですが、情報が少ないのと、実際に書いてみても全然動かなくて困ってました(^^;
今年JavaOne2013に参加したのですが、その際、Faces Flowsのデモをやっているセッションがあって、その方がGitHubにサンプルもあげてました。

そのセッションレポートは以下
Challenge JavaOne 2013 レポート - 10 Tips for Java EE 7 with PrimeFaces -
https://github.com/marfous/j1demo-pf

今回のサンプルもほとんど上記のをベースに、そこでは使われていなかったメソッドを色々試した、というものです。
やっぱり実際に動く参照があることのありがたさを身に染みました…(^^;

にほんブログ村 IT技術ブログへ
にほんブログ村
にほんブログ村 IT技術ブログ Javaへ
にほんブログ村