JSF 2.0 の詳細について
2012年8月24日 at 12:28 午前 4件のコメント
JSF 2.0 の詳細について下記に説明します(説明文章は未完成)
JavaServer Faces 2.0 (以降 JSF) の説明を行う前に、完全に Java EE 6 で作成したデモアプリを参照してください。このデモは JPA, EJB, CDI, JSF を使用して作成したアプリケーションで、JPA を使って曖昧検索を行い、オートコレクトのような機能を実装したアプリケーションです。プロジェクトの作成から実装完了まで 12 分程でできるこのアプリケーションは Java EE 6 で Web アプリケーションを開発する際の参考になるアプリケーションで、ハンズオンとして手を動かしながら共に学ぶ事のできるサンプルになるかと思います。この動画をご確認頂くと分かるのですが、統合開発環境が自動的にコードを作成してくれるため実際にコーディングを行っている部分が少なく、いかに Ajax のアプリケーションを簡単に作成できるかご確認いただけるかと思います。
デモ URL(http://youtu.be/qkOPAwr0YqY)
それでは、JSF の詳細について紹介します。
JSF はコンポーネント・ベースで開発が可能な Web アプリケーション開発フレームワークです。JSF では標準仕様中で提供されている HTML コンポーネントの他、3rd パーティ・ベンダー(ICEfaces, PrimeFaces など)が提供する Web コンポーネントを、自身の Web ページに組こみながら作成する事が可能な Web アプリケーション構築フレームワークです。
まず、JSF を使用した場合 Web アプリケーション中でどのようにして開発を行っていくのか、どこまでの部分を JSF のフレームワークがカバーするかについて紹介します。JSF フレームワークを利用して Web アプリケーションを構築する際、ブラウザ、もしくは携帯のような端末で描画されるプレゼンテーションの部分と、アプリケーションのロジックを実装する部分をそれぞれ実装します(左と真ん中部分)。
アプリケーションのロジックでは、例えば、入力データに対する整合性チェック(バリデーション)や、データの変換を行うコンバージョン、さらにはある特定の Web ページから別の Web ページへ移動するための画面遷移の仕組み、また入力データの変更通知やボタンが押下されたといった、ユーザの操作に起因するイベント処理などを JSF フレームワークを利用して実装します。その他の、DB 連携( O/R マッピング)や、ビジネスロジック等は別途、JPA や EJB といった技術と連携して実装します。
JSF は MVC アーキテクチャに基づく開発を行います。モデル部分を JSF の Managed Bean (もしくは CDI) を使って実装し、ビュー部分を JSF の Facelets (xhtml)、コントローラ部分を Faces Servlet で担当し実装を行っていきます。どの URL に対してリクエストが来たのか、どのビューのページで処理を行うのか等のコントロールを Faces Servlet が行いますが、この Faces Servlet は web.xml 設定ファイルに設定を行った後は、プログラマは直接この Servlet に対して操作を行う事はありません、実際にプログラマが開発を行う際には、ビューの部分とモデルの部分を中心に開発を行っていきます。
続いて、JSF の内部アーキテクチャについて紹介します。クライアント(ブラウザ)から HTTP リクエストをサーバに対して送信すると、Faces Servlet がリクエストを受信します。Faces Servlet は JSF に関する設定 (faces-config.xml の設定ファイル) を読み込み、各種機能、設定、プロパティ等を読み込みリクエストに対する処理を行います。
JSF 2.0 から faces-config.xml ファイルは省略可能(オプション化)となり、faces-config.xml ファイルで記載していた設定項目についてはプログラム中のアノテーションで記載できるようになりました。Faces Servlet がリクエストを受信した後、適切なコンテンツに対してリクエストをリダイレクトします。
JSF 1.2 までは Web ページを JSP を用いて記述していましたが、JSF 2.0 からは Facelets (xhtml) で記載する事を推奨しています。これにより、ビューの技術の実装箇所に、(Scriptlets のような)ビジネスロジックを埋め込む事ができなくなるため、ビューとロジックを完全に分離可能で、より可読性、保守性の高い Web ページを作成する事ができるようになります。
Facelets 中に、データの変換(コンバート)、検証(バリデート)を指定する事で、必要に応じたデータの変換、検証処理を実装できます。また、入力されたデータの取得や、ボタンが押下された際のイベント処理を行うために Managed Bean でこれらを実装し、ビュー部分とモデル部分を明確に分けて実装する事が可能になります。
以降の説明では、開発者の役割(Web ページ作成者、アプリケーション開発者、コンポーネント開発者)に応じて、それぞれ、どのようにして開発を行っていくかについて紹介します。
JSF では開発時における役割を Web のページの作成者(Webデザイナ)、アプリケーションの開発者、JSF のコンポーネント開発者の3つにを分けて考える事ができ、それぞれの役割で独立して開発ができるようになっています。Web ページの作成者は JSF における Web のデザイン部分に特化し、アプリケーション開発者は、データの整合性のチェック、変換、画面遷移、ビジネスロジックとの連携部分にフォーカスして実装を行います。また、独自の HTML コンポーネント(拡張コンポーネント)を作成する事も可能です。
コンポーネント開発者は、プロジェクト、もしくは組織毎に独自の再利用可能なコンポーネントを作成し、CSS や JavaScript といったリソースを独自コンポーネントに埋め込む事で Web ページの作成者がより容易に Web ページを作成する事ができるようになります。JSF 2.0 からは単一のコンポーネントを作成可能なだけではなく、複数のコンポーネントをまとめて一つに扱う事ができるよう、カスタムな複合コンポーネントの作成も容易にできるようになっています。JSF 1.2 と比べてコンポーネントの作成は非常に容易になっています。
まず、始めに Web ページ作成者の役割を持つ方が、どのようにして JSF の Web ページを作成していくかについて紹介します。
ご存知の通り、Web ページは HTML で提供されているコンポーネントを組み合わせて Web ページを作成していきます。一般的によくある会員登録ページでは、テキストフィールド、ラジオボタン、コンボボックス、ボタン等といったコンポーネントから Web ページを構成し、CSS や JavaScript をこのページに対して適用しながら、デザインや振る舞いなどを洗練していきます。
例えば、HTML のテキストフィールドは、HTML では < INPUT TYPE=”TEXT” > で表現しますが、JSF ではテキストフィールドを表現するために、<h:inputText > というタグを記述します。同様に <h:selectOneRadio> タグを使用してラジオボタンを表現します。
HTML のコンボボックスについては、HTML では <select>を使用して表現しますが、JSF では<h:selectOneMenu> を記述します。ボタンは<h:commandButton> を使用して表現します。このように、JSF では標準の HTML タグに一致する JSF タグが用意されており、JSF タグを記述する事で Web ページを構成していきます。
JSF の標準仕様中には、先ほど紹介したタグ以外にも HTML で提供されているタグに一致するタグが用意されており、これらのタグを利用する事で、様々な HTML コンポーネントを持つ Web ページを迅速に開発できるようになります。標準仕様で提供されているタグ一覧は
http://javaserverfaces.java.net/nonav/docs/2.0/pdldocs/facelets/
をご参照ください。
JSF の各タグは HTML 4.0 や DHTML の要素をタグ内に埋め込む事ができるため、より細かなイベント処理や、タグに対する制限の実装も容易に行う事ができます。
もちろん、JSF ページ全体や、個々のJSF タグに対してスタイルシートを適用する事も可能です。JSF でスタイルシートを適用するためには、<h:outputStylesheet> タグを使用してスタイルシートを埋め込む事ができます。JSF 2.0 ではスタイルシート、JavaScript 、画像等の外部のリソースは、基本的に resources ディレクトリに配置します。この例では、resources ディレクトリ配下の “library” で指定してディレクトリ(ここでは css)から、commonpage.css ファイルを読み込んでいます。スタイルシートを適用するためには、class で指定した ID に対して適用を行います。
また、JavaScript も同様に組み込む事が可能です。JavaScript を Facelets 中に組み込むためには、<h:outputScript>タグを使用します。スタイルシートの時と同様、library で指定したディレクトリ(ここでは javascript)にスクリプトファイルを配置し、 chackPassword.js ファイルを読み込んでいます。どのタイミングで JavaScript の関数を実行するかは、通常の HTML と同様です。ここではボタンが押下(onclick )された際に関数を呼び出しています。
JSF では 描画用文字列(メッセージ)の国際化機能も標準で提供されており非常に簡単に実装できます。具体的には、ブラウザからサーバに対して接続する際のロケールが ja_JP の時、もしくは en_US の時、それぞれのロケールに応じて表示用文字列を切り替える事ができます。
実際に、「名前」、「Name」の文字列をロケールに応じて切り替える方法について考えてみます。
<h:outputText> タグ内の value に EL 式(value=”#{msgs.userName1}”)を記載する事で切り替える事ができます。
もう少し、詳細に <h:outputText>タグについて説明します。
実際のプロパティ・ファイルとプロパティ・ファイルを取り出すための変数を faces-config.xml ファイル内で定義します。
ここでは、<resource-bundle> の <base-name> にプロパティを指定します。そしてこのプロパティに対してプログラムから参照するための変数を <var> に定義します。
実際のプロパティファイルは、クラスパスで参照できる場所に存在していなければならないため、/WEB-INF/classes に配置します。ここでは、/WEB-INF/classes/jp/co/oracle/msgs/ ディレクトリ配下に、各ロケールに対応したプロパティファイルを作成します。
- ファイル名_en_US.propeties
- ファイル名_ja_JP.properties
各ファイルは native2ascii で変換して保存します。
最後に、JSF のタグ内から プロパティ内で作成した、キー、バリューの組み合せ(キー:userName1、バリュー:ロケールが ja_JP の場合は名前、en_US の場合は Name)で文字列を取得します。
※注: JSF 1.2 までは、<f:loadbundle> タグを使用して、JSF タグ内で使用するリソースを読み込む事ができましたが、JSF 2.0 では、極力 <f:loadbundle> タグを個々のページに埋め込むのは避けてください。リソースの読み込みは、フレームワークの処理として重い処理のため、個別のページでリソースを読み込むよりも、アプリケーションレベルで設定を行った方がパフォーマンスが向上します。JSF 2.0 では、例で示すように、 <f:loadbundle> でリソースファイルを読み込む変わりに faces-config.xml ファイルへの設定・記載を推奨します。
JSF 2.0 では新たにテンプレート機能が追加されました。標準的な Web サイトでは共通項目(ヘッダ、フッダ、右ペイン、左ペインなど)と個別のページに特化した内容を分けて構成する事がよくあります。このような共通ページをテンプレート化して、テンプレートを元に個別のページを作成する事ができるようになりました (Struts のタイルのような物)。テンプレートを作成する事によって、個別ページの作成に専念でき、テンプレート内の特定のページを更新した場合も、他に対して1度の修正で全てに適用できるようになるため運用・保守性も大幅に向上します。JSF のテンプレート機能の利用方法については、参考 URL をご参照ください。
ご参考:
https://yoshio3.com/2011/01/14/jsf20-new-with-facelets-template/
ビューの実装が JSP から Facelets になる事で Web ページのデザイナーと開発者の共同作業が今まで以上やりやすくなります(する事が可能です)。基本的に、JSF の開発では JSF の標準で用意されているタグを使用して画面デザインを作成していきますが、ここでは別の方法をご紹介します。
以前の JSF 1.2、もしくは JSP ベースでの開発 (Struts も含む) を行う場合、Web デザイナから受け取った HTML デザインを元に、専用の JSP コードや専用のタグに変換して実装を行っていました。この方法ではプログラマが HTML から JSP コードに変換した後(もしくはその最中で)、デザインの変更を加えようとした場合に大変になる場合があります。例えば、デザイナが追加した HTML に対する修正を見つけ出す事が大変であったり、デザイナにとっては変換された JSP コードを確認する事が困難だったりします。また、JSP に変換したコードは Web コンテナ上でしか動作しなくなるため、必ず Web コンテナを用意しなければ、修正したデザインを確認できなくなります。つまり、デザイナーから受け取った HTML を元に、JSP にコード変換した後のデザイン変更は非常に困難になります。
JSF 2.0 では Facelets (XHTML) で Web ページのデザインを実装するようになりました。通常は前述の通り、JSF フレームワークが用意する JSF のタグを用いて実装を行いますが、JSF では別途、通常の HTML に対して jsfc という要素と対応するコンポーネントを指定し、HTML 内に埋め込む事で JSF ページとして動作させる事も可能になりました。元々の HTML タグがそのまま残っているため、ブラウザから直接 xhtml ファイルを読み込んだ場合は、単なる静的な HTML ファイルとして認識・表示され、Web コンテナ経由でファイルを読み込んだ場合は、JSF ページとして認識・表示させる事ができるようになります。全く同一の xhtml ファイルをデザイナとプログラマが共有できるため、プログラマが jsfc タグを挿入中に、デザインの変更を加えるなど、デザイン修正に対する出戻りが発生しなくなります。
この方法は、全ての場合において適用できるわけではありませんが(繰り返し項目のあるテーブルなど)、多くの場合で適用する事ができ、xhtml ファイルの開発保守性等も高まる事が期待されます。
具体的に jsfc 要素を埋め込む例を示します。通常 Web デザイナは HTML ファイル、CSS 等を記載します。これに対して、JSF では通常、<h:body> や <h:form> 等のタグを XHTML ファイルに記載していきますが、これらのタグの変わりに jsfc=”h:body” 、jsfc=”h:form” 等の要素を HTML タグに対して挿入していきます。
具体的には青文字で示す部分が JSF のプログラマが記載する部分です。元々の HTML タグを残したまま、追加の要素を挿入する事でブラウザでは静的 HTML ファイルとして参照でき、Web コンテナから参照した場合は JSF ページとして扱われます。
また、この際スタイルシートや JavaScript も同様に扱う事ができますので、CSS ファイルの置き場所だけ注意(同一の CSS を参照できるように)する事で同じファイルを参照する事ができます。
<link rel=”stylesheet” href=”./resources/css/commonpage.css” type=”text/css” jsfc=”h:outputStylesheet” library=”css” name=”commonpage.css” />
例えば、上記の例では、JSF のリソース・ファイルの置き場の決まりに従い、resources ディレクトリ配下に CSS ファイルを配置しています。XHTML ファイルを直接ブラウザからオープンして読み込んだ場合は、rel=”stylesheet” href=”./resources/css/commonpage.css” type=”text/css” の部分が有効となり、相対パスを参照する事で CSS ファイルを読み込みます。
また、Web コンテナから JSF ページとして読み込んだ場合は、jsfc=”h:outputStylesheet” library=”css” name=”commonpage.css”の部分が有効となり、resources ディレクトリ配下の、css ディレクトリに存在する、commonpage.css ファイルを読み込む事ができます。同一ディレクトリに存在する、同一 CSS ファイルを読み込みますので、環境に応じてパスを書き換えるなどの手間も不要となります。
このように、JSF は Facelets による実装となり開発効率・運用保守性が大幅に高まっています。また、JSP ベースで開発をしていた頃に比べ、View の実装部分にビジネスロジックを埋め込むような事(Scriptlet)はできなくなりましたので、出来上がったコードの可読性、保守性も大幅に高まります。さらに、JSP のように Java クラス(Servlet) に変換する必要がないため、ある一部分のコードを修正するたびに再度コンパイルし、クラスをロードするといった、画面修正から確認までに要していた待ち時間のストレスも大幅に現象します。
是非、JSP ベースでの開発は止めて、Facelets による開発へと移行してください。
さて、ここまでは Web のビューの部分の実装方法について紹介してきましたが、以降では、Web ページで入力された情報を受け取る方法や、画面遷移方法などバックエンドで必要となる処理について紹介します。
まず、はじめに JSF のリクエスト処理のライフサイクルについて紹介します。
※ 簡単な JSF の Web アプリケーションを作成する上では、ここに示すライフサイクルの詳細を十分に理解していなくてもプログラムを書く事ができます。しかし、プログラムを行っている際に何らかの問題が発生した場合、どのフェーズまでプログラムが実行されているのかを把握しておく事で原因を究明しやすくなります。また、各フェーズの実行順を越えて何らかの処理を施したいような場合、どのフェーズからどのフェーズに移行するのか、させるのかを把握して置く事は細かな処理を行う上で重要となってきます。そこで、是非このライフサイクルの概念を理解してください。
以降の説明では、この JSF のライフサイクルの各フェーズでどのような事を行っているのかについて紹介します。
まず、はじめに View の復元フェーズについて紹介します。
クライアントのブラウザから JSF のページに対してリクエストが送信された際、JSFのコントローラである、Faces Servlet は、復元フェーズで、クライアントで (Facelets で実装) で描画されている HTML の全項目に一致する、View のコンポーネント(UIViewRoot) ツリーを生成(もしくは復元)します。
※ 多くの場合このコンポーネントツリーを意識せずに、JSF で Web アプリケーションの開発を行う事ができます。しかし、内部的に Java のクラスに変換されツリー構造で扱われている事を理解しておいてください。細かな制御が必要になった場合にこのツリーを操作する事が可能です。初心者の方はこの部分は軽く流して頂いて結構ですが、本格的に使用される場合は是非理解してください。このように、ツリーで表現する方法は、JSF に限らず JavaFX でも同様にツリー構造として表されます。
例えば何らかの項目を描画(出力)する箇所では、ツリー中で UIOutput クラス(もしくはそれを継承したサブクラス)で表現され、何らかの入力が必要なコンポーネントが存在する場合は、ツリー中で UIInput クラス(もしくはそれを継承したサブクラス)として表現されます。
一旦 UIVewRoot の Java オブジェクトのツリー構造が生成されると、このツリー構造をメモリ中の FacesContext のインスタンスに対して保存します。ユーザから初めてリクエストがあった場合にツリーが生成され、2回目以降のリクエスト時に復元されます。
JSF は内部的にサーバ側でこの復元された View コンポーネントのツリー構造 (Java クラスのツリー) を操作するといっても過言ではありません。
※ 動的にコンポーネントのツリー構造を書き換えたり、各コンポーネントに設定されている属性を細かく制御したいような場合に、これらのコンポーネントを直接操作する事ができます (後述:コンポーネントバインディング)。また標準で提供されている UI コンポーネントのクラスを元に、自身で独自のコンポーネントを生成し、対応する JSF のタグを生成する事も可能です。(HTML 5 対応のコンポーネントを独自に作成する事も可能。本説明ではコンポーネントの作成に関する説明は時間の関係上割愛しています。)
次に、リクエスト値の適用フェーズについて説明します。
View の復元フェーズが終了した後、リクエスト値の適用フェーズに移行します。リクエスト値の適用フェーズは、クライアントから送信されたリクエスト値(入力データ等)を、UI コンポーネントのツリー中に含まれる全コンポーネントに対して適用していくフェーズです。
内部的には、JSF の実行環境は UI コンポーネントのルートである UIVewRoot の UIVewRoot#processDecodes() メソッドを実行し、これにより、ツリー配下に存在する全ての子コンポーネントに対して再帰的に processDecodes() メソッドを呼び出していき、結果として全 UI コンポーネントに対してリクエストされた値を適用していきます。例えば、ここに示す例のように、画面中の入力項目(inputText)に対してある値が入力された場合、新しい値がコンポーネントに対して適用されます。
リクエスト値の適用フェーズが完了すると、入力値の検証フェーズへ移行します。入力値の検証フェーズでは、入力された値が妥当かどうかの検証(バリデーション)や、入力された値を変換(コンバージョン)する処理を行います。内部的に実行される順番は、データを変換(コンバージョン)した後にデータの検証(バリデーション)を行います。
内部実装には、processDecodes() と同じように、JSF の実行環境が UI コンポーネントのルートである UIVewRoot の UIVewRoot#processValidators() を実行し、 ツリー配下に存在する全ての子コンポーネントに対して再帰的にprocessValidators() を実行していきます。各子コンポーネントはコンポーネントに関連付けられた、検証(バリデーション)や変換(コンバージョン)を実行していきます。
検証(バリデーション)では入力された値が、期待値に対して妥当か否かを検証します。JSF 2.0 では新たに Bean Validation (JSR-303)が統合されました。そこで Bean Validation で提供されている、アノテーションを使用して入力値を検証する事ができます。 (@NotNull, @Size, @Pattern @Min, @Max, @Null など) また、Bean Validation を使用して独自のバリデーションを作成する事も可能です。仮に検証に失敗した場合、失敗理由を表示するために、<h:message> タグでエラー内容を表示する事ができます。対象となる検証対象のコンポーネント ID (ここでは、<h:inputText id=”e-mail>) を <h:message for=”e-mail”> のように for で指定する事で個々のコンポーネント毎にエラーを表示する事ができます。個別にエラーを表示するのではなく、まとめて1カ所でエラーを表示させたい場合、<h:message> の変わりに <h:messages> を使用する事もできます。
※補足:
JSF 2.0 で Bean Validation を無効にしたい場合、web.xml の設定ファイル中に下記のパラメータを設定する事で無効にする事ができます。
<context-param> <param-name>javax.faces.validator.DISABLE_BEAN_VALIDATOR</param-name> <param-value>true</param-value> </context-param>
Bean Validation を利用する他、JSF フレームワークが元々用意している Validation を利用して実装する方法もあります。標準で提供されているバリデータとしては下記があります。
- LengthValidator (<f:validateLength> タグを使用)
- LongRangeValidator (<f:validateLongLength> タグを使用)
- DoubleRangeValidator (<f:validateDoubleLength> タグを使用)
- RegexValidator (<f:validateRegex> タグを使用)
また、標準で定義されているバリデーション以外に、独自のバリデータを実装したい場合、javax.faces.validator.Validator インタフェースを実装した独自のクラスを作成します。Validator インタフェースには validate(FacesContext context, UIComponent component, Object value) のメソッドが定義されており、このメソッド内に独自のバリデーションコードを実装します。
アプリケーションから、この実装したバリデーションを利用できるように、@FacesValidator アノテーションを付加しています。JSF 1.2 までは下記のような設定を faces-config.xml に記載しなければなりませんでしたが JSF 2.0 からは、このアノテーションを付加するだけで利用可能となっています。
<validator> <validator-id>emailValidation</validator-id> <validator-class> jp.co.oracle.validate.EmailValidator </validator-class> </validator>
JSF の Facelets 側では下記のように検証を実施したい JSF コンポーネントのタグ内で作成したバリデーションを指定します。
<h:inputText id="email" value="#{user.email}" size="20" required="true"> <f:validator validatorId="jp.co.oracle.validate.EmailValidator" /> </h:inputText>
検証(バリデーション)と同様、JSF では標準で下記のコンバータを提供しています。
- BigDecimalConverter : java.math.BigDecimal に変換
- BigIntegerConverter : java.math.BigInteger に変換
- BooleanConverter : java.lang.Boolean/boolean に変換
- ByteConverter : java.lang.Byte/byte に変換
- CharacterConverter : java.lang.Character/char に変換
- DateTimeConverter : java.util.Date/java.sql.date に変換
- DoubleConverter : java.lnag.Double/double に変換
- FloatConverter : java.lang.Float/float に変換
- IntegerConverter : java.lang.Integer/int に変換
- LongConverter : java.lang.Long/long に変換
- NumberConverter : java.lang.Number に変換
- ShortConverter : java.lang.Short/short に変換
上記全てのコンバータは、javax.faces.convert.Converter インタフェースを実装しています。Converter インタフェースでは下記の2つのメソッドが定義されており、独自のコンバータを実装する場合はこれらのメソッドを実装します。
public Object getAsObject(FacesContext context, UIComponent component, String value) public String getAsString(FacesContext context, UIComponent component, Object value)
引数の context はこのリクエストに紐づく FacesContext のインスタンスを指定し、component にはデータ変換を行いたいUIComponent を指定します。value には実際に変換を加えたい値を指定します。
この例では、入力された文字列を Date 型に変換しており、書式が異なる場合にエラーメッセージを表示しています。
入力値の変換、検証が行った後、モデル値の更新を行います。
モデル値の更新とは、JSF の Facelets で定義され、内部的に UIComponent のサブクラスとして表されているコンポーネントと(<inputText> で定義されたコンポーネントは 内部的にjavax.faces.component.html.HtmlInputText になる)、JSF の @ManagedBean (もしくは CDI の @Named) アノテーションを付加した POJO のクラス(モデル)で定義される変数 (ここでは name, city,phone,email 等) をバインド(結びつけ)し、入力された情報をモデルに対して代入する作業を行います。
もう少し簡単に説明すると Facelets 内で <h:inputText class=”intext” id=”username” value=”#{person.name}”/> で記載されたデータ(名前)が、POJO で実装されている @ManagedBean (もしくは@Named) アノテーションが付加されたクラスの変数 (private String name)に対して一致・更新させる作業を行います。つまり、以降のプログラミングでは複雑な UIView のツリーや UIComponent を直接操作しなくても、このモデル(POJOのJavaクラス)値に適用された値を使ってユーザからの入力データを取得したり、バックエンドの処理で利用できるようになります。
ここが、JSF が入力値などを扱う際に最も便利な点です。
※ 別途 Getter, Setter のメソッドが必要です。
もし Getter メソッドしか存在しない場合は、読み込みだけが可能となり、Setter メソッドしか存在しない場合は、書き込みだけが可能となります。
内部実装としては、他のフェーズと同様、JSF の実行環境は UI コンポーネントのルートである UIVewRoot#processUpdates( ) メソッドを実行し、再帰的に子のコンポーネントで同様のメソッドを実行していきます。他の処理と異なるのは UIInput クラス(およびそのサブクラス)では processUpdates( )をオーバライドし、追加のメソッド(updateModel( ))呼び出しを行っています。
これは、UIInput クラスのコンポーネント(テキストフィールド、メニューなど)だけがモデル(Managed Bean, CDI)に対して値を更新できる事を意味しています。
JSF でモデルとなるクラスは JPA の Entity として扱う事も可能です。JPA の Entity もまた JPA の管理下にない場合は、単なる POJO のクラスであるため、モデルとなるクラスを共通して扱う事もできます。これにより、Validation の実装においてそれぞれで実装する必要もなくなりテスト工数等も少なくする事ができます。
※ ただし、クラスの設計上は気をつけてください。JSF のモデルと JPA の Entity を共通で扱う場合、しっかりしたパッケージングを行わない場合可読性、保守性が低下する場合もあります。
8 ~ 9 割がたは、Value バインディングを用いて、モデル値を更新し、以降の処理でデータを取得し処理を実装する事ができますが、ここで示した Value バインディング以外のバインド方法を紹介します。それが、Compoenent バインドです。
Component バインディングでは、Facelets 中で直接 UIComponent に対してバインドします。具体的には value=”#{person.name}” の代わりに binding=”#{person.name}” を指定し、@ManagedBean, CDI 側では UIComponent (ここでは HtmlInputText) に対してバインド(結びつけを行います)します。
そこで疑問になるのが、Value バインディングと Component バインディングは何が違うのか?どういう時に使うのかという事です。
Value バインディングでは、モデル値の更新フェーズ(ライフサイクルの4番目)までデータを取得する事ができませんが、Component バインディングはモデル値の更新フェーズ前に入力された値等を取得する事も可能です。
実際 Component バインディングは、最初のリクエスト時 UIViewRoot のツリー構造が生成され(ライフサイクルの1番目)、クライアントブラウザに表示した後に(ライフサイクルの6番目)、UIViewRoot の構造を後々再利用するために
FacesContext に保存します。この時点で、binding で指定された UIComponent の インスタンス変数に対して setされます。もう少し具体的に例をあげて説明すると、最初のリクエストの時点が完了した時点で、private HtmlInput Text name; の
Setter メソッド、setName() が呼び出されます。
最初のリクエスト:
情報: START PHASE :RESTORE_VIEW 1 情報: END PHASE :RESTORE_VIEW 1 情報: START PHASE :RENDER_RESPONSE 6 情報: setInputComponentData() is called. (ここでHtmlInputText name に対する setName()が実行) 情報: END PHASE :RENDER_RESPONSE 6 |
2回目以降のリクエスト(必要項目に記入してボタンを押下したような場合)では、UIView のツリーが復元(ライフサイクルの1番目)された後、リクエスト値の適用フェーズ(ライフサイクルの2番目)で UIComponent に対して入力値を適用します。(この例では HtmlInput#setSubmittedValue()が実行される。) この、setSubmittedValue() された値は、入力値の検証フェーズ(ライフサイクルの3番目) が完了するまで、getSubmittedValue() で値を取り出す事ができます。入力値の検証フェーズが終わると、入力値は、setSubmittedValue(null)が実行され、HtmlInput#setValue(“foo”) が呼び出され、以降のフェーズでは入力値は HtmlInput#getValue() を利用して取得するようになります。
※ UIInput#getSubmittedValue() はdecode()とvalidate()でのみ使用すべきとAPI に記載されています。
getSubmittedValue |
2 回目のリクエストのフェーズ3まで
情報: START PHASE : RESTORE_VIEW 1 情報: setInputComponentData() is called. 情報: setInputComponentData() is called. 情報: END PHASE : RESTORE_VIEW 1 情報: AFTER: HtmlInputText#getValue() :null 情報: AFTER: HtmlInputText#getSubmittedValue() :null 情報: START PHASE : APPLY_REQUEST_VALUES 2 情報: START PHASE : PROCESS_VALIDATIONS 3 |
ここで、コンポーネントバインドを利用する例を一つ紹介します。入力されたデータのバリデーションのフェーズについて思い出してください。前述した独自のカスタム・バリデーションを作成する際、基本的に1コンポーネントに対して1つのバリデーションを実装しました。それでは複数のコンポーネントで、それぞれ入力された値に対して依存関係を検証するバリデーションを実装したい場合どうすればよいでしょう。通常の場合では複数のコンポーネントに対する依存関係を検証するバリデーションを実装する事は困難ですが、このような場合、UIComponent にバインドしておく事で、入力値の検証フェーズで入力値を取り出したり(getSubmittedValue())、検証する事が可能になります。
2 回目のリクエストのフェーズ4
情報: START PHASE :UPDATE_MODEL_VALUES 4 情報: setInputData() is called. <==== ここでバリューバインドが実行 情報: END PHASE :UPDATE_MODEL_VALUES 4 |
※ 余談:上記のように、各ライフサイクルのフェーズの詳細な動作を確認したい場合、もしくは何らかの処理を挿入したい場合、PhaseLister を実装したクラスを作成する事で、各フェーズ中に任意の操作を挿入する事ができます。
public class LifecycleConfirmListener implements PhaseListener{ public PhaseId getPhaseId() { return PhaseId.ANY_PHASE; } public void beforePhase(PhaseEvent event) { } public void afterPhase(PhaseEvent event) { } }
PhaseLister を利用可能にするため、faces-config.xml 中に独自実装した PhaseListener を登録
<faces-config version="2.1" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd"> <lifecycle> <phase-listener>jp.co.oracle.lifecycle.LifecycleConfirmListener</phase-listener> </lifecycle> </faces-config>
モデル値の更新フェーズが完了すると、アプリケーション・ロジックの呼び出しフェーズへ移行します。簡単に説明すると、ボタンやリンク等が押下された際の処理を行うフェーズだとご理解ください。モデル値の更新フェーズでユーザから入力されたデータをプログラム上から利用できるようになりましたので、この入力されたデータを元に外部のメソッドを呼び出したり、データベースでクエリを発行したり様々な処理を行います。このようなアプリケーションのロジックを実装するコードをこのフェーズで実装します。
内部実装ではルートツリーの UIVewRoot#processApplication() を実行し、 その後、ActionSource を実装する 全 UIComponent で、UIComponent#broadcast() を実行してイベントを通知します。キューイングされているイベントをブロードキャストします。
その後、任意のアクションリスナーはアクションイベントを処理します。独自のアクション・リスナーやアクション・メソッドを実装する事が可能で、ActionSource を実装する UIComponent に関連づけます。通常、デフォルトのアクション・リスナーを使用してアクション・イベントを処理します。
実際の処理について説明します。”DBへ登録”ボタンは Facelets では下記のように記載します。
<h:commandButton value="DBへ登録" action="#{customerManage.savePerson}"/>
このボタンが押下された場合、JSF の Managed Bean (もしくは CDI) で実装した下記のメソッドが呼び出されます。
@ManagedBean(name ="customerManage") //@Named(value =“customerManage”) @RequestScoped public class CustomerManage { public String savePerson() { //バックエンド処理の実装 return "success"; } }
savePerson() のメソッド中で、ブラウザ中に入力されたデータを取得(例:getUserName(), getEmail() 等)するコードを記述し、必要に応じて EJB をインジェクトしてビジネスロジックを、このメソッド中で呼び出します。
余談(中・上級者向け):上記では Facelets 中に action を指定しアプリケーションのロジックを実装する方法を紹介しましたが、action を実行する前に何らかの処理を挿入する事もできます。下記の Facelets の例に示すように、actionLister で action を呼び出す前に挿入したい処理メソッドを指定しできます。
action で指定するメソッドは String 型を返し、返り値の文字列を後述する画面遷移(ナビゲーション)で使用します。一報、actionLister で指定するメソッドは void 型で画面遷移には利用できません。そこで本メソッドは、ログの記載、action を実行する前に何らかの処理を施したい場合、画面遷移の必要無い処理などを記載するような場合に使用します。
<h:commandButton value="DBへ登録" actionListener="#{customerManage.beforeSavePerson}" action="#{customerManage.savePerson}"/>
ActionListener の実装コードを下記に記載します。
@ManagedBean(name ="customerManage") //@Named(value =“customerManage”) @RequestScoped public class CustomerManage { public void beforeSavePerson(ActionEvent event){ //押下されたボタンの ID を取得 buttonId = event.getComponent().getClientId(); } public String savePerson() { //バックエンド処理の実装 return "success"; //画面遷移先 success.xhtml へ移動 } }
アプリケーション・ロジックを呼び出したのち、必要な処理をおこなった後に画面遷移(ナビゲーション)を行います。
JSF 1.2 まではナビゲーションの定義を、faces-config.xml ファイルに XML として記載する必要がありました。XML におけるナビゲーション・ルールは、 元のページ(from-view-id)と1つ以上の遷移先の場合分け(navigation case)を定義します。各ナビゲーション・ケースでは、論理的な条件を示す”outcome” と遷移先を示す “to” (to-view-id)を持っています。例えば、上の例では現在 page1.xhtml の画面が表示されており、page1.xhtml 中のボタンやリンクで指定した outcome の属性が “next” であった場合、page2.xhtml へ遷移するという事を示しています。
もちろん JSF 2.0 においても同様に XML でナビゲーションのルールを設定する事は可能です。また JSF 2.0 から、ナビゲーション設定の柔軟性を高めるため、条件に応じてナビゲーションを行うために、XML に IF 要素を追加し IF 文を使用してルール設定も可能になっています。
<navigation-rule> <from-view-id>page1.xhtml</from-view-id> <navigation-case> <from-outcome>next</from-outcome> <to-view-id>/page2.xhtml</to-view-id> <!--- 下記 EL 式で表記された条件が true を返す場合のみ このナビゲーション・ルールを実行 <if>#{foo.someCondition}</if> </navigation-case> </navigation-rule>
しかし、XML によるナビゲーション・ルールの設定は非常に面倒です。そこで、JSF 2.0 ではナビゲーションの実装をよりかんたんにできる仕組み「暗黙的なナビゲーション」ルールを追加しました。
JSF 2.0 で”outcome” はページ内の action に直接論理的な文字列 “page2” を記述したり、page2.xhtml や page2.jsf のように直接遷移先のページ名を指定することも可能です。論理的な文字列 “page2″ を記載した場合、XML ファイル中に一致する outcome 属性が存在しない場合、”.xhtml” の文字列が自動的に追加され、”page2.xhtml” へ遷移します。
また、ボタンが押下された際に、返り値に String 型を持つメソッド public String savePerson(){} を呼び出し、その返り値の文字列から動的に導きだすことも可能です。メソッドの実装中で if 文などの条件式を実装し条件に応じて返り値の文字列を変更することで、任意のページへナビゲーションすることができます。
Entry filed under: 未分類.
1. rkyymmt | 2014年1月24日 3:58 午後
faces-config.xhtml ではなく、faces-config.xml なのではないでしょうか?
2. Yoshio Terada | 2014年1月25日 4:29 午前
ご指摘誠にありがとうございます。たしかにその通りでございました。後日修正したいと思います。ありがとうございました。m(_ _)m
3. rkyymmt | 2014年1月24日 4:01 午後
faces-config.xhtmlではなく、faces-config.xmlなのではないでしょうか?
4. JSF(JavaServer Faces) xhtmlファイルのデザイン分離 | 響雲 | 2015年10月7日 11:16 午前
[…] 検索した結果、Javaエバンジェリスト 寺田様のブログに書いてありました。 https://yoshio3.com/2012/08/24/detail-of-jsf20/ […]