第3回 JSFベースのプレゼンテーション・デザインを考える

  Page.1 Page.2

 JSFページとバッキング・ビーンをJSF-ELで関係付ける

 ビューとしてのJSFページとページ・コントローラとしてのバッキング・ビーンを関連付けるには、JSFページ内のUIコンポーネントからJSF-ELを使ってバッキング・ビーンと入出力モデルを適切に関連付けることによって実現します。

 検索条件の入力パラメータは、フォームの入力フィールドから取得されます。検索画面の例では、HTML用のUIコンポーネント<h:form>および<h:inputText>を用い、CustomerCriteriaオブジェクトに直接バインディングすることで、入力パラメータを洗練された(fine-grained)ビジネス・オブジェクトとして直接得ることができます。

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:jsp="http://java.sun.com/JSP/Page">
  <jsp:directive.page contentType="text/html;charset=UTF-8" 
                      pageEncoding="UTF-8"/>
  <f:view>
    <head>
      <title>Customer List</title>
    </head>
    <body>
      <h:outputText style="font-size: 24px" value="顧客検索"/>
      <h:form>
        <table>
          <tr>
            <td align="right">
              <h:outputText value="氏名"/>
            </td>
            <td align="left">
              <h:inputText value="#{listBean.criteria.name}"/>
            </td>
          </tr>
          <tr>
            <td align="right">
              <h:outputText value="会社名"/>
            </td>
            <td align="left">
              <h:inputText value="#{listBean.criteria.company}"/>
            </td>
          </tr>
        </table>
        <h:commandButton value="検索" action="#{listBean.find}"/>
              :
      </h:form>
    </body>
  </f:view>
</html>

コラム
本解説のJSFサンプルの書式は、JSP 2.0仕様で導入されたJSPドキュメント形式を採用しています。JSPドキュメント形式は、完全なXMLドキュメントとして記述することができるため、DTDやXML schemaによるバリデーションが可能であるほか、XSLTを用いてJSPファイルを別の目的のドキュメントに変換できるなど、さまざまなメリットがあります。もし、J2EE 1.4環境が利用可能で、J2EE 1.3環境との互換性を確保する必要がないのであれば、JSPドキュメント形式でJSFページを作成されることをお勧めします。しかし、JSFドキュメントに対応していないRADツールを使用して、JSFページのメンテナンスをすることを想定しているのであれば、従来のJSP 1.2形式でJSFを作成しても問題はありません。

 検索画面の[検索]ボタンは、JSFページ上では、UIコンポーネント<h:commandButton>で表現され、action属性にJSF-ELを用いてバッキング・ビーンListBeanのアクション・メソッドfind()にバインディングしています。

    <h:commandButton value="検索" action="#{listBean.find}"/>

 [検索]ボタンが押されたときに実行されるJSFのアクション・メソッドfind()の実装は、以下に示すように驚くほどシンプルなものです。

    public String find() {
        try {
            customerList = customerService.findCustomer(criteria);
            return "success";
        }
        catch (Exception e) {
            return "failure";
        }
    }

 サービス・オブジェクトの参照customerServiceは、JSFのDI機能によってあらかじめバッキング・ビーンに設定されているため、find()メソッドの最初からいきなり使用することができます(これは、CustomerServiceがEJBである場合も同様です。詳細は次回のSpringを利用したビジネス層の解説で紹介します)。ビジネス・メソッドfindCustomer()の引数criteriaは、find()メソッドが呼び出される前に、JSFランタイムが入力チェックを行い、CustomerCriteriaオブジェクトのプロパティnameおよびcompanyが設定された状態で参照可能な状態になっています。findCustomer()メソッドの実行結果であるCustomerBOのリストは、検索画面上で一覧表示をしなければなりませんが、ここではListオブジェクトのままプロパティcustomerListに設定するだけで十分です。後は、アクションの実行結果をString形式でメソッドの戻り値として返すだけです。

コラム
Strutsとの対比で考えると、JSFバッキング・ビーンのアクション・メソッドはStrutsのActionサブクラスに相当しますが、Strutsを利用した場合にはここまで実装を簡略化することはできないのではないでしょうか。また、HTTPリクエストのパターンごとにActionFormおよびActionのサブクラスを実装しなければならないStrutsに比べて、ページ・セントリックに、アクションをバッキング・ビーンにまとめることにより、洗練されたプレゼンテーション層のアーキテクチャを構成することができます。

 JSFランタイムは、アクション・メソッドの実行が完了すると、アクションの実行結果に基づいたページ遷移先を決定し、JSFページのレンダリングを行います。検索画面の例では、ListBeanオブジェクトに設定されたfindCustomer()の実行結果であるCustomerBOのリストを基に、UIコンポーネント<h:dataTable>がHTMLテーブル形式でページを生成します。

        <h:dataTable border="1" 
                     value="#{listBean.customerList}"
                     var="customer">
          <h:column>
            <f:facet name="header">
              <h:outputText value="氏名"/>
            </f:facet>
            <h:outputText value="#{customer.name.last}
                                 #{customer.name.first}"/>
          </h:column>
          <h:column>
            <f:facet name="header">
              <h:outputText value="会社"/>
            </f:facet>
            <h:outputText value="#{customer.company.name}"/>
          </h:column>
          <h:column>
            <h:commandButton value="詳細" immediate="true"
                             action="#{listBean.select}"/>
          </h:column>
        </h:dataTable>

 <h:dataTable>コンポーネントは、JSTLやXSLTの<for-each>タグと似た働きをします。value属性に設定されたリスト"#{listBean.customerList}"から要素を1つずつ取り出し、var属性に設定された変数名customerに格納します。<h:dataTable>の内部では<h:column>タグと<h:outputText>タグを使用して、取り出された要素customerの各プロパティを適切に出力するだけです。

 画面遷移とパラメータの引き継ぎ

 検索画面検索結果リストの各顧客の行には[詳細]ボタンを配置し、これを押すことで、顧客詳細画面に遷移させたい場合を考えます。ここで想定している画面遷移を図で表すと以下のようになります。

図4 画面遷移

 JSFにはStrutsと同等の画面遷移ルールを設定ファイルで定義することができます。図4の画面遷移をJSFの<navigation-rule>タグで定義した例を以下に示します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE faces-config PUBLIC 
  "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">
<faces-config>
  <navigation-rule>
    <description>顧客検索画面</description>
    <from-view-id>/list.jsp</from-view-id>
    <navigation-case>
      <description>詳細ボタン押下</description>
      <from-action>#{listBean.select}</from-action>
      <from-outcome>success</from-outcome>
      <to-view-id>/details.jsp</to-view-id>
    </navigation-case>
  </navigation-rule>

  <navigation-rule>
    <description>顧客情報詳細画面</description>
    <from-view-id>/details.jsp</from-view-id>
    <navigation-case>
      <description>戻るボタン押下</description>
      <from-outcome>back</from-outcome>
      <to-view-id>/list.jsp</to-view-id>
    </navigation-case>
  </navigation-rule>
</faces-config>     

 図3でも示したように、検索画面list.jsp内の[詳細]ボタンが押されたときのアクション・メソッドselect()は、ページ・セントリック・デザインに従ってListBeanクラスに定義しています。そして、ListBean#select()メソッドが実行結果“success”を返したときに、詳細画面details.jspに遷移するように<navigation-rule>を定義しています。ここで、アクション・メソッドselect()の実装は以下のとおりです。

    public String select() {
        // 選択されたCustomerBOを取得する
        FacesContext context = FacesContext.getCurrentInstance();
        Map requestMap = context.getExternalContext().getRequestMap();
        CustomerBO cust = (CustomerBO)requestMap.get("customer");

        // 選択されたCustomerBOをDetailsBeanに設定する
        detailsBean.setCustomer(cust);
        return "success";
    }

 <h:dataTable>内のボタンから選択行のオブジェクトを取得する方法の1つは、リクエスト・スコープの変数から取得することです。ボタンが押された行の要素オブジェクトは、<h:dataTable>タグのvar属性に宣言された変数名と同名のオブジェクトとしてリクエスト・スコープから取得することができます。後は、このオブジェクトを詳細画面のdetails.jspから参照できるようにしてあげればよいのですが、選択行を取得したselect()メソッドはListBeanオブジェクトにあります。ページ・セントリック・デザインの考え方に従えば、details.jspからListBeanオブジェクトを参照するのは好ましくありません。選択されたCustomerBOオブジェクトは、details.jspのバッキング・ビーンDetailsBeanオブジェクトに移してあげて、details.jspからは、DetailsBeanオブジェクトを経由で選択されたCustomerBOにアクセスさせたいと考えます。

図5 バッキング・ビーンDetailsBeanと関連コンポーネント

 1つの解決策は、ListBeanにDetailsBeanの参照を持たせるように<managed-bean>定義をしておけば、簡単に選択されたCustomerBOをDetailsBeanに移すことができます。

        detailsBean.setCustomer(cust);

 しかしこの場合、ListBeanに引きずられて、DetailsBeanもsessionスコープのオブジェクトとして宣言しなければなりません(<h:dataTable>内のボタンを正しく機能させるためには、Listモデルをsessionスコープに保存しなければなりません)。詳細画面のCustomerBOはsessionスコープである必要はないため、もし、DetailsBeanをrequestスコープで宣言するなら、JSF ApplicationオブジェクトからJSF-ELを使用して任意のmanaged-beanを参照することができます。

        context.getApplication()
            .createValueBinding("#{detailsBean.customer}")
            .setValue(context, cust);

 上記の方法を用いれば、sessionスコープのバッキング・ビーンからrequestスコープのバッキング・ビーンを参照し、値を変更することができます。この場合の欠点は、ソースコード中にJSF-ELが使用されることによる、ハードコーディングの問題とロジックの分かりにくさが挙げられます。実際のプロジェクトでどちらの方法を選択するかは、両者の利点と欠点にてんびんにかけて判断してください。

 なお、今回説明に利用したサンプル・アプリケーションのソースコードはこちらからダウンロードできます。

 JSFをプレゼンテーション層に採用する場合の注意

 上記のようにJSFを用いたプレゼンテーションの開発は、非常にシンプルでStrutsに比べて、かなりの開発工数を削減することが可能となります。JSFページそのものは、Sun Java Studio CreatorなどのRADツールを使用し、プロジェクトの初期にプロトタイプを作成し、そこで得られたJSFページをそのまま開発フェーズのベースにすることができます。

 また、図3のようなバッキング・ビーンとビジネス層との連携に必要なコンポーネントは、Sun Java Studio EnterpriseなどのUMLツールを使って簡単に描くことができ、同時に必要なクラスのソースコードを得ることができます。UMLツールによってクラスのひな型ができてしまえば、JSFページとJSFの設定ファイル以外に必要なJavaの開発作業は、アクション・メソッドを実装するだけです。しかも、アクション・メソッドの実装は先に紹介したように驚くほどシンプルです。

 しかし、実際の開発プロジェクトでは、今回説明を省略した、イベント・リスナ、バリデータ、コンバータについても理解しておく必要があります。JSF 1.1の標準仕様だけでは、実際のアプリケーションの要件には不足している部分もあります。例えば、成熟したStrutsにあってJSF 1.1にない機能として、例外ハンドリング、二度押し防止用トークン管理、ファイル・アップロード機能などが挙げられます。これらの機能はJSF 1.1に対して独自の拡張を行っているMyFacesなどのサードパーティの実装を採用するか、独自に拡張コンポーネントを実装する必要があります。

 以上、POJOの特徴を生かしたJSFベース・プレゼンテーション層のデザイン方法について、基本的な考え方をご紹介しました。

 次回は、Springフレームワークを利用したビジネス層の実現手法について解説します。

2/2  

 INDEX

第3回 JSFベースのプレゼンテーション・デザインを考える
 

Page1
POJOを意識してJSFベースのプレゼンテーションをデザインする
ページ・セントリック・デザイン

Page2
JSFページとバッキング・ビーンをJSF-ELで関係付ける
画面遷移とパラメータの引き継ぎ
JSFをプレゼンテーション層に採用する場合の注意



Java Solution全記事一覧



Java Agile フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Java Agile 記事ランキング

本日 月間